├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── megaminds │ └── actioninventory │ ├── ActionInventoryMod.java │ ├── actions │ ├── AnvilStoreAction.java │ ├── BasicAction.java │ ├── CloseAction.java │ ├── CommandAction.java │ ├── ConsumeAction.java │ ├── EmptyAction.java │ ├── GiveAction.java │ ├── GroupAction.java │ ├── MessageAction.java │ ├── OpenGui.java │ ├── RequirementAction.java │ ├── SendPropertyAction.java │ └── SoundAction.java │ ├── commands │ ├── Commands.java │ ├── LoadCommand.java │ ├── OpenCommand.java │ └── RemoveCommand.java │ ├── consumables │ ├── BasicConsumable.java │ ├── IntConsumable.java │ ├── ItemConsumable.java │ └── XpConsumable.java │ ├── gui │ ├── ActionInventoryBuilder.java │ ├── ActionInventoryGui.java │ ├── AnvilActionInventoryGui.java │ ├── BetterAnvilGui.java │ ├── BetterGui.java │ ├── BetterGuiI.java │ ├── PagedGui.java │ ├── SimpleActionInventoryGui.java │ ├── VirtualPlayerInventory.java │ ├── callback │ │ ├── ActionInventoryCallback.java │ │ ├── BetterClickCallback.java │ │ └── CancellableCallback.java │ └── elements │ │ ├── AccessableAnimatedGuiElement.java │ │ ├── AccessableElement.java │ │ ├── AccessableGuiElement.java │ │ ├── DelegatedElement.java │ │ ├── Element.java │ │ ├── SlotElement.java │ │ └── SlotFunction.java │ ├── loaders │ ├── ActionInventoryLoader.java │ └── BasicOpenerLoader.java │ ├── misc │ ├── Enums.java │ └── ItemStackish.java │ ├── openers │ ├── BasicOpener.java │ ├── BlockOpener.java │ ├── EntityOpener.java │ └── ItemOpener.java │ ├── serialization │ ├── ExcludeStrategy.java │ ├── NbtElementAdapter.java │ ├── OptionalAdapterFactory.java │ ├── PolyAdapterFactory.java │ ├── Serializer.java │ ├── TriStateAdapter.java │ └── wrappers │ │ ├── InstancedAdapterWrapper.java │ │ ├── TypeAdapterWrapper.java │ │ ├── Validated.java │ │ ├── ValidatedAdapterWrapper.java │ │ └── WrapperAdapterFactory.java │ └── util │ ├── CommandPermissions.java │ ├── ConsumableDataHelper.java │ ├── ElementHelper.java │ ├── GuiHelper.java │ ├── Helper.java │ ├── JsonHelper.java │ ├── MessageHelper.java │ ├── NbtPlaceholderParser.java │ ├── Printer.java │ ├── ValidationException.java │ └── annotations │ ├── Exclude.java │ ├── Instanced.java │ ├── Poly.java │ └── PolyName.java └── resources ├── assets └── actioninventory │ └── icon.png └── fabric.mod.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | 35 | # other 36 | 37 | ignored/ 38 | logs/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Hoid2 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ActionInventoryMod (v3) 2 | **This is a server only mod** (works on both dedicated and single player servers) 3 | 4 | 5 | A Fabric Minecraft mod that allows the creation of special inventories that can do different things when items are clicked. 6 | 7 | There are three main things added: openers (which open action inventories), action inventories, and action elements (the things in the inventories). Action elements are displayed as items stacks and execute actions when clicked. 8 | 9 | Checkout the wiki on how to create the inventories and for specifics on what can be done. 10 | 11 | This mod is similar to the [Chest Commands](https://dev.bukkit.org/projects/chest-commands) bukkit plugin. 12 | 13 | ### Some Planned Features 14 | * more consumable types 15 | * ~~items~~ Added: 3.1 16 | * money 17 | * energy 18 | * liquids 19 | * more action types 20 | * xp 21 | * choice 22 | * random 23 | * other rewards 24 | * command system 25 | * action inventory builder action inventory 26 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' version '1.5-SNAPSHOT' 3 | id 'maven-publish' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_17 7 | targetCompatibility = JavaVersion.VERSION_17 8 | 9 | archivesBaseName = project.archives_base_name 10 | version = project.mod_version 11 | group = project.maven_group 12 | 13 | repositories { 14 | // Add repositories to retrieve artifacts from in here. 15 | // You should only use this when depending on other mods because 16 | // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. 17 | // See https://docs.gradle.org/current/userguide/declaring_repositories.html 18 | // for more information about repositories. 19 | maven { url 'https://maven.nucleoid.xyz' } 20 | maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } 21 | } 22 | 23 | dependencies { 24 | // To change the versions see the gradle.properties file 25 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 26 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 27 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 28 | 29 | // Fabric API. This is technically optional, but you probably want it anyway. 30 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 31 | 32 | modImplementation include("eu.pb4:sgui:${project.sgui_version}") 33 | modImplementation include("eu.pb4:player-data-api:${project.playerdata_version}") 34 | modImplementation include("eu.pb4:placeholder-api:${project.placeholder_version}") 35 | modCompileOnly "me.lucko:fabric-permissions-api:${project.permissions_version}" 36 | } 37 | 38 | processResources { 39 | inputs.property "version", project.version 40 | 41 | filesMatching("fabric.mod.json") { 42 | expand "version": project.version 43 | } 44 | } 45 | 46 | tasks.withType(JavaCompile).configureEach { 47 | it.options.release = 17 48 | } 49 | 50 | java { 51 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 52 | // if it is present. 53 | // If you remove this line, sources will not be generated. 54 | withSourcesJar() 55 | } 56 | 57 | jar { 58 | from("LICENSE") { 59 | rename { "${it}_${project.archivesBaseName}"} 60 | } 61 | } 62 | 63 | // configure the maven publication 64 | publishing { 65 | publications { 66 | mavenJava(MavenPublication) { 67 | from components.java 68 | } 69 | } 70 | 71 | // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. 72 | repositories { 73 | // Add repositories to publish to here. 74 | // Notice: This block does NOT have the same function as the block in the top level. 75 | // The repositories here will be used for publishing your artifact, not for 76 | // retrieving dependencies. 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | 4 | # Fabric Properties 5 | # check these on https://fabricmc.net/versions.html 6 | minecraft_version=1.20.4 7 | yarn_mappings=1.20.4+build.3 8 | loader_version=0.15.7 9 | 10 | 11 | # Mod Properties 12 | mod_version = 3.7.2+1.20.4 13 | maven_group = megaminds.actioninventory 14 | archives_base_name = actioninventory 15 | 16 | # Dependencies 17 | fabric_version=0.96.4+1.20.4 18 | sgui_version=1.4.1+1.20.4 19 | playerdata_version=0.4.0+1.20.3 20 | placeholder_version=2.3.0+1.20.3 21 | permissions_version=0.2-SNAPSHOT 22 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealMegaMinds/ActionInventoryMod/24c92d47c12674b4df2068c433f534f001d782d4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | mavenCentral() 8 | gradlePluginPortal() 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/ActionInventoryMod.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory; 2 | 3 | import net.fabricmc.api.ModInitializer; 4 | import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; 5 | import net.fabricmc.fabric.api.resource.ResourceManagerHelper; 6 | import net.minecraft.resource.ResourceType; 7 | import java.util.Random; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import megaminds.actioninventory.commands.Commands; 12 | import megaminds.actioninventory.loaders.BasicOpenerLoader; 13 | import megaminds.actioninventory.loaders.ActionInventoryLoader; 14 | import megaminds.actioninventory.openers.BlockOpener; 15 | import megaminds.actioninventory.openers.EntityOpener; 16 | import megaminds.actioninventory.openers.ItemOpener; 17 | 18 | public class ActionInventoryMod implements ModInitializer { 19 | public static final Random RANDOM = new Random(); 20 | public static final String MOD_ID = "actioninventory"; 21 | public static final String MOD_NAME = "Action Inventory Mod"; 22 | public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); 23 | 24 | public static final ActionInventoryLoader INVENTORY_LOADER = new ActionInventoryLoader(); 25 | public static final BasicOpenerLoader OPENER_LOADER = new BasicOpenerLoader(); 26 | 27 | @Override 28 | public void onInitialize() { 29 | ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(INVENTORY_LOADER); 30 | ResourceManagerHelper.get(ResourceType.SERVER_DATA).registerReloadListener(OPENER_LOADER); 31 | ItemOpener.registerCallbacks(); 32 | BlockOpener.registerCallbacks(); 33 | EntityOpener.registerCallbacks(); 34 | CommandRegistrationCallback.EVENT.register(Commands::register); 35 | 36 | info("Initialized"); 37 | } 38 | 39 | public static void info(String message) { 40 | LOGGER.info(message); 41 | } 42 | 43 | public static void warn(String message) { 44 | LOGGER.warn(message); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/AnvilStoreAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import com.mojang.brigadier.StringReader; 6 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 7 | 8 | import eu.pb4.placeholders.api.PlaceholderContext; 9 | import eu.pb4.placeholders.api.Placeholders; 10 | import eu.pb4.sgui.api.ClickType; 11 | import eu.pb4.sgui.api.gui.AnvilInputGui; 12 | import megaminds.actioninventory.gui.ActionInventoryGui; 13 | import megaminds.actioninventory.serialization.wrappers.Validated; 14 | import megaminds.actioninventory.util.MessageHelper; 15 | import megaminds.actioninventory.util.annotations.PolyName; 16 | import net.fabricmc.fabric.api.util.TriState; 17 | import net.minecraft.command.argument.NbtPathArgumentType; 18 | import net.minecraft.command.argument.NbtPathArgumentType.NbtPath; 19 | import net.minecraft.nbt.NbtString; 20 | import net.minecraft.screen.slot.SlotActionType; 21 | import net.minecraft.text.Text; 22 | import net.minecraft.util.Identifier; 23 | 24 | /**@since 3.6*/ 25 | @PolyName("AnvilStore") 26 | public final class AnvilStoreAction extends BasicAction { 27 | private String storageId; 28 | private String path; 29 | 30 | public AnvilStoreAction() { 31 | } 32 | 33 | public AnvilStoreAction(String storageId, String path) { 34 | this.storageId = storageId; 35 | this.path = path; 36 | } 37 | 38 | public AnvilStoreAction(Integer requiredIndex, ClickType requiredClickType, SlotActionType requiredSlotActionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, String storageId, String path) { 39 | super(requiredIndex, requiredClickType, requiredSlotActionType, requireShift, requiredRecipe, requiredGuiName); 40 | this.storageId = storageId; 41 | this.path = path; 42 | } 43 | 44 | @Override 45 | public void validate() { 46 | Validated.validate(storageId != null && !storageId.isEmpty(), "Anvil store action requires a storageId"); 47 | Validated.validate(path != null && !path.isEmpty(), "Anvil store action requires a path"); 48 | } 49 | 50 | @Override 51 | public void accept(@NotNull ActionInventoryGui gui) { 52 | var player = gui.getPlayer(); 53 | if (!(gui instanceof AnvilInputGui a)) { 54 | player.sendMessage(Text.of("Can't execute AnvilStore action on non-anvil screen.")); 55 | return; 56 | } 57 | 58 | var placeHolderContext = PlaceholderContext.of(player); 59 | 60 | var parsedId = Placeholders.parseText(Text.of(storageId), placeHolderContext).getString(); 61 | var id = Identifier.tryParse(parsedId); 62 | if (id == null) { 63 | player.sendMessage(MessageHelper.toError("Failed to create valid path after parsing placeholders: "+parsedId)); 64 | return; 65 | } 66 | 67 | NbtPath nbtPath; 68 | try { 69 | nbtPath = NbtPathArgumentType.nbtPath().parse(new StringReader(Placeholders.parseText(Text.of(path), placeHolderContext).getString())); 70 | } catch (CommandSyntaxException e) { 71 | player.sendMessage(MessageHelper.toError("Failed to read NbtPath")); 72 | e.printStackTrace(); 73 | return; 74 | } 75 | 76 | var commandStorage = player.server.getDataCommandStorage(); 77 | var nbtCompound = commandStorage.get(id); 78 | try { 79 | nbtPath.put(nbtCompound, NbtString.of(a.getInput())); 80 | } catch (CommandSyntaxException e) { 81 | e.printStackTrace(); 82 | } 83 | commandStorage.set(id, nbtCompound); 84 | } 85 | 86 | @Override 87 | public BasicAction copy() { 88 | return new AnvilStoreAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), storageId, path); 89 | } 90 | 91 | public String getStorageId() { 92 | return storageId; 93 | } 94 | 95 | public String getPath() { 96 | return path; 97 | } 98 | 99 | public void setStorageId(String storageId) { 100 | this.storageId = storageId; 101 | } 102 | 103 | public void setPath(String path) { 104 | this.path = path; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/BasicAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import eu.pb4.sgui.api.ClickType; 8 | import megaminds.actioninventory.gui.ActionInventoryGui; 9 | import megaminds.actioninventory.gui.callback.ActionInventoryCallback; 10 | import megaminds.actioninventory.serialization.wrappers.Validated; 11 | import megaminds.actioninventory.util.annotations.Poly; 12 | import net.fabricmc.fabric.api.util.TriState; 13 | import net.minecraft.screen.slot.SlotActionType; 14 | import net.minecraft.util.Identifier; 15 | 16 | @Poly 17 | public abstract sealed class BasicAction implements ActionInventoryCallback, Validated, Consumer permits EmptyAction, OpenGui, CloseAction, CommandAction, GiveAction, MessageAction, SendPropertyAction, SoundAction, GroupAction, AnvilStoreAction { 18 | private Integer requiredIndex; 19 | private ClickType requiredClickType; 20 | private SlotActionType requiredSlotActionType; 21 | /**@since 3.1*/ 22 | private TriState requireShift = TriState.DEFAULT; 23 | /**@since 3.1*/ 24 | private Identifier requiredRecipe; 25 | private Identifier requiredGuiName; 26 | 27 | protected BasicAction() {} 28 | 29 | protected BasicAction(Integer requiredIndex, ClickType requiredClickType, SlotActionType requiredSlotActionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName) { 30 | this.requiredIndex = requiredIndex; 31 | this.requiredClickType = requiredClickType; 32 | this.requiredSlotActionType = requiredSlotActionType; 33 | this.requireShift = requireShift; 34 | this.requiredRecipe = requiredRecipe; 35 | this.requiredGuiName = requiredGuiName; 36 | } 37 | 38 | /** 39 | * Fields have been checked before calling this method. 40 | */ 41 | /**@since 3.1*/ 42 | @Override 43 | public abstract void accept(@NotNull ActionInventoryGui gui); 44 | public abstract BasicAction copy(); 45 | 46 | /** 47 | * Checks if all given arguments match this instance's fields. If so, calls internalClick. Null fields are ignored. 48 | */ 49 | @Override 50 | public boolean cancellingClick(int indexA, ClickType typeA, SlotActionType actionA, ActionInventoryGui guiA) { 51 | if (check(requiredIndex, indexA) && check(requiredClickType, typeA) && check(requiredSlotActionType, actionA) && check(requiredGuiName, guiA.getId())) { 52 | accept(guiA); 53 | } 54 | return false; 55 | } 56 | 57 | /**@since 3.1*/ 58 | public void onRecipe(Identifier recipe, boolean shift, ActionInventoryGui gui) { 59 | if (check(requiredRecipe, recipe) && check(requireShift, shift) && check(requiredGuiName, gui.getId())) { 60 | accept(gui); 61 | } 62 | } 63 | 64 | private static boolean check(E o, E e) { 65 | return o==null || o==e; 66 | } 67 | 68 | public Integer getRequiredIndex() { 69 | return requiredIndex; 70 | } 71 | 72 | public void setRequiredIndex(Integer requiredIndex) { 73 | this.requiredIndex = requiredIndex; 74 | } 75 | 76 | public ClickType getRequiredClickType() { 77 | return requiredClickType; 78 | } 79 | 80 | public void setRequiredClickType(ClickType requiredClickType) { 81 | this.requiredClickType = requiredClickType; 82 | } 83 | 84 | public SlotActionType getRequiredSlotActionType() { 85 | return requiredSlotActionType; 86 | } 87 | 88 | public void setRequiredSlotActionType(SlotActionType requiredSlotActionType) { 89 | this.requiredSlotActionType = requiredSlotActionType; 90 | } 91 | 92 | public TriState getRequireShift() { 93 | return requireShift; 94 | } 95 | 96 | public void setRequireShift(TriState requireShift) { 97 | this.requireShift = requireShift; 98 | } 99 | 100 | public Identifier getRequiredRecipe() { 101 | return requiredRecipe; 102 | } 103 | 104 | public void setRequiredRecipe(Identifier requiredRecipe) { 105 | this.requiredRecipe = requiredRecipe; 106 | } 107 | 108 | public Identifier getRequiredGuiName() { 109 | return requiredGuiName; 110 | } 111 | 112 | public void setRequiredGuiName(Identifier requiredGuiName) { 113 | this.requiredGuiName = requiredGuiName; 114 | } 115 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/CloseAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import eu.pb4.sgui.api.ClickType; 6 | import megaminds.actioninventory.gui.ActionInventoryGui; 7 | import megaminds.actioninventory.util.annotations.PolyName; 8 | import net.fabricmc.fabric.api.util.TriState; 9 | import net.minecraft.screen.slot.SlotActionType; 10 | import net.minecraft.util.Identifier; 11 | 12 | @PolyName("CloseGui") 13 | public final class CloseAction extends BasicAction { 14 | public CloseAction() {} 15 | 16 | public CloseAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName) { 17 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 18 | } 19 | 20 | @Override 21 | public void validate() { 22 | //Unused 23 | } 24 | 25 | @Override 26 | public BasicAction copy() { 27 | return new CloseAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName()); 28 | } 29 | 30 | @Override 31 | public void accept(@NotNull ActionInventoryGui gui) { 32 | gui.close(); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/CommandAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import eu.pb4.sgui.api.ClickType; 6 | import megaminds.actioninventory.gui.ActionInventoryGui; 7 | import megaminds.actioninventory.util.MessageHelper; 8 | import megaminds.actioninventory.util.annotations.PolyName; 9 | import net.fabricmc.fabric.api.util.TriState; 10 | import net.minecraft.screen.slot.SlotActionType; 11 | import net.minecraft.util.Identifier; 12 | 13 | /** 14 | * This executes a command. 15 | * Some other actions may be a better option for doing certain things. 16 | * @see ToPlayerMessageAction 17 | * @see ToAllMessageAction 18 | * @see GiveAction 19 | * @see OpenActionInventoryAction 20 | */ 21 | @PolyName("Command") 22 | public final class CommandAction extends BasicAction { 23 | private String command; 24 | private TriState fromServer = TriState.DEFAULT; 25 | /**@since 3.1*/ 26 | private TriState silent = TriState.DEFAULT; 27 | /**@since 3.1*/ 28 | private Integer higherLevel; 29 | 30 | public CommandAction() {} 31 | 32 | public CommandAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, String command, TriState fromServer, TriState silent, Integer higherLevel) { 33 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 34 | this.command = command; 35 | this.fromServer = fromServer; 36 | this.silent = silent; 37 | this.higherLevel = higherLevel; 38 | } 39 | 40 | public CommandAction(String command, TriState fromServer, TriState silent, Integer higherLevel) { 41 | this.command = command; 42 | this.fromServer = fromServer; 43 | this.silent = silent; 44 | this.higherLevel = higherLevel; 45 | } 46 | 47 | @Override 48 | public void accept(@NotNull ActionInventoryGui gui) { 49 | var player = gui.getPlayer(); 50 | 51 | var source = fromServer.orElse(false) ? player.getServer().getCommandSource() : player.getCommandSource(); 52 | if (silent.orElse(false)) source = source.withSilent(); 53 | if (higherLevel!=null) source = source.withMaxLevel(higherLevel); 54 | 55 | MessageHelper.executeCommand(source, command); 56 | } 57 | 58 | @Override 59 | public void validate() { 60 | if (command==null) command = ""; 61 | } 62 | 63 | @Override 64 | public BasicAction copy() { 65 | return new CommandAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), command, fromServer, silent, higherLevel); 66 | } 67 | 68 | public String getCommand() { 69 | return command; 70 | } 71 | 72 | public void setCommand(String command) { 73 | this.command = command; 74 | } 75 | 76 | public TriState getFromServer() { 77 | return fromServer; 78 | } 79 | 80 | public void setFromServer(TriState fromServer) { 81 | this.fromServer = fromServer; 82 | } 83 | 84 | public TriState getSilent() { 85 | return silent; 86 | } 87 | 88 | public void setSilent(TriState silent) { 89 | this.silent = silent; 90 | } 91 | 92 | public Integer getHigherLevel() { 93 | return higherLevel; 94 | } 95 | 96 | public void setHigherLevel(Integer higherLevel) { 97 | this.higherLevel = higherLevel; 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/ConsumeAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import static megaminds.actioninventory.misc.Enums.COMPLETE; 4 | 5 | import java.util.Arrays; 6 | import java.util.UUID; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import eu.pb4.sgui.api.ClickType; 11 | import megaminds.actioninventory.consumables.BasicConsumable; 12 | import megaminds.actioninventory.gui.ActionInventoryGui; 13 | import megaminds.actioninventory.util.ConsumableDataHelper; 14 | import megaminds.actioninventory.util.Helper; 15 | import megaminds.actioninventory.util.annotations.PolyName; 16 | import net.fabricmc.fabric.api.util.TriState; 17 | import net.minecraft.nbt.NbtCompound; 18 | import net.minecraft.screen.slot.SlotActionType; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.util.Identifier; 21 | 22 | @PolyName("Consume") 23 | public final class ConsumeAction extends GroupAction { 24 | /**Consumables to consume*/ 25 | private BasicConsumable[] consumables; 26 | /**True->pay first time, false->pay every time*/ 27 | private TriState singlePay = TriState.DEFAULT; 28 | /**True->Full amount is needed to consume any, false->will consume as much as possible*/ 29 | private TriState requireFull = TriState.DEFAULT; 30 | 31 | public ConsumeAction() {} 32 | 33 | public ConsumeAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, BasicAction[] actions, BasicConsumable[] consumables, TriState singlePay, TriState requireFull) { 34 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName, actions); 35 | this.consumables = consumables; 36 | this.singlePay = singlePay; 37 | this.requireFull = requireFull; 38 | } 39 | 40 | public ConsumeAction(BasicAction[] actions, BasicConsumable[] consumables, TriState singlePay, TriState requireFull) { 41 | super(actions); 42 | this.consumables = consumables; 43 | this.singlePay = singlePay; 44 | this.requireFull = requireFull; 45 | } 46 | 47 | @Override 48 | public void accept(@NotNull ActionInventoryGui gui) { 49 | if (consumables==null) { 50 | super.accept(gui); 51 | return; 52 | } 53 | 54 | var p = gui.getPlayer(); 55 | var player = p.getUuid(); 56 | var server = p.getServer(); 57 | var guiName = gui.getId().toString(); 58 | var lastAction = gui.getLastAction(); 59 | var singlePayB = singlePay.orElse(false); 60 | var requireFullB = requireFull.orElse(false); 61 | 62 | var actionData = ConsumableDataHelper.getAction(server, player, guiName, lastAction); 63 | 64 | var hasPaid = singlePayB && Helper.getBoolean(actionData.orElse(null), COMPLETE); //can be complete and is complete 65 | 66 | //the full amount has not been paid and [the full amount is not required or (the full amount is required and can be paid)]. 67 | if (!hasPaid && (!requireFullB || canPayFull(server, player, guiName, lastAction))) hasPaid = pay(server, player, guiName, lastAction); 68 | 69 | //the full amount was paid or has now been paid 70 | if (hasPaid) { 71 | if (singlePayB) { 72 | forgetData(ConsumableDataHelper.getOrCreateAction(server, player, guiName, lastAction), true); 73 | } else { 74 | ConsumableDataHelper.getAction(server, player, guiName, lastAction).ifPresent(c->forgetData(c, false)); 75 | } 76 | super.accept(gui); 77 | } 78 | } 79 | 80 | /** 81 | * Replaces consumable data with complete. 82 | */ 83 | private void forgetData(@NotNull NbtCompound compound, boolean markComplete) { 84 | compound.getKeys().clear(); 85 | if (markComplete) compound.putBoolean(COMPLETE, true); 86 | } 87 | 88 | /** 89 | * Makes each consumable consume as much as possible up to the full amount. 90 | */ 91 | private boolean pay(MinecraftServer server, UUID player, String guiName, String lastAction) { 92 | if (consumables==null) return true; 93 | 94 | var paidFull = true; 95 | for (var c : consumables) { 96 | if (!c.consume(server, player, ConsumableDataHelper.getOrCreateConsumable(server, player, guiName, lastAction, c.getStorageName()))) paidFull = false; 97 | } 98 | return paidFull; 99 | } 100 | 101 | /** 102 | * Returns true if all consumables can pay the full amount. 103 | */ 104 | private boolean canPayFull(MinecraftServer server, UUID player, String guiName, String lastAction) { 105 | if (consumables==null) return true; 106 | 107 | for (var c : consumables) { 108 | if (!c.canConsumeFull(server, player, ConsumableDataHelper.getConsumable(server, player, guiName, lastAction, c.getStorageName()).orElse(null))) { 109 | return false; 110 | } 111 | } 112 | return true; 113 | } 114 | 115 | @Override 116 | public BasicAction copy() { 117 | return new ConsumeAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), Arrays.stream(getActions()).map(BasicAction::copy).toArray(BasicAction[]::new), Arrays.stream(consumables).map(BasicConsumable::copy).toArray(BasicConsumable[]::new), singlePay, requireFull); 118 | } 119 | 120 | public BasicConsumable[] getConsumables() { 121 | return consumables; 122 | } 123 | 124 | public void setConsumables(BasicConsumable[] consumables) { 125 | this.consumables = consumables; 126 | } 127 | 128 | public TriState getSinglePay() { 129 | return singlePay; 130 | } 131 | 132 | public void setSinglePay(TriState singlePay) { 133 | this.singlePay = singlePay; 134 | } 135 | 136 | public TriState getRequireFull() { 137 | return requireFull; 138 | } 139 | 140 | public void setRequireFull(TriState requireFull) { 141 | this.requireFull = requireFull; 142 | } 143 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/EmptyAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import megaminds.actioninventory.gui.ActionInventoryGui; 8 | import megaminds.actioninventory.util.annotations.Instanced; 9 | import megaminds.actioninventory.util.annotations.PolyName; 10 | 11 | @PolyName("Empty") 12 | @Instanced 13 | public non-sealed class EmptyAction extends BasicAction { 14 | public static final EmptyAction INSTANCE = new EmptyAction(); 15 | 16 | private EmptyAction() {} 17 | 18 | @Override 19 | public void validate() { 20 | //Unused 21 | } 22 | 23 | @Override 24 | public void accept(@NotNull ActionInventoryGui gui) { 25 | //Unused 26 | } 27 | 28 | @Override 29 | public BasicAction copy() { 30 | return INSTANCE; 31 | } 32 | 33 | public static EmptyAction getNew(Consumer consumer) { 34 | return new EmptyAction() { 35 | @Override 36 | public void accept(@NotNull ActionInventoryGui gui) { 37 | consumer.accept(gui); 38 | } 39 | }; 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/GiveAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Objects; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import eu.pb4.placeholders.api.PlaceholderContext; 10 | import eu.pb4.sgui.api.ClickType; 11 | import megaminds.actioninventory.gui.ActionInventoryGui; 12 | import megaminds.actioninventory.util.Helper; 13 | import megaminds.actioninventory.util.annotations.PolyName; 14 | import net.fabricmc.fabric.api.util.TriState; 15 | import net.minecraft.item.ItemStack; 16 | import net.minecraft.loot.context.LootContextParameterSet; 17 | import net.minecraft.loot.context.LootContextParameters; 18 | import net.minecraft.loot.context.LootContextTypes; 19 | import net.minecraft.screen.slot.SlotActionType; 20 | import net.minecraft.util.Identifier; 21 | 22 | /** 23 | * This gives an item to the player (will be dropped if the player's inventory is full) 24 | */ 25 | @PolyName("Give") 26 | public final class GiveAction extends BasicAction { 27 | private static final Identifier[] EMPTY = new Identifier[0]; 28 | 29 | private Identifier[] lootTables; 30 | private TriState giveClicked = TriState.DEFAULT; 31 | 32 | public GiveAction() {} 33 | 34 | public GiveAction(Identifier[] lootTables, TriState giveClicked) { 35 | this.lootTables = lootTables; 36 | this.giveClicked = giveClicked; 37 | } 38 | 39 | public GiveAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, Identifier[] lootTables, TriState giveClicked) { 40 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 41 | this.lootTables = lootTables; 42 | this.giveClicked = giveClicked; 43 | } 44 | 45 | @Override 46 | public void accept(@NotNull ActionInventoryGui gui) { 47 | var p = gui.getPlayer(); 48 | 49 | if (giveClicked.orElse(false)) { 50 | var current = gui.getLastClicked().copy(); 51 | if (current!=null) p.getInventory().offerOrDrop(current); 52 | } 53 | 54 | var lootContext = new LootContextParameterSet.Builder(p.getServerWorld()) 55 | .add(LootContextParameters.THIS_ENTITY, p) 56 | .add(LootContextParameters.ORIGIN, p.getPos()) 57 | .luck(p.getLuck()) 58 | .build(LootContextTypes.ADVANCEMENT_REWARD); 59 | 60 | Arrays.stream(lootTables) 61 | .map(id->p.server.getLootManager().getLootTable(id).generateLoot(lootContext)) 62 | .mapMulti(List::forEach) 63 | .filter(Objects::nonNull) 64 | .map(s->Helper.parseItemStack(s, PlaceholderContext.of(p))) 65 | .forEach(p.getInventory()::offerOrDrop); 66 | 67 | p.currentScreenHandler.sendContentUpdates(); 68 | } 69 | 70 | @Override 71 | public void validate() { 72 | if (lootTables==null) lootTables = EMPTY; 73 | } 74 | 75 | @Override 76 | public BasicAction copy() { 77 | return new GiveAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), Arrays.copyOf(lootTables, lootTables.length), giveClicked); 78 | } 79 | 80 | public Identifier[] getLootTables() { 81 | return lootTables; 82 | } 83 | 84 | public void setLootTables(Identifier[] lootTables) { 85 | this.lootTables = lootTables; 86 | } 87 | 88 | public TriState getGiveClicked() { 89 | return giveClicked; 90 | } 91 | 92 | public void setGiveClicked(TriState giveClicked) { 93 | this.giveClicked = giveClicked; 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/GroupAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import eu.pb4.sgui.api.ClickType; 8 | import megaminds.actioninventory.gui.ActionInventoryGui; 9 | import megaminds.actioninventory.util.annotations.PolyName; 10 | import net.fabricmc.fabric.api.util.TriState; 11 | import net.minecraft.screen.slot.SlotActionType; 12 | import net.minecraft.util.Identifier; 13 | 14 | @PolyName("Group") 15 | public sealed class GroupAction extends BasicAction permits RequirementAction, ConsumeAction { 16 | private static final BasicAction[] EMPTY_A = new BasicAction[0]; 17 | 18 | private BasicAction[] actions; 19 | 20 | public GroupAction() {} 21 | 22 | public GroupAction(BasicAction[] actions) { 23 | this.actions = actions; 24 | } 25 | 26 | public GroupAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, BasicAction[] actions) { 27 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 28 | this.actions = actions; 29 | } 30 | 31 | @Override 32 | public void validate() { 33 | if (actions==null) actions = EMPTY_A; 34 | } 35 | 36 | @Override 37 | public void accept(@NotNull ActionInventoryGui gui) { 38 | for (var a : actions) { 39 | a.accept(gui); 40 | } 41 | } 42 | 43 | @Override 44 | public BasicAction copy() { 45 | return new GroupAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), Arrays.stream(actions).map(BasicAction::copy).toArray(BasicAction[]::new)); 46 | } 47 | 48 | public BasicAction[] getActions() { 49 | return actions; 50 | } 51 | 52 | public void setActions(BasicAction[] actions) { 53 | this.actions = actions; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/MessageAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.UUID; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import eu.pb4.sgui.api.ClickType; 11 | import megaminds.actioninventory.gui.ActionInventoryGui; 12 | import megaminds.actioninventory.util.MessageHelper; 13 | import megaminds.actioninventory.util.annotations.PolyName; 14 | import net.fabricmc.fabric.api.util.TriState; 15 | import net.minecraft.network.message.MessageType; 16 | import net.minecraft.screen.slot.SlotActionType; 17 | import net.minecraft.text.Text; 18 | import net.minecraft.util.Identifier; 19 | import net.minecraft.util.Util; 20 | 21 | /** 22 | * Sends a message to specified players 23 | */ 24 | @PolyName("Message") 25 | public final class MessageAction extends BasicAction { 26 | private Text message; 27 | /** 28 | * null -> player.uuid 29 | */ 30 | private UUID sender; 31 | /** 32 | * Receivers of the message 33 | * If null or empty -> broadcast 34 | * Contains: nil uuid -> server, null -> player 35 | */ 36 | private List receivers; 37 | private Identifier messageType; 38 | 39 | public MessageAction() {} 40 | 41 | public MessageAction(Text message, UUID sender, List receivers, Identifier messageType) { 42 | this.message = message; 43 | this.sender = sender; 44 | this.receivers = receivers; 45 | this.messageType = messageType; 46 | } 47 | 48 | public MessageAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, Text message, UUID sender, List receivers, Identifier messageType) { 49 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 50 | this.message = message; 51 | this.sender = sender; 52 | this.receivers = receivers; 53 | this.messageType = messageType; 54 | } 55 | 56 | @Override 57 | public void accept(@NotNull ActionInventoryGui gui) { 58 | var player = gui.getPlayer(); 59 | var server = player.getServer(); 60 | if (sender==null) sender = player.getUuid(); 61 | 62 | if (receivers==null || receivers.isEmpty()) { 63 | MessageHelper.broadcast(sender, message, messageType, server); 64 | return; 65 | } 66 | 67 | var temp = new ArrayList<>(receivers); 68 | if (temp.removeIf(Util.NIL_UUID::equals)) { 69 | MessageHelper.log(message, server); 70 | } 71 | if (temp.removeIf(Objects::isNull)) { 72 | temp.add(player.getUuid()); 73 | } 74 | MessageHelper.message(sender, temp, message, messageType, server); 75 | } 76 | 77 | @Override 78 | public void validate() { 79 | if (messageType==null) messageType = MessageType.CHAT.getValue(); 80 | if (message==null) message = Text.empty(); 81 | } 82 | 83 | @Override 84 | public BasicAction copy() { 85 | return new MessageAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), message.copy(), sender, receivers == null ? null : new ArrayList<>(receivers), messageType); 86 | } 87 | 88 | public Text getMessage() { 89 | return message; 90 | } 91 | 92 | public void setMessage(Text message) { 93 | this.message = message; 94 | } 95 | 96 | public UUID getSender() { 97 | return sender; 98 | } 99 | 100 | public void setSender(UUID sender) { 101 | this.sender = sender; 102 | } 103 | 104 | public List getReceivers() { 105 | return receivers; 106 | } 107 | 108 | public void setReceivers(List receivers) { 109 | this.receivers = receivers; 110 | } 111 | 112 | public Identifier getMessageType() { 113 | return messageType; 114 | } 115 | 116 | public void setMessageType(Identifier messageType) { 117 | this.messageType = messageType; 118 | } 119 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/OpenGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.UUID; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import eu.pb4.sgui.api.ClickType; 8 | import megaminds.actioninventory.ActionInventoryMod; 9 | import megaminds.actioninventory.gui.ActionInventoryGui; 10 | import megaminds.actioninventory.misc.Enums.GuiType; 11 | import megaminds.actioninventory.serialization.wrappers.Validated; 12 | import megaminds.actioninventory.util.MessageHelper; 13 | import net.fabricmc.fabric.api.util.TriState; 14 | import net.minecraft.screen.slot.SlotActionType; 15 | import net.minecraft.util.Identifier; 16 | 17 | public final class OpenGui extends BasicAction { 18 | private GuiType guiType; 19 | private Identifier guiName; 20 | private UUID playerUUID; 21 | 22 | public OpenGui() {} 23 | 24 | public OpenGui(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, GuiType guiType, Identifier guiName, UUID playerUUID) { 25 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 26 | this.guiName = guiName; 27 | this.guiType = guiType; 28 | this.playerUUID = playerUUID; 29 | } 30 | 31 | public OpenGui(GuiType guiType, Identifier guiName) { 32 | this.guiType = guiType; 33 | this.guiName = guiName; 34 | } 35 | 36 | public OpenGui(GuiType guiType, UUID playerUUID) { 37 | this.guiType = guiType; 38 | this.playerUUID = playerUUID; 39 | } 40 | 41 | @Override 42 | public void accept(@NotNull ActionInventoryGui gui) { 43 | if (guiType==null) { 44 | gui.close(); 45 | return; 46 | } 47 | 48 | var player = gui.getPlayer(); 49 | switch (guiType) { 50 | case ENDER_CHEST -> { 51 | var uuid = playerUUID!=null ? playerUUID : player.getUuid(); 52 | ActionInventoryMod.INVENTORY_LOADER.openEnderChest(player, uuid); 53 | } 54 | case NAMED_GUI -> { 55 | var b = ActionInventoryMod.INVENTORY_LOADER.getBuilder(guiName); 56 | if (b==null) { 57 | gui.close(); 58 | player.sendMessage(MessageHelper.toError("No action inventory of name: "+guiName)); 59 | } else if (!b.buildAndOpen(player)) { 60 | gui.close(); 61 | } 62 | } 63 | case PLAYER -> { 64 | var uuid = playerUUID!=null ? playerUUID : player.getUuid(); 65 | ActionInventoryMod.INVENTORY_LOADER.openInventory(player, uuid); 66 | } 67 | default -> throw new IllegalArgumentException("Unimplemented case: " + guiType); 68 | } 69 | } 70 | 71 | @Override 72 | public void validate() { 73 | if (guiType==null) return; 74 | switch(guiType) { 75 | case ENDER_CHEST, PLAYER -> guiName = null; //NOSONAR 76 | case NAMED_GUI -> { 77 | Validated.validate(guiName!=null, "guiName cannot be null."); 78 | playerUUID = null; 79 | } 80 | default-> throw new IllegalArgumentException("OpenGui action doesn't support guiType of: "+guiType); 81 | } 82 | } 83 | 84 | @Override 85 | public BasicAction copy() { 86 | var copy = new OpenGui(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), guiType, guiName, playerUUID); 87 | copy.playerUUID = playerUUID; 88 | return copy; 89 | } 90 | 91 | public GuiType getGuiType() { 92 | return guiType; 93 | } 94 | 95 | public void setGuiType(GuiType guiType) { 96 | this.guiType = guiType; 97 | } 98 | 99 | public Identifier getGuiName() { 100 | return guiName; 101 | } 102 | 103 | public void setGuiName(Identifier guiName) { 104 | this.guiName = guiName; 105 | } 106 | 107 | public UUID getPlayerUUID() { 108 | return playerUUID; 109 | } 110 | 111 | public void setPlayerUUID(UUID playerUUID) { 112 | this.playerUUID = playerUUID; 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/RequirementAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import com.mojang.brigadier.StringReader; 8 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 9 | 10 | import eu.pb4.sgui.api.ClickType; 11 | import megaminds.actioninventory.gui.ActionInventoryGui; 12 | import megaminds.actioninventory.util.annotations.Exclude; 13 | import megaminds.actioninventory.util.annotations.PolyName; 14 | import net.fabricmc.fabric.api.util.TriState; 15 | import net.minecraft.command.EntitySelector; 16 | import net.minecraft.command.EntitySelectorReader; 17 | import net.minecraft.entity.Entity; 18 | import net.minecraft.predicate.entity.EntityPredicate; 19 | import net.minecraft.screen.slot.SlotActionType; 20 | import net.minecraft.server.network.ServerPlayerEntity; 21 | import net.minecraft.util.Identifier; 22 | 23 | @PolyName("Require") 24 | public final class RequirementAction extends GroupAction { 25 | private String entitySelector; 26 | private EntityPredicate entityPredicate; 27 | private int opLevel; 28 | 29 | @Exclude private EntitySelector selector; 30 | 31 | public RequirementAction() {} 32 | 33 | public RequirementAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, BasicAction[] actions, String entitySelector, EntityPredicate entityPredicate, int opLevel) { 34 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName, actions); 35 | this.entitySelector = entitySelector; 36 | this.entityPredicate = entityPredicate; 37 | this.opLevel = opLevel; 38 | } 39 | 40 | public RequirementAction(BasicAction[] actions, String entitySelector, EntityPredicate entityPredicate, int opLevel) { 41 | super(actions); 42 | this.entitySelector = entitySelector; 43 | this.entityPredicate = entityPredicate; 44 | this.opLevel = opLevel; 45 | } 46 | 47 | @Override 48 | public void accept(@NotNull ActionInventoryGui gui) { 49 | if (test(gui.getPlayer())) { 50 | super.accept(gui); 51 | } 52 | } 53 | 54 | public boolean test(ServerPlayerEntity player) { 55 | return player.hasPermissionLevel(opLevel) && (entityPredicate == null || entityPredicate.test(player, player)) && matchesSelector(player); 56 | } 57 | 58 | private boolean matchesSelector(Entity e) { 59 | try { 60 | return selector == null || e.equals(selector.getEntity(e.getCommandSource().withMaxLevel(2))); 61 | } catch (CommandSyntaxException e1) { 62 | return false; 63 | } 64 | } 65 | 66 | private void validateSelector() { 67 | if (entitySelector==null || entitySelector.isBlank()) return; 68 | 69 | var whole = "@s"+entitySelector.strip(); 70 | 71 | try { 72 | this.selector = new EntitySelectorReader(new StringReader(whole)).read(); 73 | } catch (CommandSyntaxException e) { 74 | throw new IllegalArgumentException("Failed to read entity selector for an EntityOpener.", e); 75 | } 76 | } 77 | 78 | @Override 79 | public void validate() { 80 | super.validate(); 81 | validateSelector(); 82 | } 83 | 84 | @Override 85 | public BasicAction copy() { 86 | var copy = new RequirementAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), Arrays.stream(getActions()).map(BasicAction::copy).toArray(BasicAction[]::new), entitySelector, entityPredicate, opLevel); 87 | copy.selector = selector; 88 | return copy; 89 | } 90 | 91 | public String getEntitySelector() { 92 | return entitySelector; 93 | } 94 | 95 | public void setEntitySelector(String entitySelector) { 96 | this.entitySelector = entitySelector; 97 | } 98 | 99 | public EntityPredicate getEntityPredicate() { 100 | return entityPredicate; 101 | } 102 | 103 | public void setEntityPredicate(EntityPredicate entityPredicate) { 104 | this.entityPredicate = entityPredicate; 105 | } 106 | 107 | public int getOpLevel() { 108 | return opLevel; 109 | } 110 | 111 | public void setOpLevel(int opLevel) { 112 | this.opLevel = opLevel; 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/SendPropertyAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import eu.pb4.sgui.api.ClickType; 6 | import eu.pb4.sgui.api.ScreenProperty; 7 | import megaminds.actioninventory.gui.ActionInventoryGui; 8 | import megaminds.actioninventory.util.annotations.PolyName; 9 | import net.fabricmc.fabric.api.util.TriState; 10 | import net.minecraft.screen.slot.SlotActionType; 11 | import net.minecraft.util.Identifier; 12 | 13 | @PolyName("SendProperty") 14 | public final class SendPropertyAction extends BasicAction { 15 | private ScreenProperty property; 16 | private int value; 17 | 18 | public SendPropertyAction() {} 19 | 20 | public SendPropertyAction(ScreenProperty property, int value) { 21 | this.property = property; 22 | this.value = value; 23 | } 24 | 25 | public SendPropertyAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, ScreenProperty property, int value) { 26 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 27 | this.property = property; 28 | this.value = value; 29 | } 30 | 31 | @Override 32 | public void accept(@NotNull ActionInventoryGui gui) { 33 | gui.sendProperty(property, value); 34 | } 35 | 36 | @Override 37 | public void validate() { 38 | if (property==null) property = ScreenProperty.FIRE_LEVEL; 39 | } 40 | 41 | @Override 42 | public BasicAction copy() { 43 | return new SendPropertyAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), property, value); 44 | } 45 | 46 | public ScreenProperty getProperty() { 47 | return property; 48 | } 49 | 50 | public void setProperty(ScreenProperty property) { 51 | this.property = property; 52 | } 53 | 54 | public int getValue() { 55 | return value; 56 | } 57 | 58 | public void setValue(int value) { 59 | this.value = value; 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/actions/SoundAction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.actions; 2 | 3 | import java.util.Objects; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import eu.pb4.sgui.api.ClickType; 8 | import megaminds.actioninventory.ActionInventoryMod; 9 | import megaminds.actioninventory.gui.ActionInventoryGui; 10 | import megaminds.actioninventory.serialization.wrappers.Validated; 11 | import megaminds.actioninventory.util.annotations.PolyName; 12 | import net.fabricmc.fabric.api.util.TriState; 13 | import net.minecraft.network.packet.s2c.play.PlaySoundS2CPacket; 14 | import net.minecraft.registry.Registries; 15 | import net.minecraft.screen.slot.SlotActionType; 16 | import net.minecraft.sound.SoundCategory; 17 | import net.minecraft.sound.SoundEvent; 18 | import net.minecraft.sound.SoundEvents; 19 | import net.minecraft.util.Identifier; 20 | import net.minecraft.util.math.Vec3d; 21 | 22 | /** 23 | * This plays a sound 24 | */ 25 | @PolyName("Sound") 26 | public final class SoundAction extends BasicAction { 27 | private SoundEvent sound; 28 | private SoundCategory category; 29 | private Vec3d position; 30 | private Float volume; 31 | private Float pitch; 32 | 33 | public SoundAction() {} 34 | 35 | public SoundAction(SoundEvent sound, SoundCategory category, Vec3d position, Float volume, Float pitch) { 36 | this.sound = sound; 37 | this.category = category; 38 | this.position = position; 39 | this.volume = volume; 40 | this.pitch = pitch; 41 | } 42 | 43 | @SuppressWarnings("java:S107") 44 | public SoundAction(Integer requiredIndex, ClickType clicktype, SlotActionType actionType, TriState requireShift, Identifier requiredRecipe, Identifier requiredGuiName, SoundEvent sound, SoundCategory category, Vec3d position, Float volume, Float pitch) { 45 | super(requiredIndex, clicktype, actionType, requireShift, requiredRecipe, requiredGuiName); 46 | this.sound = sound; 47 | this.category = category; 48 | this.position = position; 49 | this.volume = volume; 50 | this.pitch = pitch; 51 | } 52 | 53 | @Override 54 | public void accept(@NotNull ActionInventoryGui gui) { 55 | var player = gui.getPlayer(); 56 | var pos = Objects.requireNonNullElseGet(position, player::getPos); 57 | player.networkHandler.sendPacket(new PlaySoundS2CPacket(Registries.SOUND_EVENT.getEntry(sound), category, pos.x, pos.y, pos.z, volume, pitch, ActionInventoryMod.RANDOM.nextLong())); 58 | } 59 | 60 | @Override 61 | public void validate() { 62 | if (sound==null) sound = Registries.SOUND_EVENT.get(SoundEvents.UI_BUTTON_CLICK.registryKey()); 63 | if (category==null) category = SoundCategory.MASTER; 64 | if (volume==null) volume = 1f; 65 | if (pitch==null) pitch = 1f; 66 | Validated.validate(volume>=0, "Sound action requires volume to be 0 or above, but it is: "+volume); 67 | Validated.validate(pitch>=0, "Sound action requires pitch to be 0 or above, but it is: "+pitch); 68 | } 69 | 70 | @Override 71 | public BasicAction copy() { 72 | return new SoundAction(getRequiredIndex(), getRequiredClickType(), getRequiredSlotActionType(), getRequireShift(), getRequiredRecipe(), getRequiredGuiName(), sound, category, position, volume, pitch); 73 | } 74 | 75 | public SoundEvent getSound() { 76 | return sound; 77 | } 78 | 79 | public void setSound(SoundEvent sound) { 80 | this.sound = sound; 81 | } 82 | 83 | public SoundCategory getCategory() { 84 | return category; 85 | } 86 | 87 | public void setCategory(SoundCategory category) { 88 | this.category = category; 89 | } 90 | 91 | public Vec3d getPosition() { 92 | return position; 93 | } 94 | 95 | public void setPosition(Vec3d position) { 96 | this.position = position; 97 | } 98 | 99 | public Float getVolume() { 100 | return volume; 101 | } 102 | 103 | public void setVolume(Float volume) { 104 | this.volume = volume; 105 | } 106 | 107 | public Float getPitch() { 108 | return pitch; 109 | } 110 | 111 | public void setPitch(Float pitch) { 112 | this.pitch = pitch; 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/commands/Commands.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.commands; 2 | 3 | import static net.minecraft.server.command.CommandManager.literal; 4 | 5 | import com.mojang.brigadier.CommandDispatcher; 6 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 7 | import com.mojang.brigadier.context.CommandContext; 8 | import megaminds.actioninventory.ActionInventoryMod; 9 | import megaminds.actioninventory.util.CommandPermissions; 10 | import megaminds.actioninventory.util.MessageHelper; 11 | import net.minecraft.command.CommandRegistryAccess; 12 | import net.minecraft.server.command.CommandManager.RegistrationEnvironment; 13 | import net.minecraft.server.command.ServerCommandSource; 14 | import net.minecraft.text.Text; 15 | 16 | public class Commands { 17 | private Commands() {} 18 | 19 | @SuppressWarnings("unused") //Used as CommandRegistrationCallback 20 | public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess, RegistrationEnvironment environment) { 21 | LiteralArgumentBuilder root = literal(ActionInventoryMod.MOD_ID); 22 | 23 | root.then(literal("list").requires(CommandPermissions.requires(root.getLiteral()+".list", 2)).executes(Commands::list)); 24 | OpenCommand.register(root); 25 | LoadCommand.register(root); 26 | RemoveCommand.register(root); 27 | 28 | dispatcher.register(root); 29 | } 30 | 31 | private static int list(CommandContext context) { 32 | var names = ActionInventoryMod.INVENTORY_LOADER.builderNames(); 33 | var size = names.size(); 34 | var combined = new StringBuilder(size*10); 35 | names.forEach(i->combined.append("\n"+i.toString())); 36 | 37 | //Append to empty Text so success formatting doesn't carry to other appended texts 38 | var message = Text.empty().append(MessageHelper.toSuccess(size+" Action Inventories are loaded.")); 39 | 40 | if (size>0) message.append(combined.toString()); 41 | context.getSource().sendFeedback(() -> message, false); 42 | return size; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/commands/LoadCommand.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.commands; 2 | 3 | import static net.minecraft.server.command.CommandManager.argument; 4 | import static net.minecraft.server.command.CommandManager.literal; 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | import com.mojang.brigadier.arguments.BoolArgumentType; 11 | import com.mojang.brigadier.arguments.StringArgumentType; 12 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 13 | import com.mojang.brigadier.context.CommandContext; 14 | import megaminds.actioninventory.ActionInventoryMod; 15 | import megaminds.actioninventory.serialization.Serializer; 16 | import megaminds.actioninventory.util.CommandPermissions; 17 | import megaminds.actioninventory.util.ValidationException; 18 | import net.minecraft.server.command.ServerCommandSource; 19 | import net.minecraft.text.Text; 20 | 21 | public class LoadCommand { 22 | private LoadCommand() {} 23 | 24 | public static void register(LiteralArgumentBuilder root) { 25 | root.then(literal("load") 26 | .requires(CommandPermissions.requires(root.getLiteral()+".load", 4)) 27 | .then(literal("opener") 28 | .then(argument("path", StringArgumentType.string()) 29 | .executes(LoadCommand::loadOpener))) 30 | .then(literal("inventory") 31 | .then(argument("path", StringArgumentType.string()) 32 | .executes(c->LoadCommand.loadInventory(c, false)) 33 | .then(argument("override", BoolArgumentType.bool()) 34 | .executes(c->LoadCommand.loadInventory(c, BoolArgumentType.getBool(c, "override"))))))); 35 | } 36 | 37 | private static int loadOpener(CommandContext context) { 38 | var path = StringArgumentType.getString(context, "path"); 39 | var p = Path.of(path); 40 | 41 | try (var br = Files.newBufferedReader(p)) { 42 | var opener = Serializer.openerFromJson(br); 43 | ActionInventoryMod.OPENER_LOADER.addOpener(opener); 44 | context.getSource().sendFeedback(() -> Text.of("Loaded opener."), false); 45 | return 1; 46 | } catch (IOException e) { 47 | var msg = e.getMessage(); 48 | context.getSource().sendError(Text.of("Unable to read file: "+p+". "+(msg!=null?msg:""))); 49 | } catch (ValidationException e) { 50 | var msg = e.getMessage(); 51 | context.getSource().sendError(Text.of("Failed to create opener. "+(msg!=null?msg:""))); 52 | } 53 | return 0; 54 | } 55 | 56 | private static int loadInventory(CommandContext context, boolean override) { 57 | var path = StringArgumentType.getString(context, "path"); 58 | var p = Path.of(path); 59 | 60 | try (var br = Files.newBufferedReader(p)) { 61 | var builder = Serializer.builderFromJson(br); 62 | if (override || !ActionInventoryMod.INVENTORY_LOADER.hasBuilder(builder.getName())) { 63 | ActionInventoryMod.INVENTORY_LOADER.addBuilder(builder); 64 | context.getSource().sendFeedback(() -> Text.of("Loaded action inventory: "+builder.getName()), false); 65 | return 1; 66 | } 67 | 68 | context.getSource().sendError(Text.of("A loaded action inventory already has this name.")); 69 | } catch (IOException e) { 70 | var msg = e.getMessage(); 71 | context.getSource().sendError(Text.of("Unable to read file: "+p+". "+(msg!=null&&!msg.equals(p.toString())?msg:""))); 72 | } catch (ValidationException e) { 73 | var msg = e.getMessage(); 74 | context.getSource().sendError(Text.of("Failed to create action inventory. "+(msg!=null?msg:""))); 75 | } 76 | return 0; 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/commands/OpenCommand.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.commands; 2 | 3 | import static net.minecraft.server.command.CommandManager.argument; 4 | import static net.minecraft.server.command.CommandManager.literal; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | import com.mojang.brigadier.arguments.BoolArgumentType; 10 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 11 | import com.mojang.brigadier.context.CommandContext; 12 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 13 | import com.mojang.brigadier.suggestion.SuggestionProvider; 14 | 15 | import megaminds.actioninventory.ActionInventoryMod; 16 | import megaminds.actioninventory.util.CommandPermissions; 17 | import net.minecraft.command.CommandSource; 18 | import net.minecraft.command.argument.EntityArgumentType; 19 | import net.minecraft.command.argument.IdentifierArgumentType; 20 | import net.minecraft.server.command.ServerCommandSource; 21 | import net.minecraft.server.network.ServerPlayerEntity; 22 | import net.minecraft.text.Text; 23 | 24 | public class OpenCommand { 25 | private static final SuggestionProvider NAME_SUGGESTIONS = (c, b) -> CommandSource.suggestIdentifiers(ActionInventoryMod.INVENTORY_LOADER.builderNames(builder -> builder.canOpen(c.getSource().getPlayer())), b);; 26 | 27 | private OpenCommand() {} 28 | 29 | private static final String SILENT_ARG = "silent"; 30 | 31 | public static void register(LiteralArgumentBuilder root) { 32 | root.then(literal("open") 33 | .then(argument("guiName", IdentifierArgumentType.identifier()) 34 | .suggests(NAME_SUGGESTIONS) 35 | .executes(OpenCommand::open) 36 | .then(argument(SILENT_ARG, BoolArgumentType.bool()) 37 | .executes(OpenCommand::open)) 38 | .then(argument("targets", EntityArgumentType.players()) 39 | .requires(CommandPermissions.requires(root.getLiteral()+".open", 2)) 40 | .executes(OpenCommand::open) 41 | .then(argument(SILENT_ARG, BoolArgumentType.bool()) 42 | .executes(OpenCommand::open))))); 43 | } 44 | 45 | private static int open(CommandContext context) throws CommandSyntaxException { 46 | Collection targets; 47 | try { 48 | targets = EntityArgumentType.getOptionalPlayers(context, "targets"); 49 | } catch (IllegalArgumentException e) { 50 | targets = List.of(context.getSource().getPlayerOrThrow()); 51 | } 52 | 53 | boolean silent = false; 54 | try { 55 | silent = BoolArgumentType.getBool(context, SILENT_ARG); 56 | } catch (IllegalArgumentException e) { 57 | //default 58 | } 59 | 60 | var name = IdentifierArgumentType.getIdentifier(context, "guiName"); 61 | var builder = ActionInventoryMod.INVENTORY_LOADER.getBuilder(name); 62 | 63 | if (builder==null) { 64 | context.getSource().sendError(Text.of("No Action Inventory with name: "+name)); 65 | return 0; 66 | } 67 | 68 | var success = 0; 69 | for (var target : targets) { 70 | if (builder.buildAndOpen(target)) { 71 | success++; 72 | if (!silent) context.getSource().sendFeedback(() -> Text.literal("Opened "+name+" for ").append(target.getName()), false); 73 | } else if (!silent) { 74 | context.getSource().sendError(Text.literal("Failed to open "+name+" for ").append(target.getName())); 75 | } 76 | } 77 | return success; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/commands/RemoveCommand.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.commands; 2 | 3 | import static net.minecraft.server.command.CommandManager.literal; 4 | import static net.minecraft.server.command.CommandManager.argument; 5 | 6 | import com.mojang.brigadier.builder.LiteralArgumentBuilder; 7 | import com.mojang.brigadier.context.CommandContext; 8 | import com.mojang.brigadier.suggestion.SuggestionProvider; 9 | 10 | import megaminds.actioninventory.ActionInventoryMod; 11 | import megaminds.actioninventory.util.CommandPermissions; 12 | import net.minecraft.command.CommandSource; 13 | import net.minecraft.command.argument.IdentifierArgumentType; 14 | import net.minecraft.server.command.ServerCommandSource; 15 | import net.minecraft.text.Text; 16 | 17 | public class RemoveCommand { 18 | private static final SuggestionProvider NAME_SUGGESTIONS = (c, b)->CommandSource.suggestIdentifiers(ActionInventoryMod.INVENTORY_LOADER.builderNames(), b); 19 | 20 | private RemoveCommand() {} 21 | 22 | public static void register(LiteralArgumentBuilder root) { 23 | root.then(literal("remove") 24 | .requires(CommandPermissions.requires(root.getLiteral()+".remove", 4)) 25 | .then(argument("guiName", IdentifierArgumentType.identifier()) 26 | .suggests(NAME_SUGGESTIONS) 27 | .executes(RemoveCommand::remove))); 28 | } 29 | 30 | private static int remove(CommandContext context) { 31 | var name = IdentifierArgumentType.getIdentifier(context, "guiName"); 32 | 33 | if (!ActionInventoryMod.INVENTORY_LOADER.hasBuilder(name)) { 34 | context.getSource().sendError(Text.of("No Action Inventory with name: "+name)); 35 | return 0; 36 | } 37 | 38 | ActionInventoryMod.INVENTORY_LOADER.removeBuilder(name); 39 | context.getSource().sendFeedback(() -> Text.of("Removed Action Inventory with name: "+name), false); 40 | return 1; 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/consumables/BasicConsumable.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.consumables; 2 | 3 | import java.util.UUID; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import megaminds.actioninventory.serialization.wrappers.Validated; 9 | import megaminds.actioninventory.util.annotations.Poly; 10 | import net.fabricmc.fabric.api.util.TriState; 11 | import net.minecraft.nbt.NbtCompound; 12 | import net.minecraft.server.MinecraftServer; 13 | 14 | @Poly 15 | public abstract sealed class BasicConsumable implements Validated permits XpConsumable, IntConsumable { 16 | /**True->Full amount is needed to consume any, false->will consume as much as possible*/ 17 | private TriState requireFull = TriState.DEFAULT; 18 | 19 | protected BasicConsumable() {} 20 | 21 | /** 22 | * Returns true if the player has paid or can pay the full amount. 23 | */ 24 | public abstract boolean canConsumeFull(MinecraftServer server, UUID player, @Nullable NbtCompound storage); 25 | /** 26 | * Consumes maximum from player up to the correct amount. 27 | *
28 | * Returns true if the full amount was consumed. 29 | */ 30 | public abstract boolean consume(MinecraftServer server, UUID player, @NotNull NbtCompound storage); 31 | /** 32 | * Returns the name of the storage this consumable accesses 33 | */ 34 | public abstract String getStorageName(); 35 | 36 | public abstract BasicConsumable copy(); 37 | 38 | public TriState getRequireFull() { 39 | return requireFull; 40 | } 41 | 42 | public void setRequireFull(TriState requireFull) { 43 | this.requireFull = requireFull; 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/consumables/IntConsumable.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.consumables; 2 | 3 | import static megaminds.actioninventory.misc.Enums.COMPLETE; 4 | 5 | import java.util.UUID; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import megaminds.actioninventory.util.Helper; 11 | import net.minecraft.nbt.NbtCompound; 12 | import net.minecraft.server.MinecraftServer; 13 | 14 | /**@since 3.1*/ 15 | public abstract sealed class IntConsumable extends BasicConsumable permits ItemConsumable { 16 | private static final String AMOUNT_KEY = "amount"; 17 | /**This is the paid amount*/ 18 | private int amount; 19 | 20 | protected IntConsumable() {} 21 | 22 | protected IntConsumable(int amount) { 23 | this.amount = amount; 24 | } 25 | 26 | public boolean hasConsumedFull(@Nullable NbtCompound storage) { 27 | return Helper.getBoolean(storage, COMPLETE); 28 | } 29 | 30 | @Override 31 | public boolean canConsumeFull(MinecraftServer server, UUID player, @Nullable NbtCompound storage) { 32 | return Helper.getBoolean(storage, COMPLETE) || canConsumeFull(server, player, amount-Helper.getInt(storage, AMOUNT_KEY)); 33 | } 34 | 35 | @Override 36 | public boolean consume(MinecraftServer server, UUID player, @NotNull NbtCompound storage){ 37 | var requireFullB = getRequireFull().orElse(false); 38 | var hasPaid = Helper.getBoolean(storage, COMPLETE); 39 | 40 | var left = amount-storage.getInt(AMOUNT_KEY); 41 | 42 | //the full amount has not been paid and [the full amount is not required or (the full amount is required and can be paid)]. 43 | if (!hasPaid && (!requireFullB || canConsumeFull(server, player, left))) { 44 | left = consume(server, player, left); 45 | hasPaid = left<=0; 46 | } 47 | 48 | //the full amount was paid or has now been paid 49 | if (hasPaid) { 50 | storage.remove(AMOUNT_KEY); 51 | storage.putBoolean(COMPLETE, true); 52 | } else { 53 | storage.putInt(AMOUNT_KEY, amount-left); 54 | } 55 | 56 | return hasPaid; 57 | } 58 | 59 | public abstract boolean canConsumeFull(MinecraftServer server, UUID player, int leftToConsume); 60 | /** 61 | * Returns how much is left 62 | */ 63 | public abstract int consume(MinecraftServer server, UUID player, int leftToConsume); 64 | 65 | public int getAmount() { 66 | return amount; 67 | } 68 | 69 | public void setAmount(int amount) { 70 | this.amount = amount; 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/consumables/ItemConsumable.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.consumables; 2 | 3 | import java.util.Set; 4 | import java.util.UUID; 5 | 6 | import megaminds.actioninventory.misc.ItemStackish; 7 | import megaminds.actioninventory.misc.Enums.TagOption; 8 | import megaminds.actioninventory.serialization.wrappers.Validated; 9 | import megaminds.actioninventory.util.Helper; 10 | import megaminds.actioninventory.util.annotations.PolyName; 11 | import net.minecraft.server.MinecraftServer; 12 | import net.minecraft.util.Identifier; 13 | 14 | @PolyName("Item") 15 | public final class ItemConsumable extends IntConsumable { 16 | private ItemStackish stack; 17 | private Set tags; 18 | private TagOption tagOption; 19 | 20 | public ItemConsumable() {} 21 | 22 | public ItemConsumable(ItemStackish stack, Set tags, TagOption tagOption) { 23 | this.stack = stack; 24 | this.tags = tags; 25 | this.tagOption = tagOption; 26 | } 27 | 28 | public ItemConsumable(int amount, ItemStackish stack, Set tags, TagOption tagOption) { 29 | super(amount); 30 | this.stack = stack!=null ? stack : ItemStackish.MATCH_ALL; 31 | this.tags = tags; 32 | this.tagOption = tagOption!=null ? tagOption : TagOption.ANY; 33 | } 34 | 35 | @Override 36 | public boolean canConsumeFull(MinecraftServer server, UUID player, int left) { 37 | var p = Helper.getPlayer(server, player); 38 | var inv = p.getInventory(); 39 | 40 | for (int i = 0, size = inv.size(); i < size; i++) { 41 | var s = inv.getStack(i); 42 | if (stack.specifiedEquals(s) && (tags==null || tagOption.matches(tags, s.streamTags()))) { 43 | left -= s.getCount(); 44 | if (left<=0) return true; 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | @Override 51 | public int consume(MinecraftServer server, UUID player, int left) { 52 | var p = Helper.getPlayer(server, player); 53 | var inv = p.getInventory(); 54 | 55 | for (int i = 0, size = inv.size(); i < size; i++) { 56 | var s = inv.getStack(i); 57 | if (stack.specifiedEquals(s) && (tags==null || tagOption.matches(tags, s.streamTags()))) { 58 | var count = Math.min(s.getCount(), left); 59 | left -= count; 60 | s.setCount(s.getCount()-count); 61 | if (left<=0) break; 62 | } 63 | } 64 | return left; 65 | } 66 | 67 | @Override 68 | public void validate() { 69 | Validated.validate(getAmount()>=0, "Item consumable requires amount to be 0 or above, but it is: "+getAmount()); 70 | if (stack==null) stack = ItemStackish.MATCH_ALL; 71 | if (tagOption==null) tagOption = TagOption.ANY; 72 | } 73 | 74 | @Override 75 | public String getStorageName() { 76 | return "Item"; 77 | } 78 | 79 | @Override 80 | public BasicConsumable copy() { 81 | return new ItemConsumable(getAmount(), stack, tags == null ? null : Set.copyOf(tags), tagOption); 82 | } 83 | 84 | public ItemStackish getStack() { 85 | return stack; 86 | } 87 | 88 | public void setStack(ItemStackish stack) { 89 | this.stack = stack; 90 | } 91 | 92 | public Set getTags() { 93 | return tags; 94 | } 95 | 96 | public void setTags(Set tags) { 97 | this.tags = tags; 98 | } 99 | 100 | public TagOption getTagOption() { 101 | return tagOption; 102 | } 103 | 104 | public void setTagOption(TagOption tagOption) { 105 | this.tagOption = tagOption; 106 | } 107 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/consumables/XpConsumable.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.consumables; 2 | 3 | import static megaminds.actioninventory.misc.Enums.COMPLETE; 4 | 5 | import java.util.UUID; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import megaminds.actioninventory.misc.Enums; 11 | import megaminds.actioninventory.serialization.wrappers.Validated; 12 | import megaminds.actioninventory.util.Helper; 13 | import megaminds.actioninventory.util.annotations.PolyName; 14 | import net.minecraft.nbt.NbtCompound; 15 | import net.minecraft.server.MinecraftServer; 16 | 17 | @PolyName("Xp") 18 | public final class XpConsumable extends BasicConsumable { 19 | private static final String LEVEL_KEY = "level"; 20 | private static final String AMOUNT_KEY = "amount"; 21 | 22 | private int level; 23 | private int amount; 24 | 25 | public XpConsumable() {} 26 | 27 | public XpConsumable(int level, int amount) { 28 | super(); 29 | this.level = level; 30 | this.amount = amount; 31 | } 32 | 33 | @Override 34 | public boolean canConsumeFull(MinecraftServer server, UUID player, @Nullable NbtCompound storage) { 35 | if (Helper.getBoolean(storage, Enums.COMPLETE)) return true; 36 | 37 | var left = getLeft(storage); 38 | var p = Helper.getPlayer(server, player); 39 | 40 | var extra = p.experienceLevel-left[0]; 41 | if (extra<0) return false; 42 | return Helper.getTotalExperienceForLevel(extra)+p.experienceProgress*p.getNextLevelExperience() >= left[1]; 43 | } 44 | 45 | @Override 46 | public boolean consume(MinecraftServer server, UUID player, @NotNull NbtCompound storage){ 47 | var requireFullB = getRequireFull().orElse(false); 48 | var hasPaid = Helper.getBoolean(storage, COMPLETE); 49 | 50 | var left = getLeft(storage); 51 | 52 | //the full amount has not been paid and [the full amount is not required or (the full amount is required and can be paid)]. 53 | if (!hasPaid && (!requireFullB || canConsumeFull(server, player, storage))) { 54 | var p = Helper.getPlayer(server, player); 55 | var extra = p.experienceLevel - left[0]; 56 | if (extra<0) { 57 | left[0] -= p.experienceLevel; 58 | p.addExperienceLevels(-p.experienceLevel); 59 | } else { 60 | p.addExperienceLevels(-left[0]); 61 | left[0] = 0; 62 | 63 | if (Helper.getTotalExperienceForLevel(p.experienceLevel) >= left[1]) { 64 | p.addExperience(-left[1]); 65 | left[1] = 0; 66 | } else { 67 | left[1] -= Helper.getTotalExperienceForLevel(p.experienceLevel); 68 | p.addExperience(-Helper.getTotalExperienceForLevel(p.experienceLevel)); 69 | } 70 | } 71 | 72 | hasPaid = left[0]<=0 && left[1]<=0; 73 | } 74 | 75 | //the full amount was paid or has now been paid 76 | if (hasPaid) { 77 | storage.remove(LEVEL_KEY); 78 | storage.remove(AMOUNT_KEY); 79 | storage.putBoolean(COMPLETE, true); 80 | } else { 81 | storage.putInt(LEVEL_KEY, level-left[0]); 82 | storage.putInt(AMOUNT_KEY, amount-left[1]); 83 | } 84 | 85 | return hasPaid; 86 | } 87 | 88 | /** 89 | * Returns an array of form [levelLeft, amountLeft] 90 | */ 91 | private int[] getLeft(@Nullable NbtCompound storage) { 92 | if (Helper.getBoolean(storage, COMPLETE)) return new int[2]; 93 | return new int[] {level-Helper.getInt(storage, LEVEL_KEY), amount-Helper.getInt(storage, AMOUNT_KEY)}; 94 | } 95 | 96 | @Override 97 | public String getStorageName() { 98 | return "Xp"; 99 | } 100 | 101 | @Override 102 | public void validate() { 103 | Validated.validate(level>=0, "Xp consumable requires level to be 0 or above, but it is: "+level); 104 | Validated.validate(amount>=0, "Xp consumable requires amount to be 0 or above, but it is: "+amount); 105 | } 106 | 107 | @Override 108 | public BasicConsumable copy() { 109 | return new XpConsumable(level, amount); 110 | } 111 | 112 | public int getLevel() { 113 | return level; 114 | } 115 | 116 | public void setLevel(int level) { 117 | this.level = level; 118 | } 119 | 120 | public int getAmount() { 121 | return amount; 122 | } 123 | 124 | public void setAmount(int amount) { 125 | this.amount = amount; 126 | } 127 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/ActionInventoryBuilder.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import java.util.Arrays; 4 | 5 | import eu.pb4.placeholders.api.PlaceholderContext; 6 | import eu.pb4.placeholders.api.Placeholders; 7 | import eu.pb4.sgui.api.GuiHelpers; 8 | import megaminds.actioninventory.actions.BasicAction; 9 | import megaminds.actioninventory.actions.EmptyAction; 10 | import megaminds.actioninventory.actions.RequirementAction; 11 | import megaminds.actioninventory.gui.elements.SlotElement; 12 | import megaminds.actioninventory.serialization.wrappers.Validated; 13 | import megaminds.actioninventory.util.MessageHelper; 14 | import megaminds.actioninventory.util.ValidationException; 15 | import megaminds.actioninventory.util.annotations.Exclude; 16 | import net.fabricmc.fabric.api.util.TriState; 17 | import net.minecraft.registry.Registries; 18 | import net.minecraft.screen.ScreenHandlerType; 19 | import net.minecraft.server.network.ServerPlayerEntity; 20 | import net.minecraft.text.Text; 21 | import net.minecraft.util.Identifier; 22 | 23 | /** 24 | * Adapted from {@link eu.pb4.sgui.api.gui.SimpleGuiBuilder}. 25 | */ 26 | public final class ActionInventoryBuilder implements Validated { 27 | private static final SlotElement[] EMPTY = new SlotElement[0]; 28 | 29 | private ScreenHandlerType type; 30 | private Identifier name; 31 | private Text title; 32 | private TriState includePlayer = TriState.DEFAULT; 33 | private TriState lockPlayerInventory = TriState.DEFAULT; 34 | 35 | private SlotElement[] elements; 36 | /**@since 3.1*/ 37 | private BasicAction openAction; 38 | /**@since 3.1*/ 39 | private BasicAction closeAction; 40 | /**@since 3.1*/ 41 | private BasicAction anyClickAction; 42 | /**@since 3.1*/ 43 | private BasicAction recipeAction; 44 | /**@since 3.6.3*/ 45 | private RequirementAction checkOpenAction; 46 | /**@since 3.2*/ 47 | private TriState chained = TriState.DEFAULT; 48 | 49 | @Exclude private int size; 50 | 51 | private ActionInventoryBuilder() {} 52 | 53 | @Override 54 | public void validate() { 55 | Validated.validate(type!=null, "ActionInventories requires type to be non-null."); 56 | Validated.validate(name!=null, "ActionInventories requires name to be non-null."); 57 | if (title==null) title = Text.empty(); 58 | if (openAction==null) openAction = EmptyAction.INSTANCE; 59 | if (closeAction==null) closeAction = EmptyAction.INSTANCE; 60 | if (anyClickAction==null) anyClickAction = EmptyAction.INSTANCE; 61 | if (recipeAction==null) recipeAction = EmptyAction.INSTANCE; 62 | 63 | size = GuiHelpers.getHeight(type)*GuiHelpers.getWidth(type) + (includePlayer.orElse(false) ? 36 : 0); 64 | 65 | if (elements==null) { 66 | elements = EMPTY; 67 | return; 68 | } 69 | var len = elements.length; 70 | Validated.validate(len<=size, ()->"Too many elements. Screen handler type "+Registries.SCREEN_HANDLER.getId(type)+" requires there to be a maximum of "+size+" SlotElements"); 71 | var test = new boolean[size]; 72 | for (var e : elements) { 73 | if (e!=null) { 74 | var i = e.getIndex(); 75 | Validated.validate(i"Screen handler type "+Registries.SCREEN_HANDLER.getId(type)+" requires SlotElements to have an index below "+size); 76 | if (i>=0) { 77 | Validated.validate(!test[i], "A slot has declared an already used index: "+i); 78 | test[i] = true; 79 | } 80 | } 81 | } 82 | } 83 | 84 | public ActionInventoryBuilder(ScreenHandlerType type, Identifier name, TriState includePlayerInventorySlots) { 85 | this.type = type; 86 | this.name = name; 87 | this.includePlayer = includePlayerInventorySlots; 88 | this.lockPlayerInventory = includePlayerInventorySlots; 89 | 90 | this.title = Text.empty(); 91 | this.closeAction = EmptyAction.INSTANCE; 92 | this.openAction = EmptyAction.INSTANCE; 93 | this.anyClickAction = EmptyAction.INSTANCE; 94 | 95 | this.size = GuiHelpers.getHeight(type)*GuiHelpers.getWidth(type) + (includePlayer.orElse(false) ? 36 : 0); 96 | this.elements = new SlotElement[this.size]; 97 | } 98 | 99 | /** 100 | * {@link #validate()} is called when using this constructor 101 | */ 102 | @SuppressWarnings("java:S107") 103 | public ActionInventoryBuilder(ScreenHandlerType type, Identifier name, Text title, TriState includePlayerInventorySlots, SlotElement[] elements, BasicAction openAction, BasicAction closeAction, BasicAction anyClickAction, RequirementAction checkOpenAction) throws ValidationException { 104 | this.type = type; 105 | this.name = name; 106 | this.title = title; 107 | this.includePlayer = includePlayerInventorySlots; 108 | this.lockPlayerInventory = includePlayerInventorySlots; 109 | this.elements = elements; 110 | this.closeAction = closeAction; 111 | this.openAction = openAction; 112 | this.anyClickAction = anyClickAction; 113 | this.checkOpenAction = checkOpenAction; 114 | 115 | this.size = GuiHelpers.getHeight(type)*GuiHelpers.getWidth(type) + (includePlayer.orElse(false) ? 36 : 0); 116 | 117 | validate(); 118 | } 119 | 120 | public ActionInventoryGui forceBuild(ServerPlayerEntity player) { 121 | ActionInventoryGui gui; 122 | if (ScreenHandlerType.ANVIL.equals(type)) { 123 | gui = new AnvilActionInventoryGui(player, includePlayer.orElse(false), name, openAction, closeAction, anyClickAction, recipeAction); 124 | } else { 125 | gui = new SimpleActionInventoryGui(type, player, includePlayer.orElse(false), name, openAction, closeAction, anyClickAction, recipeAction); 126 | } 127 | gui.setTitle(Placeholders.parseText(title, PlaceholderContext.of(player))); 128 | gui.setLockPlayerInventory(lockPlayerInventory.orElse(false)); 129 | gui.setChained(chained.orElse(false)); 130 | 131 | for (var element : elements) { 132 | if (element != null) { 133 | element.copy().apply(gui, player); 134 | } 135 | } 136 | 137 | return gui; 138 | } 139 | 140 | public boolean buildAndOpen(ServerPlayerEntity player) { 141 | if (!canOpen(player)) { 142 | player.sendMessage(MessageHelper.toError("You don't meet the requirements to open action inventory of name: "+name)); 143 | return false; 144 | } 145 | 146 | return forceBuild(player).open(); 147 | } 148 | 149 | public boolean canOpen(ServerPlayerEntity player) { 150 | return player == null || checkOpenAction == null || checkOpenAction.test(player); 151 | } 152 | 153 | public void setLockPlayerInventory(TriState lockPlayerInventory) { 154 | if (!includePlayer.orElse(false)) this.lockPlayerInventory = lockPlayerInventory; 155 | } 156 | 157 | public ActionInventoryBuilder copy() { 158 | var builder = new ActionInventoryBuilder(); 159 | builder.type = type; 160 | builder.name = name; 161 | builder.title = title.copy(); 162 | builder.includePlayer = includePlayer; 163 | builder.lockPlayerInventory = lockPlayerInventory; 164 | builder.elements = Arrays.stream(elements).map(SlotElement::copy).toArray(SlotElement[]::new); 165 | builder.anyClickAction = anyClickAction; 166 | builder.closeAction = closeAction; 167 | builder.openAction = openAction; 168 | builder.recipeAction = recipeAction; 169 | 170 | builder.size = size; 171 | return builder; 172 | } 173 | 174 | @SuppressWarnings("java:S1452")//I don't know how to do this without wildcard 175 | public ScreenHandlerType getType() { 176 | return type; 177 | } 178 | 179 | public Identifier getName() { 180 | return name; 181 | } 182 | 183 | public Text getTitle() { 184 | return title; 185 | } 186 | 187 | public TriState getIncludePlayer() { 188 | return includePlayer; 189 | } 190 | 191 | public TriState getLockPlayerInventory() { 192 | return lockPlayerInventory; 193 | } 194 | 195 | public SlotElement[] getElements() { 196 | return elements; 197 | } 198 | 199 | public BasicAction getOpenAction() { 200 | return openAction; 201 | } 202 | 203 | public BasicAction getCloseAction() { 204 | return closeAction; 205 | } 206 | 207 | public BasicAction getAnyClickAction() { 208 | return anyClickAction; 209 | } 210 | 211 | public BasicAction getRecipeAction() { 212 | return recipeAction; 213 | } 214 | 215 | public TriState getChained() { 216 | return chained; 217 | } 218 | 219 | public int getSize() { 220 | return size; 221 | } 222 | 223 | public void setTitle(Text title) { 224 | this.title = title; 225 | } 226 | 227 | public void setElements(SlotElement[] elements) { 228 | this.elements = elements; 229 | } 230 | 231 | public void setOpenAction(BasicAction openAction) { 232 | this.openAction = openAction; 233 | } 234 | 235 | public void setCloseAction(BasicAction closeAction) { 236 | this.closeAction = closeAction; 237 | } 238 | 239 | public void setAnyClickAction(BasicAction anyClickAction) { 240 | this.anyClickAction = anyClickAction; 241 | } 242 | 243 | public void setRecipeAction(BasicAction recipeAction) { 244 | this.recipeAction = recipeAction; 245 | } 246 | 247 | public void setChained(TriState chained) { 248 | this.chained = chained; 249 | } 250 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/ActionInventoryGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import eu.pb4.sgui.api.ClickType; 4 | import megaminds.actioninventory.actions.BasicAction; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.recipe.RecipeEntry; 7 | import net.minecraft.screen.slot.SlotActionType; 8 | import net.minecraft.util.Identifier; 9 | 10 | public interface ActionInventoryGui extends BetterGuiI { 11 | public void setLastClicked(ItemStack stack); 12 | public void setLastAction(String action); 13 | 14 | /** 15 | * Returns the last clicked stack. Currently only used for GiveAction. 16 | */ 17 | public ItemStack getLastClicked(); 18 | 19 | /** 20 | * Returns the name of the last action. Currently only used for ConsumableAction. 21 | */ 22 | public String getLastAction(); 23 | 24 | /**@since 3.1*/ 25 | @Override 26 | default void onClose() { 27 | setLastClicked(ItemStack.EMPTY); 28 | setLastAction("onClose"); 29 | getCloseAction().accept(this); 30 | } 31 | 32 | /**@since 3.1*/ 33 | @Override 34 | default void onOpen() { 35 | setLastClicked(ItemStack.EMPTY); 36 | setLastAction("onOpen"); 37 | getOpenAction().accept(this); 38 | } 39 | 40 | /**@since 3.1*/ 41 | @Override 42 | default boolean onAnyClick(int index, ClickType type, SlotActionType action) { 43 | setLastClicked(getStack(index)); 44 | setLastAction("onAnyClick"); 45 | getAnyClickAction().click(index, type, action, this); 46 | setLastAction("click"+index); 47 | return BetterGuiI.super.onAnyClick(index, type, action); 48 | } 49 | 50 | /**@since 3.1*/ 51 | default void onCraftRequest(Identifier recipe, boolean shift) { 52 | var server = getPlayer().getServer(); 53 | setLastClicked(server.getRecipeManager().get(recipe).map(RecipeEntry::value).map(r -> r.getResult(server.getRegistryManager())).orElse(ItemStack.EMPTY)); 54 | setLastAction("onCraft"); 55 | getRecipeAction().onRecipe(recipe, shift, this); 56 | } 57 | 58 | /** 59 | * Returns the stack at the current slot. 60 | */ 61 | private ItemStack getStack(int slot) { 62 | var e = getSlot(slot); 63 | if (e!=null) return e.getItemStack(); 64 | 65 | var r = getSlotRedirect(slot); 66 | if (r!=null) return r.getStack(); 67 | 68 | return ItemStack.EMPTY; 69 | } 70 | 71 | public BasicAction getOpenAction(); 72 | public BasicAction getCloseAction(); 73 | public BasicAction getAnyClickAction(); 74 | public BasicAction getRecipeAction(); 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/AnvilActionInventoryGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import megaminds.actioninventory.actions.BasicAction; 4 | import megaminds.actioninventory.actions.EmptyAction; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.server.network.ServerPlayerEntity; 7 | import net.minecraft.util.Identifier; 8 | 9 | public class AnvilActionInventoryGui extends BetterAnvilGui implements ActionInventoryGui { 10 | private final BasicAction openAction; 11 | private final BasicAction closeAction; 12 | private final BasicAction anyClickAction; 13 | private final BasicAction recipeAction; 14 | private ItemStack lastClicked; 15 | private String lastAction; 16 | 17 | public AnvilActionInventoryGui(ServerPlayerEntity player, boolean includePlayerInventorySlots, Identifier name) { 18 | this(player, includePlayerInventorySlots, name, EmptyAction.INSTANCE, EmptyAction.INSTANCE, EmptyAction.INSTANCE, EmptyAction.INSTANCE); 19 | } 20 | 21 | public AnvilActionInventoryGui(ServerPlayerEntity player, boolean includePlayerInventorySlots, Identifier name, BasicAction openAction, BasicAction closeAction, BasicAction anyClickAction, BasicAction recipeAction) { 22 | super(player, includePlayerInventorySlots); 23 | this.setId(name); 24 | this.openAction = openAction; 25 | this.closeAction = closeAction; 26 | this.anyClickAction = anyClickAction; 27 | this.recipeAction = recipeAction; 28 | } 29 | 30 | @Override 31 | public void setLastClicked(ItemStack stack) { 32 | lastClicked = stack.copy(); 33 | } 34 | 35 | @Override 36 | public void setLastAction(String action) { 37 | lastAction = action; 38 | } 39 | 40 | /** 41 | * Returns the last clicked stack. Currently only used for GiveAction. 42 | */ 43 | @Override 44 | public ItemStack getLastClicked() { 45 | return lastClicked.copy(); 46 | } 47 | 48 | /** 49 | * Returns the name of the last action. Currently only used for ConsumableAction. 50 | */ 51 | @Override 52 | public String getLastAction() { 53 | return lastAction; 54 | } 55 | 56 | @Override 57 | public BasicAction getOpenAction() { 58 | return openAction; 59 | } 60 | 61 | @Override 62 | public BasicAction getCloseAction() { 63 | return closeAction; 64 | } 65 | 66 | @Override 67 | public BasicAction getAnyClickAction() { 68 | return anyClickAction; 69 | } 70 | 71 | @Override 72 | public BasicAction getRecipeAction() { 73 | return recipeAction; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/BetterAnvilGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import java.util.function.Consumer; 4 | import java.util.function.Function; 5 | import java.util.function.UnaryOperator; 6 | 7 | import eu.pb4.sgui.api.gui.AnvilInputGui; 8 | import megaminds.actioninventory.gui.callback.BetterClickCallback; 9 | import net.minecraft.server.network.ServerPlayerEntity; 10 | import net.minecraft.util.Identifier; 11 | 12 | public class BetterAnvilGui extends AnvilInputGui implements BetterGuiI { 13 | private Identifier id; 14 | /** 15 | * Note, this is set on open. 16 | */ 17 | private BetterGuiI previousGui; 18 | private boolean chained; 19 | /** 20 | * Happens every character. 21 | */ 22 | private UnaryOperator filter; 23 | /** 24 | * Happens before finishing. 25 | */ 26 | private UnaryOperator postFilter; 27 | private Consumer onFinish; 28 | 29 | public BetterAnvilGui(ServerPlayerEntity player, boolean includePlayerInventorySlots) { 30 | super(player, includePlayerInventorySlots); 31 | //setSlot(1, ElementHelper.getCancel(null)); 32 | //setSlot(2, ElementHelper.getConfirm(CONFIRM)); 33 | } 34 | 35 | private static final BetterClickCallback CONFIRM = (i,t,a,g)->{ 36 | if (g instanceof BetterAnvilGui v) { 37 | if (v.doFilter(v.getInput(), v.postFilter)) { 38 | if (v.onFinish!=null) v.onFinish.accept(v.getInput()); 39 | } else { 40 | return true; 41 | } 42 | } 43 | return false; 44 | }; 45 | 46 | @Override 47 | public void onInput(String input) { 48 | doFilter(input, filter); 49 | } 50 | 51 | /** 52 | * True if input passed the filter. 53 | */ 54 | private boolean doFilter(String input, UnaryOperator filter) { 55 | String filtered; 56 | if (filter!=null) { 57 | filtered = filter.apply(input); 58 | if (!input.equals(filtered)) { 59 | setDefaultInputValue(filtered); 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | @Override 67 | public boolean reOpen() { 68 | if (isOpen()) sendGui(); 69 | return false; 70 | } 71 | 72 | @Override 73 | public boolean open(BetterGuiI previous) { 74 | previousGui = previous; 75 | return open(); 76 | } 77 | 78 | @Override 79 | public void clearPrevious() { 80 | previousGui = null; 81 | } 82 | 83 | @Override 84 | public Identifier getId() { 85 | return id; 86 | } 87 | 88 | @Override 89 | public void setId(Identifier id) { 90 | this.id = id; 91 | } 92 | 93 | @Override 94 | public BetterGuiI getPreviousGui() { 95 | return previousGui; 96 | } 97 | 98 | @Override 99 | public boolean isChained() { 100 | return chained; 101 | } 102 | 103 | @Override 104 | public void setChained(boolean chained) { 105 | this.chained = chained; 106 | } 107 | 108 | public Function getFilter() { 109 | return filter; 110 | } 111 | 112 | public void setFilter(UnaryOperator filter) { 113 | this.filter = filter; 114 | } 115 | 116 | public Function getPostFilter() { 117 | return postFilter; 118 | } 119 | 120 | public void setPostFilter(UnaryOperator postFilter) { 121 | this.postFilter = postFilter; 122 | } 123 | 124 | public Consumer getOnFinish() { 125 | return onFinish; 126 | } 127 | 128 | public void setOnFinish(Consumer onFinish) { 129 | this.onFinish = onFinish; 130 | } 131 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/BetterGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import eu.pb4.sgui.api.gui.SimpleGui; 4 | import net.minecraft.screen.ScreenHandlerType; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | import net.minecraft.util.Identifier; 7 | 8 | public class BetterGui extends SimpleGui implements BetterGuiI { 9 | private Identifier id; 10 | 11 | /** 12 | * Note, this is set on open. 13 | */ 14 | private BetterGuiI previousGui; 15 | private boolean chained; 16 | 17 | public BetterGui(ScreenHandlerType type, ServerPlayerEntity player, boolean includePlayerInventorySlots) { 18 | super(type, player, includePlayerInventorySlots); 19 | } 20 | 21 | @Override 22 | public boolean open(BetterGuiI previous) { 23 | this.previousGui = previous; 24 | return open(); 25 | } 26 | 27 | @Override 28 | public void clearPrevious() { 29 | previousGui = null; 30 | } 31 | 32 | @Override 33 | public boolean reOpen() { 34 | if (isOpen()) sendGui(); 35 | return false; 36 | } 37 | 38 | @Override 39 | public Identifier getId() { 40 | return id; 41 | } 42 | 43 | @Override 44 | public void setId(Identifier id) { 45 | this.id = id; 46 | } 47 | 48 | @Override 49 | public BetterGuiI getPreviousGui() { 50 | return previousGui; 51 | } 52 | 53 | @Override 54 | public boolean isChained() { 55 | return chained; 56 | } 57 | 58 | @Override 59 | public void setChained(boolean chained) { 60 | this.chained = chained; 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/BetterGuiI.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import eu.pb4.sgui.api.gui.SlotGuiInterface; 4 | import net.minecraft.util.Identifier; 5 | 6 | public interface BetterGuiI extends SlotGuiInterface { 7 | /** 8 | * Sets this gui's id. Can be null. 9 | */ 10 | void setId(Identifier id); 11 | /** 12 | * Returns this gui's id. Can be null. 13 | */ 14 | Identifier getId(); 15 | /** 16 | * Returns the previous gui or null if there was no previous. 17 | */ 18 | BetterGuiI getPreviousGui(); 19 | /** 20 | * Sets whether the gui should open the previous gui on close. 21 | */ 22 | void setChained(boolean chain); 23 | /** 24 | * Returns whether the gui should open the previous gui on close. 25 | */ 26 | boolean isChained(); 27 | /** 28 | * Opens this gui and sets the previous gui to the given one. 29 | */ 30 | boolean open(BetterGuiI previous); 31 | /** 32 | * Should set previous to null. 33 | */ 34 | void clearPrevious(); 35 | 36 | /** 37 | * Resends the gui if it is already open, otherwise nothing. 38 | *
39 | * Returns true is successful 40 | */ 41 | boolean reOpen(); 42 | 43 | /** 44 | * {@inheritDoc} 45 | *
46 | * Reopens previous gui. 47 | */ 48 | @Override 49 | default void onClose() { 50 | BetterGuiI lastGui = getPreviousGui(); 51 | if (isChained() && lastGui!=null) { 52 | clearPrevious(); 53 | lastGui.reOpen(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/SimpleActionInventoryGui.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import megaminds.actioninventory.actions.BasicAction; 4 | import megaminds.actioninventory.actions.EmptyAction; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.screen.ScreenHandlerType; 7 | import net.minecraft.server.network.ServerPlayerEntity; 8 | import net.minecraft.util.Identifier; 9 | 10 | public class SimpleActionInventoryGui extends BetterGui implements ActionInventoryGui { 11 | /**@since 3.1*/ 12 | private final BasicAction openAction; 13 | /**@since 3.1*/ 14 | private final BasicAction closeAction; 15 | /**@since 3.1*/ 16 | private final BasicAction anyClickAction; 17 | /**@since 3.1*/ 18 | private final BasicAction recipeAction; 19 | /**@since 3.1*/ 20 | private ItemStack lastClicked; 21 | /**@since 3.1*/ 22 | private String lastAction; 23 | 24 | public SimpleActionInventoryGui(ScreenHandlerType type, ServerPlayerEntity player, boolean includePlayerInventorySlots, Identifier name) { 25 | this(type, player, includePlayerInventorySlots, name, EmptyAction.INSTANCE, EmptyAction.INSTANCE, EmptyAction.INSTANCE, EmptyAction.INSTANCE); 26 | } 27 | 28 | public SimpleActionInventoryGui(ScreenHandlerType type, ServerPlayerEntity player, boolean includePlayerInventorySlots, Identifier name, BasicAction openAction, BasicAction closeAction, BasicAction anyClickAction, BasicAction recipeAction) { 29 | super(type, player, includePlayerInventorySlots); 30 | this.setId(name); 31 | this.openAction = openAction; 32 | this.closeAction = closeAction; 33 | this.anyClickAction = anyClickAction; 34 | this.recipeAction = recipeAction; 35 | } 36 | 37 | @Override 38 | public void setLastClicked(ItemStack stack) { 39 | lastClicked = stack.copy(); 40 | } 41 | 42 | @Override 43 | public void setLastAction(String action) { 44 | lastAction = action; 45 | } 46 | 47 | /** 48 | * Returns the last clicked stack. Currently only used for GiveAction. 49 | */ 50 | @Override 51 | public ItemStack getLastClicked() { 52 | return lastClicked.copy(); 53 | } 54 | 55 | /** 56 | * Returns the name of the last action. Currently only used for ConsumableAction. 57 | */ 58 | @Override 59 | public String getLastAction() { 60 | return lastAction; 61 | } 62 | 63 | @Override 64 | public BasicAction getOpenAction() { 65 | return openAction; 66 | } 67 | 68 | @Override 69 | public BasicAction getCloseAction() { 70 | return closeAction; 71 | } 72 | 73 | @Override 74 | public BasicAction getAnyClickAction() { 75 | return anyClickAction; 76 | } 77 | 78 | @Override 79 | public BasicAction getRecipeAction() { 80 | return recipeAction; 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/VirtualPlayerInventory.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElementBuilder; 4 | import eu.pb4.sgui.api.gui.SimpleGui; 5 | import net.minecraft.enchantment.EnchantmentHelper; 6 | import net.minecraft.entity.EquipmentSlot; 7 | import net.minecraft.entity.LivingEntity; 8 | import net.minecraft.entity.player.PlayerEntity; 9 | import net.minecraft.entity.player.PlayerInventory; 10 | import net.minecraft.inventory.Inventory; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.item.Items; 13 | import net.minecraft.screen.ScreenHandlerType; 14 | import net.minecraft.screen.slot.Slot; 15 | import net.minecraft.server.network.ServerPlayerEntity; 16 | import net.minecraft.text.Text; 17 | 18 | public class VirtualPlayerInventory extends SimpleGui { 19 | public VirtualPlayerInventory(ServerPlayerEntity player, boolean includePlayerInventorySlots, ServerPlayerEntity toLookAt) { 20 | super(ScreenHandlerType.GENERIC_9X6, player, includePlayerInventorySlots); 21 | PlayerInventory inv = toLookAt.getInventory(); 22 | setTitle(toLookAt.getName().copy().append(Text.of("'s Inventory"))); 23 | 24 | GuiElementBuilder builder = new GuiElementBuilder(Items.BLACK_STAINED_GLASS_PANE); 25 | int i = 0; 26 | for (; i < PlayerInventory.MAIN_SIZE-PlayerInventory.getHotbarSize(); i++) { 27 | this.setSlotRedirect(i, new Slot(inv, i+PlayerInventory.getHotbarSize(), 0, 0)); 28 | } 29 | for (int j = 0; j < PlayerInventory.getHotbarSize(); j++, i++) { 30 | this.setSlotRedirect(i, new Slot(inv, j, 0, 0)); 31 | } 32 | this.setSlot(i, builder.setName(Text.of("Armor")).build()); 33 | i++; 34 | for (int j = PlayerInventory.MAIN_SIZE+PlayerInventory.ARMOR_SLOTS.length-1, k = 0; j>=PlayerInventory.MAIN_SIZE; j--, k++, i++) { 35 | this.setSlotRedirect(i, new ArmorSlot(inv, j, k)); 36 | } 37 | this.setSlot(i, builder.setName(Text.of("Offhand")).build()); 38 | i++; 39 | this.setSlotRedirect(i, new Slot(inv, PlayerInventory.OFF_HAND_SLOT, 0, 0)); 40 | i++; 41 | builder.setName(Text.empty()); 42 | for(; i < this.getSize(); i++) { 43 | this.setSlot(i, builder.build()); 44 | } 45 | } 46 | 47 | private class ArmorSlot extends Slot { 48 | private EquipmentSlot equipmentSlot; 49 | ArmorSlot(Inventory inv, int index, int armorIndex) { 50 | super(inv, index, 0, 0); 51 | this.equipmentSlot = EquipmentSlot.fromTypeIndex(EquipmentSlot.Type.ARMOR, 3-armorIndex); 52 | } 53 | @Override 54 | public int getMaxItemCount() { 55 | return 1; 56 | } 57 | 58 | @Override 59 | public boolean canInsert(ItemStack stack) { 60 | return equipmentSlot == LivingEntity.getPreferredEquipmentSlot(stack); 61 | } 62 | 63 | @Override 64 | public boolean canTakeItems(PlayerEntity playerEntity) { 65 | ItemStack itemStack = this.getStack(); 66 | if (!itemStack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemStack)) { 67 | return false; 68 | } 69 | return super.canTakeItems(playerEntity); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/callback/ActionInventoryCallback.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.callback; 2 | 3 | import eu.pb4.sgui.api.ClickType; 4 | import megaminds.actioninventory.gui.ActionInventoryGui; 5 | import megaminds.actioninventory.gui.BetterGuiI; 6 | import net.minecraft.screen.slot.SlotActionType; 7 | 8 | public interface ActionInventoryCallback extends BetterClickCallback { 9 | @Override 10 | default boolean cancellingClick(int index, ClickType type, SlotActionType action, BetterGuiI gui) { 11 | if (gui instanceof ActionInventoryGui g) { 12 | return cancellingClick(index, type, action, g); 13 | } 14 | return false; 15 | } 16 | 17 | boolean cancellingClick(int index, ClickType type, SlotActionType action, ActionInventoryGui gui); 18 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/callback/BetterClickCallback.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.callback; 2 | 3 | import eu.pb4.sgui.api.ClickType; 4 | import eu.pb4.sgui.api.gui.SlotGuiInterface; 5 | import megaminds.actioninventory.gui.BetterGuiI; 6 | import net.minecraft.screen.slot.SlotActionType; 7 | 8 | public interface BetterClickCallback extends CancellableCallback { 9 | @Override 10 | default boolean cancellingClick(int index, ClickType type, SlotActionType action, SlotGuiInterface gui) { 11 | if (gui instanceof BetterGuiI g) { 12 | return cancellingClick(index, type, action, g); 13 | } 14 | return false; 15 | } 16 | 17 | boolean cancellingClick(int index, ClickType type, SlotActionType action, BetterGuiI gui); 18 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/callback/CancellableCallback.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.callback; 2 | 3 | import eu.pb4.sgui.api.ClickType; 4 | import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; 5 | import eu.pb4.sgui.api.gui.SlotGuiInterface; 6 | import net.minecraft.screen.slot.SlotActionType; 7 | 8 | public interface CancellableCallback extends ClickCallback { 9 | @Override 10 | default void click(int index, ClickType type, SlotActionType action, SlotGuiInterface gui) { 11 | cancellingClick(index, type, action, gui); 12 | } 13 | 14 | /** 15 | * Returns true if further callbacks should be cancelled. 16 | */ 17 | boolean cancellingClick(int index, ClickType type, SlotActionType action, SlotGuiInterface gui); 18 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/AccessableAnimatedGuiElement.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import java.util.Arrays; 4 | 5 | import eu.pb4.placeholders.api.PlaceholderContext; 6 | import eu.pb4.sgui.api.gui.GuiInterface; 7 | import megaminds.actioninventory.ActionInventoryMod; 8 | import megaminds.actioninventory.actions.BasicAction; 9 | import megaminds.actioninventory.serialization.wrappers.Validated; 10 | import megaminds.actioninventory.util.Helper; 11 | import megaminds.actioninventory.util.annotations.Exclude; 12 | import megaminds.actioninventory.util.annotations.PolyName; 13 | import net.fabricmc.fabric.api.util.TriState; 14 | import net.minecraft.item.ItemStack; 15 | 16 | /** 17 | * Adapted from {@link eu.pb4.sgui.api.elements.AnimatedGuiElement} 18 | */ 19 | @PolyName("Animated") 20 | public final class AccessableAnimatedGuiElement extends AccessableElement { 21 | private static final ItemStack[] EMPTY = {ItemStack.EMPTY}; 22 | 23 | private ItemStack[] items; 24 | private int interval; 25 | private TriState random = TriState.DEFAULT; 26 | 27 | @Exclude private int frame; 28 | @Exclude private int tick; 29 | 30 | public AccessableAnimatedGuiElement() {} 31 | 32 | public AccessableAnimatedGuiElement(int index, BasicAction action, ItemStack[] items, int interval, TriState random) { 33 | super(index, action); 34 | this.items = items; 35 | this.interval = interval; 36 | this.random = random; 37 | } 38 | 39 | @Override 40 | public void validate() { 41 | super.validate(); 42 | Validated.validate(interval>=0, "Animated gui element requires interval to be 0 or greater."); 43 | if (items==null) items = EMPTY; 44 | } 45 | 46 | @Override 47 | public ItemStack getItemStackForDisplay(GuiInterface gui) { 48 | if (items.length==1) return items[0]; 49 | 50 | var cFrame = frame; 51 | 52 | tick += 1; 53 | if (tick >= interval) { 54 | tick = 0; 55 | if (random.orElse(false)) { 56 | this.frame = ActionInventoryMod.RANDOM.nextInt(items.length); 57 | } else { 58 | frame += 1; 59 | if (frame >= items.length) frame = 0; 60 | } 61 | } 62 | 63 | var stack = Helper.parseItemStack(this.items[cFrame].copy(), PlaceholderContext.of(gui.getPlayer())); 64 | lastDisplayed = stack; 65 | return stack; 66 | } 67 | 68 | @Override 69 | public SlotElement copy() { 70 | return new AccessableAnimatedGuiElement(getIndex(), getGuiCallback().copy(), Arrays.stream(items).map(ItemStack::copy).toArray(ItemStack[]::new), interval, random); 71 | } 72 | 73 | public ItemStack[] getItems() { 74 | return items; 75 | } 76 | 77 | public void setItems(ItemStack[] items) { 78 | this.items = items; 79 | } 80 | 81 | public int getInterval() { 82 | return interval; 83 | } 84 | 85 | public void setInterval(int interval) { 86 | this.interval = interval; 87 | } 88 | 89 | public TriState getRandom() { 90 | return random; 91 | } 92 | 93 | public void setRandom(TriState random) { 94 | this.random = random; 95 | } 96 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/AccessableElement.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElementInterface; 4 | import megaminds.actioninventory.actions.BasicAction; 5 | import megaminds.actioninventory.actions.EmptyAction; 6 | import megaminds.actioninventory.gui.ActionInventoryGui; 7 | import megaminds.actioninventory.serialization.wrappers.Validated; 8 | import megaminds.actioninventory.util.annotations.Exclude; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | 12 | public abstract sealed class AccessableElement extends SlotElement implements GuiElementInterface, Validated permits AccessableGuiElement, AccessableAnimatedGuiElement { 13 | private BasicAction action; 14 | @Exclude 15 | protected ItemStack lastDisplayed; 16 | 17 | protected AccessableElement() {} 18 | 19 | protected AccessableElement(int index, BasicAction action) { 20 | super(index); 21 | this.action = action; 22 | } 23 | 24 | @Override 25 | public void validate() { 26 | if (action==null) action = EmptyAction.INSTANCE; 27 | } 28 | 29 | @Override 30 | public BasicAction getGuiCallback() { 31 | return action; 32 | } 33 | 34 | @Override 35 | public void apply(ActionInventoryGui gui, ServerPlayerEntity p) { 36 | gui.setSlot(getCheckedIndex(gui), this); 37 | } 38 | 39 | public void setAction(BasicAction action) { 40 | this.action = action; 41 | } 42 | 43 | @Override 44 | public ItemStack getItemStack() { 45 | return lastDisplayed; 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/AccessableGuiElement.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import eu.pb4.placeholders.api.PlaceholderContext; 4 | import eu.pb4.sgui.api.gui.GuiInterface; 5 | import megaminds.actioninventory.actions.BasicAction; 6 | import megaminds.actioninventory.util.Helper; 7 | import megaminds.actioninventory.util.annotations.PolyName; 8 | import net.minecraft.item.ItemStack; 9 | 10 | /** 11 | * Adapted from {@link eu.pb4.sgui.api.elements.GuiElement} 12 | */ 13 | @PolyName("Normal") 14 | public final class AccessableGuiElement extends AccessableElement { 15 | private ItemStack item; 16 | 17 | public AccessableGuiElement() {} 18 | 19 | public AccessableGuiElement(int index, BasicAction action, ItemStack item) { 20 | super(index, action); 21 | this.item = item; 22 | } 23 | 24 | @Override 25 | public ItemStack getItemStackForDisplay(GuiInterface gui) { 26 | lastDisplayed = Helper.parseItemStack(item.copy(), PlaceholderContext.of(gui.getPlayer())); 27 | return lastDisplayed; 28 | } 29 | 30 | @Override 31 | public void validate() { 32 | super.validate(); 33 | if (item==null) item = ItemStack.EMPTY; 34 | } 35 | 36 | @Override 37 | public SlotElement copy() { 38 | return new AccessableGuiElement(getIndex(), getGuiCallback().copy(), item.copy()); 39 | } 40 | 41 | public void setItem(ItemStack item) { 42 | this.item = item; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/DelegatedElement.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElementInterface; 4 | import eu.pb4.sgui.api.gui.GuiInterface; 5 | import eu.pb4.sgui.api.gui.SlotGuiInterface; 6 | import megaminds.actioninventory.util.ElementHelper; 7 | import net.minecraft.item.ItemStack; 8 | 9 | public class DelegatedElement implements GuiElementInterface { 10 | private ClickCallback guiCallback; 11 | private GuiElementInterface delegate; 12 | 13 | public DelegatedElement(GuiElementInterface delegate, ClickCallback callback) { 14 | this.delegate = delegate; 15 | this.guiCallback = callback; 16 | } 17 | 18 | @Override 19 | public ItemStack getItemStack() { 20 | return delegate.getItemStack(); 21 | } 22 | 23 | public void appendCallback(ClickCallback callback) { 24 | this.guiCallback = ElementHelper.combine(this.guiCallback, callback); 25 | } 26 | 27 | @Override 28 | public ItemStack getItemStackForDisplay(GuiInterface gui) { 29 | return delegate.getItemStackForDisplay(gui); 30 | } 31 | 32 | @Override 33 | public void onAdded(SlotGuiInterface gui) { 34 | delegate.onAdded(gui); 35 | } 36 | 37 | @Override 38 | public void onRemoved(SlotGuiInterface gui) { 39 | delegate.onRemoved(gui); 40 | } 41 | 42 | @Override 43 | public ClickCallback getGuiCallback() { 44 | return guiCallback; 45 | } 46 | 47 | public void setGuiCallback(ClickCallback guiCallback) { 48 | this.guiCallback = guiCallback; 49 | } 50 | 51 | public GuiElementInterface getDelegate() { 52 | return delegate; 53 | } 54 | 55 | public void setDelegate(GuiElementInterface delegate) { 56 | this.delegate = delegate; 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/Element.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import eu.pb4.sgui.api.elements.GuiElementInterface; 4 | import net.minecraft.screen.slot.Slot; 5 | 6 | public class Element { 7 | private GuiElementInterface el; 8 | private Slot redirect; 9 | public Element() {} 10 | public Element(GuiElementInterface el) {this.el=el;} 11 | public Element(Slot redirect) {this.redirect=redirect;} 12 | public void setRedirect(Slot redirect) {this.redirect=redirect;el=null;} 13 | public void setElement(GuiElementInterface el) {this.el=el;redirect=null;} 14 | public void set(Element el) { 15 | this.el = el.el; 16 | this.redirect = el.redirect; 17 | } 18 | public GuiElementInterface getEl() { 19 | return el; 20 | } 21 | public Slot getRedirect() { 22 | return redirect; 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/SlotElement.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import megaminds.actioninventory.gui.ActionInventoryGui; 4 | import megaminds.actioninventory.util.annotations.Poly; 5 | import net.minecraft.server.network.ServerPlayerEntity; 6 | 7 | @Poly 8 | public abstract sealed class SlotElement permits SlotFunction, AccessableElement { 9 | private int index; 10 | 11 | protected SlotElement() {} 12 | 13 | protected SlotElement(int index) { 14 | this.index = index; 15 | } 16 | 17 | /** 18 | * Use {@link ActionInventoryGui#setSlot} 19 | */ 20 | public abstract void apply(ActionInventoryGui gui, ServerPlayerEntity player); 21 | 22 | public abstract SlotElement copy(); 23 | 24 | /** 25 | * @throws IllegalArgumentException If {@code test} is negative and {@code gui} has no empty slots. 26 | */ 27 | public int getCheckedIndex(ActionInventoryGui gui) { 28 | int test = getIndex(); 29 | if (test<0) test = gui.getFirstEmptySlot(); 30 | if (test<0) throw new IllegalArgumentException("No more empty slots. Index must be specified."); 31 | return test; 32 | } 33 | 34 | public int getIndex() { 35 | return index; 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/gui/elements/SlotFunction.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.gui.elements; 2 | 3 | import java.util.UUID; 4 | 5 | import megaminds.actioninventory.misc.Enums.GuiType; 6 | import megaminds.actioninventory.serialization.wrappers.Validated; 7 | import megaminds.actioninventory.util.annotations.PolyName; 8 | import megaminds.actioninventory.gui.ActionInventoryGui; 9 | import net.minecraft.screen.slot.Slot; 10 | import net.minecraft.server.network.ServerPlayerEntity; 11 | 12 | @PolyName("Redirect") 13 | public non-sealed class SlotFunction extends SlotElement implements Validated { 14 | private GuiType guiType; 15 | private UUID name; 16 | private int slotIndex; 17 | 18 | public SlotFunction() {} 19 | 20 | public SlotFunction(int index, GuiType guiType, UUID name, int slotIndex) { 21 | super(index); 22 | this.guiType = guiType; 23 | this.name = name; 24 | this.slotIndex = slotIndex; 25 | } 26 | 27 | public Slot getSlot(ServerPlayerEntity p) { 28 | ServerPlayerEntity real = name==null ? p : p.getServer().getPlayerManager().getPlayer(name); 29 | return switch (guiType) { 30 | case PLAYER -> new Slot(real.getInventory(), slotIndex, 0, 0); 31 | case ENDER_CHEST -> new Slot(real.getEnderChestInventory(), slotIndex, 0, 0); 32 | default -> throw new IllegalArgumentException("Unimplemented case: " + guiType); 33 | }; 34 | } 35 | 36 | @Override 37 | public void validate() { 38 | if (guiType==null) guiType = GuiType.PLAYER; 39 | Validated.validate(slotIndex>=0, "Redirect requires slotIndex to be 0 or greater"); 40 | } 41 | 42 | @Override 43 | public void apply(ActionInventoryGui gui, ServerPlayerEntity p) { 44 | gui.setSlotRedirect(getCheckedIndex(gui), getSlot(p)); 45 | } 46 | 47 | @Override 48 | public SlotElement copy() { 49 | SlotFunction copy = new SlotFunction(); 50 | copy.guiType = guiType; 51 | copy.name = name; 52 | copy.slotIndex = slotIndex; 53 | return copy; 54 | } 55 | 56 | public GuiType getGuiType() { 57 | return guiType; 58 | } 59 | 60 | public void setGuiType(GuiType guiType) { 61 | this.guiType = guiType; 62 | } 63 | 64 | public UUID getName() { 65 | return name; 66 | } 67 | 68 | public void setName(UUID name) { 69 | this.name = name; 70 | } 71 | 72 | public int getSlotIndex() { 73 | return slotIndex; 74 | } 75 | 76 | public void setSlotIndex(int slotIndex) { 77 | this.slotIndex = slotIndex; 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/loaders/ActionInventoryLoader.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.loaders; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStreamReader; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.Set; 9 | import java.util.UUID; 10 | import java.util.function.Predicate; 11 | import java.util.stream.Collectors; 12 | 13 | import megaminds.actioninventory.ActionInventoryMod; 14 | import megaminds.actioninventory.gui.ActionInventoryBuilder; 15 | import megaminds.actioninventory.gui.VirtualPlayerInventory; 16 | import megaminds.actioninventory.serialization.Serializer; 17 | import megaminds.actioninventory.util.ValidationException; 18 | import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; 19 | import net.minecraft.resource.ResourceManager; 20 | import net.minecraft.screen.GenericContainerScreenHandler; 21 | import net.minecraft.screen.SimpleNamedScreenHandlerFactory; 22 | import net.minecraft.server.network.ServerPlayerEntity; 23 | import net.minecraft.text.Text; 24 | import net.minecraft.util.Identifier; 25 | 26 | public class ActionInventoryLoader implements SimpleSynchronousResourceReloadListener { 27 | private static final Identifier LOADER_ID = new Identifier(ActionInventoryMod.MOD_ID, "inventories"); 28 | 29 | private final Map builders = new HashMap<>(); 30 | 31 | @Override 32 | public void reload(ResourceManager manager) { 33 | builders.clear(); 34 | 35 | var count = new int[2]; 36 | var resources = Map.copyOf(manager.findResources(ActionInventoryMod.MOD_ID+"/inventories", s->s.getPath().endsWith(".json"))); 37 | for (var resource : resources.entrySet()) { 38 | try (var res = resource.getValue().getInputStream()) { 39 | var builder = Serializer.builderFromJson(new InputStreamReader(res)); 40 | addBuilder(builder); 41 | count[0]++; //success 42 | continue; 43 | } catch (ValidationException e) { 44 | ActionInventoryMod.warn("Action Inventory Validation Exception: "+e.getMessage()); 45 | } catch (IOException e) { 46 | ActionInventoryMod.warn("Failed to read Action Inventory from: "+resource.getKey()); 47 | } 48 | count[1]++; //fail 49 | } 50 | ActionInventoryMod.info("Loaded "+count[0]+" Action Inventories. Failed to load "+count[1]+"."); 51 | } 52 | 53 | @Override 54 | public Identifier getFabricId() { 55 | return LOADER_ID; 56 | } 57 | 58 | public void openEnderChest(ServerPlayerEntity openFor, UUID toOpen) { 59 | var p = openFor.getServer().getPlayerManager().getPlayer(toOpen); 60 | openFor.openHandledScreen(new SimpleNamedScreenHandlerFactory((syncId, inventory, player) -> GenericContainerScreenHandler.createGeneric9x3(syncId, inventory, p.getEnderChestInventory()), p.getName().copy().append(Text.of("'s ")).append(Text.translatable("container.enderchest")))); 61 | } 62 | 63 | public void openInventory(ServerPlayerEntity openFor, UUID toOpen) { 64 | var p = openFor.getServer().getPlayerManager().getPlayer(toOpen); 65 | new VirtualPlayerInventory(openFor, false, p).open(); 66 | } 67 | 68 | public ActionInventoryBuilder getBuilder(Identifier name) { 69 | return builders.get(name); 70 | } 71 | 72 | public void addBuilder(ActionInventoryBuilder builder) { 73 | builders.put(builder.getName(), builder); 74 | } 75 | 76 | public void removeBuilder(Identifier name) { 77 | builders.remove(name); 78 | } 79 | 80 | public boolean hasBuilder(Identifier name) { 81 | return builders.containsKey(name); 82 | } 83 | 84 | public Set builderNames() { 85 | return Set.copyOf(builders.keySet()); 86 | } 87 | 88 | public Set builderNames(Predicate filter) { 89 | return builders.entrySet().stream().filter(e -> filter.test(e.getValue())).map(Entry::getKey).collect(Collectors.toUnmodifiableSet()); 90 | } 91 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/loaders/BasicOpenerLoader.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.loaders; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStreamReader; 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import megaminds.actioninventory.ActionInventoryMod; 11 | import megaminds.actioninventory.openers.BasicOpener; 12 | import megaminds.actioninventory.serialization.Serializer; 13 | import megaminds.actioninventory.util.ValidationException; 14 | import net.fabricmc.fabric.api.resource.SimpleSynchronousResourceReloadListener; 15 | import net.minecraft.resource.ResourceManager; 16 | import net.minecraft.util.Identifier; 17 | 18 | public class BasicOpenerLoader implements SimpleSynchronousResourceReloadListener { 19 | private static final Identifier LOADER_ID = new Identifier(ActionInventoryMod.MOD_ID, "openers"); 20 | 21 | private final Map> openers = new HashMap<>(); 22 | 23 | @Override 24 | public void reload(ResourceManager manager) { 25 | openers.clear(); 26 | 27 | var count = new int[2]; 28 | var resources = Map.copyOf(manager.findResources(ActionInventoryMod.MOD_ID+"/openers", s->s.getPath().endsWith(".json"))); 29 | for (var resource : resources.entrySet()) { 30 | try (var res = resource.getValue().getInputStream()) { 31 | var opener = Serializer.openerFromJson(new InputStreamReader(res)); 32 | addOpener(opener); 33 | count[0]++; //success 34 | continue; 35 | } catch (ValidationException e) { 36 | ActionInventoryMod.warn("Opener Validation Exception: "+e.getMessage()); 37 | } catch (IOException e) { 38 | ActionInventoryMod.warn("Failed to read Opener from: "+resource.getKey()); 39 | } 40 | count[1]++; //fail 41 | } 42 | ActionInventoryMod.info("Loaded "+count[0]+" Openers. Failed to load "+count[1]+"."); 43 | } 44 | 45 | public void addOpener(BasicOpener opener) { 46 | openers.computeIfAbsent(opener.getType(), t->new ArrayList<>()).add(opener); 47 | } 48 | 49 | public List getOpeners(Identifier type) { 50 | var list = openers.get(type); 51 | return list!=null ? list : Collections.emptyList(); 52 | } 53 | 54 | @Override 55 | public Identifier getFabricId() { 56 | return LOADER_ID; 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/misc/Enums.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.misc; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.stream.Stream; 6 | 7 | import net.minecraft.registry.tag.TagKey; 8 | import net.minecraft.util.Identifier; 9 | 10 | /** 11 | * Enums/constants/whatever else I put here 12 | */ 13 | public class Enums { 14 | public static final String COMPLETE = "COMPLETE"; 15 | public enum GuiType {PLAYER, ENDER_CHEST, NAMED_GUI, GENERATED} 16 | public enum MessagePlaceHolders {PLAYER, SERVER, BROADCAST} 17 | 18 | private Enums() {} 19 | 20 | public enum TagOption { 21 | ALL { 22 | @Override 23 | public boolean matches(Set checkFor, Stream> checkIn) { 24 | return checkIn.map(TagKey::id).collect(HashSet::new, Set::add, Set::addAll).containsAll(checkFor); 25 | } 26 | }, 27 | NONE { 28 | @Override 29 | public boolean matches(Set checkFor, Stream> checkIn) { 30 | return checkIn.map(TagKey::id).noneMatch(checkFor::contains); 31 | } 32 | }, 33 | EXACT { 34 | @Override 35 | public boolean matches(Set checkFor, Stream> checkIn) { 36 | return checkIn.map(TagKey::id).collect(HashSet::new, Set::add, Set::addAll).equals(checkFor); 37 | } 38 | }, 39 | ANY { 40 | @Override 41 | public boolean matches(Set checkFor, Stream> checkIn) { 42 | return checkIn.map(TagKey::id).anyMatch(checkFor::contains); 43 | } 44 | }; 45 | 46 | public abstract boolean matches(Set checkFor, Stream> checkIn); 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/openers/BasicOpener.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.openers; 2 | 3 | import megaminds.actioninventory.ActionInventoryMod; 4 | import megaminds.actioninventory.serialization.wrappers.Validated; 5 | import megaminds.actioninventory.util.MessageHelper; 6 | import megaminds.actioninventory.util.annotations.Poly; 7 | import net.minecraft.server.network.ServerPlayerEntity; 8 | import net.minecraft.util.Identifier; 9 | 10 | @Poly 11 | public abstract sealed class BasicOpener implements Validated permits BlockOpener, EntityOpener, ItemOpener { 12 | private Identifier guiName; 13 | 14 | protected BasicOpener() {} 15 | protected BasicOpener(Identifier guiName) { 16 | this.guiName = guiName; 17 | } 18 | 19 | /** 20 | * Returns true if the action inventory should have opened; this may not be the case if the builder for it doesn't exist. 21 | */ 22 | @SuppressWarnings("unused") //Used by subclasses 23 | public boolean open(ServerPlayerEntity player, Object... context) { 24 | var b = ActionInventoryMod.INVENTORY_LOADER.getBuilder(guiName); 25 | if (b==null) { 26 | player.sendMessage(MessageHelper.toError("No action inventory of name: "+guiName)); 27 | } else { 28 | b.buildAndOpen(player); 29 | } 30 | return true; 31 | } 32 | 33 | @Override 34 | public void validate() { 35 | Validated.validate(guiName!=null, "Openers require guiName to not be null."); 36 | } 37 | 38 | public Identifier getGuiName() { 39 | return guiName; 40 | } 41 | 42 | public void setGuiName(Identifier guiName) { 43 | this.guiName = guiName; 44 | } 45 | 46 | public abstract Identifier getType(); 47 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/openers/BlockOpener.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.openers; 2 | 3 | import java.util.Optional; 4 | import java.util.Set; 5 | 6 | import megaminds.actioninventory.ActionInventoryMod; 7 | import megaminds.actioninventory.misc.Enums.TagOption; 8 | import megaminds.actioninventory.util.Helper; 9 | import megaminds.actioninventory.util.annotations.PolyName; 10 | import net.fabricmc.fabric.api.event.player.AttackBlockCallback; 11 | import net.fabricmc.fabric.api.event.player.UseBlockCallback; 12 | import net.minecraft.block.Block; 13 | import net.minecraft.block.BlockState; 14 | import net.minecraft.block.entity.BlockEntity; 15 | import net.minecraft.block.entity.BlockEntityType; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.util.ActionResult; 18 | import net.minecraft.util.Identifier; 19 | import net.minecraft.util.math.BlockPos; 20 | 21 | @PolyName("Block") 22 | public final class BlockOpener extends BasicOpener { 23 | private static final Identifier TYPE = new Identifier(ActionInventoryMod.MOD_ID, "block"); 24 | 25 | private Block block; 26 | private BlockPos position; 27 | private Optional> entityType; 28 | private Set tags; 29 | private TagOption tagOption; 30 | 31 | public BlockOpener() {} 32 | 33 | public BlockOpener(Identifier guiName, Block block, BlockPos position, Optional> entityType, Set tags, TagOption tagOption) { 34 | super(guiName); 35 | this.block = block; 36 | this.position = position; 37 | this.entityType = entityType; 38 | this.tags = tags; 39 | this.tagOption = tagOption; 40 | } 41 | 42 | /** 43 | * context[0] = Block
44 | * context[1] = BlockPos
45 | * context[2] = BlockEntity 46 | */ 47 | @Override 48 | public boolean open(ServerPlayerEntity player, Object... context) { 49 | if (checkEntity((BlockEntity)context[2]) && checkBlock((BlockState)context[0], (BlockPos)context[1])) { 50 | return super.open(player, context); 51 | } 52 | return false; 53 | } 54 | 55 | private boolean checkBlock(BlockState b, BlockPos bPos) { 56 | return (block==null||block.equals(b.getBlock())) 57 | && (position==null||position.equals(bPos)) 58 | && (tags==null||tagOption.matches(tags, b.streamTags())); 59 | } 60 | 61 | private boolean checkEntity(BlockEntity be) { 62 | return entityType==null || entityType.isEmpty()&&be==null || entityType.isPresent()&&be!=null&&entityType.get()==be.getType(); 63 | } 64 | 65 | public static boolean tryOpen(ServerPlayerEntity p, BlockState b, BlockPos bp, BlockEntity be) { 66 | return Helper.getFirst(ActionInventoryMod.OPENER_LOADER.getOpeners(TYPE), o->o.open(p, b, bp, be))!=null; 67 | } 68 | 69 | public static void registerCallbacks() { 70 | UseBlockCallback.EVENT.register((p,w,h,r)-> !w.isClient&&BlockOpener.tryOpen((ServerPlayerEntity)p, w.getBlockState(r.getBlockPos()), r.getBlockPos(), w.getBlockEntity(r.getBlockPos())) ? ActionResult.SUCCESS : ActionResult.PASS); 71 | AttackBlockCallback.EVENT.register((p,w,h,b,d)-> !w.isClient&&BlockOpener.tryOpen((ServerPlayerEntity)p, w.getBlockState(b), b, w.getBlockEntity(b)) ? ActionResult.SUCCESS : ActionResult.PASS); 72 | } 73 | 74 | @Override 75 | public void validate() { 76 | super.validate(); 77 | if (tagOption==null) tagOption = TagOption.ALL; 78 | } 79 | 80 | @Override 81 | public Identifier getType() { 82 | return TYPE; 83 | } 84 | 85 | public Block getBlock() { 86 | return block; 87 | } 88 | 89 | public void setBlock(Block block) { 90 | this.block = block; 91 | } 92 | 93 | public BlockPos getPosition() { 94 | return position; 95 | } 96 | 97 | public void setPosition(BlockPos position) { 98 | this.position = position; 99 | } 100 | 101 | public Optional> getEntityType() { 102 | return entityType; 103 | } 104 | 105 | public void setEntityType(Optional> entityType) { 106 | this.entityType = entityType; 107 | } 108 | 109 | public Set getTags() { 110 | return tags; 111 | } 112 | 113 | public void setTags(Set tags) { 114 | this.tags = tags; 115 | } 116 | 117 | public TagOption getTagOption() { 118 | return tagOption; 119 | } 120 | 121 | public void setTagOption(TagOption tagOption) { 122 | this.tagOption = tagOption; 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/openers/EntityOpener.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.openers; 2 | 3 | import com.mojang.brigadier.StringReader; 4 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 5 | 6 | import megaminds.actioninventory.ActionInventoryMod; 7 | import megaminds.actioninventory.util.Helper; 8 | import megaminds.actioninventory.util.annotations.Exclude; 9 | import megaminds.actioninventory.util.annotations.PolyName; 10 | import net.fabricmc.fabric.api.event.player.AttackEntityCallback; 11 | import net.fabricmc.fabric.api.event.player.UseEntityCallback; 12 | import net.minecraft.command.EntitySelector; 13 | import net.minecraft.command.EntitySelectorReader; 14 | import net.minecraft.entity.Entity; 15 | import net.minecraft.predicate.entity.EntityPredicate; 16 | import net.minecraft.server.network.ServerPlayerEntity; 17 | import net.minecraft.util.ActionResult; 18 | import net.minecraft.util.Identifier; 19 | 20 | @PolyName("Entity") 21 | public final class EntityOpener extends BasicOpener { 22 | private static final Identifier TYPE = new Identifier(ActionInventoryMod.MOD_ID, "entity"); 23 | 24 | private String entitySelector; 25 | private EntityPredicate entityPredicate; 26 | 27 | @Exclude private EntitySelector selector; 28 | 29 | public EntityOpener() {} 30 | 31 | public EntityOpener(Identifier guiName, String entitySelector, EntityPredicate entityPredicate) { 32 | super(guiName); 33 | this.entitySelector = entitySelector; 34 | this.entityPredicate = entityPredicate; 35 | } 36 | 37 | @Override 38 | public boolean open(ServerPlayerEntity player, Object... context) { 39 | var e = (Entity) context[0]; 40 | if ((entityPredicate==null || entityPredicate.test(player, e)) && (selector==null || matches(e))) { 41 | return super.open(player, context); 42 | } 43 | return false; 44 | } 45 | 46 | private boolean matches(Entity e) { 47 | try { 48 | return e.equals(selector.getEntity(e.getCommandSource().withMaxLevel(2))); 49 | } catch (CommandSyntaxException e1) { 50 | return false; 51 | } 52 | } 53 | 54 | private void validateSelector() { 55 | if (entitySelector==null || entitySelector.isBlank()) return; 56 | 57 | var whole = "@s"+entitySelector.strip(); 58 | 59 | try { 60 | this.selector = new EntitySelectorReader(new StringReader(whole)).read(); 61 | } catch (CommandSyntaxException e) { 62 | throw new IllegalArgumentException("Failed to read entity selector for an EntityOpener.", e); 63 | } 64 | } 65 | 66 | public static boolean tryOpen(ServerPlayerEntity p, Entity e) { 67 | return Helper.getFirst(ActionInventoryMod.OPENER_LOADER.getOpeners(TYPE), o->o.open(p, e))!=null; 68 | } 69 | 70 | public static void registerCallbacks() { 71 | UseEntityCallback.EVENT.register((p,w,h,e,r) -> !w.isClient&&tryOpen((ServerPlayerEntity)p, e) ? ActionResult.SUCCESS : ActionResult.PASS); 72 | AttackEntityCallback.EVENT.register((p,w,h,e,r) -> !w.isClient&&tryOpen((ServerPlayerEntity)p, e) ? ActionResult.SUCCESS : ActionResult.PASS); 73 | } 74 | 75 | @Override 76 | public void validate() { 77 | super.validate(); 78 | validateSelector(); 79 | } 80 | 81 | @Override 82 | public Identifier getType() { 83 | return TYPE; 84 | } 85 | 86 | public String getEntitySelector() { 87 | return entitySelector; 88 | } 89 | 90 | public void setEntitySelector(String entitySelector) { 91 | this.entitySelector = entitySelector; 92 | } 93 | 94 | public EntityPredicate getEntityPredicate() { 95 | return entityPredicate; 96 | } 97 | 98 | public void setEntityPredicate(EntityPredicate entityPredicate) { 99 | this.entityPredicate = entityPredicate; 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/openers/ItemOpener.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.openers; 2 | 3 | import java.util.Set; 4 | 5 | import megaminds.actioninventory.util.annotations.PolyName; 6 | import megaminds.actioninventory.misc.ItemStackish; 7 | import megaminds.actioninventory.ActionInventoryMod; 8 | import megaminds.actioninventory.misc.Enums.TagOption; 9 | import megaminds.actioninventory.util.Helper; 10 | import net.fabricmc.fabric.api.event.player.UseItemCallback; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.server.network.ServerPlayerEntity; 13 | import net.minecraft.util.Identifier; 14 | import net.minecraft.util.TypedActionResult; 15 | 16 | @PolyName("Item") 17 | public final class ItemOpener extends BasicOpener { 18 | private static final Identifier TYPE = new Identifier(ActionInventoryMod.MOD_ID, "item"); 19 | 20 | private ItemStackish stack; 21 | private Set tags; 22 | private TagOption tagOption; 23 | 24 | public ItemOpener(Identifier guiName, ItemStackish stack, Set tags, TagOption tagOption) { 25 | super(guiName); 26 | this.stack = stack; 27 | this.tags = tags; 28 | this.tagOption = tagOption; 29 | } 30 | 31 | @Override 32 | public boolean open(ServerPlayerEntity player, Object... context) { 33 | var s = (ItemStack) context[0]; 34 | if (stack.specifiedEquals(s) && (tags==null || tagOption.matches(tags, s.streamTags()))) { 35 | return super.open(player, context); 36 | } 37 | return false; 38 | } 39 | 40 | public static boolean tryOpen(ServerPlayerEntity p, ItemStack s) { 41 | return Helper.getFirst(ActionInventoryMod.OPENER_LOADER.getOpeners(TYPE), o->o.open(p, s))!=null; 42 | } 43 | 44 | public static void registerCallbacks() { 45 | UseItemCallback.EVENT.register((p,w,h)-> 46 | !w.isClient&&ItemOpener.tryOpen((ServerPlayerEntity)p, p.getStackInHand(h)) ? TypedActionResult.success(ItemStack.EMPTY) : TypedActionResult.pass(ItemStack.EMPTY)); 47 | } 48 | 49 | @Override 50 | public void validate() { 51 | super.validate(); 52 | if (stack==null) stack = ItemStackish.MATCH_ALL; 53 | if (tagOption==null) tagOption = TagOption.ALL; 54 | } 55 | 56 | @Override 57 | public Identifier getType() { 58 | return TYPE; 59 | } 60 | 61 | public ItemStackish getStack() { 62 | return stack; 63 | } 64 | 65 | public void setStack(ItemStackish stack) { 66 | this.stack = stack; 67 | } 68 | 69 | public Set getTags() { 70 | return tags; 71 | } 72 | 73 | public void setTags(Set tags) { 74 | this.tags = tags; 75 | } 76 | 77 | public TagOption getTagOption() { 78 | return tagOption; 79 | } 80 | 81 | public void setTagOption(TagOption tagOption) { 82 | this.tagOption = tagOption; 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/ExcludeStrategy.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import com.google.gson.ExclusionStrategy; 4 | import com.google.gson.FieldAttributes; 5 | import megaminds.actioninventory.util.annotations.Exclude; 6 | 7 | public class ExcludeStrategy implements ExclusionStrategy { 8 | @Override 9 | public boolean shouldSkipClass(Class clazz) { 10 | return false; 11 | } 12 | 13 | @Override 14 | public boolean shouldSkipField(FieldAttributes field) { 15 | return field.getAnnotation(Exclude.class) != null; 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/NbtElementAdapter.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import java.io.IOException; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonWriter; 8 | import com.mojang.brigadier.StringReader; 9 | import com.mojang.brigadier.exceptions.CommandSyntaxException; 10 | 11 | import net.minecraft.nbt.NbtElement; 12 | import net.minecraft.nbt.StringNbtReader; 13 | import net.minecraft.nbt.visitor.StringNbtWriter; 14 | 15 | public class NbtElementAdapter extends TypeAdapter { 16 | @Override 17 | public void write(JsonWriter out, NbtElement value) throws IOException { 18 | out.value(new StringNbtWriter().apply(value)); 19 | } 20 | 21 | @Override 22 | public NbtElement read(JsonReader in) throws IOException { 23 | var s = in.nextString(); 24 | if (s.isEmpty()) return null; 25 | try { 26 | return new StringNbtReader(new StringReader(s)).parseElement(); 27 | } catch (CommandSyntaxException e) { 28 | throw new IllegalArgumentException(e); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/OptionalAdapterFactory.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.ParameterizedType; 5 | import java.util.Optional; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.TypeAdapter; 9 | import com.google.gson.TypeAdapterFactory; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.google.gson.stream.JsonReader; 12 | import com.google.gson.stream.JsonToken; 13 | import com.google.gson.stream.JsonWriter; 14 | 15 | public class OptionalAdapterFactory implements TypeAdapterFactory { 16 | @SuppressWarnings({ "unchecked", "rawtypes" }) 17 | @Override 18 | public TypeAdapter create(Gson gson, TypeToken type) { 19 | if (type.getRawType()==Optional.class) { 20 | TypeAdapter delegate = gson.getAdapter(TypeToken.get(((ParameterizedType)type.getType()).getActualTypeArguments()[0])); 21 | return new OptionalAdapter(delegate); 22 | } 23 | return null; 24 | } 25 | 26 | /** 27 | * Null==Unspecified 28 | * !Null==Specified 29 | */ 30 | private static class OptionalAdapter extends TypeAdapter> { 31 | private TypeAdapter delegate; 32 | private OptionalAdapter(TypeAdapter delegate) { 33 | this.delegate = delegate; 34 | } 35 | @Override 36 | public void write(JsonWriter out, Optional value) throws IOException { 37 | if (value==null) { 38 | out.nullValue(); 39 | return; 40 | } 41 | 42 | out.beginArray(); 43 | if (value.isPresent()) delegate.write(out, value.orElseThrow()); 44 | out.endArray(); 45 | } 46 | 47 | @Override 48 | public Optional read(JsonReader in) throws IOException { 49 | JsonToken next = in.peek(); 50 | if (next==JsonToken.NULL) { 51 | in.nextNull(); 52 | return null; 53 | } else if (next==JsonToken.BEGIN_ARRAY) { 54 | in.beginArray(); 55 | if (in.peek()==JsonToken.END_ARRAY) { 56 | in.endArray(); 57 | return Optional.empty(); 58 | } 59 | Optional op = Optional.of(delegate.read(in)); 60 | in.endArray(); 61 | return op; 62 | } else { 63 | return Optional.ofNullable(delegate.read(in)); 64 | } 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/PolyAdapterFactory.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Modifier; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import com.google.common.collect.BiMap; 9 | import com.google.common.collect.HashBiMap; 10 | import com.google.gson.Gson; 11 | import com.google.gson.JsonElement; 12 | import com.google.gson.JsonObject; 13 | import com.google.gson.TypeAdapter; 14 | import com.google.gson.TypeAdapterFactory; 15 | import com.google.gson.internal.Streams; 16 | import com.google.gson.reflect.TypeToken; 17 | import com.google.gson.stream.JsonReader; 18 | import com.google.gson.stream.JsonToken; 19 | import com.google.gson.stream.JsonWriter; 20 | 21 | import megaminds.actioninventory.util.annotations.Poly; 22 | import megaminds.actioninventory.util.annotations.PolyName; 23 | 24 | /** 25 | * This returns the type adapter that is used for the first parent as the adapter for each subclass. 26 | */ 27 | public class PolyAdapterFactory implements TypeAdapterFactory { 28 | @SuppressWarnings({ "unchecked", "rawtypes" }) 29 | @Override 30 | public TypeAdapter create(Gson gson, TypeToken type) { 31 | Class raw = type.getRawType(); 32 | if (raw.isAnnotationPresent(Poly.class)) { 33 | Class root = findRoot(raw); 34 | if (!root.isSealed()) throw new IllegalArgumentException("Classes must be sealed to use @Poly"); 35 | if (root.isInterface()) throw new IllegalArgumentException("Types cannot be an interface to use @Poly"); 36 | 37 | if (root==raw) { 38 | return new PolyAdapter(gson, root); 39 | } 40 | return (TypeAdapter) gson.getAdapter(root); 41 | } 42 | return null; 43 | } 44 | 45 | private static Class findRoot(Class start) { 46 | Class parent = start.getSuperclass(); 47 | return parent!=null&&parent.isAnnotationPresent(Poly.class) ? findRoot(parent) : start; 48 | } 49 | 50 | private final class PolyAdapter extends TypeAdapter { 51 | private final Gson gson; 52 | private final BiMap, String> classToName; 53 | private final Map, TypeAdapter> classToAdapter; 54 | 55 | private PolyAdapter(Gson gson, Class root) { 56 | this.gson = gson; 57 | this.classToName = HashBiMap.create(); 58 | this.classToAdapter = new HashMap<>(); 59 | retrieveClasses(root); 60 | } 61 | 62 | @Override 63 | public void write(JsonWriter out, T value) throws IOException { 64 | if (value==null) { 65 | out.nullValue(); 66 | return; 67 | } 68 | Class raw = value.getClass(); 69 | JsonElement obj = getAdapter(raw).toJsonTree(value); 70 | obj.getAsJsonObject().addProperty("type", classToName.get(raw)); 71 | Streams.write(obj, out); 72 | } 73 | 74 | @Override 75 | public T read(JsonReader in) throws IOException { 76 | if (in.peek()==JsonToken.NULL) return null; 77 | JsonObject obj = Streams.parse(in).getAsJsonObject(); 78 | JsonElement type = obj.remove("type"); 79 | if (type==null) { 80 | throw new IllegalArgumentException("No type was specified!"); 81 | } 82 | TypeAdapter adapter = getAdapter(type.getAsString()); 83 | return adapter.fromJsonTree(obj); 84 | } 85 | 86 | @SuppressWarnings("unchecked") 87 | private TypeAdapter getAdapter(Class clazz) { 88 | return (TypeAdapter) classToAdapter.computeIfAbsent(clazz, c->gson.getDelegateAdapter(PolyAdapterFactory.this, TypeToken.get(c))); 89 | } 90 | 91 | private TypeAdapter getAdapter(String name) { 92 | Class clazz = classToName.inverse().get(name); 93 | if (clazz==null) { 94 | throw new IllegalArgumentException("Unknown type: "+name); 95 | } 96 | return getAdapter(clazz); 97 | } 98 | 99 | private void retrieveClasses(Class clazz) { 100 | if (!(clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers()))) classToName.put(clazz, getName(clazz)); 101 | 102 | if (clazz.isSealed()) { 103 | for (Class c : clazz.getPermittedSubclasses()) { 104 | retrieveClasses(c); 105 | } 106 | } 107 | } 108 | 109 | private static String getName(Class clazz) { 110 | PolyName n = clazz.getAnnotation(PolyName.class); 111 | return n==null ? clazz.getSimpleName() : n.value(); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/Serializer.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import java.io.Reader; 4 | import java.lang.reflect.Type; 5 | import java.util.function.Function; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.GsonBuilder; 9 | import com.google.gson.JsonDeserializationContext; 10 | import com.google.gson.JsonDeserializer; 11 | import com.google.gson.JsonElement; 12 | import com.google.gson.JsonNull; 13 | import com.google.gson.JsonParseException; 14 | import com.google.gson.JsonSerializationContext; 15 | import com.google.gson.JsonSerializer; 16 | import com.mojang.serialization.JsonOps; 17 | 18 | import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; 19 | import megaminds.actioninventory.actions.BasicAction; 20 | import megaminds.actioninventory.gui.ActionInventoryBuilder; 21 | import megaminds.actioninventory.misc.ItemStackish; 22 | import megaminds.actioninventory.openers.BasicOpener; 23 | import megaminds.actioninventory.serialization.wrappers.InstancedAdapterWrapper; 24 | import megaminds.actioninventory.serialization.wrappers.ValidatedAdapterWrapper; 25 | import megaminds.actioninventory.serialization.wrappers.WrapperAdapterFactory; 26 | import megaminds.actioninventory.util.ValidationException; 27 | import net.fabricmc.fabric.api.util.TriState; 28 | import net.minecraft.block.Block; 29 | import net.minecraft.block.entity.BlockEntity; 30 | import net.minecraft.enchantment.Enchantment; 31 | import net.minecraft.entity.EntityType; 32 | import net.minecraft.entity.attribute.EntityAttribute; 33 | import net.minecraft.entity.effect.StatusEffect; 34 | import net.minecraft.item.Item; 35 | import net.minecraft.item.ItemStack; 36 | import net.minecraft.nbt.NbtElement; 37 | import net.minecraft.particle.ParticleType; 38 | import net.minecraft.predicate.entity.EntityPredicate; 39 | import net.minecraft.registry.Registries; 40 | import net.minecraft.registry.Registry; 41 | import net.minecraft.screen.ScreenHandlerType; 42 | import net.minecraft.sound.SoundEvent; 43 | import net.minecraft.text.Text; 44 | import net.minecraft.util.Identifier; 45 | 46 | public class Serializer { 47 | public static final Gson GSON; 48 | 49 | private Serializer() {} 50 | 51 | public static ActionInventoryBuilder builderFromJson(Reader json) throws ValidationException { 52 | return GSON.fromJson(json, ActionInventoryBuilder.class); 53 | } 54 | 55 | public static ActionInventoryBuilder builderFromJson(String json) throws ValidationException { 56 | return GSON.fromJson(json, ActionInventoryBuilder.class); 57 | } 58 | 59 | public static BasicOpener openerFromJson(Reader json) throws ValidationException { 60 | return GSON.fromJson(json, BasicOpener.class); 61 | } 62 | 63 | public static BasicOpener openerFromJson(String json) throws ValidationException { 64 | return GSON.fromJson(json, BasicOpener.class); 65 | } 66 | 67 | static { 68 | GSON = new GsonBuilder() 69 | .disableHtmlEscaping() 70 | .setPrettyPrinting() 71 | .enableComplexMapKeySerialization() 72 | .setExclusionStrategies(new ExcludeStrategy()) 73 | 74 | .registerTypeHierarchyAdapter(NbtElement.class, new NbtElementAdapter().nullSafe()) 75 | .registerTypeHierarchyAdapter(Text.class, basic(Text.Serialization::fromJsonTree, Text.Serialization::toJsonTree)) 76 | 77 | .registerTypeAdapter(ClickCallback.class, delegate(BasicAction.class, ClickCallback.class::cast, BasicAction.class::cast)) 78 | .registerTypeAdapter(ItemStack.class, delegate(ItemStackish.class, ItemStackish::toStack, ItemStackish::new)) 79 | .registerTypeAdapter(Identifier.class, delegate(String.class, s->new Identifier(s.toLowerCase()), Identifier::toString)) 80 | .registerTypeAdapter(TriState.class, new TriStateAdapter()) 81 | 82 | .registerTypeAdapter(Item.class, registryDelegate(Registries.ITEM)) 83 | .registerTypeAdapter(Enchantment.class, registryDelegate(Registries.ENCHANTMENT)) 84 | .registerTypeAdapter(EntityAttribute.class, registryDelegate(Registries.ATTRIBUTE)) 85 | .registerTypeAdapter(Block.class, registryDelegate(Registries.BLOCK)) 86 | .registerTypeAdapter(BlockEntity.class, registryDelegate(Registries.BLOCK_ENTITY_TYPE)) 87 | .registerTypeAdapter(EntityType.class, registryDelegate(Registries.ENTITY_TYPE)) 88 | .registerTypeAdapter(SoundEvent.class, registryDelegate(Registries.SOUND_EVENT)) 89 | .registerTypeAdapter(ScreenHandlerType.class, registryDelegate(Registries.SCREEN_HANDLER)) 90 | .registerTypeAdapter(StatusEffect.class, registryDelegate(Registries.STATUS_EFFECT)) 91 | .registerTypeAdapter(ParticleType.class, registryDelegate(Registries.PARTICLE_TYPE)) 92 | .registerTypeAdapter(EntityPredicate.class, basic(j -> EntityPredicate.CODEC.parse(JsonOps.INSTANCE, j).result().orElse(null), p -> EntityPredicate.CODEC.encodeStart(JsonOps.INSTANCE, p).result().orElse(null))) 93 | .registerTypeAdapterFactory(new WrapperAdapterFactory(new InstancedAdapterWrapper(), new ValidatedAdapterWrapper())) 94 | .registerTypeAdapterFactory(new PolyAdapterFactory()) 95 | .registerTypeAdapterFactory(new OptionalAdapterFactory()) 96 | 97 | .create(); 98 | } 99 | 100 | private static Both basic(Function from, Function to) { 101 | return new Both() { 102 | @Override public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {return from.apply(json);} 103 | @Override public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) {return to.apply(src);} 104 | }; 105 | } 106 | 107 | private static Both delegate(Class delegate, Function from, Function to) { 108 | return new Both(){ 109 | @Override public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 110 | D d = context.deserialize(json, delegate); 111 | return d==null ? null : from.apply(d); 112 | } 113 | @Override public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { 114 | return src==null ? JsonNull.INSTANCE : context.serialize(to.apply(src), delegate); 115 | } 116 | }; 117 | } 118 | 119 | private static Both registryDelegate(Registry registry) { 120 | return delegate(Identifier.class, registry::get, registry::getId); 121 | } 122 | 123 | private static interface Both extends JsonDeserializer, JsonSerializer {} 124 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/TriStateAdapter.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization; 2 | 3 | import java.io.IOException; 4 | 5 | import com.google.gson.TypeAdapter; 6 | import com.google.gson.stream.JsonReader; 7 | import com.google.gson.stream.JsonToken; 8 | import com.google.gson.stream.JsonWriter; 9 | 10 | import net.fabricmc.fabric.api.util.TriState; 11 | 12 | public class TriStateAdapter extends TypeAdapter { 13 | @Override 14 | public TriState read(JsonReader in) throws IOException { 15 | if (in.peek()==JsonToken.NULL) { 16 | in.nextNull(); 17 | return TriState.DEFAULT; 18 | } 19 | return TriState.of(in.nextBoolean()); 20 | } 21 | 22 | @Override 23 | public void write(JsonWriter out, TriState value) throws IOException { 24 | out.value(value==null ? null : value.getBoxed()); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/wrappers/InstancedAdapterWrapper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization.wrappers; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.Field; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import com.google.gson.Gson; 9 | import com.google.gson.TypeAdapter; 10 | import com.google.gson.reflect.TypeToken; 11 | import com.google.gson.stream.JsonReader; 12 | import com.google.gson.stream.JsonToken; 13 | import com.google.gson.stream.JsonWriter; 14 | 15 | import megaminds.actioninventory.util.annotations.Instanced; 16 | 17 | public class InstancedAdapterWrapper implements TypeAdapterWrapper { 18 | @SuppressWarnings({ "unchecked" }) 19 | @Override 20 | public TypeAdapter wrapAdapter(@NotNull TypeAdapter adapter, Gson gson, TypeToken type) { 21 | Class raw = type.getRawType(); 22 | if (!raw.isAnnotationPresent(Instanced.class)) return adapter; 23 | 24 | T instance = null; 25 | try { 26 | Field f = raw.getDeclaredField("INSTANCE"); 27 | f.setAccessible(true); //NOSONAR necessary 28 | instance = (T) f.get(null); 29 | } catch (Exception e) {/*Handled below*/} 30 | if (instance==null) return adapter; 31 | 32 | return new InstancedAdapter<>(adapter, instance); 33 | } 34 | 35 | private class InstancedAdapter extends TypeAdapter { 36 | private final TypeAdapter delegate; 37 | private final T instance; 38 | 39 | private InstancedAdapter(TypeAdapter delegate, T instance) { 40 | this.delegate = delegate; 41 | this.instance = instance; 42 | } 43 | 44 | @Override 45 | public void write(JsonWriter out, T value) throws IOException { 46 | delegate.write(out, value); 47 | } 48 | 49 | @Override 50 | public T read(JsonReader in) throws IOException { 51 | if (in.peek()==JsonToken.NULL) return null; 52 | 53 | in.skipValue(); 54 | // delegate.read(in); //Not sure which is best 55 | 56 | return instance; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/wrappers/TypeAdapterWrapper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization.wrappers; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.TypeAdapter; 7 | import com.google.gson.reflect.TypeToken; 8 | 9 | public interface TypeAdapterWrapper { 10 | TypeAdapter wrapAdapter(@NotNull TypeAdapter adapter, Gson gson, TypeToken type); 11 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/wrappers/Validated.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization.wrappers; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import megaminds.actioninventory.util.ValidationException; 6 | 7 | public interface Validated { 8 | /** 9 | * b==true else throws an error 10 | */ 11 | public static void validate(boolean b, String message) { 12 | if (!b) throw new ValidationException(message); 13 | } 14 | 15 | /** 16 | * b==true else throws an error 17 | */ 18 | public static void validate(boolean b, Supplier message) { 19 | if (!b) throw new ValidationException(message.get()); 20 | } 21 | 22 | /** 23 | * Called after an object of this type is loaded by Gson.
24 | * Implementors should correct any fields needing it and call other initializing methods.
25 | * Implementors may throw IllegalArgumentException but should try to correct mistakes/use defaults if they can. 26 | */ 27 | void validate(); 28 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/wrappers/ValidatedAdapterWrapper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization.wrappers; 2 | 3 | import java.io.IOException; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import com.google.gson.Gson; 8 | import com.google.gson.TypeAdapter; 9 | import com.google.gson.reflect.TypeToken; 10 | import com.google.gson.stream.JsonReader; 11 | import com.google.gson.stream.JsonWriter; 12 | 13 | public class ValidatedAdapterWrapper implements TypeAdapterWrapper { 14 | @SuppressWarnings({ "unchecked", "rawtypes" }) 15 | @Override 16 | public TypeAdapter wrapAdapter(@NotNull TypeAdapter adapter, Gson gson, TypeToken type) { 17 | if (!Validated.class.isAssignableFrom(type.getRawType())) return adapter; 18 | return new ValidatedAdapter(adapter); 19 | } 20 | 21 | private class ValidatedAdapter extends TypeAdapter { 22 | private final TypeAdapter delegate; 23 | 24 | private ValidatedAdapter(TypeAdapter delegate) { 25 | this.delegate = delegate; 26 | } 27 | 28 | @Override 29 | public void write(JsonWriter out, T value) throws IOException { 30 | delegate.write(out, value); 31 | } 32 | 33 | @Override 34 | public T read(JsonReader in) throws IOException { 35 | T t = delegate.read(in); 36 | if (t!=null) t.validate(); 37 | return t; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/serialization/wrappers/WrapperAdapterFactory.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.serialization.wrappers; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.TypeAdapterFactory; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | public class WrapperAdapterFactory implements TypeAdapterFactory { 9 | private final TypeAdapterWrapper[] adapters; 10 | 11 | /** 12 | * Wrappers are applied in order. If a wrapper potentially ignores previous adapters, it should be listed first. 13 | */ 14 | public WrapperAdapterFactory(TypeAdapterWrapper... adapters) { 15 | this.adapters = adapters!=null ? adapters : new TypeAdapterWrapper[0]; 16 | } 17 | 18 | @Override 19 | public TypeAdapter create(Gson gson, TypeToken type) { 20 | TypeAdapter adapter = gson.getDelegateAdapter(this, type); 21 | for (int i = 0; i < adapters.length; i++) { 22 | if (adapter==null) return null; 23 | adapter = adapters[i].wrapAdapter(adapter, gson, type); 24 | } 25 | return adapter; 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/CommandPermissions.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.util.function.Predicate; 4 | 5 | import me.lucko.fabric.api.permissions.v0.Permissions; 6 | import net.fabricmc.loader.api.FabricLoader; 7 | import net.minecraft.server.command.ServerCommandSource; 8 | 9 | public interface CommandPermissions { 10 | CommandPermissions INSTANCE = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0") ? new FabricPermissions() : new VanillaPermissions(); 11 | 12 | static Predicate requires(String command, int def) { 13 | return s->INSTANCE.hasPermission(s, command, def); 14 | } 15 | 16 | boolean hasPermission(ServerCommandSource source, String command, int def); 17 | 18 | final class VanillaPermissions implements CommandPermissions { 19 | @Override 20 | public boolean hasPermission(ServerCommandSource source, String command, int def) { 21 | return source.hasPermissionLevel(def); 22 | } 23 | } 24 | 25 | final class FabricPermissions implements CommandPermissions { 26 | @Override 27 | public boolean hasPermission(ServerCommandSource source, String command, int def) { 28 | return Permissions.check(source, command, def); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/ConsumableDataHelper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.util.Optional; 4 | import java.util.UUID; 5 | import eu.pb4.playerdata.api.PlayerDataApi; 6 | import eu.pb4.playerdata.api.storage.NbtDataStorage; 7 | import eu.pb4.playerdata.api.storage.PlayerDataStorage; 8 | import megaminds.actioninventory.ActionInventoryMod; 9 | import net.minecraft.nbt.NbtCompound; 10 | import net.minecraft.server.MinecraftServer; 11 | 12 | /** 13 | * This is just a helper class for consumables working with PlayerDataApi. 14 | */ 15 | public class ConsumableDataHelper { 16 | private static final PlayerDataStorage CONSUMABLE_DATA = new NbtDataStorage(ActionInventoryMod.MOD_ID); 17 | 18 | static { 19 | PlayerDataApi.register(CONSUMABLE_DATA); 20 | } 21 | 22 | private ConsumableDataHelper() {} 23 | 24 | public static void setPlayer(MinecraftServer server, UUID player, NbtCompound value) { 25 | PlayerDataApi.setCustomDataFor(server, player, CONSUMABLE_DATA, value); 26 | } 27 | 28 | public static void removePlayer(MinecraftServer server, UUID player) { 29 | PlayerDataApi.setCustomDataFor(server, player, CONSUMABLE_DATA, null); 30 | } 31 | 32 | public static Optional getPlayer(MinecraftServer server, UUID player) { 33 | return Optional.ofNullable(PlayerDataApi.getCustomDataFor(server, player, CONSUMABLE_DATA)); 34 | } 35 | 36 | public static NbtCompound getOrCreatePlayer(MinecraftServer server, UUID player) { 37 | var store = getPlayer(server, player).orElse(null); 38 | if (store==null) { 39 | store = new NbtCompound(); 40 | PlayerDataApi.setCustomDataFor(server, player, CONSUMABLE_DATA, store); 41 | } 42 | return store; 43 | } 44 | 45 | public static void setGui(MinecraftServer server, UUID player, String guiName, NbtCompound value) { 46 | getOrCreatePlayer(server, player).put(guiName, value); 47 | } 48 | 49 | public static void removeGui(MinecraftServer server, UUID player, String guiName) { 50 | getPlayer(server, player).ifPresent(n->n.remove(guiName)); 51 | } 52 | 53 | public static Optional getGui(MinecraftServer server, UUID player, String guiName) { 54 | return getPlayer(server, player).filter(n->n.contains(guiName)).map(n->n.getCompound(guiName)); 55 | } 56 | 57 | public static NbtCompound getOrCreateGui(MinecraftServer server, UUID player, String guiName) { 58 | return Helper.computeIfAbsent(getOrCreatePlayer(server, player), s->new NbtCompound(), guiName); 59 | } 60 | 61 | public static void removeAction(MinecraftServer server, UUID player, String guiName, String lastAction, NbtCompound data) { 62 | getOrCreateGui(server, player, guiName).put(lastAction, data); 63 | } 64 | 65 | public static void setAction(MinecraftServer server, UUID player, String guiName, String lastAction) { 66 | getGui(server, player, guiName).ifPresent(n->n.remove(lastAction)); 67 | } 68 | 69 | public static Optional getAction(MinecraftServer server, UUID player, String guiName, String lastAction) { 70 | return getGui(server, player, guiName).filter(n->n.contains(lastAction)).map(n->n.getCompound(lastAction)); 71 | } 72 | 73 | public static NbtCompound getOrCreateAction(MinecraftServer server, UUID player, String guiName, String lastAction) { 74 | return Helper.computeIfAbsent(getOrCreateGui(server, player, guiName), s->new NbtCompound(), lastAction); 75 | } 76 | 77 | public static void setConsumable(MinecraftServer server, UUID player, String guiName, String lastAction, String consumable, NbtCompound data) { 78 | getOrCreateAction(server, player, guiName, lastAction).put(consumable, data); 79 | } 80 | 81 | public static void removeConsumable(MinecraftServer server, UUID player, String guiName, String lastAction, String consumable) { 82 | getAction(server, player, guiName, lastAction).ifPresent(n->n.remove(consumable)); 83 | } 84 | 85 | public static Optional getConsumable(MinecraftServer server, UUID player, String guiName, String lastAction, String consumable) { 86 | return getAction(server, player, guiName, lastAction).filter(n->n.contains(consumable)).map(n->n.getCompound(consumable)); 87 | } 88 | 89 | public static NbtCompound getOrCreateConsumable(MinecraftServer server, UUID player, String guiName, String lastAction, String consumable) { 90 | return Helper.computeIfAbsent(getOrCreateAction(server, player, guiName, lastAction), s->new NbtCompound(), consumable); 91 | } 92 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/ElementHelper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.util.Map; 4 | import eu.pb4.sgui.api.elements.GuiElement; 5 | import eu.pb4.sgui.api.elements.GuiElementInterface; 6 | import eu.pb4.sgui.api.elements.GuiElementInterface.ClickCallback; 7 | import megaminds.actioninventory.gui.callback.CancellableCallback; 8 | import megaminds.actioninventory.gui.elements.DelegatedElement; 9 | import net.minecraft.enchantment.EnchantmentHelper; 10 | import net.minecraft.item.Item; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.item.ItemStack.TooltipSection; 13 | import net.minecraft.item.Items; 14 | import net.minecraft.text.Text; 15 | 16 | public class ElementHelper { 17 | public static final ClickCallback CLOSE = (i,t,a,g)->g.close(); 18 | 19 | private ElementHelper() {} 20 | 21 | public static GuiElementInterface getLast(ClickCallback callback) { 22 | return of(Items.ARROW, "Last", 1, true, callback); 23 | } 24 | 25 | public static GuiElementInterface getFirst(ClickCallback callback) { 26 | return of(Items.ARROW, "First", 1, true, callback); 27 | } 28 | 29 | public static GuiElementInterface getNext(ClickCallback callback, int nextIndex) { 30 | return of(Items.ARROW, "Next", nextIndex+1, callback); 31 | } 32 | 33 | public static GuiElementInterface getPrevious(ClickCallback callback, int previousIndex) { 34 | return previousIndex<0 ? null : of(Items.ARROW, "Previous", previousIndex+1, callback); 35 | } 36 | 37 | public static GuiElementInterface getCancel(ClickCallback callback) { 38 | return of(Items.RED_WOOL, "Cancel", 1, combine(callback, CLOSE)); 39 | } 40 | 41 | public static GuiElementInterface getConfirm(ClickCallback callback) { 42 | return getDone(callback, "Confirm"); 43 | } 44 | 45 | public static GuiElementInterface getDone(ClickCallback callback, String name) { 46 | return of(Items.GREEN_WOOL, name, 1, combine(callback, CLOSE)); 47 | } 48 | 49 | public static GuiElementInterface of(Item item, String name, int count, ClickCallback callback) { 50 | return of(item, name, count, false, callback); 51 | } 52 | 53 | public static GuiElementInterface of(Item item, String name, int count, boolean glint, ClickCallback callback) { 54 | ItemStack stack = new ItemStack(item, count); 55 | stack.setCustomName(Text.of(name)); 56 | if (glint) addGlint(stack); 57 | return new GuiElement(hideAllFlags(stack), callback); 58 | } 59 | 60 | public static GuiElementInterface getDelegate(GuiElementInterface el, ClickCallback callback, boolean appendCallback) { 61 | if (el instanceof DelegatedElement e) { 62 | return e; 63 | } 64 | return new DelegatedElement(el, appendCallback ? combine(el.getGuiCallback(), callback) : callback); 65 | } 66 | 67 | public static ItemStack addGlint(ItemStack stack) { 68 | if (!stack.hasGlint()) { 69 | stack.addEnchantment(null, 0); 70 | } 71 | return stack; 72 | } 73 | 74 | public static ItemStack removeEnchants(ItemStack stack) { 75 | EnchantmentHelper.set(Map.of(), stack); 76 | return stack; 77 | } 78 | 79 | public static ItemStack hideAllFlags(ItemStack stack) { 80 | for (TooltipSection f : TooltipSection.values()) { 81 | stack.addHideFlag(f); 82 | } 83 | return stack; 84 | } 85 | 86 | public static ClickCallback combine(ClickCallback one, ClickCallback two) { 87 | if (one==null && two==null) return GuiElementInterface.EMPTY_CALLBACK; 88 | if (one!=null && two==null) return one; 89 | if (one==null) return two; 90 | 91 | if (one instanceof CancellableCallback c) { 92 | return (CancellableCallback)(i,t,a,g) -> { 93 | if (!c.cancellingClick(i,t,a,g)) { 94 | two.click(i, t, a, g); 95 | return false; 96 | } 97 | return true; 98 | }; 99 | } 100 | return (i,t,a,g) -> { 101 | one.click(i, t, a, g); 102 | two.click(i, t, a, g); 103 | }; 104 | } 105 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/GuiHelper.java: -------------------------------------------------------------------------------- 1 | //package megaminds.actioninventory.util; 2 | // 3 | //import java.util.List; 4 | //import java.util.function.BiConsumer; 5 | //import java.util.function.Consumer; 6 | //import java.util.function.Function; 7 | //import java.util.regex.Pattern; 8 | // 9 | //import eu.pb4.sgui.api.elements.GuiElementInterface; 10 | //import it.unimi.dsi.fastutil.booleans.BooleanConsumer; 11 | //import it.unimi.dsi.fastutil.ints.Int2IntFunction; 12 | //import it.unimi.dsi.fastutil.ints.IntConsumer; 13 | //import megaminds.actioninventory.gui.BetterAnvilGui; 14 | //import megaminds.actioninventory.gui.BetterGui; 15 | //import megaminds.actioninventory.gui.BetterGuiI; 16 | //import megaminds.actioninventory.gui.callback.BetterClickCallback; 17 | //import net.minecraft.screen.ScreenHandlerType; 18 | //import net.minecraft.text.Text; 19 | // 20 | //public class GuiHelper { 21 | // private static final Pattern NON_NUMBER = Pattern.compile("\\D"); 22 | // 23 | // public static ScreenHandlerType getHandler(int rows) { 24 | // if (rows<1) throw new IllegalArgumentException("Rows cannot be less than 1."); 25 | // if (rows>6) throw new IllegalArgumentException("Rows cannot be greater than 6."); 26 | // 27 | // return switch (rows) { 28 | // case 1 -> ScreenHandlerType.GENERIC_9X1; 29 | // case 2 -> ScreenHandlerType.GENERIC_9X2; 30 | // case 3 -> ScreenHandlerType.GENERIC_9X3; 31 | // case 4 -> ScreenHandlerType.GENERIC_9X4; 32 | // case 5 -> ScreenHandlerType.GENERIC_9X5; 33 | // case 6 -> ScreenHandlerType.GENERIC_9X6; 34 | // default -> throw new IllegalArgumentException("Unexpected value: " + rows); 35 | // }; 36 | // } 37 | // 38 | // public static BetterClickCallback getInt(Text question, int defaultAnswer, Int2IntFunction filter, Int2IntFunction postFilter, IntConsumer onFinish) { 39 | // return getString(question, ""+defaultAnswer, s-> { 40 | // return "" + getInt(s, filter); 41 | // }, s-> { 42 | // return "" + getInt(s, postFilter); 43 | // }, s->onFinish.accept(Integer.parseInt(s))); 44 | // } 45 | // 46 | // private static int getInt(String s, Int2IntFunction filter) { 47 | // s = NON_NUMBER.matcher(s).replaceAll(""); 48 | // int x = s.isBlank() ? 0 : Integer.parseInt(s); 49 | // return filter!=null ? filter.applyAsInt(x) : x; 50 | // } 51 | // 52 | // public static BetterClickCallback getString(Text question, String defaultAnswer, Function filter, Function postFilter, Consumer onFinish) { 53 | // return (i,t,a,g) -> { 54 | // BetterAnvilGui input = new BetterAnvilGui(g.getPlayer(), false); 55 | // input.setDefaultInputValue(defaultAnswer); 56 | // input.setTitle(question); 57 | // input.setChained(true); 58 | // input.setFilter(filter); 59 | // input.setPostFilter(postFilter); 60 | // input.setOnFinish(onFinish); 61 | // input.open(g); 62 | // return false; 63 | // }; 64 | // } 65 | // 66 | // public static BetterClickCallback getBoolean(Text question, BooleanConsumer onFinish) { 67 | // return (i,t,a,g) -> { 68 | // BetterGui gui = new BetterGui(ScreenHandlerType.GENERIC_9X1, g.getPlayer(), false); 69 | // gui.setChained(true); 70 | // gui.setSlot(1, ElementHelper.getDone((i2,t2,a2,g2)->onFinish.accept(true), "True")); 71 | // gui.setSlot(3, ElementHelper.getDone((i2,t2,a2,g2)->onFinish.accept(false), "False")); 72 | // gui.setSlot(7, ElementHelper.getCancel(null)); 73 | // gui.open(g); 74 | // return false; 75 | // }; 76 | // } 77 | // 78 | // private static BetterClickCallback getOptionsSafe(Text question, List choices, Function toEl, Consumer onFinish) { 79 | // return (i,t,a,g) -> { 80 | // int l = choices.size(); 81 | // int r = l/9 + (l%9==0?0:1); 82 | // 83 | // BiConsumer adder; 84 | // Consumer opener; 85 | // if (r<=10) { 86 | // BetterGui gui = new BetterGui(getHandler(r), g.getPlayer(), r>6); 87 | // gui.setChained(true); 88 | // gui.setTitle(question); 89 | // adder = (j,x)->gui.setSlot(j, toEl.apply(x)); 90 | // opener = gui::open; 91 | // } else { 92 | // PagedGuiBuilder b = PagedGui.builder(r, false) 93 | // .chained() 94 | // .enableAutoNext(true) 95 | // .title(question); 96 | // adder = (j,x)->b.element(toEl.apply(x)); 97 | // opener = q->b.build(q.getPlayer()).open(q); 98 | // } 99 | // for (int j = 0; j < l; j++) { 100 | // adder.accept(j, choices.get(j)); 101 | // } 102 | // opener.accept(g); 103 | // return false; 104 | // }; 105 | // } 106 | // 107 | // public static BetterClickCallback getOptions(Text question, List choices, Function toEl, Consumer onFinish) { 108 | // return getOptionsSafe(question, choices, e ->ElementHelper.getDelegate(toEl.apply(e), ElementHelper.combine((i2,t2,a2,g2)->onFinish.accept(e), ElementHelper.CLOSE), true), onFinish); 109 | // } 110 | // 111 | // public static BetterClickCallback getOptionsDefStack(Text question, List choices, Function toStr, Consumer onFinish) { 112 | // return getOptionsSafe(question, choices, e -> ElementHelper.getDone((i2,t2,a2,g2)->onFinish.accept(e), toStr.apply(e)), onFinish); 113 | // } 114 | //} -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/Helper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.InvalidPathException; 6 | import java.nio.file.Path; 7 | import java.util.Collection; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.Objects; 13 | import java.util.Set; 14 | import java.util.UUID; 15 | import java.util.function.Consumer; 16 | import java.util.function.Function; 17 | import java.util.function.Predicate; 18 | import java.util.function.Supplier; 19 | 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | import eu.pb4.placeholders.api.PlaceholderContext; 24 | import megaminds.actioninventory.ActionInventoryMod; 25 | import net.minecraft.item.ItemStack; 26 | import net.minecraft.nbt.NbtCompound; 27 | import net.minecraft.nbt.NbtElement; 28 | import net.minecraft.server.MinecraftServer; 29 | import net.minecraft.server.network.ServerPlayerEntity; 30 | 31 | /** 32 | * Just some random helper methods. 33 | */ 34 | public class Helper { 35 | private static final String GLOBAL = "GLOBAL"; 36 | private static final String WORLD = "WORLD"; 37 | 38 | private Helper() {} 39 | 40 | /** 41 | * Parses nbt of the item stack. The same stack is returned with the changes. 42 | */ 43 | public static ItemStack parseItemStack(ItemStack stack, PlaceholderContext context) { 44 | if (stack.hasNbt()) { 45 | NbtPlaceholderParser.replaceCompound(context, stack.getNbt()); 46 | stack.getItem().postProcessNbt(stack.getNbt()); 47 | } 48 | 49 | return stack; 50 | } 51 | 52 | /** 53 | * False if compound==null 54 | */ 55 | public static boolean getBoolean(@Nullable NbtCompound compound, String key) { 56 | return compound!=null && compound.getBoolean(key); 57 | } 58 | 59 | /** 60 | * 0 if compound==null 61 | */ 62 | public static int getInt(@Nullable NbtCompound compound, String key) { 63 | return compound==null ? 0 : compound.getInt(key); 64 | } 65 | 66 | /** 67 | * 0 if compound==null 68 | */ 69 | public static long getLong(@Nullable NbtCompound compound, String key) { 70 | return compound==null ? 0 : compound.getInt(key); 71 | } 72 | 73 | /** 74 | * Throws error if there is no player for the UUID. 75 | */ 76 | @NotNull 77 | public static ServerPlayerEntity getPlayer(MinecraftServer server, UUID playerUuid) { 78 | var player = server.getPlayerManager().getPlayer(playerUuid); 79 | //TODO add toggle for this error 80 | Objects.requireNonNull(player, ()->"No Player Exists for UUID: "+playerUuid); 81 | return player; 82 | } 83 | 84 | public static int getTotalExperienceForLevel(int level) { 85 | if (level<17) { 86 | return level*level + 6*level; 87 | } else if (level<32) { 88 | return (int) (2.5*level*level - 40.5*level + 360); 89 | } else { 90 | return (int) (4.5*level*level - 162.5*level + 2220); 91 | } 92 | } 93 | 94 | @SuppressWarnings("unchecked") 95 | public static R computeIfAbsent(NbtCompound holder, Function creator, String key) { 96 | if (holder.contains(key)) { 97 | return (R) holder.get(key); 98 | } 99 | 100 | var r = creator.apply(key); 101 | holder.put(key, r); 102 | return r; 103 | } 104 | 105 | public static boolean containsAny(Collection col, Predicate tester) { 106 | return getFirst(col, tester)!=null; 107 | } 108 | 109 | /** 110 | * Returns the first object in the given collection that matches the given predicate.
111 | * Returns null if none match. 112 | */ 113 | public static E getFirst(Collection col, Predicate tester) { 114 | for (E e : col) { 115 | if (tester.test(e)) { 116 | return e; 117 | } 118 | } 119 | return null; 120 | } 121 | 122 | public static boolean ifDo(E e, Consumer consumer) { 123 | if (e!=null) { 124 | consumer.accept(e); 125 | return true; 126 | } 127 | return false; 128 | } 129 | 130 | public static boolean ifOrDo(E e, Predicate test, Consumer consumer) { 131 | if (e!=null || test.test(e)) { 132 | consumer.accept(e); 133 | return true; 134 | } 135 | return false; 136 | } 137 | 138 | public static Map mapEach(Map m, Function func, R defaultObj, boolean allowNull) { 139 | if (m==null) return null; //NOSONAR Part of contract 140 | 141 | Map map = new HashMap<>(); 142 | for (Entry e : m.entrySet()) { 143 | R r = apply(e.getValue(), func, defaultObj); 144 | if (allowNull || r!=null) { 145 | map.put(e.getKey(), r); 146 | } 147 | } 148 | return map; 149 | } 150 | 151 | public static Map mapEach(Map m, Function keyFunc, Function valueFunc, R1 defaultKey, R2 defaultValue, boolean allowNull) { 152 | if (m==null) return null; //NOSONAR Part of contract 153 | 154 | Map map = new HashMap<>(); 155 | for (Entry e : m.entrySet()) { 156 | R1 rk = apply(e.getKey(), keyFunc, defaultKey); 157 | R2 rv = apply(e.getValue(), valueFunc, defaultValue); 158 | if (allowNull || rk!=null&&rv!=null) { 159 | map.put(rk, rv); 160 | } 161 | } 162 | return map; 163 | } 164 | 165 | public static boolean notNullAnd(E o, Predicate func) { 166 | return o!=null && func.test(o); 167 | } 168 | 169 | public static NbtCompound mapToCompound(Map map) { 170 | NbtCompound c = new NbtCompound(); 171 | map.forEach(c::put); 172 | return c; 173 | } 174 | 175 | /** 176 | * Assumes contents of compound are all of type V. 177 | */ 178 | @SuppressWarnings("unchecked") 179 | public static Map compoundToMap(NbtCompound c) { 180 | Set keys = c.getKeys(); 181 | Map map = new HashMap<>(keys.size()); 182 | for (String s : keys) { 183 | map.put(s, (V)c.get(s)); 184 | } 185 | return map; 186 | } 187 | 188 | public static List resolvePaths(List paths, Path global, Path server) { 189 | return paths.stream().map(s->resolvePath(s, global, server)).filter(Objects::nonNull).toList(); 190 | } 191 | 192 | public static Path resolvePath(String path, Path global, Path server) { 193 | Path p; 194 | if (path.startsWith(GLOBAL)) { 195 | p = global.resolve(path.substring(GLOBAL.length()+1)); 196 | } else if (path.startsWith(WORLD)) { 197 | p = server.resolve(path.substring(WORLD.length()+1)); 198 | } else { 199 | try { 200 | p = Path.of(path); 201 | } catch (InvalidPathException e) { 202 | p = null; 203 | } 204 | } 205 | return p!=null&&checkDir(p) ? p : null; 206 | } 207 | 208 | public static boolean checkDir(Path p) { 209 | try { 210 | Files.createDirectories(p); 211 | return true; 212 | } catch (IOException e) { 213 | ActionInventoryMod.warn("Couldn't create directory: "+p); 214 | e.printStackTrace(); 215 | return false; 216 | } 217 | } 218 | 219 | /** 220 | * Returns null if from is null. 221 | */ 222 | public static R apply(E from, Function func) { 223 | return apply(from, func, (R)null); 224 | } 225 | 226 | /** 227 | * Returns def if from is null. 228 | */ 229 | public static R apply(E from, Function func, R def) { 230 | return from!=null ? func.apply(from) : def; 231 | } 232 | 233 | /** 234 | * Returns def.get() if from is null 235 | */ 236 | public static R apply(E from, Function func, Supplier def) { 237 | return from!=null ? func.apply(from) : def.get(); 238 | } 239 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/JsonHelper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.lang.reflect.Array; 4 | import java.lang.reflect.Type; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.function.BiFunction; 9 | import java.util.function.Consumer; 10 | import java.util.function.Function; 11 | import java.util.function.IntSupplier; 12 | import java.util.function.Supplier; 13 | import java.util.stream.Stream; 14 | 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import com.google.gson.JsonArray; 18 | import com.google.gson.JsonDeserializationContext; 19 | import com.google.gson.JsonElement; 20 | import com.google.gson.JsonParseException; 21 | import com.google.gson.reflect.TypeToken; 22 | import net.minecraft.text.Text; 23 | import net.minecraft.util.Identifier; 24 | 25 | public class JsonHelper { 26 | private JsonHelper() {} 27 | 28 | public static boolean isNull(JsonElement e) { 29 | return e==null || e.isJsonNull(); 30 | } 31 | 32 | public static boolean notNull(JsonElement e) { 33 | return e!=null&&!e.isJsonNull(); 34 | } 35 | 36 | public static E notNull(E e, E def) { 37 | return e!=null ? e : def; 38 | } 39 | 40 | public static E notNull(JsonElement e, Function func, String error) { 41 | if (notNull(e)) { 42 | return func.apply(e); 43 | } 44 | throw new JsonParseException(error); 45 | } 46 | 47 | /** 48 | * If the given object is null, does nothing. Returns false.
49 | * If the given object is not null, executes the given consumer. Returns true. 50 | */ 51 | public static boolean ifDo(E e, Consumer consumer) { 52 | if (notNull(e)) { 53 | consumer.accept(e); 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | /** 60 | * If the given object is not null and the result of the function is not null, executes the given consumer. 61 | * Otherwise does nothing. 62 | */ 63 | public static void getDo(JsonElement e, Function func, Consumer consumer) { 64 | R temp; 65 | if (notNull(e) && (temp=func.apply(e))!=null) { 66 | consumer.accept(temp); 67 | } 68 | } 69 | 70 | public static void getDo(JsonElement e, Class clazz, BiFunction, R> func, Consumer consumer) { 71 | R temp; 72 | if (notNull(e) && (temp=func.apply(e, clazz))!=null) { 73 | consumer.accept(temp); 74 | } 75 | } 76 | 77 | public static R orError(JsonElement e, Function func, String error) { 78 | if (e==null) throw new JsonParseException(error); 79 | return func.apply(e); 80 | } 81 | 82 | //GET AS TYPE HELPERS 83 | @NotNull 84 | public static JsonArray array(JsonElement e) { 85 | return array(e, JsonArray::new); 86 | } 87 | 88 | public static JsonArray array(JsonElement e, Supplier def) { 89 | if (notNull(e)) { 90 | if (e.isJsonArray()) { 91 | return e.getAsJsonArray(); 92 | } 93 | 94 | JsonArray arr = new JsonArray(1); 95 | arr.add(e); 96 | return arr; 97 | } 98 | return def.get(); 99 | } 100 | 101 | /** 102 | * Returns null if e is null. 103 | */ 104 | public static String string(JsonElement e) { 105 | return string(e, null); 106 | } 107 | 108 | public static String string(JsonElement e, String def) { 109 | return notNull(e) ? e.getAsString() : def; 110 | } 111 | 112 | /** 113 | * Returns null if e is null. 114 | */ 115 | public static Identifier identifier(JsonElement e) { 116 | return identifier(e, (Identifier)null); 117 | } 118 | 119 | public static Identifier identifier(JsonElement e, Identifier def) { 120 | return notNull(e) ? new Identifier(e.getAsString()) : def; 121 | } 122 | 123 | public static Identifier identifier(JsonElement e, Supplier def) { 124 | return notNull(e) ? new Identifier(e.getAsString()) : def.get(); 125 | } 126 | 127 | /** 128 | * Returns 0 if e is null. 129 | */ 130 | public static int integer(JsonElement e) { 131 | return integer(e, 0); 132 | } 133 | 134 | public static int integer(JsonElement e, int def) { 135 | return notNull(e) ? e.getAsInt() : def; 136 | } 137 | 138 | public static int integer(JsonElement e, IntSupplier def) { 139 | return notNull(e) ? e.getAsInt() : def.getAsInt(); 140 | } 141 | 142 | /** 143 | * Returns 0 if e is null. 144 | */ 145 | 146 | public static float floatt(JsonElement e) { 147 | return floatt(e, 0); 148 | } 149 | 150 | public static float floatt(JsonElement e, float def) { 151 | return notNull(e) ? e.getAsFloat() : def; 152 | } 153 | 154 | /** 155 | * Returns false if e is null. 156 | */ 157 | public static boolean bool(JsonElement e) { 158 | return bool(e, false); 159 | } 160 | 161 | public static boolean bool(JsonElement e, boolean def) { 162 | return notNull(e) ? e.getAsBoolean() : def; 163 | } 164 | 165 | public static Optional optional(JsonElement e, JsonDeserializationContext context, Type inner) { 166 | return context.deserialize(e, TypeToken.getParameterized(Optional.class, inner).getType()); 167 | } 168 | 169 | /** 170 | * Returns {@link TextContent#empty()} if e is null. 171 | */ 172 | @NotNull 173 | public static Text text(JsonElement e) { 174 | return text(e, Text.empty()); 175 | } 176 | 177 | public static Text text(JsonElement e, Text def) { 178 | return notNull(e) ? notNull(Text.Serialization.fromJsonTree(e), def) : def; 179 | } 180 | 181 | /** 182 | * Returns null if e is null. 183 | */ 184 | public static E clazz(JsonElement e, Class clazz, JsonDeserializationContext context) { 185 | return clazz(e, clazz, context, (E)null); 186 | } 187 | 188 | public static E clazz(JsonElement e, Class clazz, JsonDeserializationContext context, E def) { 189 | return notNull(e) ? context.deserialize(e, clazz) : def; 190 | } 191 | 192 | public static E clazz(JsonElement e, Class clazz, JsonDeserializationContext context, Supplier def) { 193 | return notNull(e) ? context.deserialize(e, clazz) : def.get(); 194 | } 195 | 196 | /** 197 | * Returns null if e is null. 198 | */ 199 | public static E custom(JsonElement e, Function func) { 200 | return custom(e, func, (E)null); 201 | } 202 | 203 | public static E custom(JsonElement e, Function func, E def) { 204 | return notNull(e) ? func.apply(e) : def; 205 | } 206 | 207 | public static E custom(JsonElement e, Function func, Supplier def) { 208 | return notNull(e) ? func.apply(e) : def.get(); 209 | } 210 | 211 | @NotNull 212 | public static List stringList(JsonElement e, boolean allowNull) { 213 | List list = new ArrayList<>(); 214 | array(e).forEach(s->Helper.ifOrDo(string(s), t->allowNull, list::add)); 215 | return list; 216 | } 217 | 218 | @NotNull 219 | public static List clazzList(JsonElement e, Class clazz, JsonDeserializationContext context, boolean allowNull) { 220 | List list = new ArrayList<>(); 221 | array(e).forEach(c->Helper.ifOrDo(clazz(c, clazz, context), t->allowNull, list::add)); 222 | return list; 223 | } 224 | 225 | @NotNull 226 | public static List customList(JsonElement e, Function func, boolean allowNull) { 227 | List list = new ArrayList<>(); 228 | array(e).forEach(c->Helper.ifOrDo(custom(c, func), t->allowNull, list::add)); 229 | return list; 230 | } 231 | 232 | @NotNull 233 | public static > R customList(JsonElement e, Function func, boolean allowNull, R list) { 234 | array(e).forEach(c->Helper.ifOrDo(custom(c, func), t->allowNull, list::add)); 235 | return list; 236 | } 237 | 238 | @SuppressWarnings("unchecked") 239 | @NotNull 240 | public static E[] clazzArr(JsonElement e, Class clazz, JsonDeserializationContext context, boolean allowNull) { 241 | return clazzList(e, clazz, context, allowNull).toArray(i->(E[])Array.newInstance(clazz, i)); 242 | } 243 | 244 | @NotNull 245 | public static E[] clazzArr(JsonElement e, Class clazz, JsonDeserializationContext context) { 246 | return clazzArr(e, clazz, context, false); 247 | } 248 | 249 | @NotNull 250 | public static Stream clazzStream(JsonElement e, Class clazz, JsonDeserializationContext context, boolean allowNull) { 251 | return clazzList(e, clazz, context, allowNull).stream(); 252 | } 253 | 254 | @NotNull 255 | public static Stream clazzStream(JsonElement e, Class clazz, JsonDeserializationContext context) { 256 | return clazzStream(e, clazz, context, false); 257 | } 258 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/MessageHelper.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import java.util.UUID; 6 | 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import com.mojang.authlib.GameProfile; 10 | 11 | import eu.pb4.placeholders.api.PlaceholderContext; 12 | import eu.pb4.placeholders.api.Placeholders; 13 | import eu.pb4.placeholders.api.node.TextNode; 14 | import net.minecraft.network.message.MessageType; 15 | import net.minecraft.network.message.SentMessage; 16 | import net.minecraft.network.message.SignedMessage; 17 | import net.minecraft.registry.RegistryKey; 18 | import net.minecraft.registry.RegistryKeys; 19 | import net.minecraft.server.MinecraftServer; 20 | import net.minecraft.server.PlayerManager; 21 | import net.minecraft.server.command.ServerCommandSource; 22 | import net.minecraft.server.network.ServerPlayerEntity; 23 | import net.minecraft.text.MutableText; 24 | import net.minecraft.text.Text; 25 | import net.minecraft.util.Formatting; 26 | import net.minecraft.util.Identifier; 27 | import static megaminds.actioninventory.util.Helper.getPlayer; 28 | 29 | public class MessageHelper { 30 | private static final Formatting ERROR = Formatting.RED; 31 | private static final Formatting SUCCESS = Formatting.GREEN; 32 | 33 | private MessageHelper() {} 34 | 35 | public static MutableText toSuccess(String msg) { 36 | return Text.literal(msg).formatted(SUCCESS); 37 | } 38 | 39 | public static MutableText toError(String error) { 40 | return Text.literal(error).formatted(ERROR); 41 | } 42 | 43 | /** 44 | * Sends a message to the given players. 45 | */ 46 | public static void message(@Nullable UUID from, List to, Text message, @Nullable Identifier type, MinecraftServer server) { 47 | if (from == null) { 48 | message = Placeholders.parseText(message, PlaceholderContext.of(server)); 49 | for (var uuid : to) { 50 | getPlayer(server, uuid).sendMessage(message); 51 | } 52 | } else { 53 | for (var uuid : to) { 54 | var p = getPlayer(server, from); 55 | message = Placeholders.parseText(message, PlaceholderContext.of(p)); 56 | 57 | var msg = SentMessage.of(SignedMessage.ofUnsigned(from, "").withUnsignedContent(message)); 58 | var source = p.getCommandSource(); 59 | var params = MessageType.params(idToKey(type, server).orElse(MessageType.CHAT), source); 60 | var reciever = getPlayer(server, uuid); 61 | reciever.sendChatMessage(msg, source.shouldFilterText(reciever), params); 62 | } 63 | } 64 | } 65 | 66 | private static Optional> idToKey(Identifier id, MinecraftServer server) { 67 | var reg = server.getRegistryManager().get(RegistryKeys.MESSAGE_TYPE); 68 | return reg.getKey(reg.get(id)); 69 | } 70 | 71 | /** 72 | * Broadcasts a message to all players. 73 | */ 74 | public static void broadcast(@Nullable UUID from, Text message, @Nullable Identifier type, MinecraftServer server) { 75 | if (from == null) { 76 | message = Placeholders.parseText(message, PlaceholderContext.of(server)); 77 | server.getPlayerManager().broadcast(message, false); 78 | } else { 79 | var p = getPlayer(server, from); 80 | message = Placeholders.parseText(message, PlaceholderContext.of(p)); 81 | 82 | var msg = SignedMessage.ofUnsigned(from, "").withUnsignedContent(message); 83 | var source = p.getCommandSource(); 84 | var params = MessageType.params(idToKey(type, server).orElse(MessageType.CHAT), source); 85 | server.getPlayerManager().broadcast(msg, source, params); 86 | } 87 | } 88 | 89 | /** 90 | * Logs a message to the server. 91 | */ 92 | public static void log(Text message, MinecraftServer server) { 93 | server.sendMessage(Placeholders.parseText(message, PlaceholderContext.of(server))); 94 | } 95 | 96 | /** 97 | * Executes the given command as the server. 98 | */ 99 | public static void executeCommand(MinecraftServer server, String command) { 100 | executeCommand(server.getCommandSource(), command); 101 | } 102 | 103 | /** 104 | * Executes the given command as the player.
105 | * Command may fail if the player has incorrect permissions. 106 | */ 107 | public static void executeCommand(ServerPlayerEntity player, String command) { 108 | executeCommand(player.getCommandSource(), command); 109 | } 110 | 111 | public static void executeCommand(ServerCommandSource source, String command) { 112 | source.getServer().getCommandManager().executeWithPrefix(source, Placeholders.parseText(TextNode.of(command), PlaceholderContext.of(source)).getString()); 113 | } 114 | 115 | /** 116 | * Executes the given command as the given player.
117 | * If the player was not already an op, they made an op before executing the command and deopped after completing the command. 118 | */ 119 | public static void executeOppedCommand(ServerPlayerEntity player, String command) { 120 | PlayerManager manager = player.getServer().getPlayerManager(); 121 | GameProfile profile = player.getGameProfile(); 122 | boolean wasOp = manager.isOperator(profile); 123 | 124 | if (!wasOp) manager.addToOperators(profile); 125 | executeCommand(player, command); 126 | if (!wasOp) manager.removeFromOperators(profile); 127 | } 128 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/NbtPlaceholderParser.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | import eu.pb4.placeholders.api.PlaceholderContext; 4 | import eu.pb4.placeholders.api.Placeholders; 5 | import net.minecraft.nbt.NbtCompound; 6 | import net.minecraft.nbt.NbtElement; 7 | import net.minecraft.nbt.NbtList; 8 | import net.minecraft.nbt.NbtString; 9 | import net.minecraft.text.Text; 10 | 11 | public class NbtPlaceholderParser { 12 | private NbtPlaceholderParser() {} 13 | 14 | public static void replaceCompound(PlaceholderContext context, NbtCompound compound) { 15 | for (var key : compound.getKeys()) { 16 | var type = compound.getType(key); 17 | if (type == NbtElement.STRING_TYPE) { 18 | replaceString(context, compound, key); 19 | } else if (type == NbtElement.LIST_TYPE) { 20 | replaceList(context, compound.getList(key, NbtElement.STRING_TYPE)); 21 | } else if (type == NbtElement.COMPOUND_TYPE) { 22 | replaceCompound(context, compound.getCompound(key)); 23 | } 24 | } 25 | } 26 | 27 | public static void replaceString(PlaceholderContext context, NbtCompound compound, String key) { 28 | compound.put(key, parseString(context, compound.getString(key))); 29 | } 30 | 31 | public static void replaceList(PlaceholderContext context, NbtList list) { 32 | for (var i = 0; i < list.size(); i++) { 33 | list.set(i, parseString(context, list.getString(i))); 34 | } 35 | } 36 | 37 | public static NbtString parseString(PlaceholderContext context, String string) { 38 | return NbtString.of(Placeholders.parseText(Text.of(string), context).getString()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/Printer.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | //import java.io.IOException; 4 | //import java.lang.annotation.Documented; 5 | //import java.lang.annotation.ElementType; 6 | //import java.lang.annotation.Inherited; 7 | //import java.lang.annotation.Retention; 8 | //import java.lang.annotation.RetentionPolicy; 9 | //import java.lang.annotation.Target; 10 | //import java.nio.file.Files; 11 | //import java.nio.file.Path; 12 | //import java.nio.file.StandardOpenOption; 13 | //import java.util.ArrayList; 14 | //import java.util.Arrays; 15 | //import java.util.Collections; 16 | //import java.util.HashMap; 17 | //import java.util.List; 18 | //import java.util.Map; 19 | //import java.util.Set; 20 | //import java.util.UUID; 21 | // 22 | //import com.google.gson.Gson; 23 | //import com.google.gson.GsonBuilder; 24 | //import com.google.gson.TypeAdapter; 25 | //import com.google.gson.TypeAdapterFactory; 26 | //import com.google.gson.annotations.JsonAdapter; 27 | //import com.google.gson.reflect.TypeToken; 28 | //import com.google.gson.stream.JsonReader; 29 | //import com.google.gson.stream.JsonWriter; 30 | // 31 | //import eu.pb4.sgui.api.ScreenProperty; 32 | //import megaminds.actioninventory.ActionInventoryMod; 33 | //import megaminds.actioninventory.actions.BasicAction; 34 | //import megaminds.actioninventory.actions.OpenGui; 35 | //import megaminds.actioninventory.actions.CloseAction; 36 | //import megaminds.actioninventory.actions.CommandAction; 37 | //import megaminds.actioninventory.actions.ConsumeAction; 38 | //import megaminds.actioninventory.actions.GiveAction; 39 | //import megaminds.actioninventory.actions.GroupAction; 40 | //import megaminds.actioninventory.actions.MessageAction; 41 | //import megaminds.actioninventory.actions.RequirementAction; 42 | //import megaminds.actioninventory.actions.SendPropertyAction; 43 | //import megaminds.actioninventory.actions.SoundAction; 44 | //import megaminds.actioninventory.consumables.BasicConsumable; 45 | //import megaminds.actioninventory.consumables.XpConsumable; 46 | //import megaminds.actioninventory.gui.NamedGuiBuilder; 47 | //import megaminds.actioninventory.gui.elements.AccessableGuiElement; 48 | //import megaminds.actioninventory.gui.elements.SlotElement; 49 | //import megaminds.actioninventory.misc.Enums.GuiType; 50 | //import megaminds.actioninventory.misc.Enums.TagOption; 51 | //import megaminds.actioninventory.openers.BlockOpener; 52 | //import megaminds.actioninventory.openers.EntityOpener; 53 | //import megaminds.actioninventory.openers.ItemOpener; 54 | //import megaminds.actioninventory.serialization.PolyAdapterFactory; 55 | //import megaminds.actioninventory.serialization.Serializer; 56 | //import megaminds.actioninventory.serialization.wrappers.InstancedAdapterWrapper; 57 | //import megaminds.actioninventory.serialization.wrappers.TypeAdapterWrapper; 58 | //import megaminds.actioninventory.serialization.wrappers.Validated; 59 | //import megaminds.actioninventory.serialization.wrappers.ValidatedAdapterWrapper; 60 | //import megaminds.actioninventory.serialization.wrappers.WrapperAdapterFactory; 61 | //import megaminds.actioninventory.util.annotations.Instanced; 62 | //import megaminds.actioninventory.util.annotations.Poly; 63 | //import megaminds.actioninventory.util.annotations.PolyName; 64 | //import net.minecraft.block.Blocks; 65 | //import net.minecraft.item.ItemStack; 66 | //import net.minecraft.item.Items; 67 | //import net.minecraft.network.MessageType; 68 | //import net.minecraft.screen.ScreenHandlerType; 69 | //import net.minecraft.sound.SoundCategory; 70 | //import net.minecraft.sound.SoundEvent; 71 | //import net.minecraft.sound.SoundEvents; 72 | //import net.minecraft.tag.ItemTags; 73 | //import net.minecraft.text.LiteralText; 74 | //import net.minecraft.util.Identifier; 75 | 76 | @SuppressWarnings("all") //Testing only class 77 | public class Printer { 78 | // public static void print(NamedGuiBuilder builder, Path folder) { 79 | // if (Helper.checkDir(folder)) { 80 | // try { 81 | // Files.writeString(nextFile(folder, builder.getName().toUnderscoreSeparatedString()), Serializer.GSON.toJson(builder), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 82 | // } catch (IOException e) { 83 | // ActionInventoryMod.warn("Failed to save builder"); 84 | // e.printStackTrace(); 85 | // } 86 | // } 87 | // } 88 | // 89 | // public static void print(ItemStack stack) { 90 | // System.err.println(Serializer.GSON.toJson(stack)); 91 | // } 92 | // 93 | // public static void dump(Path gameDir) { 94 | // try { 95 | // Files.writeString(nextFile(gameDir, "FurnaceBuilder"), Serializer.GSON.toJson(createFurnaceBuilder()), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 96 | // Files.writeString(nextFile(gameDir, "9x9Builder"), Serializer.GSON.toJson(create9x6Builder()), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 97 | // Files.writeString(nextFile(gameDir, "Bopener"), Serializer.GSON.toJson(createBlockOpener()), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 98 | // Files.writeString(nextFile(gameDir, "Eopener"), Serializer.GSON.toJson(createEntityOpener()), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 99 | // Files.writeString(nextFile(gameDir, "Iopener"), Serializer.GSON.toJson(createItemOpener()), StandardOpenOption.CREATE, StandardOpenOption.WRITE); 100 | // } catch (IOException e) { 101 | // e.printStackTrace(); 102 | // } 103 | // } 104 | // 105 | // private static Path nextFile(Path p, String name) { 106 | // Path r = p.resolve(name+".json"); 107 | // int i = 1; 108 | // while (Files.exists(r)) r = p.resolve(name+" ("+ i++ +").json"); 109 | // return r; 110 | // } 111 | // 112 | // private static NamedGuiBuilder create9x6Builder() { 113 | // ItemStack[] items = { 114 | // new ItemStack(Items.ACACIA_BOAT, 1), 115 | // new ItemStack(Items.DIAMOND_SWORD), 116 | // new ItemStack(Items.AZALEA_LEAVES_FLOWERS, 5) 117 | // }; 118 | // 119 | // BasicConsumable[] consumables = { 120 | // new XpConsumable(false, 2, 0) 121 | // }; 122 | // 123 | // BasicAction[] actions2 = { 124 | // new CommandAction("/help", false, false), 125 | // new GiveAction(items) 126 | // }; 127 | // 128 | // BasicAction[] actions3 = { 129 | // new MessageAction(new LiteralText("Helooo"), null, null, MessageType.CHAT), 130 | // new CloseAction() 131 | // }; 132 | // 133 | // BasicAction[] actions4 = { 134 | // new SoundAction(SoundEvents.AMBIENT_CAVE, SoundCategory.AMBIENT, null, null, null), 135 | // new CommandAction("/kill @p", true, false) 136 | // }; 137 | // 138 | // ConsumeAction ca2 = new ConsumeAction(actions2, consumables, false, true); 139 | // GroupAction ga = new GroupAction(actions3); 140 | // RequirementAction ra = new RequirementAction(actions4, "[gamemode=spectator]"); 141 | // 142 | // List elements = new ArrayList<>( 143 | // Arrays.asList( 144 | // new AccessableGuiElement(3, ca2, new ItemStack(Items.AZURE_BLUET, 6)), 145 | // new AccessableGuiElement(14, ga, new ItemStack(Items.AXOLOTL_SPAWN_EGG, 17)), 146 | // new AccessableGuiElement(22, ra, new ItemStack(Items.ZOMBIE_HEAD, 51)) 147 | // )); 148 | // 149 | // NamedGuiBuilder builder = new NamedGuiBuilder(ScreenHandlerType.GENERIC_9X6, new Identifier("9x6builder"), new LiteralText("9x6 Builder"), false, elements.toArray(SlotElement[]::new)); 150 | // return builder; 151 | // } 152 | // 153 | // private static NamedGuiBuilder createFurnaceBuilder() { 154 | // BasicConsumable[] consumables = { 155 | // new XpConsumable(false, 2, 0) 156 | // }; 157 | // 158 | // BasicAction[] actions1 = { 159 | // new CommandAction("/give @s minecraft:diamond 64", false, true), 160 | // new SendPropertyAction(ScreenProperty.CURRENT_PROGRESS, 100) 161 | // }; 162 | // 163 | // OpenGui cga = new OpenGui(GuiType.PLAYER, (UUID)null); 164 | // OpenGui cga2 = new OpenGui(GuiType.NAMED_GUI, new Identifier("9x6builder")); 165 | // ConsumeAction ca = new ConsumeAction(actions1, consumables, false, false); 166 | // 167 | // List elements = new ArrayList<>( 168 | // Arrays.asList( 169 | // new AccessableGuiElement(0, cga, new ItemStack(Items.PORKCHOP, 12)), 170 | // new AccessableGuiElement(1, cga2, new ItemStack(Items.COAL, 1)), 171 | // new AccessableGuiElement(2, ca, new ItemStack(Items.ACACIA_PLANKS, 10)) 172 | // )); 173 | // 174 | // NamedGuiBuilder builder = new NamedGuiBuilder(ScreenHandlerType.FURNACE, new Identifier("furnacegui"), new LiteralText("Furnace Builder"), false, elements.toArray(SlotElement[]::new)); 175 | // return builder; 176 | // } 177 | // 178 | // private static BlockOpener createBlockOpener() { 179 | // return new BlockOpener(new Identifier("furnacegui"), Blocks.GOLD_BLOCK, null, null, null, null); 180 | // } 181 | // private static EntityOpener createEntityOpener() { 182 | // return new EntityOpener(new Identifier("furnacegui"), null); 183 | // } 184 | // private static ItemOpener createItemOpener() { 185 | // return new ItemOpener(new Identifier("furnacegui"), null, Set.of(ItemTags.LOGS.getId(), ItemTags.WOOL.getId()), TagOption.ANY); 186 | // } 187 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/ValidationException.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util; 2 | 3 | @SuppressWarnings("serial") 4 | public class ValidationException extends RuntimeException { 5 | public ValidationException() { 6 | super(); 7 | } 8 | 9 | public ValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 10 | super(message, cause, enableSuppression, writableStackTrace); 11 | } 12 | 13 | public ValidationException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | public ValidationException(String message) { 18 | super(message); 19 | } 20 | 21 | public ValidationException(Throwable cause) { 22 | super(cause); 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/annotations/Exclude.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Exclude {} -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/annotations/Instanced.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util.annotations; 2 | 3 | import static java.lang.annotation.ElementType.LOCAL_VARIABLE; 4 | import static java.lang.annotation.ElementType.METHOD; 5 | import static java.lang.annotation.ElementType.TYPE; 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | import java.lang.annotation.Documented; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * Types annotated with this should have a static field {@code INSTANCE} of the type the class, which is the single instance of the class.
14 | * When deserialized by Gson, {@code INSTANCE} is returned. 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target({ TYPE, METHOD, LOCAL_VARIABLE }) 19 | public @interface Instanced { 20 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/annotations/Poly.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util.annotations; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Inherited; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import megaminds.actioninventory.serialization.PolyAdapterFactory; 12 | 13 | /** 14 | * Signals that a type uses {@link PolyAdapterFactory} 15 | */ 16 | @Documented 17 | @Retention(RUNTIME) 18 | @Target(TYPE) 19 | @Inherited 20 | public @interface Poly { 21 | } -------------------------------------------------------------------------------- /src/main/java/megaminds/actioninventory/util/annotations/PolyName.java: -------------------------------------------------------------------------------- 1 | package megaminds.actioninventory.util.annotations; 2 | 3 | import static java.lang.annotation.ElementType.TYPE; 4 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | import megaminds.actioninventory.serialization.PolyAdapterFactory; 11 | 12 | @Documented 13 | @Retention(RUNTIME) 14 | @Target(TYPE) 15 | public @interface PolyName { 16 | /** 17 | * Used to set custom name for types using {@link PolyAdapterFactory}. 18 | * Default is Class.getSimpleName(). 19 | */ 20 | String value(); 21 | } -------------------------------------------------------------------------------- /src/main/resources/assets/actioninventory/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RealMegaMinds/ActionInventoryMod/24c92d47c12674b4df2068c433f534f001d782d4/src/main/resources/assets/actioninventory/icon.png -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "actioninventory", 4 | "version": "${version}", 5 | "name": "Action Inventory Mod", 6 | "description": "Allows the creation of special inventories that do things when items are clicked.", 7 | "authors": [ 8 | "Hoid2" 9 | ], 10 | "contributors": [], 11 | "contact": {}, 12 | "license": "MIT", 13 | "icon": "assets/actioninventory/icon.png", 14 | "environment": "*", 15 | "entrypoints": { 16 | "main": [ 17 | "megaminds.actioninventory.ActionInventoryMod" 18 | ], 19 | "client": [], 20 | "server": [] 21 | }, 22 | "depends": { 23 | "fabric": "*", 24 | "minecraft": "~1.20.2" 25 | } 26 | } --------------------------------------------------------------------------------