├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── dev │ └── arxenix │ └── bettershulkers │ ├── asm │ ├── EarlyRiser.java │ └── ShulkerTarget.java │ ├── ducks │ └── EnchantmentHolder.java │ └── mixin │ ├── BlockItemMixin.java │ ├── BuiltinModelItemRendererMixin.java │ ├── EnchantmentHelperMixin.java │ ├── EnchantmentTargetMixin.java │ ├── ItemAccessor.java │ ├── ItemStackAccessor.java │ ├── ItemStackMixin.java │ ├── PlayerInventoryMixin.java │ ├── ShulkerBoxBlockEntityMixin.java │ ├── ShulkerBoxBlockEntityRendererMixin.java │ └── ShulkerBoxBlockMixin.java ├── kotlin └── dev │ └── arxenix │ └── bettershulkers │ ├── BetterShulkers.kt │ ├── BetterShulkersClient.kt │ ├── ShulkerUtils.kt │ ├── compatibility │ ├── ExtendedShulkerBoxPreviewProvider.kt │ └── ShulkerBoxTooltipHook.kt │ └── enchantments │ ├── Enlarge.kt │ ├── Restock.kt │ └── Vacuum.kt └── resources ├── assets └── bettershulkers │ ├── icon.png │ └── lang │ ├── de_de.json │ └── en_us.json ├── bettershulkers.mixins.json └── fabric.mod.json /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | .classpath 7 | .project 8 | .settings/ 9 | bin/ 10 | 11 | # idea 12 | 13 | .idea/ 14 | *.iml 15 | *.ipr 16 | *.iws 17 | 18 | # fabric 19 | 20 | run/ 21 | minecraft/ 22 | logs/ 23 | 24 | production/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Better Shulkers 2 | 3 | This is a Minecraft mod for the Fabric Modloader, currently compatible with 1.16.2 and 1.16.3 4 | 5 | It adds several new features and enchantments to shulkerboxes to make them even more useful. 6 | 7 | Ideas from Xisumavoid's video https://www.youtube.com/watch?v=FMu8T8KriQY 8 | 9 | # Current Features 10 | 11 | 3 added enchantments 12 | 13 | 14 | ### Enlarge 15 | 16 | 3 levels (I, II, III). Each level increases the size of the shulker box by 1 row. 17 | 18 | 19 | ### Restock 20 | 21 | If you place a block, it will attempt to give you back that block from a Restock shulker in your inventory. 22 | 23 | 24 | ### Vacuum 25 | 26 | If a shulker with the vacuum enchant is in your inventory, then whenever you pick up an item, it will attempt to put it into the shulker if there is a stack of the same type in the shulker. 27 | 28 | 29 | # Planned Features / Improvements 30 | 31 | - Make Restock work with consumable items (e.g. foods, minecarts, boats) 32 | - the default shulker tooltip only shows items in the first 3 rows, so fix that 33 | - possibly incorporate nice tooltips similar to ShulkerBoxTooltip, or find a way to make it compatible 34 | - add Backpack functionality (will not be an enchant) 35 | - "ender shulkers" as described in xisuma's video are currently _not planned_ 36 | - customize shulker box GUI to show enchants 37 | 38 | 39 | # Known Bugs 40 | - If you make use of Loot Tables for shulker boxes, they will not work, as the mod currently completely overwrites the Shulker box drop function 41 | - Probably incompatible with any other mod that touches shulker boxes. Let me know which ones and I'll attempt to make it compatible 42 | - Pick block in creative mode does not copy the shulker box enchantments 43 | - Currently, if you use a grindstone on a shulker with the enlarge enchant, the items become inaccessible. 44 | 45 | 46 | ## Dependencies 47 | 48 | - [fabric modloader](https://fabricmc.net/use/) 49 | - [fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api) 50 | - [fabric-language-kotlin](https://github.com/FabricMC/fabric-language-kotlin) >=1.3.60, <=1.4.0 51 | 52 | 53 | ## TODO (dev stuff) 54 | 55 | - [ ] don't override the shulker box loot table - make it compatible by adding a custom LootFunction 56 | - [ ] make the enchantment glow less intense, so you can see the shulker colors better 57 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'fabric-loom' 3 | id 'maven-publish' 4 | id "org.jetbrains.kotlin.jvm" 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_1_8 8 | targetCompatibility = JavaVersion.VERSION_1_8 9 | 10 | archivesBaseName = project.archives_base_name 11 | version = project.mod_version 12 | group = project.maven_group 13 | 14 | minecraft { 15 | } 16 | 17 | 18 | repositories { 19 | maven { 20 | name = "Fabric" 21 | url = "http://maven.fabricmc.net/" 22 | } 23 | 24 | maven { 25 | name = "jitpack" 26 | url = 'https://jitpack.io' 27 | } 28 | 29 | maven { 30 | name = "ShulkerBoxTooltip" 31 | url = "https://maven.misterpemodder.com/libs-release" 32 | } 33 | jcenter() 34 | } 35 | 36 | dependencies { 37 | //to change the versions see the gradle.properties file 38 | minecraft "com.mojang:minecraft:${project.minecraft_version}" 39 | mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" 40 | modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" 41 | 42 | // Fabric API. This is technically optional, but you probably want it anyway. 43 | modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" 44 | 45 | modImplementation "net.fabricmc:fabric-language-kotlin:${project.fabric_kotlin_version}" 46 | 47 | // fabric-asm / Manningham-Mills 48 | modImplementation 'com.github.Chocohead:Fabric-ASM:v2.0.1' 49 | include 'com.github.Chocohead:Fabric-ASM:v2.0.1' 50 | 51 | // ShulkerBoxTooltip 52 | modImplementation "com.misterpemodder:shulkerboxtooltip:${project.shulkerboxtooltip_version}" 53 | 54 | // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. 55 | // You may need to force-disable transitiveness on them. 56 | 57 | dependencies { 58 | } 59 | } 60 | 61 | processResources { 62 | inputs.property "version", project.version 63 | 64 | from(sourceSets.main.resources.srcDirs) { 65 | include "fabric.mod.json" 66 | expand "version": project.version 67 | } 68 | 69 | from(sourceSets.main.resources.srcDirs) { 70 | exclude "fabric.mod.json" 71 | } 72 | } 73 | 74 | // ensure that the encoding is set to UTF-8, no matter what the system default is 75 | // this fixes some edge cases with special characters not displaying correctly 76 | // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html 77 | tasks.withType(JavaCompile) { 78 | options.encoding = "UTF-8" 79 | } 80 | 81 | // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task 82 | // if it is present. 83 | // If you remove this task, sources will not be generated. 84 | task sourcesJar(type: Jar, dependsOn: classes) { 85 | classifier = "sources" 86 | from sourceSets.main.allSource 87 | } 88 | 89 | jar { 90 | from "LICENSE" 91 | } 92 | 93 | // configure the maven publication 94 | publishing { 95 | publications { 96 | mavenJava(MavenPublication) { 97 | // add all the jars that should be included when publishing to maven 98 | artifact(remapJar) { 99 | builtBy remapJar 100 | } 101 | artifact(sourcesJar) { 102 | builtBy remapSourcesJar 103 | } 104 | } 105 | } 106 | 107 | // select the repositories you want to publish to 108 | repositories { 109 | // uncomment to publish to the local maven 110 | // mavenLocal() 111 | } 112 | } 113 | 114 | compileKotlin.kotlinOptions.jvmTarget = "1.8" 115 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | org.gradle.jvmargs=-Xmx3g 3 | 4 | # Fabric Properties 5 | # Check these on https://modmuss50.me/fabric.html 6 | minecraft_version=1.16.3 7 | yarn_mappings=1.16.3+build.11 8 | loader_version=0.9.3+build.207 9 | 10 | #Fabric api 11 | fabric_version=0.21.0+build.407-1.16 12 | 13 | loom_version=0.4-SNAPSHOT 14 | 15 | # ShulkerBoxTooltip 16 | shulkerboxtooltip_version=2.3.1+1.16.3 17 | 18 | # Mod Properties 19 | mod_version = 0.1.0 20 | maven_group = dev.arxenix 21 | archives_base_name = bettershulkers 22 | 23 | # Kotlin 24 | kotlin_version=1.3.72 25 | fabric_kotlin_version=1.3.72+build.1 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arxenix/better-shulkers/0da6ef3730f25b5bb1073932e217ede881d48f2b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jul 27 10:29:07 IDT 2019 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | jcenter() 4 | maven { 5 | name = 'Fabric' 6 | url = 'https://maven.fabricmc.net/' 7 | } 8 | mavenCentral() 9 | gradlePluginPortal() 10 | } 11 | 12 | plugins { 13 | id 'fabric-loom' version loom_version 14 | id "org.jetbrains.kotlin.jvm" version kotlin_version 15 | id "org.jetbrains.kotlin.plugin.serialization" version kotlin_version 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/asm/EarlyRiser.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.asm; 2 | 3 | import com.chocohead.mm.api.ClassTinkerers; 4 | import net.fabricmc.loader.api.FabricLoader; 5 | 6 | public class EarlyRiser implements Runnable{ 7 | @Override 8 | public void run() { 9 | final String EnchantmentTarget = FabricLoader.getInstance().getMappingResolver().mapClassName("intermediary", "net.minecraft.class_1886"); 10 | ClassTinkerers.enumBuilder(EnchantmentTarget, new Class[0]).addEnumSubclass("SHULKER_BOX", "dev.arxenix.bettershulkers.asm.ShulkerTarget").build(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/asm/ShulkerTarget.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.asm; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import dev.arxenix.bettershulkers.mixin.EnchantmentTargetMixin; 5 | import net.minecraft.item.Item; 6 | 7 | public class ShulkerTarget extends EnchantmentTargetMixin { 8 | @Override 9 | public boolean isAcceptableItem(Item item) { 10 | //System.out.println("checking item for ShulkerTarget"); 11 | //return item == Items.SHULKER_BOX; 12 | return ShulkerUtilsKt.isShulker(item); 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/ducks/EnchantmentHolder.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.ducks; 2 | 3 | import net.minecraft.nbt.ListTag; 4 | 5 | public interface EnchantmentHolder { 6 | ListTag getEnchantments(); 7 | void setEnchantments(ListTag tag); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/BlockItemMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import net.minecraft.item.BlockItem; 5 | import net.minecraft.item.Item; 6 | import net.minecraft.item.ItemPlacementContext; 7 | import net.minecraft.item.ItemStack; 8 | import net.minecraft.util.ActionResult; 9 | import net.minecraft.util.Hand; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | @Mixin(BlockItem.class) 16 | abstract public class BlockItemMixin extends Item { 17 | public BlockItemMixin(Settings settings) { 18 | super(settings); 19 | } 20 | 21 | @Override 22 | public boolean isEnchantable(ItemStack stack) { 23 | if (ShulkerUtilsKt.isShulker(this)) 24 | return true; 25 | else return super.isEnchantable(stack); 26 | } 27 | 28 | @Override 29 | public int getEnchantability() { 30 | if (ShulkerUtilsKt.isShulker(this)) 31 | return 1; 32 | else 33 | return 0; 34 | } 35 | 36 | 37 | @Inject( 38 | at=@At("RETURN"), 39 | method= "place(Lnet/minecraft/item/ItemPlacementContext;)Lnet/minecraft/util/ActionResult;" 40 | ) 41 | private void place(ItemPlacementContext ctx, CallbackInfoReturnable cir) { 42 | if (cir.getReturnValue().isAccepted()) { 43 | if (ctx.getPlayer() != null) { 44 | int slot = 40; //offhand 45 | if (ctx.getPlayer().getActiveHand() == Hand.MAIN_HAND) { 46 | slot = ctx.getPlayer().inventory.selectedSlot; 47 | } 48 | ShulkerUtilsKt.processItemConsume(ctx.getPlayer(), ctx.getStack(), slot); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/BuiltinModelItemRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import dev.arxenix.bettershulkers.ducks.EnchantmentHolder; 5 | import net.minecraft.block.entity.BlockEntity; 6 | import net.minecraft.block.entity.ShulkerBoxBlockEntity; 7 | import net.minecraft.client.render.VertexConsumerProvider; 8 | import net.minecraft.client.render.block.entity.BlockEntityRenderDispatcher; 9 | import net.minecraft.client.render.item.BuiltinModelItemRenderer; 10 | import net.minecraft.client.render.model.json.ModelTransformation; 11 | import net.minecraft.client.util.math.MatrixStack; 12 | import net.minecraft.item.ItemStack; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | 17 | @Mixin(BuiltinModelItemRenderer.class) 18 | public class BuiltinModelItemRendererMixin { 19 | // forward item enchantment data to the ShulkerBoxBlockEntityRenderer, so it knows whether to render glint or not 20 | @Redirect( 21 | method="render", 22 | at=@At( 23 | value = "INVOKE", 24 | target = "Lnet/minecraft/client/render/block/entity/BlockEntityRenderDispatcher;renderEntity(Lnet/minecraft/block/entity/BlockEntity;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;II)Z" 25 | ) 26 | ) 27 | private boolean renderEntity(BlockEntityRenderDispatcher instance, BlockEntity blockEntity, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, int j, 28 | ItemStack stack, ModelTransformation.Mode mode, MatrixStack matrixStack2, VertexConsumerProvider vertexConsumerProvider2, int i2, int j2) { 29 | if (ShulkerUtilsKt.isShulker(stack) && ShulkerUtilsKt.isShulker(blockEntity)) { 30 | ShulkerBoxBlockEntity sbe = (ShulkerBoxBlockEntity) blockEntity; 31 | ((EnchantmentHolder) sbe).setEnchantments(stack.getEnchantments()); 32 | } 33 | return instance.renderEntity(blockEntity, matrixStack, vertexConsumerProvider, i, j); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/EnchantmentHelperMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import net.minecraft.enchantment.Enchantment; 5 | import net.minecraft.enchantment.EnchantmentHelper; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.nbt.CompoundTag; 8 | import net.minecraft.nbt.ListTag; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 14 | 15 | import java.util.Map; 16 | 17 | @Mixin(EnchantmentHelper.class) 18 | public class EnchantmentHelperMixin { 19 | // sometimes the enchanted boxes are not created through an enchanting table 20 | // e.g. anvils 21 | // this injection updates the BET for these cases 22 | @Inject( 23 | method = "set(Ljava/util/Map;Lnet/minecraft/item/ItemStack;)V", 24 | at = @At("TAIL"), 25 | locals = LocalCapture.CAPTURE_FAILSOFT 26 | ) 27 | private static void set(Map enchantments, ItemStack stack, CallbackInfo ci, 28 | ListTag tag) { 29 | if (ShulkerUtilsKt.isShulker(stack)) { 30 | CompoundTag bet = stack.getOrCreateSubTag("BlockEntityTag"); 31 | bet.put("Enchantments", tag); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/EnchantmentTargetMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import net.minecraft.enchantment.EnchantmentTarget; 4 | import net.minecraft.item.Item; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Shadow; 7 | 8 | @Mixin(EnchantmentTarget.class) 9 | public abstract class EnchantmentTargetMixin { 10 | // necessary for asm/ShulkerTarget 11 | @Shadow 12 | public abstract boolean isAcceptableItem(Item item); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ItemAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemGroup; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(Item.class) 9 | public interface ItemAccessor { 10 | @Accessor("group") 11 | void setGroup(ItemGroup group); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ItemStackAccessor.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import net.minecraft.item.Item; 4 | import net.minecraft.item.ItemStack; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.gen.Accessor; 7 | 8 | @Mixin(ItemStack.class) 9 | public interface ItemStackAccessor { 10 | @Accessor("item") 11 | Item getRealItem(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ItemStackMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import net.minecraft.enchantment.Enchantment; 5 | import net.minecraft.item.Item; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.nbt.CompoundTag; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | 14 | @Mixin(ItemStack.class) 15 | abstract class ItemStackMixin { 16 | @Shadow public abstract Item getItem(); 17 | // nullable 18 | @Shadow public abstract CompoundTag getTag(); 19 | 20 | @Shadow public abstract CompoundTag getOrCreateSubTag(String key); 21 | 22 | @Inject( 23 | method= "addEnchantment(Lnet/minecraft/enchantment/Enchantment;I)V", 24 | at=@At("TAIL") 25 | ) 26 | public void addEnchantment(Enchantment enchantment, int level, CallbackInfo ci) { 27 | //System.out.println("addEnchantment called"); 28 | if (ShulkerUtilsKt.isShulker(getItem())) { 29 | //System.out.println("adding BET to item"); 30 | CompoundTag beTag = getOrCreateSubTag("BlockEntityTag"); 31 | beTag.put("Enchantments", this.getTag().get("Enchantments")); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/PlayerInventoryMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import net.minecraft.entity.player.PlayerEntity; 5 | import net.minecraft.entity.player.PlayerInventory; 6 | import net.minecraft.item.ItemStack; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | @Mixin(PlayerInventory.class) 15 | public class PlayerInventoryMixin { 16 | 17 | @Shadow @Final public PlayerEntity player; 18 | 19 | @Inject( 20 | at = @At("HEAD"), 21 | method = "insertStack(Lnet/minecraft/item/ItemStack;)Z", 22 | cancellable = true 23 | ) 24 | public void insertStack(ItemStack stack, CallbackInfoReturnable cir) { 25 | boolean didVacuum = ShulkerUtilsKt.processItemGet(player, stack); 26 | if (didVacuum) { 27 | cir.setReturnValue(true); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ShulkerBoxBlockEntityMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.BetterShulkersKt; 4 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 5 | import dev.arxenix.bettershulkers.ducks.EnchantmentHolder; 6 | import net.minecraft.block.BlockState; 7 | import net.minecraft.block.entity.ShulkerBoxBlockEntity; 8 | import net.minecraft.enchantment.Enchantment; 9 | import net.minecraft.enchantment.EnchantmentHelper; 10 | import net.minecraft.entity.player.PlayerInventory; 11 | import net.minecraft.nbt.CompoundTag; 12 | import net.minecraft.nbt.ListTag; 13 | import net.minecraft.screen.GenericContainerScreenHandler; 14 | import net.minecraft.screen.ScreenHandler; 15 | import net.minecraft.screen.ScreenHandlerType; 16 | import net.minecraft.screen.slot.ShulkerBoxSlot; 17 | import net.minecraft.screen.slot.Slot; 18 | import net.minecraft.text.Style; 19 | import net.minecraft.text.Text; 20 | import net.minecraft.text.TranslatableText; 21 | import net.minecraft.util.Formatting; 22 | import net.minecraft.util.math.Direction; 23 | import org.spongepowered.asm.mixin.Mixin; 24 | import org.spongepowered.asm.mixin.Overwrite; 25 | import org.spongepowered.asm.mixin.Unique; 26 | import org.spongepowered.asm.mixin.injection.At; 27 | import org.spongepowered.asm.mixin.injection.Inject; 28 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 29 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 30 | 31 | import java.util.HashMap; 32 | import java.util.Map; 33 | import java.util.stream.IntStream; 34 | 35 | @Mixin(ShulkerBoxBlockEntity.class) 36 | public class ShulkerBoxBlockEntityMixin implements EnchantmentHolder { 37 | //@Shadow private DefaultedList inventory; 38 | @Unique 39 | private ListTag enchantmentData = new ListTag(); 40 | @Unique 41 | private Map enchantmentMap = new HashMap<>(); 42 | 43 | @Override 44 | public ListTag getEnchantments() { 45 | return enchantmentData; 46 | } 47 | 48 | @Override 49 | public void setEnchantments(ListTag tag) { 50 | this.enchantmentData = tag; 51 | this.enchantmentMap = EnchantmentHelper.fromTag(enchantmentData); 52 | } 53 | 54 | /* 55 | @Inject( 56 | method = "(Lnet/minecraft/util/DyeColor;)V", 57 | at=@At("TAIL") 58 | ) 59 | public void init(DyeColor color, CallbackInfo ci) { 60 | this.inventory = DefaultedList.ofSize(54, ItemStack.EMPTY); 61 | } 62 | */ 63 | 64 | @Inject( 65 | method= "fromTag(Lnet/minecraft/block/BlockState;Lnet/minecraft/nbt/CompoundTag;)V", 66 | at=@At( 67 | value="INVOKE", 68 | target="Lnet/minecraft/block/entity/ShulkerBoxBlockEntity;deserializeInventory(Lnet/minecraft/nbt/CompoundTag;)V" 69 | ) 70 | ) 71 | public void fromTag(BlockState state, CompoundTag tag, CallbackInfo ci) { 72 | //System.out.println("fromTag called"); 73 | //System.out.println(tag.toString()); 74 | if (tag.contains("Enchantments", 9)) { 75 | //System.out.println("has Enchantments!!"); 76 | setEnchantments(tag.getList("Enchantments", 10)); 77 | } 78 | 79 | /* enable glint on be 80 | World world = ((ShulkerBoxBlockEntity)(Object)this).getWorld(); 81 | if (world != null && !world.isClient) { 82 | sync(); 83 | } 84 | */ 85 | } 86 | 87 | /** 88 | * @author arxenix 89 | * @reason BetterShulkers 90 | */ 91 | @Overwrite 92 | public int size() { 93 | return ShulkerUtilsKt.getShulkerSizeFromEnchantmentsMap(enchantmentMap); 94 | } 95 | 96 | @Inject( 97 | method = "toTag(Lnet/minecraft/nbt/CompoundTag;)Lnet/minecraft/nbt/CompoundTag;", 98 | at =@At("TAIL"), 99 | cancellable = true 100 | ) 101 | public void toTag(CompoundTag tag, CallbackInfoReturnable cir) { 102 | //System.out.println("toTag called!"); 103 | //System.out.println(tag.toString()); 104 | tag.put("Enchantments", enchantmentData); 105 | } 106 | 107 | /** 108 | * @author arxenix 109 | * @reason BetterShulkers 110 | */ 111 | @Overwrite 112 | public int[] getAvailableSlots(Direction side) { 113 | return IntStream.range(0, ShulkerUtilsKt.getShulkerSizeFromEnchantmentsTag(enchantmentData)).toArray(); 114 | } 115 | 116 | 117 | /** 118 | * @author arxenix 119 | * @reason BetterShulkers 120 | */ 121 | @Overwrite 122 | public Text getContainerName() { 123 | TranslatableText name = new TranslatableText("container.shulkerBox"); 124 | if (!enchantmentMap.isEmpty()) { 125 | name.setStyle(Style.EMPTY.withFormatting(Formatting.AQUA)); 126 | } 127 | return name; 128 | } 129 | 130 | /** 131 | * @author arxenix 132 | * @reason BetterShulkers 133 | */ 134 | @Overwrite 135 | public ScreenHandler createScreenHandler(int syncId, PlayerInventory playerInventory) { 136 | int extraRows = enchantmentMap.getOrDefault(BetterShulkersKt.getENLARGE_ENCHANT(), 0); 137 | int rows = 3 + Math.min(extraRows, 3); 138 | 139 | //noinspection rawtypes 140 | ScreenHandlerType type = ScreenHandlerType.GENERIC_9X3; 141 | switch(rows) { 142 | case 4: 143 | type = ScreenHandlerType.GENERIC_9X4; 144 | break; 145 | case 5: 146 | type = ScreenHandlerType.GENERIC_9X5; 147 | break; 148 | case 6: 149 | type = ScreenHandlerType.GENERIC_9X6; 150 | break; 151 | } 152 | 153 | ScreenHandler newHandler = new GenericContainerScreenHandler(type, syncId, playerInventory, (ShulkerBoxBlockEntity) (Object) this, rows); 154 | for (int i=0; i<9*rows; i++) { 155 | Slot oldSlot = newHandler.slots.get(i); 156 | Slot newSlot = new ShulkerBoxSlot(oldSlot.inventory, i, oldSlot.x, oldSlot.y); 157 | newSlot.id = oldSlot.id; 158 | newHandler.slots.set(i, newSlot); 159 | } 160 | return newHandler; 161 | } 162 | 163 | 164 | /* enable glint on blockentity 165 | @Override 166 | public void fromClientTag(CompoundTag tag) { 167 | this.setEnchantments(tag.getList("Enchantments", 10)); 168 | } 169 | 170 | @Override 171 | public CompoundTag toClientTag(CompoundTag tag) { 172 | tag.put("Enchantments", enchantmentData); 173 | return tag; 174 | } 175 | */ 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ShulkerBoxBlockEntityRendererMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ducks.EnchantmentHolder; 4 | import net.minecraft.block.entity.ShulkerBoxBlockEntity; 5 | import net.minecraft.client.render.RenderLayer; 6 | import net.minecraft.client.render.SpriteTexturedVertexConsumer; 7 | import net.minecraft.client.render.VertexConsumer; 8 | import net.minecraft.client.render.VertexConsumerProvider; 9 | import net.minecraft.client.render.block.entity.ShulkerBoxBlockEntityRenderer; 10 | import net.minecraft.client.render.item.ItemRenderer; 11 | import net.minecraft.client.util.SpriteIdentifier; 12 | import net.minecraft.client.util.math.MatrixStack; 13 | import net.minecraft.util.Identifier; 14 | import org.spongepowered.asm.mixin.Mixin; 15 | import org.spongepowered.asm.mixin.injection.At; 16 | import org.spongepowered.asm.mixin.injection.Redirect; 17 | 18 | import java.util.function.Function; 19 | 20 | @Mixin(ShulkerBoxBlockEntityRenderer.class) 21 | public class ShulkerBoxBlockEntityRendererMixin { 22 | 23 | // give shulker boxes glint 24 | @Redirect( 25 | method = "render", 26 | at = @At( 27 | value = "INVOKE", 28 | target = "net/minecraft/client/util/SpriteIdentifier.getVertexConsumer(Lnet/minecraft/client/render/VertexConsumerProvider;Ljava/util/function/Function;)Lnet/minecraft/client/render/VertexConsumer;" 29 | ) 30 | ) 31 | private VertexConsumer getVertexConsumer(SpriteIdentifier spriteIdentifier, VertexConsumerProvider vertexConsumerProvider, Function layerFactory, 32 | ShulkerBoxBlockEntity shulkerBoxBlockEntity, float f, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider2, int i, int j) { 33 | return new SpriteTexturedVertexConsumer( 34 | ItemRenderer.getDirectGlintVertexConsumer( 35 | vertexConsumerProvider, 36 | spriteIdentifier.getRenderLayer(layerFactory), 37 | false, 38 | !((EnchantmentHolder)shulkerBoxBlockEntity).getEnchantments().isEmpty() 39 | ), 40 | spriteIdentifier.getSprite() 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/dev/arxenix/bettershulkers/mixin/ShulkerBoxBlockMixin.java: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.mixin; 2 | 3 | import dev.arxenix.bettershulkers.ShulkerUtilsKt; 4 | import net.minecraft.block.Block; 5 | import net.minecraft.block.BlockState; 6 | import net.minecraft.block.ShulkerBoxBlock; 7 | import net.minecraft.block.entity.BlockEntity; 8 | import net.minecraft.block.entity.ShulkerBoxBlockEntity; 9 | import net.minecraft.entity.ItemEntity; 10 | import net.minecraft.entity.player.PlayerEntity; 11 | import net.minecraft.item.ItemStack; 12 | import net.minecraft.loot.context.LootContext; 13 | import net.minecraft.loot.context.LootContextParameters; 14 | import net.minecraft.util.math.BlockPos; 15 | import net.minecraft.world.World; 16 | import org.spongepowered.asm.mixin.Mixin; 17 | import org.spongepowered.asm.mixin.Overwrite; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | @Mixin(ShulkerBoxBlock.class) 23 | abstract class ShulkerBoxBlockMixin extends Block { 24 | public ShulkerBoxBlockMixin(Settings settings) { 25 | super(settings); 26 | } 27 | 28 | /*@Inject( 29 | method = "getDroppedStacks(Lnet/minecraft/block/BlockState;Lnet/minecraft/loot/context/LootContext$Builder;)Ljava/util/List;", 30 | at = @At( 31 | "TAIL" 32 | ), 33 | locals = LocalCapture.PRINT 34 | ) 35 | public void getDroppedStacks(BlockState state, LootContext.Builder builder, CallbackInfoReturnable> ci) { 36 | System.out.println("dropping stacks"); 37 | } 38 | 39 | */ 40 | 41 | /** 42 | * @author arxenix 43 | * @reason BetterShulkers 44 | */ 45 | @Overwrite 46 | public List getDroppedStacks(BlockState state, LootContext.Builder builder) { 47 | BlockEntity blockEntity = builder.getNullable(LootContextParameters.BLOCK_ENTITY); 48 | if (blockEntity instanceof ShulkerBoxBlockEntity) { 49 | ShulkerBoxBlockEntity sbe = (ShulkerBoxBlockEntity)blockEntity; 50 | ItemStack is = ShulkerUtilsKt.itemStackFromBlockEntity(sbe); 51 | ArrayList ret = new ArrayList<>(); 52 | ret.add(is); 53 | return ret; 54 | } 55 | return super.getDroppedStacks(state, builder); 56 | } 57 | 58 | /** 59 | * @author arxenix 60 | * @reason BetterShulkers 61 | */ 62 | @Overwrite 63 | public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player) { 64 | BlockEntity be = world.getBlockEntity(pos); 65 | if (be instanceof ShulkerBoxBlockEntity) { 66 | if (!world.isClient && player.isCreative()) { 67 | //System.out.println("is a shulkerboxblockentity!"); 68 | ShulkerBoxBlockEntity sbe = (ShulkerBoxBlockEntity) be; 69 | 70 | ItemStack itemStack = ShulkerUtilsKt.itemStackFromBlockEntity(sbe); 71 | 72 | ItemEntity itemEntity = new ItemEntity(world, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, itemStack); 73 | itemEntity.setToDefaultPickupDelay(); 74 | world.spawnEntity(itemEntity); 75 | } 76 | 77 | } 78 | super.onBreak(world, pos, state, player); 79 | } 80 | 81 | /* 82 | @Inject( 83 | method= "onBreak(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/player/PlayerEntity;)V", 84 | at = @At( 85 | "HEAD" 86 | ), 87 | cancellable = true 88 | ) 89 | public void onBreak(World world, BlockPos pos, BlockState state, PlayerEntity player, CallbackInfo ci) { 90 | 91 | ci.cancel(); 92 | } 93 | */ 94 | } 95 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/BetterShulkers.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers 2 | 3 | import com.chocohead.mm.api.ClassTinkerers 4 | import dev.arxenix.bettershulkers.enchantments.Enlarge 5 | import dev.arxenix.bettershulkers.enchantments.Restock 6 | import dev.arxenix.bettershulkers.enchantments.Vacuum 7 | import dev.arxenix.bettershulkers.mixin.ItemAccessor 8 | import net.fabricmc.api.ModInitializer 9 | import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder 10 | import net.minecraft.enchantment.Enchantment 11 | import net.minecraft.enchantment.EnchantmentTarget 12 | import net.minecraft.item.ItemGroup 13 | import net.minecraft.item.ItemStack 14 | import net.minecraft.item.Items 15 | import net.minecraft.util.Identifier 16 | import net.minecraft.util.registry.Registry 17 | import org.apache.logging.log4j.LogManager 18 | import org.apache.logging.log4j.Logger 19 | 20 | val LOGGER: Logger = LogManager.getLogger() 21 | const val MODID = "bettershulkers" 22 | 23 | var SHULKER_ENCHANTMENT_TARGET: EnchantmentTarget? = null 24 | var RESTOCK_ENCHANT: Restock? = null 25 | var ENLARGE_ENCHANT: Enlarge? = null 26 | var VACUUM_ENCHANT: Vacuum? = null 27 | var SHULKER_ITEM_GROUP: ItemGroup? = null 28 | 29 | class BetterShulkers: ModInitializer { 30 | override fun onInitialize() { 31 | LOGGER.info("BetterShulkers - fabric mod initialized") 32 | 33 | SHULKER_ENCHANTMENT_TARGET = ClassTinkerers.getEnum(EnchantmentTarget::class.java, "SHULKER_BOX") 34 | //val target = ClassTinkerers.getEnum(EnchantmentTarget::class.java, "SHULKER_BOX") 35 | //LOGGER.info("Can enchant shulker? " + target.isAcceptableItem(Items.SHULKER_BOX)), 36 | RESTOCK_ENCHANT = Registry.register( 37 | Registry.ENCHANTMENT, 38 | Identifier(MODID, "restock"), 39 | Restock(Enchantment.Rarity.VERY_RARE, SHULKER_ENCHANTMENT_TARGET!!, arrayOf()) 40 | ) 41 | 42 | ENLARGE_ENCHANT = Registry.register( 43 | Registry.ENCHANTMENT, 44 | Identifier(MODID, "enlarge"), 45 | Enlarge(Enchantment.Rarity.RARE, SHULKER_ENCHANTMENT_TARGET!!, arrayOf()) 46 | ) 47 | 48 | VACUUM_ENCHANT = Registry.register( 49 | Registry.ENCHANTMENT, 50 | Identifier(MODID, "vacuum"), 51 | Vacuum(Enchantment.Rarity.VERY_RARE, SHULKER_ENCHANTMENT_TARGET!!, arrayOf()) 52 | ) 53 | 54 | SHULKER_ITEM_GROUP = FabricItemGroupBuilder.create( 55 | Identifier(MODID, "item_group") 56 | ) 57 | .icon { ItemStack(Items.SHULKER_BOX) } 58 | //.appendItems { 59 | // shulkerItems.map { ItemStack(it) } 60 | //} 61 | .build() 62 | SHULKER_ITEM_GROUP!!.setEnchantments(SHULKER_ENCHANTMENT_TARGET) 63 | 64 | for (item in SHULKER_ITEMS) { 65 | (item as ItemAccessor).setGroup(SHULKER_ITEM_GROUP) 66 | } 67 | } 68 | } 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/BetterShulkersClient.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers 2 | 3 | import net.fabricmc.api.ClientModInitializer 4 | import net.fabricmc.api.EnvType 5 | import net.fabricmc.api.Environment 6 | 7 | @Environment(EnvType.CLIENT) 8 | class BetterShulkersClient: ClientModInitializer { 9 | override fun onInitializeClient() { 10 | println("BetterShulkersClient - fabric mod initialized") 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/ShulkerUtils.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers 2 | 3 | import dev.arxenix.bettershulkers.ducks.EnchantmentHolder 4 | import dev.arxenix.bettershulkers.mixin.ItemStackAccessor 5 | import net.minecraft.block.Block 6 | import net.minecraft.block.ShulkerBoxBlock 7 | import net.minecraft.block.entity.BlockEntity 8 | import net.minecraft.block.entity.ShulkerBoxBlockEntity 9 | import net.minecraft.enchantment.Enchantment 10 | import net.minecraft.enchantment.EnchantmentHelper 11 | import net.minecraft.entity.player.PlayerEntity 12 | import net.minecraft.inventory.Inventories 13 | import net.minecraft.inventory.Inventory 14 | import net.minecraft.item.Item 15 | import net.minecraft.item.ItemStack 16 | import net.minecraft.item.Items 17 | import net.minecraft.nbt.CompoundTag 18 | import net.minecraft.nbt.ListTag 19 | import net.minecraft.tag.BlockTags 20 | import net.minecraft.util.collection.DefaultedList 21 | import kotlin.math.min 22 | 23 | val SHULKER_ITEMS = mutableSetOf( 24 | Items.SHULKER_BOX, 25 | Items.WHITE_SHULKER_BOX, 26 | Items.ORANGE_SHULKER_BOX, 27 | Items.MAGENTA_SHULKER_BOX, 28 | Items.LIGHT_BLUE_SHULKER_BOX, 29 | Items.YELLOW_SHULKER_BOX, 30 | Items.LIME_SHULKER_BOX, 31 | Items.PINK_SHULKER_BOX, 32 | Items.GRAY_SHULKER_BOX, 33 | Items.LIGHT_GRAY_SHULKER_BOX, 34 | Items.CYAN_SHULKER_BOX, 35 | Items.PURPLE_SHULKER_BOX, 36 | Items.BLUE_SHULKER_BOX, 37 | Items.BROWN_SHULKER_BOX, 38 | Items.GREEN_SHULKER_BOX, 39 | Items.RED_SHULKER_BOX, 40 | Items.BLACK_SHULKER_BOX 41 | ) 42 | 43 | fun isShulker(blockEntity: BlockEntity): Boolean { 44 | return blockEntity is ShulkerBoxBlockEntity 45 | } 46 | 47 | fun isShulker(itemStack: ItemStack): Boolean { 48 | return isShulker(itemStack.item) 49 | } 50 | 51 | fun isShulker(item: Item): Boolean { 52 | return SHULKER_ITEMS.contains(item) 53 | } 54 | 55 | fun isShulker(block: Block): Boolean { 56 | return block.isIn(BlockTags.SHULKER_BOXES) 57 | } 58 | 59 | fun getShulkerSizeFromEnchantmentsTag(tag: ListTag): Int { 60 | return getShulkerSizeFromEnchantmentsMap(EnchantmentHelper.fromTag(tag)) 61 | } 62 | 63 | fun getShulkerSizeFromEnchantmentsMap(enchants: Map): Int { 64 | return 9 * (min(3, enchants.getOrDefault(ENLARGE_ENCHANT as Enchantment, 0)) + 3) 65 | } 66 | 67 | fun getShulkerSizeFromBlockEntityTag(tag: CompoundTag): Int { 68 | return if (tag.contains("Enchantments", 9)) { 69 | getShulkerSizeFromEnchantmentsTag(tag.getList("Enchantments", 10)) 70 | } 71 | else 27 72 | } 73 | 74 | fun getShulkerInv(itemStack: ItemStack): DefaultedList { 75 | // TODO - changeme when adding support for different shulker sizes 76 | val tag = itemStack.getSubTag("BlockEntityTag") 77 | if (tag != null) { 78 | val size = getShulkerSizeFromBlockEntityTag(tag) 79 | val inv = DefaultedList.ofSize(size, ItemStack.EMPTY); 80 | if (tag.contains("Items", 9)) { 81 | Inventories.fromTag(tag, inv) 82 | } 83 | return inv 84 | } 85 | else return DefaultedList.ofSize(27, ItemStack.EMPTY) 86 | } 87 | 88 | fun setShulkerInv(itemStack: ItemStack, inv: DefaultedList) { 89 | val tag = itemStack.getOrCreateSubTag("BlockEntityTag") 90 | Inventories.toTag(tag, inv) 91 | } 92 | 93 | fun hasShulkers(inv: Inventory): Boolean { 94 | return inv.containsAny(SHULKER_ITEMS) 95 | } 96 | 97 | fun getShulkers(inv: Inventory): List> { 98 | val shulkerIndexList = mutableListOf>() 99 | for (i in 0..inv.size()) { 100 | val stack = inv.getStack(i) 101 | if (isShulker(stack)) 102 | shulkerIndexList.add(Pair(i, stack)) 103 | } 104 | return shulkerIndexList 105 | } 106 | 107 | fun itemStackFromBlockEntity(sbe: ShulkerBoxBlockEntity): ItemStack { 108 | val itemStack = ShulkerBoxBlock.getItemStack(sbe.color) 109 | val compoundTag = sbe.serializeInventory(CompoundTag()) 110 | val enchantmentData = (sbe as EnchantmentHolder).enchantments 111 | if (enchantmentData != null) { 112 | //System.out.println("we have enchantment data!"); 113 | itemStack.putSubTag("Enchantments", enchantmentData) 114 | compoundTag.put("Enchantments", enchantmentData) 115 | } 116 | if (!compoundTag.isEmpty) { 117 | itemStack.putSubTag("BlockEntityTag", compoundTag) 118 | } 119 | if (sbe.hasCustomName()) { 120 | itemStack.setCustomName(sbe.customName) 121 | } 122 | return itemStack 123 | } 124 | 125 | fun canTransfer(from: ItemStack, to: ItemStack): Boolean { 126 | val toRealTag = to.tag 127 | val toHasTag = toRealTag != null && !toRealTag.isEmpty 128 | return if ((to as ItemStackAccessor).realItem !== from.item) { 129 | false 130 | } /*else if (stack2.count + stack1.count > stack2.maxCount) { 131 | false 132 | } */else if (toHasTag xor from.hasTag()) { 133 | false 134 | } else { 135 | !toHasTag || to.tag == from.tag 136 | } 137 | } 138 | 139 | fun processItemConsume(player: PlayerEntity, stack: ItemStack, slot: Int) { 140 | // TODO - adapt for consuming multiple of the item at once? 141 | // TODO - make it work when the stack size is zero 142 | //if (stack.isEmpty) return 143 | val shulkers = getShulkers(player.inventory) 144 | val restockers = shulkers.filter { EnchantmentHelper.getLevel(RESTOCK_ENCHANT, it.second) > 0 } 145 | for (restocker in restockers) { 146 | val shulker = restocker.second 147 | val stacks = getShulkerInv(shulker) 148 | for (shulkerStack in stacks) { 149 | if (!shulkerStack.isEmpty) { 150 | if (canTransfer(shulkerStack, stack)) { 151 | shulkerStack.decrement(1) 152 | stack.increment(1) 153 | setShulkerInv(shulker, stacks) 154 | return 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | fun canMerge(from: ItemStack, to: ItemStack): Boolean { 162 | return if (from.item !== to.item) { 163 | false 164 | } else if (to.count >= to.maxCount) { 165 | false 166 | } else if (to.hasTag() xor from.hasTag()) { 167 | false 168 | } else { 169 | !from.hasTag() || from.tag == to.tag 170 | } 171 | } 172 | 173 | fun tryMerge(from: ItemStack, to: ItemStack): Boolean { 174 | if (canMerge(from, to)) { 175 | // merge 176 | val maxTransferable = min(min(to.maxCount, 64) - to.count, from.count) 177 | from.decrement(maxTransferable) 178 | to.increment(maxTransferable) 179 | return true 180 | } 181 | return false 182 | } 183 | 184 | fun processItemGet(player: PlayerEntity, stack: ItemStack): Boolean { 185 | //println("got item $stack ${stack.tag.toString()}") 186 | val shulkers = getShulkers(player.inventory) 187 | val vacuums = shulkers.filter { EnchantmentHelper.getLevel(VACUUM_ENCHANT, it.second) > 0 } 188 | //println("have vacuums: ${vacuums.size}") 189 | var didVacuum = false 190 | for (vacuum in vacuums) { 191 | if (stack.isEmpty) return didVacuum 192 | val shulker = vacuum.second 193 | val stacks = getShulkerInv(shulker) 194 | 195 | var updatedShulker = false 196 | for (shulkerStack in stacks) { 197 | if (!shulkerStack.isEmpty) { 198 | if (tryMerge(stack, shulkerStack)) { 199 | //println("merge success!") 200 | updatedShulker = true 201 | if (stack.isEmpty) 202 | break 203 | } 204 | } 205 | } 206 | if (updatedShulker) { 207 | //println("updated shulker!") 208 | didVacuum = true 209 | setShulkerInv(shulker, stacks) 210 | } 211 | } 212 | return didVacuum 213 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/compatibility/ExtendedShulkerBoxPreviewProvider.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.compatibility 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.PreviewContext 4 | import com.misterpemodder.shulkerboxtooltip.api.provider.BlockEntityPreviewProvider 5 | import dev.arxenix.bettershulkers.getShulkerSizeFromBlockEntityTag 6 | import dev.arxenix.bettershulkers.isShulker 7 | import net.minecraft.block.Block 8 | import net.minecraft.block.ShulkerBoxBlock 9 | import net.minecraft.item.BlockItem 10 | import net.minecraft.util.DyeColor 11 | 12 | class ExtendedShulkerBoxPreviewProvider: BlockEntityPreviewProvider(27, true) { 13 | private val SHULKER_BOX_COLOR: FloatArray = floatArrayOf(0.592f, 0.403f, 0.592f) 14 | 15 | override fun showTooltipHints(context: PreviewContext): Boolean { 16 | return true; 17 | } 18 | 19 | override fun getWindowColor(context: PreviewContext): FloatArray { 20 | val dye = (Block.getBlockFromItem(context.getStack().getItem()) as ShulkerBoxBlock).getColor() 21 | 22 | if (dye != null) { 23 | val components = dye.getColorComponents() 24 | 25 | return floatArrayOf(Math.max(0.15f, components[0]), Math.max(0.15f, components[1]), 26 | Math.max(0.15f, components[2])) 27 | } else { 28 | return SHULKER_BOX_COLOR 29 | } 30 | } 31 | 32 | override fun getPriority(): Int { 33 | // Set priority higher than ShulkerBoxPreviewProvider 34 | return 2000 35 | } 36 | 37 | override fun getInventoryMaxSize(context: PreviewContext): Int { 38 | val stack = context.getStack() 39 | 40 | if (isShulker(stack)) { 41 | val tag = stack.getSubTag("BlockEntityTag") 42 | 43 | if (tag != null) 44 | return getShulkerSizeFromBlockEntityTag(tag) 45 | } 46 | return this.maxInvSize 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/compatibility/ShulkerBoxTooltipHook.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.compatibility 2 | 3 | import com.misterpemodder.shulkerboxtooltip.api.ShulkerBoxTooltipApi 4 | import com.misterpemodder.shulkerboxtooltip.api.provider.PreviewProvider 5 | import dev.arxenix.bettershulkers.MODID 6 | import dev.arxenix.bettershulkers.SHULKER_ITEMS 7 | import net.minecraft.item.Item 8 | 9 | class ShulkerBoxTooltipHook : ShulkerBoxTooltipApi { 10 | override fun getModId(): String { 11 | return MODID 12 | } 13 | 14 | override fun registerProviders(previewProviders: MutableMap>) { 15 | previewProviders.put(ExtendedShulkerBoxPreviewProvider(), SHULKER_ITEMS.toList()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/enchantments/Enlarge.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.enchantments 2 | 3 | import dev.arxenix.bettershulkers.isShulker 4 | import net.minecraft.enchantment.Enchantment 5 | import net.minecraft.enchantment.EnchantmentTarget 6 | import net.minecraft.entity.EquipmentSlot 7 | import net.minecraft.item.ItemStack 8 | 9 | 10 | class Enlarge(weight: Rarity, type: EnchantmentTarget, slotTypes: Array) : 11 | Enchantment(weight, type, slotTypes) { 12 | 13 | override fun getMinPower(level: Int): Int { 14 | return 5 + (level - 1) * 8 15 | } 16 | 17 | override fun getMaxPower(level: Int): Int { 18 | return super.getMinPower(level) + 50 19 | } 20 | 21 | override fun getMaxLevel(): Int { 22 | return 3 23 | } 24 | 25 | override fun canAccept(other: Enchantment): Boolean { 26 | return super.canAccept(other) 27 | } 28 | 29 | override fun isAcceptableItem(stack: ItemStack): Boolean { 30 | return isShulker(stack) 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/enchantments/Restock.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.enchantments 2 | 3 | import dev.arxenix.bettershulkers.VACUUM_ENCHANT 4 | import dev.arxenix.bettershulkers.isShulker 5 | import net.minecraft.enchantment.Enchantment 6 | import net.minecraft.enchantment.EnchantmentTarget 7 | import net.minecraft.entity.EquipmentSlot 8 | import net.minecraft.item.ItemStack 9 | 10 | 11 | class Restock(weight: Rarity, type: EnchantmentTarget, slotTypes: Array) : 12 | Enchantment(weight, type, slotTypes) { 13 | override fun getMinPower(level: Int): Int { 14 | return 25 15 | } 16 | 17 | override fun getMaxPower(level: Int): Int { 18 | return 50 19 | } 20 | 21 | override fun getMaxLevel(): Int { 22 | return 1 23 | } 24 | 25 | override fun canAccept(other: Enchantment): Boolean { 26 | return super.canAccept(other) && other != VACUUM_ENCHANT 27 | } 28 | 29 | override fun isAcceptableItem(stack: ItemStack): Boolean { 30 | return isShulker(stack) 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/dev/arxenix/bettershulkers/enchantments/Vacuum.kt: -------------------------------------------------------------------------------- 1 | package dev.arxenix.bettershulkers.enchantments 2 | 3 | import dev.arxenix.bettershulkers.RESTOCK_ENCHANT 4 | import dev.arxenix.bettershulkers.isShulker 5 | import net.minecraft.enchantment.Enchantment 6 | import net.minecraft.enchantment.EnchantmentTarget 7 | import net.minecraft.entity.EquipmentSlot 8 | import net.minecraft.item.ItemStack 9 | 10 | 11 | class Vacuum(weight: Rarity, type: EnchantmentTarget, slotTypes: Array) : 12 | Enchantment(weight, type, slotTypes) { 13 | override fun getMinPower(level: Int): Int { 14 | return 25 15 | } 16 | 17 | override fun getMaxPower(level: Int): Int { 18 | return 50 19 | } 20 | 21 | override fun getMaxLevel(): Int { 22 | return 1 23 | } 24 | 25 | override fun canAccept(other: Enchantment): Boolean { 26 | return super.canAccept(other) && other != RESTOCK_ENCHANT 27 | } 28 | 29 | override fun isAcceptableItem(stack: ItemStack): Boolean { 30 | return isShulker(stack) 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/resources/assets/bettershulkers/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arxenix/better-shulkers/0da6ef3730f25b5bb1073932e217ede881d48f2b/src/main/resources/assets/bettershulkers/icon.png -------------------------------------------------------------------------------- /src/main/resources/assets/bettershulkers/lang/de_de.json: -------------------------------------------------------------------------------- 1 | { 2 | "enchantment.bettershulkers.restock": "Nachfüllen", 3 | "enchantment.bettershulkers.vacuum": "Vakuum", 4 | "enchantment.bettershulkers.enlarge": "Vergrößern", 5 | "itemGroup.bettershulkers.item_group": "Better Shulkers" 6 | } -------------------------------------------------------------------------------- /src/main/resources/assets/bettershulkers/lang/en_us.json: -------------------------------------------------------------------------------- 1 | { 2 | "enchantment.bettershulkers.restock": "Restock", 3 | "enchantment.bettershulkers.vacuum": "Vacuum", 4 | "enchantment.bettershulkers.enlarge": "Enlarge", 5 | "itemGroup.bettershulkers.item_group": "Better Shulkers" 6 | } -------------------------------------------------------------------------------- /src/main/resources/bettershulkers.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "package": "dev.arxenix.bettershulkers.mixin", 4 | "compatibilityLevel": "JAVA_8", 5 | "mixins": [ 6 | "BlockItemMixin", 7 | "BuiltinModelItemRendererMixin", 8 | "EnchantmentHelperMixin", 9 | "EnchantmentTargetMixin", 10 | "ItemAccessor", 11 | "ItemStackAccessor", 12 | "ItemStackMixin", 13 | "PlayerInventoryMixin", 14 | "ShulkerBoxBlockEntityMixin", 15 | "ShulkerBoxBlockEntityRendererMixin", 16 | "ShulkerBoxBlockMixin" 17 | ], 18 | "client": [ 19 | ], 20 | "server": [ 21 | ], 22 | "injectors": { 23 | "defaultRequire": 1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "bettershulkers", 4 | "version": "${version}", 5 | 6 | "name": "Better Shulkers", 7 | "description": "Adds various useful features and enchantments to shulker boxes", 8 | "authors": [ 9 | "arxenix" 10 | ], 11 | "contact": { 12 | "homepage": "https://fabricmc.net/", 13 | "sources": "https://github.com/arxenix/better-shulkers" 14 | }, 15 | 16 | "license": "CC0-1.0", 17 | "icon": "assets/bettershulkers/icon.png", 18 | 19 | "environment": "*", 20 | "entrypoints": { 21 | "main": [ 22 | { 23 | "adapter": "kotlin", 24 | "value": "dev.arxenix.bettershulkers.BetterShulkers" 25 | } 26 | ], 27 | "mm:early_risers": [ 28 | "dev.arxenix.bettershulkers.asm.EarlyRiser" 29 | ], 30 | "client": [ 31 | { 32 | "adapter": "kotlin", 33 | "value": "dev.arxenix.bettershulkers.BetterShulkersClient" 34 | } 35 | ], 36 | "shulkerboxtooltip": [ 37 | { 38 | "adapter": "kotlin", 39 | "value": "dev.arxenix.bettershulkers.compatibility.ShulkerBoxTooltipHook" 40 | } 41 | ] 42 | }, 43 | "mixins": [ 44 | "bettershulkers.mixins.json" 45 | ], 46 | "depends": { 47 | "fabricloader": ">=0.7.1", 48 | "fabric": "*", 49 | "fabric-language-kotlin": "*" 50 | }, 51 | "recommends": { 52 | "minecraft": ["1.16.2", "1.16.3"] 53 | }, 54 | "suggests": { 55 | "shulkerboxtooltip": ">=2.3.0" 56 | }, 57 | "breaks": { 58 | "shulkerboxtooltip": "<2.3.0" 59 | } 60 | } 61 | --------------------------------------------------------------------------------