diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..73cec8a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,33 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +tab_width = 4 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.gradle] +indent_style = tab + +[*.java] +indent_style = tab + +[*.json] +indent_style = space +indent_size = 2 + +[quilt.mod.json] +indent_style = tab +tab_width = 2 + +[*.toml] +indent_style = tab +tab_width = 2 + +[*.properties] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..99d0367 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,30 @@ +*.java text eol=lf diff=java +*.gradle text eol=lf diff=java +*.kt text eol=lf diff=kotlin +*.kts text eol=lf diff=kotlin +gradlew text eol=lf +*.bat text eol=crlf + +*.md text eol=lf diff=markdown + +.editorconfig text eol=lf + +*.json text eol=lf +*.json5 text eol=lf +*.properties text eol=lf +*.toml text eol=lf +*.xml text eol=lf diff=html + +# Modding specific +*.accesswidener text eol=lf + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.jks binary +*.png binary +*.so binary +*.war binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de6cdcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Gradle +.gradle/ +build/ +out/ +classes/ + +# Quilt Loom +remappedSrc/ +run/ + +# Eclipse +*.launch + +# IntelliJ Idea +.idea/ +*.iml +*.ipr +*.iws + +# Fleet +.fleet/ + +# Visual Studio Code +.settings/ +.vscode/ +bin/ +.classpath +.project + +# Eclipse JDT LS +workspace/ + +# macOS +*.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eeed246 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022-2024 Cynthia Foxwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..d315d6f --- /dev/null +++ b/build.gradle @@ -0,0 +1,83 @@ +plugins { + alias libs.plugins.quilt.loom +} + +base { + archivesName = project.archives_base_name +} + +version = "$project.version+${libs.versions.minecraft.get()}" +group = project.maven_group + +repositories { + maven { + name = "TerraformersMC" + url = "https://maven.terraformersmc.com/" + } + maven { + name = "Ladysnake Libs" + url = "https://maven.ladysnake.org/releases" + } + maven { + name = "Sleeping Town" + url = "https://repo.sleeping.town" + content { + includeGroup "com.unascribed" + } + } +} + +loom { + // Loom and Loader both use this block in order to gather more information about your mod. + mods { + // This should match your mod id. + "scout" { + // Tell Loom about each source set used by your mod here. This ensures that your mod's classes are properly transformed by Loader. + sourceSet("main") + // If you shade (directly include classes, not JiJ) a dependency into your mod, include it here using one of these methods: + // dependency("com.example.shadowedmod:1.2.3") + // configuration("exampleShadedConfigurationName") + } + } +} + +dependencies { + minecraft libs.minecraft + mappings variantOf(libs.quilt.mappings) { classifier 'intermediary-v2' } + modImplementation libs.quilt.loader + + modImplementation libs.quilted.fabric.api + + modApi include("dev.emi:trinkets:${libs.versions.trinkets.get()}") + modApi include("com.unascribed:lib39-core:${libs.versions.lib39.get()}+${libs.versions.minecraft.get()}") + modApi include("com.unascribed:lib39-dessicant:${libs.versions.lib39.get()}+${libs.versions.minecraft.get()}") + + modCompileOnly "dev.emi:emi-fabric:${libs.versions.emi.get()}+${libs.versions.minecraft.get()}:api" + modLocalRuntime "dev.emi:emi-fabric:${libs.versions.emi.get()}+${libs.versions.minecraft.get()}" +} + +processResources { + inputs.properties 'version': version, 'group': project.group + + filesMatching('quilt.mod.json') { + expand 'version': version, 'group': project.group + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.encoding = 'UTF-8' + // Minecraft 1.18 (1.18-pre2) upwards uses Java 17. + it.options.release = 17 +} + +java { + // Still required by IDEs such as Eclipse and Visual Studio Code + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +jar { + from('LICENSE') { + rename { "${it}_${base.archivesName.get()}" } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..59af25e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,10 @@ +# Gradle Properties +org.gradle.jvmargs = -Xmx1G +org.gradle.parallel = true + +# Mod Properties +version = 2.0.0 +maven_group = pm.c7.scout +archives_base_name = Scout + +# Dependencies are managed at gradle/libs.versions.toml diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..593eda4 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,28 @@ +[versions] +# The latest versions are available at https://lambdaurora.dev/tools/import_quilt.html +minecraft = "1.20.1" +loom = "1.4.1" + +quilt_mappings = "1.20.1+build.23" +quilt_loader = "0.21.0" + +quilted_fabric_api = "7.4.0+0.90.0-1.20.1" + +trinkets = "3.7.2" +lib39 = "1.5.0-experimental6.1" +emi = "1.1.3" + +[libraries] +minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" } +quilt_mappings = { module = "org.quiltmc:quilt-mappings", version.ref = "quilt_mappings" } +quilt_loader = { module = "org.quiltmc:quilt-loader", version.ref = "quilt_loader" } + +quilted_fabric_api = { module = "org.quiltmc.quilted-fabric-api:quilted-fabric-api", version.ref = "quilted_fabric_api" } +quilted_fabric_api_deprecated = { module = "org.quiltmc.quilted-fabric-api:quilted-fabric-api-deprecated", version.ref = "quilted_fabric_api" } + +# If you have multiple similar dependencies, you can declare a dependency bundle and reference it on the build script with "libs.bundles.example". +[bundles] +quilted_fabric_api = ["quilted_fabric_api", "quilted_fabric_api_deprecated"] + +[plugins] +quilt_loom = { id = "org.quiltmc.loom", version.ref = "loom" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..d969e34 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +pluginManagement { + repositories { + maven { + name = 'Quilt' + url = 'https://maven.quiltmc.org/repository/release' + } + // Currently needed for Intermediary and other temporary dependencies + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/pm/c7/scout/Scout.java b/src/main/java/pm/c7/scout/Scout.java new file mode 100644 index 0000000..550485f --- /dev/null +++ b/src/main/java/pm/c7/scout/Scout.java @@ -0,0 +1,22 @@ +package pm.c7.scout; + +import com.unascribed.lib39.core.api.AutoRegistry; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import org.quiltmc.loader.api.ModContainer; +import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; +import org.quiltmc.qsl.item.group.api.QuiltItemGroup; +import pm.c7.scout.config.ScoutConfigHandler; +import pm.c7.scout.registry.ScoutItems; + +public class Scout implements ModInitializer { + public static final AutoRegistry AUTOREGISTRY = AutoRegistry.of(ScoutUtil.MOD_ID); + public static final ItemGroup ITEM_GROUP = QuiltItemGroup.createWithIcon(new Identifier("scout", "itemgroup"), () -> new ItemStack(ScoutItems.SATCHEL)); + + @Override + public void onInitialize(ModContainer mod) { + new ScoutConfigHandler(); + ScoutItems.init(); + } +} diff --git a/src/main/java/pm/c7/scout/ScoutMixin.java b/src/main/java/pm/c7/scout/ScoutMixin.java new file mode 100644 index 0000000..40de953 --- /dev/null +++ b/src/main/java/pm/c7/scout/ScoutMixin.java @@ -0,0 +1,53 @@ +package pm.c7.scout; + +import com.unascribed.lib39.core.api.AutoMixin; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import pm.c7.scout.mixinsupport.ClassNodeTransformer; + +import java.util.HashMap; +import java.util.Map; + +// "you could also embrace chaos" --Una +// https://git.sleeping.town/unascribed/Yttr/src/branch/1.19.2/src/main/java/com/unascribed/yttr/YttrMixin.java +public class ScoutMixin extends AutoMixin { + private static final Logger LOGGER = LoggerFactory.getLogger("Scout:MixinPlugin"); + + public @interface Transformer { + Class value(); + } + + private final Map transformers = new HashMap<>(); + + @Override + protected boolean shouldMixinBeSkipped(String name, ClassNode node) { + if (name.endsWith("Transformer")) { + return true; + } + return super.shouldMixinBeSkipped(name, node); + } + + @Override + protected boolean shouldAnnotationSkipMixin(String name, AnnotationNode an) { + if (an.desc.equals("Lpm/c7/scout/ScoutMixin$Transformer;")) { + var params = decodeAnnotationParams(an); + Type type = (Type)params.get("value"); + try { + transformers.put(name, (ClassNodeTransformer) Class.forName(type.getClassName()).newInstance()); + } catch (Exception e) { + LOGGER.error("Transformer class for mixin {} not found", name, e); + } + } + return super.shouldAnnotationSkipMixin(name, an); + } + + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + transformers.getOrDefault(mixinClassName, (s, cn) -> {}).transform(targetClassName, targetClass); + super.postApply(targetClassName, targetClass, mixinClassName, mixinInfo); + } +} diff --git a/src/main/java/pm/c7/scout/ScoutNetworking.java b/src/main/java/pm/c7/scout/ScoutNetworking.java new file mode 100644 index 0000000..5c5bdfe --- /dev/null +++ b/src/main/java/pm/c7/scout/ScoutNetworking.java @@ -0,0 +1,7 @@ +package pm.c7.scout; + +import net.minecraft.util.Identifier; + +public class ScoutNetworking { + public static final Identifier ENABLE_SLOTS = new Identifier(ScoutUtil.MOD_ID, "enable_slots"); +} diff --git a/src/main/java/pm/c7/scout/ScoutScreenHandler.java b/src/main/java/pm/c7/scout/ScoutScreenHandler.java new file mode 100644 index 0000000..ad994a8 --- /dev/null +++ b/src/main/java/pm/c7/scout/ScoutScreenHandler.java @@ -0,0 +1,10 @@ +package pm.c7.scout; + +import net.minecraft.util.collection.DefaultedList; +import pm.c7.scout.screen.BagSlot; + +public interface ScoutScreenHandler { + DefaultedList scout$getSatchelSlots(); + DefaultedList scout$getLeftPouchSlots(); + DefaultedList scout$getRightPouchSlots(); +} diff --git a/src/main/java/pm/c7/scout/ScoutUtil.java b/src/main/java/pm/c7/scout/ScoutUtil.java new file mode 100644 index 0000000..27aa7a0 --- /dev/null +++ b/src/main/java/pm/c7/scout/ScoutUtil.java @@ -0,0 +1,151 @@ +package pm.c7.scout; + +import dev.emi.trinkets.api.SlotReference; +import dev.emi.trinkets.api.TrinketComponent; +import dev.emi.trinkets.api.TrinketsApi; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.BeaconScreen; +import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.ingame.MerchantScreen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.screen.PlayerScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.util.Identifier; +import net.minecraft.util.Pair; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; +import pm.c7.scout.screen.BagSlot; + +import java.util.Optional; + +public class ScoutUtil { + public static final Logger LOGGER = LoggerFactory.getLogger("Scout"); + public static final String MOD_ID = "scout"; + public static final Identifier SLOT_TEXTURE = new Identifier("scout", "textures/gui/slots.png"); + + public static final int MAX_SATCHEL_SLOTS = 18; + public static final int MAX_POUCH_SLOTS = 6; + public static final int TOTAL_SLOTS = MAX_SATCHEL_SLOTS + MAX_POUCH_SLOTS + MAX_POUCH_SLOTS; + + public static final int SATCHEL_SLOT_START = -1100; + public static final int LEFT_POUCH_SLOT_START = SATCHEL_SLOT_START - MAX_SATCHEL_SLOTS; + public static final int RIGHT_POUCH_SLOT_START = LEFT_POUCH_SLOT_START - MAX_POUCH_SLOTS; + public static final int BAG_SLOTS_END = RIGHT_POUCH_SLOT_START - MAX_POUCH_SLOTS; + + public static ItemStack findBagItem(PlayerEntity player, BaseBagItem.BagType type, boolean right) { + ItemStack targetStack = ItemStack.EMPTY; + + boolean hasFirstPouch = false; + Optional _component = TrinketsApi.getTrinketComponent(player); + if (_component.isPresent()) { + TrinketComponent component = _component.get(); + for (Pair pair : component.getAllEquipped()) { + ItemStack slotStack = pair.getRight(); + + if (slotStack.getItem() instanceof BaseBagItem bagItem) { + if (bagItem.getType() == type) { + if (type == BagType.POUCH) { + if (right && !hasFirstPouch) { + hasFirstPouch = true; + } else { + targetStack = slotStack; + break; + } + } else { + targetStack = slotStack; + break; + } + } + } + } + } + + return targetStack; + } + + public static NbtList inventoryToTag(Inventory inventory) { + NbtList tag = new NbtList(); + + for(int i = 0; i < inventory.size(); i++) { + NbtCompound stackTag = new NbtCompound(); + stackTag.putInt("Slot", i); + stackTag.put("Stack", inventory.getStack(i).writeNbt(new NbtCompound())); + tag.add(stackTag); + } + + return tag; + } + + public static void inventoryFromTag(NbtList tag, Inventory inventory) { + inventory.clear(); + + tag.forEach(element -> { + NbtCompound stackTag = (NbtCompound) element; + int slot = stackTag.getInt("Slot"); + ItemStack stack = ItemStack.fromNbt(stackTag.getCompound("Stack")); + inventory.setStack(slot, stack); + }); + } + + public static boolean isBagSlot(int slot) { + return slot <= SATCHEL_SLOT_START && slot > BAG_SLOTS_END; + } + + public static @Nullable Slot getBagSlot(int slot, PlayerScreenHandler playerScreenHandler) { + var scoutScreenHandler = (ScoutScreenHandler) playerScreenHandler; + if (slot <= SATCHEL_SLOT_START && slot > LEFT_POUCH_SLOT_START) { + int realSlot = MathHelper.abs(slot - SATCHEL_SLOT_START); + var slots = scoutScreenHandler.scout$getSatchelSlots(); + + return slots.get(realSlot); + } else if (slot <= LEFT_POUCH_SLOT_START && slot > RIGHT_POUCH_SLOT_START) { + int realSlot = MathHelper.abs(slot - LEFT_POUCH_SLOT_START); + var slots = scoutScreenHandler.scout$getLeftPouchSlots(); + + return slots.get(realSlot); + } else if (slot <= RIGHT_POUCH_SLOT_START && slot > BAG_SLOTS_END) { + int realSlot = MathHelper.abs(slot - RIGHT_POUCH_SLOT_START); + var slots = scoutScreenHandler.scout$getRightPouchSlots(); + + return slots.get(realSlot); + } else { + return null; + } + } + + public static DefaultedList getAllBagSlots(PlayerScreenHandler playerScreenHandler) { + var scoutScreenHandler = (ScoutScreenHandler) playerScreenHandler; + DefaultedList out = DefaultedList.ofSize(TOTAL_SLOTS); + out.addAll(scoutScreenHandler.scout$getSatchelSlots()); + out.addAll(scoutScreenHandler.scout$getLeftPouchSlots()); + out.addAll(scoutScreenHandler.scout$getRightPouchSlots()); + return out; + } + + public static @Nullable PlayerScreenHandler getPlayerScreenHandler() { + var client = MinecraftClient.getInstance(); + if (client != null && client.player != null) { + return client.player.playerScreenHandler; + } + + return null; + } + + // FIXME: registry system for mods to register their own blacklisted screens + public static boolean isScreenBlacklisted(Screen screen) { + return screen instanceof CreativeInventoryScreen + || screen instanceof MerchantScreen // FIXME: needs repositioning + || screen instanceof BeaconScreen; // FIXME: needs repositioning + } +} diff --git a/src/main/java/pm/c7/scout/client/ScoutClient.java b/src/main/java/pm/c7/scout/client/ScoutClient.java new file mode 100644 index 0000000..dc95f8f --- /dev/null +++ b/src/main/java/pm/c7/scout/client/ScoutClient.java @@ -0,0 +1,204 @@ +package pm.c7.scout.client; + +import net.fabricmc.fabric.api.client.rendering.v1.LivingEntityFeatureRendererRegistrationCallback; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.ingame.ShulkerBoxScreen; +import net.minecraft.entity.EntityType; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import net.minecraft.util.collection.DefaultedList; +import org.quiltmc.loader.api.ModContainer; +import org.quiltmc.qsl.base.api.entrypoint.client.ClientModInitializer; +import org.quiltmc.qsl.networking.api.client.ClientPlayNetworking; +import org.quiltmc.qsl.screen.api.client.ScreenEvents; +import org.quiltmc.qsl.tooltip.api.client.TooltipComponentCallback; +import pm.c7.scout.ScoutNetworking; +import pm.c7.scout.ScoutScreenHandler; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.client.gui.BagTooltipComponent; +import pm.c7.scout.client.render.SatchelFeatureRenderer; +import pm.c7.scout.item.BagTooltipData; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; +import pm.c7.scout.client.render.PouchFeatureRenderer; +import pm.c7.scout.mixin.client.HandledScreenAccessor; +import pm.c7.scout.screen.BagSlot; + +public class ScoutClient implements ClientModInitializer { + @Override + public void onInitializeClient(ModContainer mod) { + ClientPlayNetworking.registerGlobalReceiver(ScoutNetworking.ENABLE_SLOTS, (client, handler, packet, sender) -> { + client.execute(() -> { + assert client.player != null; + ScoutScreenHandler screenHandler = (ScoutScreenHandler) client.player.playerScreenHandler; + + ItemStack satchelStack = ScoutUtil.findBagItem(client.player, BagType.SATCHEL, false); + DefaultedList satchelSlots = screenHandler.scout$getSatchelSlots(); + + for (int i = 0; i < ScoutUtil.MAX_SATCHEL_SLOTS; i++) { + BagSlot slot = satchelSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!satchelStack.isEmpty()) { + BaseBagItem satchelItem = (BaseBagItem) satchelStack.getItem(); + Inventory satchelInv = satchelItem.getInventory(satchelStack); + + for (int i = 0; i < satchelItem.getSlotCount(); i++) { + BagSlot slot = satchelSlots.get(i); + slot.setInventory(satchelInv); + slot.setEnabled(true); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(client.player, BagType.POUCH, false); + DefaultedList leftPouchSlots = screenHandler.scout$getLeftPouchSlots(); + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + BagSlot slot = leftPouchSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!leftPouchStack.isEmpty()) { + BaseBagItem leftPouchItem = (BaseBagItem) leftPouchStack.getItem(); + Inventory leftPouchInv = leftPouchItem.getInventory(leftPouchStack); + + for (int i = 0; i < leftPouchItem.getSlotCount(); i++) { + BagSlot slot = leftPouchSlots.get(i); + slot.setInventory(leftPouchInv); + slot.setEnabled(true); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(client.player, BagType.POUCH, true); + DefaultedList rightPouchSlots = screenHandler.scout$getRightPouchSlots(); + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + BagSlot slot = rightPouchSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!rightPouchStack.isEmpty()) { + BaseBagItem rightPouchItem = (BaseBagItem) rightPouchStack.getItem(); + Inventory rightPouchInv = rightPouchItem.getInventory(rightPouchStack); + + for (int i = 0; i < rightPouchItem.getSlotCount(); i++) { + BagSlot slot = rightPouchSlots.get(i); + slot.setInventory(rightPouchInv); + slot.setEnabled(true); + } + } + }); + }); + + TooltipComponentCallback.EVENT.register(data -> { + if (data instanceof BagTooltipData d) { + return new BagTooltipComponent(d); + } + + return null; + }); + + LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper, context) -> { + if (entityType == EntityType.PLAYER) { + registrationHelper.register(new PouchFeatureRenderer<>(entityRenderer, context.getHeldItemRenderer())); + registrationHelper.register(new SatchelFeatureRenderer<>(entityRenderer)); + } + }); + + ScreenEvents.AFTER_INIT.register((screen, client, scaledWidth, scaledHeight) -> { + if (screen instanceof HandledScreen handledScreen && client.player != null) { + if (ScoutUtil.isScreenBlacklisted(screen)) { + // realistically no one is going to have a screen bigger than 2147483647 pixels + for (Slot slot : ScoutUtil.getAllBagSlots(client.player.playerScreenHandler)) { + BagSlot bagSlot = (BagSlot) slot; + bagSlot.setX(Integer.MAX_VALUE); + bagSlot.setY(Integer.MAX_VALUE); + } + return; + } + + var handledScreenAccessor = (HandledScreenAccessor) handledScreen; + + var sx = handledScreenAccessor.getX(); + var sy = handledScreenAccessor.getY(); + var sw = handledScreenAccessor.getBackgroundWidth(); + var sh = handledScreenAccessor.getBackgroundHeight(); + + // satchel + int x = sx; + int y = sy + sh + 2; + + if (screen instanceof GenericContainerScreen || screen instanceof ShulkerBoxScreen) { + y -= 1; + } + + for (int i = 0; i < ScoutUtil.MAX_SATCHEL_SLOTS; i++) { + if (i % 9 == 0) { + x = sx + 8; + } + + BagSlot slot = (BagSlot) ScoutUtil.getBagSlot(ScoutUtil.SATCHEL_SLOT_START - i, client.player.playerScreenHandler); + if (slot != null) { + slot.setX(x - sx); + slot.setY(y - sy); + } + + x += 18; + + if ((i + 1) % 9 == 0) { + y += 18; + } + } + + // left pouch + x = sx + 8; + y = (sy + sh) - 100; + + if (screen instanceof GenericContainerScreen || screen instanceof ShulkerBoxScreen) { + y -= 1; + } + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + if (i % 3 == 0) { + x -= 18; + y += 54; + } + + BagSlot slot = (BagSlot) ScoutUtil.getBagSlot(ScoutUtil.LEFT_POUCH_SLOT_START - i, client.player.playerScreenHandler); + if (slot != null) { + slot.setX(x - sx); + slot.setY(y - sy); + } + + y -= 18; + } + + // right pouch + x = sx + sw - 24; + y = (sy + sh) - 100; + + if (screen instanceof GenericContainerScreen || screen instanceof ShulkerBoxScreen) { + y -= 1; + } + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + if (i % 3 == 0) { + x += 18; + y += 54; + } + + BagSlot slot = (BagSlot) ScoutUtil.getBagSlot(ScoutUtil.RIGHT_POUCH_SLOT_START - i, client.player.playerScreenHandler); + if (slot != null) { + slot.setX(x - sx); + slot.setY(y - sy); + } + + y -= 18; + } + } + }); + } +} diff --git a/src/main/java/pm/c7/scout/client/compat/ScoutEmiPlugin.java b/src/main/java/pm/c7/scout/client/compat/ScoutEmiPlugin.java new file mode 100644 index 0000000..c7a7263 --- /dev/null +++ b/src/main/java/pm/c7/scout/client/compat/ScoutEmiPlugin.java @@ -0,0 +1,45 @@ +package pm.c7.scout.client.compat; + +import dev.emi.emi.api.EmiPlugin; +import dev.emi.emi.api.EmiRegistry; +import dev.emi.emi.api.widget.Bounds; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.item.ItemStack; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; +import pm.c7.scout.mixin.client.HandledScreenAccessor; + +public class ScoutEmiPlugin implements EmiPlugin { + @Override + public void register(EmiRegistry registry) { + registry.addExclusionArea(InventoryScreen.class, (screen, consumer) -> { + MinecraftClient client = MinecraftClient.getInstance(); + ItemStack leftPouchStack = ScoutUtil.findBagItem(client.player, BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + int x = ((HandledScreenAccessor) screen).getX() - (columns * 18); + int y = ((HandledScreenAccessor) screen).getY() + 76; + + consumer.accept(new Bounds(x, y, columns * 18, 68)); + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(client.player, BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + int x = ((HandledScreenAccessor) screen).getX() + 176; + int y = ((HandledScreenAccessor) screen).getY() + 76; + + consumer.accept(new Bounds(x, y, columns * 18, 68)); + } + }); + } + +} diff --git a/src/main/java/pm/c7/scout/client/gui/BagTooltipComponent.java b/src/main/java/pm/c7/scout/client/gui/BagTooltipComponent.java new file mode 100644 index 0000000..838b8b9 --- /dev/null +++ b/src/main/java/pm/c7/scout/client/gui/BagTooltipComponent.java @@ -0,0 +1,60 @@ +package pm.c7.scout.client.gui; + +import com.google.common.math.IntMath; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.tooltip.TooltipComponent; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BagTooltipData; + +import java.math.RoundingMode; + +public class BagTooltipComponent implements TooltipComponent { + private final DefaultedList inventory; + private final int slotCount; + + public BagTooltipComponent(BagTooltipData data) { + this.inventory = data.getInventory(); + this.slotCount = data.getSlotCount(); + } + + @Override + public int getHeight() { + return (18 * IntMath.divide(slotCount, 6, RoundingMode.UP)) + 2; + } + + @Override + public int getWidth(TextRenderer textRenderer) { + return 18 * (Math.min(slotCount, 6)); + } + + @Override + public void drawItems(TextRenderer textRenderer, int x, int y, MatrixStack matrices, ItemRenderer itemRenderer, int z) { + int originalX = x; + + for (int i = 0; i < slotCount; i++) { + ItemStack itemStack = this.inventory.get(i); + this.drawSlot(matrices, x, y, z); + itemRenderer.renderInGuiWithOverrides(itemStack, x + 1, y + 1, i); + itemRenderer.renderGuiItemOverlay(textRenderer, itemStack, x + 1, y + 1); + + x += 18; + if ((i + 1) % 6 == 0) { + y += 18; + x = originalX; + } + } + } + + private void drawSlot(MatrixStack matrices, int x, int y, int z) { + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.setShaderTexture(0, ScoutUtil.SLOT_TEXTURE); + DrawableHelper.drawTexture(matrices, x, y, z, 46, 7, 18, 18, 256, 256); + } + +} diff --git a/src/main/java/pm/c7/scout/client/model/SatchelModel.java b/src/main/java/pm/c7/scout/client/model/SatchelModel.java new file mode 100644 index 0000000..a98a4a6 --- /dev/null +++ b/src/main/java/pm/c7/scout/client/model/SatchelModel.java @@ -0,0 +1,45 @@ +package pm.c7.scout.client.model; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.model.*; +import net.minecraft.client.render.entity.model.SinglePartEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; + +public class SatchelModel extends SinglePartEntityModel { + private final ModelPart root; + private final ModelPart satchel; + private final ModelPart strap; + + public SatchelModel(ModelPart root) { + super(); + this.root = root; + this.satchel = root.getChild("satchel"); + this.strap = this.satchel.getChild("strap"); + } + + public static TexturedModelData getTexturedModelData() { + ModelData modelData = new ModelData(); + ModelPartData root = modelData.getRoot(); + + ModelPartData satchel = root.addChild("satchel", ModelPartBuilder.create().uv(10, 0).cuboid(-6.0F, -12.0F, -2.5F, 2.0F, 3.0F, 5.0F, new Dilation(0.275F)), ModelTransform.pivot(0.0F, 24.0F, 0.0F)); + ModelPartData strap = satchel.addChild("strap", ModelPartBuilder.create().uv(0, 0).cuboid(-1.0F, -13.0F, -2.0F, 1.0F, 14.0F, 4.0F, new Dilation(0.275F)), ModelTransform.of(-3.0F, -13.0F, 0.0F, 0.0F, 0.0F, 0.5672F)); + + return TexturedModelData.of(modelData, 32, 32); + } + + @Override + public void render(MatrixStack matrices, VertexConsumer vertices, int light, int overlay, float red, float green, float blue, float alpha) { + satchel.render(matrices, vertices, light, overlay, red, green, blue, alpha); + } + + @Override + public ModelPart getPart() { + return this.root; + } + + @Override + public void setAngles(T entity, float limbAngle, float limbDistance, float animationProgress, float headYaw, float headPitch) { + + } +} diff --git a/src/main/java/pm/c7/scout/client/render/PouchFeatureRenderer.java b/src/main/java/pm/c7/scout/client/render/PouchFeatureRenderer.java new file mode 100644 index 0000000..5f82cc6 --- /dev/null +++ b/src/main/java/pm/c7/scout/client/render/PouchFeatureRenderer.java @@ -0,0 +1,50 @@ +package pm.c7.scout.client.render; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.item.HeldItemRenderer; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.math.Vec3f; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; + +public class PouchFeatureRenderer> extends FeatureRenderer { + private final HeldItemRenderer heldItemRenderer; + + public PouchFeatureRenderer(FeatureRendererContext context, HeldItemRenderer heldItemRenderer) { + super(context); + this.heldItemRenderer = heldItemRenderer; + } + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + var leftPouch = ScoutUtil.findBagItem((PlayerEntity) entity, BaseBagItem.BagType.POUCH, false); + var rightPouch = ScoutUtil.findBagItem((PlayerEntity) entity, BaseBagItem.BagType.POUCH, true); + + if (!leftPouch.isEmpty()) { + matrices.push(); + ((PlayerEntityModel) this.getContextModel()).leftLeg.rotate(matrices); + matrices.multiply(Vec3f.POSITIVE_X.getDegreesQuaternion(180.0F)); + matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(-90.0F)); + matrices.scale(0.325F, 0.325F, 0.325F); + matrices.translate(0F, -0.325F, -0.475F); + this.heldItemRenderer.renderItem(entity, leftPouch, ModelTransformation.Mode.FIXED, false, matrices, vertexConsumers, light); + matrices.pop(); + } + if (!rightPouch.isEmpty()) { + matrices.push(); + ((PlayerEntityModel) this.getContextModel()).rightLeg.rotate(matrices); + matrices.multiply(Vec3f.POSITIVE_X.getDegreesQuaternion(180.0F)); + matrices.multiply(Vec3f.POSITIVE_Y.getDegreesQuaternion(-90.0F)); + matrices.scale(0.325F, 0.325F, 0.325F); + matrices.translate(0F, -0.325F, 0.475F); + this.heldItemRenderer.renderItem(entity, rightPouch, ModelTransformation.Mode.FIXED, false, matrices, vertexConsumers, light); + matrices.pop(); + } + } +} diff --git a/src/main/java/pm/c7/scout/client/render/SatchelFeatureRenderer.java b/src/main/java/pm/c7/scout/client/render/SatchelFeatureRenderer.java new file mode 100644 index 0000000..24e143b --- /dev/null +++ b/src/main/java/pm/c7/scout/client/render/SatchelFeatureRenderer.java @@ -0,0 +1,53 @@ +package pm.c7.scout.client.render; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.model.TexturedModelData; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.client.model.SatchelModel; +import pm.c7.scout.item.BaseBagItem; + +public class SatchelFeatureRenderer> extends FeatureRenderer { + private static final Identifier SATCHEL_TEXTURE = new Identifier(ScoutUtil.MOD_ID, "textures/entity/satchel.png"); + private static final Identifier UPGRADED_SATCHEL_TEXTURE = new Identifier(ScoutUtil.MOD_ID, "textures/entity/upgraded_satchel.png"); + + private final SatchelModel satchel; + + public SatchelFeatureRenderer(FeatureRendererContext context) { + super(context); + TexturedModelData modelData = SatchelModel.getTexturedModelData(); + this.satchel = new SatchelModel<>(modelData.createModel()); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + var satchel = ScoutUtil.findBagItem((PlayerEntity) entity, BaseBagItem.BagType.SATCHEL, false); + + if (!satchel.isEmpty()) { + BaseBagItem satchelItem = (BaseBagItem) satchel.getItem(); + var texture = SATCHEL_TEXTURE; + if (satchelItem.getSlotCount() == ScoutUtil.MAX_SATCHEL_SLOTS) + texture = UPGRADED_SATCHEL_TEXTURE; + + matrices.push(); + ((PlayerEntityModel) this.getContextModel()).body.rotate(matrices); + this.getContextModel().copyStateTo(this.satchel); + VertexConsumer vertexConsumer = ItemRenderer.getArmorGlintConsumer( + vertexConsumers, RenderLayer.getArmorCutoutNoCull(texture), false, satchel.hasGlint() + ); + this.satchel.render(matrices, vertexConsumer, light, OverlayTexture.DEFAULT_UV, 1.0F, 1.0F, 1.0F, 1.0F); + matrices.pop(); + } + } +} diff --git a/src/main/java/pm/c7/scout/config/ScoutConfig.java b/src/main/java/pm/c7/scout/config/ScoutConfig.java new file mode 100644 index 0000000..ed8d5a5 --- /dev/null +++ b/src/main/java/pm/c7/scout/config/ScoutConfig.java @@ -0,0 +1,12 @@ +package pm.c7.scout.config; + +import org.quiltmc.config.api.WrappedConfig; +import org.quiltmc.config.api.annotations.Comment; + +public class ScoutConfig extends WrappedConfig { + @Comment("Allow shulker boxes to be placed in bags. Bags are already blacklisted from shulker boxes with no toggle.") + public final boolean allowShulkers = true; + + @Comment("Allow bags to act as a quiver and pull arrows.") + public final boolean useArrows = true; +} diff --git a/src/main/java/pm/c7/scout/config/ScoutConfigHandler.java b/src/main/java/pm/c7/scout/config/ScoutConfigHandler.java new file mode 100644 index 0000000..2e30566 --- /dev/null +++ b/src/main/java/pm/c7/scout/config/ScoutConfigHandler.java @@ -0,0 +1,17 @@ +package pm.c7.scout.config; + +import org.quiltmc.config.api.values.TrackedValue; +import org.quiltmc.loader.api.config.QuiltConfig; +import pm.c7.scout.ScoutUtil; + +import java.util.List; + +public class ScoutConfigHandler { + public static final ScoutConfig CONFIG = QuiltConfig.create("", ScoutUtil.MOD_ID, ScoutConfig.class); + + public ScoutConfigHandler() {} + + public static TrackedValue getConfigValue(String key) { + return CONFIG.getValue(List.of(key)); + } +} diff --git a/src/main/java/pm/c7/scout/item/BagTooltipData.java b/src/main/java/pm/c7/scout/item/BagTooltipData.java new file mode 100644 index 0000000..0f39535 --- /dev/null +++ b/src/main/java/pm/c7/scout/item/BagTooltipData.java @@ -0,0 +1,23 @@ +package pm.c7.scout.item; + +import net.minecraft.client.item.TooltipData; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +public class BagTooltipData implements TooltipData { + private final DefaultedList inventory; + private final int slotCount; + + public BagTooltipData(DefaultedList inventory, int slots) { + this.inventory = inventory; + this.slotCount = slots; + } + + public DefaultedList getInventory() { + return this.inventory; + } + + public int getSlotCount() { + return this.slotCount; + } +} diff --git a/src/main/java/pm/c7/scout/item/BaseBagItem.java b/src/main/java/pm/c7/scout/item/BaseBagItem.java new file mode 100644 index 0000000..c7a7230 --- /dev/null +++ b/src/main/java/pm/c7/scout/item/BaseBagItem.java @@ -0,0 +1,226 @@ +package pm.c7.scout.item; + +import dev.emi.trinkets.api.SlotReference; +import dev.emi.trinkets.api.TrinketItem; +import io.netty.buffer.Unpooled; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.client.item.TooltipData; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtList; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; +import org.quiltmc.qsl.networking.api.ServerPlayNetworking; +import pm.c7.scout.ScoutNetworking; +import pm.c7.scout.ScoutScreenHandler; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.screen.BagSlot; + +import java.util.List; +import java.util.Optional; + +public class BaseBagItem extends TrinketItem { + private static final String ITEMS_KEY = "Items"; + + private final int slots; + private final BagType type; + + public BaseBagItem(Settings settings, int slots, BagType type) { + super(settings); + + if (type == BagType.SATCHEL && slots > ScoutUtil.MAX_SATCHEL_SLOTS) { + throw new IllegalArgumentException("Satchel has too many slots."); + } + if (type == BagType.POUCH && slots > ScoutUtil.MAX_POUCH_SLOTS) { + throw new IllegalArgumentException("Pouch has too many slots."); + } + + this.slots = slots; + this.type = type; + } + + public int getSlotCount() { + return this.slots; + } + + public BagType getType() { + return this.type; + } + + @Override + public void appendTooltip(ItemStack stack, @Nullable World world, List tooltip, TooltipContext context) { + super.appendTooltip(stack, world, tooltip, context); + tooltip.add(Text.translatable("tooltip.scout.slots", Text.literal(String.valueOf(this.slots)).formatted(Formatting.BLUE)).formatted(Formatting.GRAY)); + } + + public Inventory getInventory(ItemStack stack) { + SimpleInventory inventory = new SimpleInventory(this.slots) { + @Override + public void markDirty() { + stack.getOrCreateNbt().put(ITEMS_KEY, ScoutUtil.inventoryToTag(this)); + super.markDirty(); + } + }; + + NbtCompound compound = stack.getOrCreateNbt(); + if (!compound.contains(ITEMS_KEY)) { + compound.put(ITEMS_KEY, new NbtList()); + } + + NbtList items = compound.getList(ITEMS_KEY, 10); + + ScoutUtil.inventoryFromTag(items, inventory); + + return inventory; + } + + @Override + public Optional getTooltipData(ItemStack stack) { + DefaultedList stacks = DefaultedList.of(); + Inventory inventory = getInventory(stack); + + for (int i = 0; i < slots; i++) { + stacks.add(inventory.getStack(i)); + } + + if (stacks.stream().allMatch(ItemStack::isEmpty)) return Optional.empty(); + + return Optional.of(new BagTooltipData(stacks, slots)); + } + + @Override + public void onEquip(ItemStack stack, SlotReference slotRef, LivingEntity entity) { + if (entity instanceof PlayerEntity player) + updateSlots(player); + } + + @Override + public void onUnequip(ItemStack stack, SlotReference slotRef, LivingEntity entity) { + if (entity instanceof PlayerEntity player) + updateSlots(player); + } + + private void updateSlots(PlayerEntity player) { + ScoutScreenHandler handler = (ScoutScreenHandler) player.playerScreenHandler; + + ItemStack satchelStack = ScoutUtil.findBagItem(player, BagType.SATCHEL, false); + DefaultedList satchelSlots = handler.scout$getSatchelSlots(); + + for (int i = 0; i < ScoutUtil.MAX_SATCHEL_SLOTS; i++) { + BagSlot slot = satchelSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!satchelStack.isEmpty()) { + BaseBagItem satchelItem = (BaseBagItem) satchelStack.getItem(); + Inventory satchelInv = satchelItem.getInventory(satchelStack); + + for (int i = 0; i < satchelItem.getSlotCount(); i++) { + BagSlot slot = satchelSlots.get(i); + slot.setInventory(satchelInv); + slot.setEnabled(true); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(player, BagType.POUCH, false); + DefaultedList leftPouchSlots = handler.scout$getLeftPouchSlots(); + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + BagSlot slot = leftPouchSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!leftPouchStack.isEmpty()) { + BaseBagItem leftPouchItem = (BaseBagItem) leftPouchStack.getItem(); + Inventory leftPouchInv = leftPouchItem.getInventory(leftPouchStack); + + for (int i = 0; i < leftPouchItem.getSlotCount(); i++) { + BagSlot slot = leftPouchSlots.get(i); + slot.setInventory(leftPouchInv); + slot.setEnabled(true); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(player, BagType.POUCH, true); + DefaultedList rightPouchSlots = handler.scout$getRightPouchSlots(); + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + BagSlot slot = rightPouchSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + if (!rightPouchStack.isEmpty()) { + BaseBagItem rightPouchItem = (BaseBagItem) rightPouchStack.getItem(); + Inventory rightPouchInv = rightPouchItem.getInventory(rightPouchStack); + + for (int i = 0; i < rightPouchItem.getSlotCount(); i++) { + BagSlot slot = rightPouchSlots.get(i); + slot.setInventory(rightPouchInv); + slot.setEnabled(true); + } + } + + PacketByteBuf packet = new PacketByteBuf(Unpooled.buffer()); + if (player instanceof ServerPlayerEntity serverPlayer) { + ServerPlayNetworking.send(serverPlayer, ScoutNetworking.ENABLE_SLOTS, packet); + } + } + + @Override + public boolean canEquip(ItemStack stack, SlotReference slot, LivingEntity entity) { + Item item = stack.getItem(); + + ItemStack slotStack = slot.inventory().getStack(slot.index()); + Item slotItem = slotStack.getItem(); + + if (slotItem instanceof BaseBagItem) { + if (((BaseBagItem) item).getType() == BagType.SATCHEL) { + if (((BaseBagItem) slotItem).getType() == BagType.SATCHEL) { + return true; + } else { + return ScoutUtil.findBagItem((PlayerEntity) entity, BagType.SATCHEL, false).isEmpty(); + } + } else if (((BaseBagItem) item).getType() == BagType.POUCH) { + if (((BaseBagItem) slotItem).getType() == BagType.POUCH) { + return true; + } else { + return ScoutUtil.findBagItem((PlayerEntity) entity, BagType.POUCH, true).isEmpty(); + } + } + } else { + if (((BaseBagItem) item).getType() == BagType.SATCHEL) { + return ScoutUtil.findBagItem((PlayerEntity) entity, BagType.SATCHEL, false).isEmpty(); + } else if (((BaseBagItem) item).getType() == BagType.POUCH) { + return ScoutUtil.findBagItem((PlayerEntity) entity, BagType.POUCH, true).isEmpty(); + } + } + + return false; + } + + @Override + public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) { + var inv = getInventory(stack); + + for (int i = 0; i < inv.size(); i++) { + var invStack = inv.getStack(i); + invStack.inventoryTick(world, entity, i, false); + } + } + + public enum BagType { + SATCHEL, + POUCH + } +} diff --git a/src/main/java/pm/c7/scout/mixin/BowItemMixin.java b/src/main/java/pm/c7/scout/mixin/BowItemMixin.java new file mode 100644 index 0000000..49570bb --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/BowItemMixin.java @@ -0,0 +1,85 @@ +package pm.c7.scout.mixin; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.BowItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.config.ScoutConfigHandler; +import pm.c7.scout.item.BaseBagItem; + +@Mixin(BowItem.class) +public class BowItemMixin { + @Inject(method = "onStoppedUsing", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/World;playSound(Lnet/minecraft/entity/player/PlayerEntity;DDDLnet/minecraft/sound/SoundEvent;Lnet/minecraft/sound/SoundCategory;FF)V"), locals = LocalCapture.CAPTURE_FAILHARD) + public void scout$arrowsFromBags(ItemStack stack, World world, LivingEntity user, int remainingUseTicks, CallbackInfo ci, PlayerEntity playerEntity, boolean bl, ItemStack itemStack, int maxTime, float f) { + if ((boolean) ScoutConfigHandler.getConfigValue("useArrows").value()) { + boolean infinity = bl && itemStack.isOf(Items.ARROW); + boolean hasRan = false; + + if (!infinity && !playerEntity.getAbilities().creativeMode) { + var leftPouch = ScoutUtil.findBagItem(playerEntity, BaseBagItem.BagType.POUCH, false); + var rightPouch = ScoutUtil.findBagItem(playerEntity, BaseBagItem.BagType.POUCH, true); + var satchel = ScoutUtil.findBagItem(playerEntity, BaseBagItem.BagType.SATCHEL, false); + + if (!leftPouch.isEmpty()) { + BaseBagItem item = (BaseBagItem) leftPouch.getItem(); + var inv = item.getInventory(leftPouch); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (ItemStack.areEqual(invStack, itemStack)) { + invStack.decrement(1); + if (invStack.isEmpty()) { + inv.setStack(i, ItemStack.EMPTY); + } + inv.markDirty(); + hasRan = true; + break; + } + } + } + if (!rightPouch.isEmpty() && !hasRan) { + BaseBagItem item = (BaseBagItem) rightPouch.getItem(); + var inv = item.getInventory(rightPouch); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (ItemStack.areEqual(invStack, itemStack)) { + invStack.decrement(1); + if (invStack.isEmpty()) { + inv.setStack(i, ItemStack.EMPTY); + } + inv.markDirty(); + hasRan = true; + break; + } + } + } + if (!satchel.isEmpty() && !hasRan) { + BaseBagItem item = (BaseBagItem) satchel.getItem(); + var inv = item.getInventory(satchel); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (ItemStack.areEqual(invStack, itemStack)) { + invStack.decrement(1); + if (invStack.isEmpty()) { + inv.setStack(i, ItemStack.EMPTY); + } + inv.markDirty(); + hasRan = true; + break; + } + } + } + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/ItemStackMixin.java b/src/main/java/pm/c7/scout/mixin/ItemStackMixin.java new file mode 100644 index 0000000..bb533ca --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/ItemStackMixin.java @@ -0,0 +1,23 @@ +package pm.c7.scout.mixin; + +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.item.BaseBagItem; + +@Mixin(ItemStack.class) +public class ItemStackMixin { + + // Trinkets calls isItemEqual to check whether it should unequip old and equip new (https://github.com/emilyploszaj/trinkets/blob/37ee13d6/src/main/java/dev/emi/trinkets/mixin/LivingEntityMixin.java#L196-L199) + // Excluding ourselves from this check to force unequip/equip when switching bag items fixes a duplication bug (GH-12) + // Gross and hacky but oh well, can't mixin mixins. + @Inject(method = "isItemEqual", at = @At("HEAD"), cancellable = true) + private void scout$grossTrinketsEquipFix(ItemStack newStack, CallbackInfoReturnable callbackInfo) { + ItemStack self = (ItemStack) (Object) this; + if (self.getItem() instanceof BaseBagItem && newStack.getItem() instanceof BaseBagItem) { + callbackInfo.setReturnValue(false); + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/PlayerEntityMixin.java b/src/main/java/pm/c7/scout/mixin/PlayerEntityMixin.java new file mode 100644 index 0000000..900b2b2 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/PlayerEntityMixin.java @@ -0,0 +1,61 @@ +package pm.c7.scout.mixin; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.config.ScoutConfigHandler; +import pm.c7.scout.item.BaseBagItem; + +import java.util.function.Predicate; + +@Mixin(PlayerEntity.class) +public class PlayerEntityMixin { + @Inject(method = "getArrowType", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/item/RangedWeaponItem;getProjectiles()Ljava/util/function/Predicate;"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true) + public void scout$arrowsFromBags(ItemStack stack, CallbackInfoReturnable cir, Predicate predicate, ItemStack itemStack) { + if ((boolean) ScoutConfigHandler.getConfigValue("useArrows").value()) { + var self = (PlayerEntity) (Object) this; + var leftPouch = ScoutUtil.findBagItem(self, BaseBagItem.BagType.POUCH, false); + var rightPouch = ScoutUtil.findBagItem(self, BaseBagItem.BagType.POUCH, true); + var satchel = ScoutUtil.findBagItem(self, BaseBagItem.BagType.SATCHEL, false); + + if (!leftPouch.isEmpty()) { + BaseBagItem item = (BaseBagItem) leftPouch.getItem(); + var inv = item.getInventory(leftPouch); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (predicate.test(invStack)) { + cir.setReturnValue(invStack); + } + } + } + if (!rightPouch.isEmpty()) { + BaseBagItem item = (BaseBagItem) rightPouch.getItem(); + var inv = item.getInventory(rightPouch); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (predicate.test(invStack)) { + cir.setReturnValue(invStack); + } + } + } + if (!satchel.isEmpty()) { + BaseBagItem item = (BaseBagItem) satchel.getItem(); + var inv = item.getInventory(satchel); + + for(int i = 0; i < inv.size(); ++i) { + ItemStack invStack = inv.getStack(i); + if (predicate.test(invStack)) { + cir.setReturnValue(invStack); + } + } + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/PlayerScreenHandlerMixin.java b/src/main/java/pm/c7/scout/mixin/PlayerScreenHandlerMixin.java new file mode 100644 index 0000000..2e65f38 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/PlayerScreenHandlerMixin.java @@ -0,0 +1,99 @@ +package pm.c7.scout.mixin; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.screen.PlayerScreenHandler; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.collection.DefaultedList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import pm.c7.scout.ScoutScreenHandler; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.screen.BagSlot; + +@Mixin(value = PlayerScreenHandler.class, priority = 950) +public abstract class PlayerScreenHandlerMixin extends ScreenHandler implements ScoutScreenHandler { + protected PlayerScreenHandlerMixin() { + super(null, 0); + } + + @Unique + public final DefaultedList scout$satchelSlots = DefaultedList.ofSize(ScoutUtil.MAX_SATCHEL_SLOTS); + @Unique + public final DefaultedList scout$leftPouchSlots = DefaultedList.ofSize(ScoutUtil.MAX_POUCH_SLOTS); + @Unique + public final DefaultedList scout$rightPouchSlots = DefaultedList.ofSize(ScoutUtil.MAX_POUCH_SLOTS); + + @Inject(method = "", at = @At("RETURN")) + private void scout$addSlots(PlayerInventory inventory, boolean onServer, PlayerEntity owner, CallbackInfo callbackInfo) { + // satchel + int x = 8; + int y = 168; + + for (int i = 0; i < ScoutUtil.MAX_SATCHEL_SLOTS; i++) { + if (i % 9 == 0) { + x = 8; + } + + BagSlot slot = new BagSlot(i, x, y); + slot.id = ScoutUtil.SATCHEL_SLOT_START - i; + scout$satchelSlots.add(slot); + + x += 18; + + if ((i + 1) % 9 == 0) { + y += 18; + } + } + + // left pouch + x = 8; + y = 66; + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + if (i % 3 == 0) { + x -= 18; + y += 54; + } + + BagSlot slot = new BagSlot(i, x, y); + slot.id = ScoutUtil.LEFT_POUCH_SLOT_START - i; + scout$leftPouchSlots.add(slot); + + y -= 18; + } + + // right pouch + x = 152; + y = 66; + + for (int i = 0; i < ScoutUtil.MAX_POUCH_SLOTS; i++) { + if (i % 3 == 0) { + x += 18; + y += 54; + } + + BagSlot slot = new BagSlot(i, x, y); + slot.id = ScoutUtil.RIGHT_POUCH_SLOT_START - i; + scout$rightPouchSlots.add(slot); + + y -= 18; + } + } + + @Override + public final DefaultedList scout$getSatchelSlots() { + return scout$satchelSlots; + } + @Override + public final DefaultedList scout$getLeftPouchSlots() { + return scout$leftPouchSlots; + } + @Override + public final DefaultedList scout$getRightPouchSlots() { + return scout$rightPouchSlots; + } +} diff --git a/src/main/java/pm/c7/scout/mixin/ScreenHandlerMixin.java b/src/main/java/pm/c7/scout/mixin/ScreenHandlerMixin.java new file mode 100644 index 0000000..3411e88 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/ScreenHandlerMixin.java @@ -0,0 +1,68 @@ +package pm.c7.scout.mixin; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import net.minecraft.screen.slot.SlotActionType; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import pm.c7.scout.ScoutMixin.Transformer; +import pm.c7.scout.ScoutUtil; + +@Mixin(ScreenHandler.class) +@Transformer(ScreenHandlerTransformer.class) +public abstract class ScreenHandlerMixin { + @Inject(method = "getSlot", at = @At("HEAD"), cancellable = true) + public void scout$fixGetSlot(int index, CallbackInfoReturnable cir) { + var playerScreenHandler = ScoutUtil.getPlayerScreenHandler(); + if (ScoutUtil.isBagSlot(index)) { + if (playerScreenHandler != null) { + cir.setReturnValue(ScoutUtil.getBagSlot(index, playerScreenHandler)); + } else { + cir.setReturnValue(null); + } + } + } + + @Inject(method = "internalOnSlotClick", at = @At(value = "INVOKE_ASSIGN", target = "Lnet/minecraft/screen/ScreenHandler;getCursorStack()Lnet/minecraft/item/ItemStack;", ordinal = 11), locals = LocalCapture.CAPTURE_FAILEXCEPTION) + public void scout$fixDoubleClick(int slotIndex, int button, SlotActionType actionType, PlayerEntity player, CallbackInfo ci, PlayerInventory playerInventory, Slot slot3) { + var cursorStack = this.getCursorStack(); + if (!cursorStack.isEmpty() && (!slot3.hasStack() || !slot3.canTakeItems(player))) { + var slots = ScoutUtil.getAllBagSlots(player.playerScreenHandler); + var k = button == 0 ? 0 : ScoutUtil.TOTAL_SLOTS - 1; + var o = button == 0 ? 1 : -1; + + for (int n = 0; n < 2; ++n) { + for (int p = k; p >= 0 && p < slots.size() && cursorStack.getCount() < cursorStack.getMaxCount(); p += o) { + Slot slot4 = slots.get(p); + if (slot4.hasStack() && canInsertItemIntoSlot(slot4, cursorStack, true) && slot4.canTakeItems(player) && this.canInsertIntoSlot(cursorStack, slot4)) { + ItemStack itemStack6 = slot4.getStack(); + if (n != 0 || itemStack6.getCount() != itemStack6.getMaxCount()) { + ItemStack itemStack7 = slot4.takeStackRange(itemStack6.getCount(), cursorStack.getMaxCount() - cursorStack.getCount(), player); + cursorStack.increment(itemStack7.getCount()); + } + } + } + } + } + } + + @Shadow + public static boolean canInsertItemIntoSlot(@Nullable Slot slot, ItemStack stack, boolean allowOverflow) { + return false; + } + @Shadow + public boolean canInsertIntoSlot(ItemStack stack, Slot slot) { + return true; + } + @Shadow + public abstract ItemStack getCursorStack(); +} diff --git a/src/main/java/pm/c7/scout/mixin/ScreenHandlerTransformer.java b/src/main/java/pm/c7/scout/mixin/ScreenHandlerTransformer.java new file mode 100644 index 0000000..7cba273 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/ScreenHandlerTransformer.java @@ -0,0 +1,155 @@ +package pm.c7.scout.mixin; + +import org.objectweb.asm.tree.*; +import org.quiltmc.loader.api.QuiltLoader; +import pm.c7.scout.mixinsupport.ClassNodeTransformer; + +import static org.objectweb.asm.Opcodes.*; + +public class ScreenHandlerTransformer implements ClassNodeTransformer { + @Override + public void transform(String name, ClassNode node) { + var internalOnSlotClick = "m_nqfgpzfl"; + var insertItem = "m_jpjdgbxy"; + + var PlayerEntity = "net/minecraft/unmapped/C_jzrpycqo"; + var playerScreenHandler = "f_xvlfpipb"; + + var PlayerScreenHandler = "net/minecraft/unmapped/C_wgehrbdx"; + var Slot = "net/minecraft/unmapped/C_nhvqfffd"; + var DefaultedList = "net/minecraft/unmapped/C_rnrfftze"; + + if (QuiltLoader.isDevelopmentEnvironment()) { + internalOnSlotClick = "internalOnSlotClick"; + insertItem = "insertItem"; + + PlayerEntity = "net/minecraft/entity/player/PlayerEntity"; + playerScreenHandler = "playerScreenHandler"; + + PlayerScreenHandler = "net/minecraft/screen/PlayerScreenHandler"; + Slot = "net/minecraft/screen/slot/Slot"; + DefaultedList = "net/minecraft/util/collection/DefaultedList"; + } + + var LPlayerScreenHandler = L(PlayerScreenHandler); + var LSlot = L(Slot); + + for (var mn : node.methods) { + if (mn.name.equals(internalOnSlotClick)) { + for (var insn : mn.instructions) { + if (insn instanceof VarInsnNode vin) { + if (vin.getOpcode() == ILOAD && vin.var == 1) { + if (insn.getNext() instanceof JumpInsnNode nextInsn && nextInsn.getOpcode() == IFGE) { + var jumpTo = nextInsn.label; + mn.instructions.insert(nextInsn, insns( + ILOAD(1), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "isBagSlot", "(I)Z"), + IFNE(jumpTo) + )); + } + } else if (vin.getOpcode() == ASTORE && (vin.var == 6 || vin.var == 7)) { + if (vin.getPrevious() instanceof TypeInsnNode prevInsn && prevInsn.getOpcode() == CHECKCAST && prevInsn.desc.equals(Slot)) { + if (prevInsn.getPrevious() instanceof MethodInsnNode prevPrevInsn && prevPrevInsn.getOpcode() == INVOKEVIRTUAL) { + if(prevPrevInsn.owner.equals(DefaultedList)) { + LabelNode LnotBag = new LabelNode(); + LabelNode Lend = new LabelNode(); + mn.instructions.insertBefore(prevPrevInsn.getPrevious().getPrevious().getPrevious(), insns( + ILOAD(1), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "isBagSlot", "(I)Z"), + IFEQ(LnotBag), + ILOAD(1), + ALOAD(4), + GETFIELD(PlayerEntity, playerScreenHandler, LPlayerScreenHandler), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "getBagSlot", "(I" + LPlayerScreenHandler + ")" + LSlot), + CHECKCAST(Slot), + ASTORE(vin.var), + GOTO(Lend), + LnotBag + )); + mn.instructions.insert(vin, insns( + Lend + )); + } + } + } + } + } + } + }/* else if (mn.name.equals(insertItem)) { + for (var insn : mn.instructions) { + if (insn instanceof VarInsnNode vin && vin.getOpcode() == ASTORE && vin.var == 7) { + if (vin.getPrevious() instanceof TypeInsnNode prevInsn && prevInsn.getOpcode() == CHECKCAST && prevInsn.desc.equals(Slot)) { + if (prevInsn.getPrevious() instanceof MethodInsnNode prevPrevInsn && prevPrevInsn.getOpcode() == INVOKEVIRTUAL) { + if(prevPrevInsn.owner.equals(DefaultedList)) { + LabelNode LnotBag = new LabelNode(); + LabelNode LpastSlot = new LabelNode(); + mn.instructions.insertBefore(prevPrevInsn.getPrevious().getPrevious().getPrevious(), insns( + INVOKESTATIC("pm/c7/scout/ScoutUtil", "getPlayerScreenHandler", "()" + LPlayerScreenHandler), + ASTORE(20), + ALOAD(20), + IFNULL(LpastSlot), + ILOAD(6), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "isBagSlot", "(I)Z"), + IFEQ(LnotBag), + ILOAD(6), + ALOAD(20), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "getBagSlot", "(I" + LPlayerScreenHandler + ")" + LSlot), + CHECKCAST(Slot), + ASTORE(vin.var), + LnotBag, + ILOAD(6), + INVOKESTATIC("pm/c7/scout/ScoutUtil", "isBagSlot", "(I)Z"), + IFNE(LpastSlot) + )); + mn.instructions.insert(vin, insns( + LpastSlot + )); + } + } + } + } + } + }*/ + } + } + + private String L(String clazz) { + return "L" + clazz + ";"; + } + + private InsnList insns(AbstractInsnNode... insns) { + var li = new InsnList(); + for (var i : insns) li.add(i); + return li; + } + private static VarInsnNode ILOAD(int var) { + return new VarInsnNode(ILOAD, var); + } + private static MethodInsnNode INVOKESTATIC(String owner, String name, String desc) { + return new MethodInsnNode(INVOKESTATIC, owner, name, desc); + } + private static JumpInsnNode IFNE(LabelNode var) { + return new JumpInsnNode(IFNE, var); + } + private static JumpInsnNode IFEQ(LabelNode var) { + return new JumpInsnNode(IFEQ, var); + } + private static VarInsnNode ALOAD(int var) { + return new VarInsnNode(ALOAD, var); + } + private static VarInsnNode ASTORE(int var) { + return new VarInsnNode(ASTORE, var); + } + private static FieldInsnNode GETFIELD(String owner, String name, String desc) { + return new FieldInsnNode(GETFIELD, owner, name, desc); + } + private static JumpInsnNode IFNULL(LabelNode var) { + return new JumpInsnNode(IFNULL, var); + } + private static TypeInsnNode CHECKCAST(String desc) { + return new TypeInsnNode(CHECKCAST, desc); + } + private static JumpInsnNode GOTO(LabelNode var) { + return new JumpInsnNode(GOTO, var); + } +} diff --git a/src/main/java/pm/c7/scout/mixin/ServerPlayerEntityMixin.java b/src/main/java/pm/c7/scout/mixin/ServerPlayerEntityMixin.java new file mode 100644 index 0000000..eec5513 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/ServerPlayerEntityMixin.java @@ -0,0 +1,94 @@ +package pm.c7.scout.mixin; + +import io.netty.buffer.Unpooled; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.GameRules; +import org.quiltmc.qsl.networking.api.ServerPlayNetworking; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import pm.c7.scout.ScoutNetworking; +import pm.c7.scout.ScoutScreenHandler; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; +import pm.c7.scout.screen.BagSlot; + +@Mixin(ServerPlayerEntity.class) +public class ServerPlayerEntityMixin { + @Inject(method = "onDeath", at = @At("HEAD")) + private void scout$attemptFixGraveMods(DamageSource source, CallbackInfo callbackInfo) { + ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; + ScoutScreenHandler handler = (ScoutScreenHandler) player.playerScreenHandler; + + if (!player.world.getGameRules().getBoolean(GameRules.KEEP_INVENTORY)) { + ItemStack backStack = ScoutUtil.findBagItem(player, BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + + DefaultedList bagSlots = handler.scout$getSatchelSlots(); + + for (int i = 0; i < slots; i++) { + BagSlot slot = bagSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + + PacketByteBuf packet = new PacketByteBuf(Unpooled.buffer()); + packet.writeBoolean(false); + packet.writeInt(0); + packet.writeItemStack(backStack); + + ServerPlayNetworking.send(player, ScoutNetworking.ENABLE_SLOTS, packet); + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(player, BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + + DefaultedList bagSlots = handler.scout$getLeftPouchSlots(); + + for (int i = 0; i < slots; i++) { + BagSlot slot = bagSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + + PacketByteBuf packet = new PacketByteBuf(Unpooled.buffer()); + packet.writeBoolean(false); + packet.writeInt(0); + packet.writeItemStack(leftPouchStack); + + ServerPlayNetworking.send(player, ScoutNetworking.ENABLE_SLOTS, packet); + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(player, BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + + DefaultedList bagSlots = handler.scout$getRightPouchSlots(); + + for (int i = 0; i < slots; i++) { + BagSlot slot = bagSlots.get(i); + slot.setInventory(null); + slot.setEnabled(false); + } + + PacketByteBuf packet = new PacketByteBuf(Unpooled.buffer()); + packet.writeBoolean(false); + packet.writeInt(1); + packet.writeItemStack(rightPouchStack); + + ServerPlayNetworking.send(player, ScoutNetworking.ENABLE_SLOTS, packet); + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/ShulkerBoxSlotMixin.java b/src/main/java/pm/c7/scout/mixin/ShulkerBoxSlotMixin.java new file mode 100644 index 0000000..5b8c750 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/ShulkerBoxSlotMixin.java @@ -0,0 +1,19 @@ +package pm.c7.scout.mixin; + +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.ShulkerBoxSlot; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.item.BaseBagItem; + +@Mixin(ShulkerBoxSlot.class) +public class ShulkerBoxSlotMixin { + @Inject(method = "canInsert", at = @At("HEAD"), cancellable = true) + public void scout$noNBTOverflow(ItemStack stack, CallbackInfoReturnable cir) { + if (stack.getItem() instanceof BaseBagItem) { + cir.setReturnValue(false); + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/AbstractFurnaceScreenMixin.java b/src/main/java/pm/c7/scout/mixin/client/AbstractFurnaceScreenMixin.java new file mode 100644 index 0000000..b44b278 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/AbstractFurnaceScreenMixin.java @@ -0,0 +1,63 @@ +package pm.c7.scout.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.screen.ingame.AbstractFurnaceScreen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.AbstractFurnaceScreenHandler; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; + +@Environment(EnvType.CLIENT) +@Mixin(AbstractFurnaceScreen.class) +public abstract class AbstractFurnaceScreenMixin extends HandledScreen implements RecipeBookProvider { + public AbstractFurnaceScreenMixin() { + super(null, null, null); + } + + @Inject(method = "isClickOutsideBounds", at = @At("TAIL"), cancellable = true) + private void scout$adjustOutsideBounds(double mouseX, double mouseY, int left, int top, int button, CallbackInfoReturnable callbackInfo) { + if (this.client != null && this.client.player != null) { + ItemStack backStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + int rows = (int) Math.ceil(slots / 9); + + if (mouseY < (top + this.backgroundHeight) + 8 + (18 * rows) && mouseY >= (top + this.backgroundHeight) && mouseX >= left && mouseY < (left + this.backgroundWidth)) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= left - (columns * 18) && mouseX < left && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= (left + this.backgroundWidth) && mouseX < (left + this.backgroundWidth) + (columns * 18) && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/CraftingScreenMixin.java b/src/main/java/pm/c7/scout/mixin/client/CraftingScreenMixin.java new file mode 100644 index 0000000..cd121a4 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/CraftingScreenMixin.java @@ -0,0 +1,63 @@ +package pm.c7.scout.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.screen.ingame.CraftingScreen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.CraftingScreenHandler; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; + +@Environment(EnvType.CLIENT) +@Mixin(CraftingScreen.class) +public abstract class CraftingScreenMixin extends HandledScreen implements RecipeBookProvider { + public CraftingScreenMixin() { + super(null, null, null); + } + + @Inject(method = "isClickOutsideBounds", at = @At("TAIL"), cancellable = true) + private void scout$adjustOutsideBounds(double mouseX, double mouseY, int left, int top, int button, CallbackInfoReturnable callbackInfo) { + if (this.client != null && this.client.player != null) { + ItemStack backStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + int rows = (int) Math.ceil(slots / 9); + + if (mouseY < (top + this.backgroundHeight) + 8 + (18 * rows) && mouseY >= (top + this.backgroundHeight) && mouseX >= left && mouseY < (left + this.backgroundWidth)) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= left - (columns * 18) && mouseX < left && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= (left + this.backgroundWidth) && mouseX < (left + this.backgroundWidth) + (columns * 18) && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/HandledScreenAccessor.java b/src/main/java/pm/c7/scout/mixin/client/HandledScreenAccessor.java new file mode 100644 index 0000000..b2f4ead --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/HandledScreenAccessor.java @@ -0,0 +1,20 @@ +package pm.c7.scout.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Environment(EnvType.CLIENT) +@Mixin(HandledScreen.class) +public interface HandledScreenAccessor { + @Accessor("x") + int getX(); + @Accessor("y") + int getY(); + @Accessor("backgroundWidth") + int getBackgroundWidth(); + @Accessor("backgroundHeight") + int getBackgroundHeight(); +} diff --git a/src/main/java/pm/c7/scout/mixin/client/HandledScreenMixin.java b/src/main/java/pm/c7/scout/mixin/client/HandledScreenMixin.java new file mode 100644 index 0000000..45d9181 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/HandledScreenMixin.java @@ -0,0 +1,328 @@ +package pm.c7.scout.mixin.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.GenericContainerScreen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.client.gui.screen.ingame.ScreenHandlerProvider; +import net.minecraft.client.gui.screen.ingame.ShulkerBoxScreen; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.ScoutMixin; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.screen.BagSlot; + +@Environment(EnvType.CLIENT) +@ScoutMixin.Transformer(HandledScreenTransformer.class) +@Mixin(value = HandledScreen.class, priority = 950) +public abstract class HandledScreenMixin extends Screen implements ScreenHandlerProvider { + protected HandledScreenMixin() { + super(null); + } + + @Shadow + @Nullable + protected Slot focusedSlot; + @Shadow + protected int x; + @Shadow + protected int y; + @Shadow + protected int backgroundWidth; + @Shadow + protected int backgroundHeight; + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawBackground(Lnet/minecraft/client/util/math/MatrixStack;FII)V")) + private void scout$drawSatchelRow(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (this.client != null && this.client.player != null && !ScoutUtil.isScreenBlacklisted(this)) { + ItemStack backStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, ScoutUtil.SLOT_TEXTURE); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + + int x = this.x; + int y = this.y + this.backgroundHeight - 3; + + if ((Object) this instanceof GenericContainerScreen || (Object) this instanceof ShulkerBoxScreen) { + y -= 1; + } + + this.drawTexture(matrices, x, y, 0, 32, 176, 4); + y += 4; + + int u = 0; + int v = 36; + + for (int slot = 0; slot < slots; slot++) { + if (slot % 9 == 0) { + x = this.x; + u = 0; + this.drawTexture(matrices, x, y, u, v, 7, 18); + x += 7; + u += 7; + } + + this.drawTexture(matrices, x, y, u, v, 18, 18); + + x += 18; + u += 18; + + if ((slot + 1) % 9 == 0) { + this.drawTexture(matrices, x, y, u, v, 7, 18); + y += 18; + } + } + + x = this.x; + this.drawTexture(matrices, x, y, 0, 54, 176, 7); + } + } + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;disableDepthTest()V")) + private void scout$drawPouchSlots(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (this.client != null && this.client.player != null && !ScoutUtil.isScreenBlacklisted(this)) { + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + int x = this.x; + int y = (this.y + this.backgroundHeight) - 29; + + if ((Object) this instanceof GenericContainerScreen || (Object) this instanceof ShulkerBoxScreen) { + y -= 1; + } + + RenderSystem.setShaderTexture(0, ScoutUtil.SLOT_TEXTURE); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + + this.drawTexture(matrices, x, y, 18, 25, 7, 7); + for (int i = 0; i < columns; i++) { + x -= 11; + this.drawTexture(matrices, x, y, 7, 25, 11, 7); + } + if (columns > 1) { + for (int i = 0; i < columns - 1; i++) { + x -= 7; + this.drawTexture(matrices, x, y, 7, 25, 7, 7); + } + } + x -= 7; + this.drawTexture(matrices, x, y, 0, 25, 7, 7); + + x = this.x + 7; + y -= 54; + for (int slot = 0; slot < slots; slot++) { + if (slot % 3 == 0) { + x -= 18; + y += 54; + } + y -= 18; + this.drawTexture(matrices, x, y, 7, 7, 18, 18); + } + + x -= 7; + y += 54; + for (int i = 0; i < 3; i++) { + y -= 18; + this.drawTexture(matrices, x, y, 0, 7, 7, 18); + } + + x = this.x; + y -= 7; + this.drawTexture(matrices, x, y, 18, 0, 7, 7); + for (int i = 0; i < columns; i++) { + x -= 11; + this.drawTexture(matrices, x, y, 7, 0, 11, 7); + } + if (columns > 1) { + for (int i = 0; i < columns - 1; i++) { + x -= 7; + this.drawTexture(matrices, x, y, 7, 0, 7, 7); + } + } + x -= 7; + this.drawTexture(matrices, x, y, 0, 0, 7, 7); + + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + int x = this.x + this.backgroundWidth - 7; + int y = (this.y + this.backgroundHeight) - 29; + + if ((Object) this instanceof GenericContainerScreen || (Object) this instanceof ShulkerBoxScreen) { + y -= 1; + } + + RenderSystem.setShaderTexture(0, ScoutUtil.SLOT_TEXTURE); + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + + this.drawTexture(matrices, x, y, 25, 25, 7, 7); + x += 7; + for (int i = 0; i < columns; i++) { + this.drawTexture(matrices, x, y, 7, 25, 11, 7); + x += 11; + } + if (columns > 1) { + for (int i = 0; i < columns - 1; i++) { + this.drawTexture(matrices, x, y, 7, 25, 7, 7); + x += 7; + } + } + this.drawTexture(matrices, x, y, 32, 25, 7, 7); + + x = this.x + this.backgroundWidth - 25; + y -= 54; + for (int slot = 0; slot < slots; slot++) { + if (slot % 3 == 0) { + x += 18; + y += 54; + } + y -= 18; + this.drawTexture(matrices, x, y, 7, 7, 18, 18); + } + + x += 18; + y += 54; + for (int i = 0; i < 3; i++) { + y -= 18; + this.drawTexture(matrices, x, y, 32, 7, 7, 18); + } + + x = this.x + this.backgroundWidth - 7; + y -= 7; + this.drawTexture(matrices, x, y, 25, 0, 7, 7); + x += 7; + for (int i = 0; i < columns; i++) { + this.drawTexture(matrices, x, y, 7, 0, 11, 7); + x += 11; + } + if (columns > 1) { + for (int i = 0; i < columns - 1; i++) { + this.drawTexture(matrices, x, y, 7, 0, 7, 7); + x += 7; + } + } + this.drawTexture(matrices, x, y, 32, 0, 7, 7); + + RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); + } + } + } + + @Inject(method = "isClickOutsideBounds", at = @At("TAIL"), cancellable = true) + private void scout$adjustOutsideBounds(double mouseX, double mouseY, int left, int top, int button, CallbackInfoReturnable callbackInfo) { + if (this.client != null && this.client.player != null && !ScoutUtil.isScreenBlacklisted(this)) { + ItemStack backStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + int rows = (int) Math.ceil(slots / 9); + + if (mouseY < (top + this.backgroundHeight) + 8 + (18 * rows) && mouseY >= (top + this.backgroundHeight) && mouseX >= left && mouseY < (left + this.backgroundWidth)) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= left - (columns * 18) && mouseX < left && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(this.client.player, BaseBagItem.BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= (left + this.backgroundWidth) && mouseX < (left + this.backgroundWidth) + (columns * 18) && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + } + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawForeground(Lnet/minecraft/client/util/math/MatrixStack;II)V")) + public void scout$drawOurSlots(MatrixStack matrices, int mouseX, int mouseY, float delta, CallbackInfo ci) { + if (this.client != null && this.client.player != null && !ScoutUtil.isScreenBlacklisted(this)) { + for (int i = ScoutUtil.SATCHEL_SLOT_START; i > ScoutUtil.BAG_SLOTS_END; i--) { + BagSlot slot = (BagSlot) ScoutUtil.getBagSlot(i, this.client.player.playerScreenHandler); + if (slot != null && slot.isEnabled()) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + this.drawSlot(matrices, slot); + } + + if (this.isPointOverSlot(slot, mouseX, mouseY) && slot != null && slot.isEnabled()) { + this.focusedSlot = slot; + int slotX = slot.getX(); + int slotY = slot.getY(); + drawSlotHighlight(matrices, slotX, slotY, this.getZOffset()); + } + } + } + } + + @Inject(method = "isPointOverSlot", at = @At("HEAD"), cancellable = true) + public void scout$fixSlotPos(Slot slot, double pointX, double pointY, CallbackInfoReturnable cir) { + if (slot instanceof BagSlot bagSlot) { + cir.setReturnValue(this.isPointWithinBounds(bagSlot.getX(), bagSlot.getY(), 16, 16, pointX, pointY)); + } + } + + @Inject(method = "getSlotAt", at = @At("RETURN"), cancellable = true) + public void scout$addSlots(double x, double y, CallbackInfoReturnable cir) { + if (this.client != null && this.client.player != null && !ScoutUtil.isScreenBlacklisted(this)) { + for (int i = ScoutUtil.SATCHEL_SLOT_START; i > ScoutUtil.BAG_SLOTS_END; i--) { + BagSlot slot = (BagSlot) ScoutUtil.getBagSlot(i, this.client.player.playerScreenHandler); + if (this.isPointOverSlot(slot, x, y) && slot != null && slot.isEnabled()) { + cir.setReturnValue(slot); + } + } + } + } + + @Shadow + private void drawSlot(MatrixStack matrices, Slot slot) {} + @Shadow + public static void drawSlotHighlight(MatrixStack matrices, int x, int y, int z) {} + @Shadow + private boolean isPointOverSlot(Slot slot, double pointX, double pointY) { + return false; + } + @Shadow + protected boolean isPointWithinBounds(int x, int y, int width, int height, double pointX, double pointY) { + return false; + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/HandledScreenTransformer.java b/src/main/java/pm/c7/scout/mixin/client/HandledScreenTransformer.java new file mode 100644 index 0000000..67b1a12 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/HandledScreenTransformer.java @@ -0,0 +1,84 @@ +package pm.c7.scout.mixin.client; + +import org.objectweb.asm.tree.*; +import org.quiltmc.loader.api.QuiltLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import pm.c7.scout.mixinsupport.ClassNodeTransformer; + +import static org.objectweb.asm.Opcodes.*; + +public class HandledScreenTransformer implements ClassNodeTransformer { + private static Logger LOGGER = LoggerFactory.getLogger("Scout:HandledScreenTransformer"); + @Override + public void transform(String name, ClassNode node) { + var drawSlot = "m_zioswvnu"; + var Slot = "net/minecraft/unmapped/C_nhvqfffd"; + var y = "f_tttqoodj"; + + if (QuiltLoader.isDevelopmentEnvironment()) { + drawSlot = "drawSlot"; + Slot = "net/minecraft/screen/slot/Slot"; + y = "y"; + } + + for (var mn : node.methods) { + if (mn.name.equals(drawSlot)) { + for (var insn : mn.instructions) { + if (insn instanceof FieldInsnNode fin) { + if (fin.getOpcode() == GETFIELD) { + if(fin.owner.equals(Slot) && fin.name.equals(y)) { + if (fin.getNext() instanceof VarInsnNode vin && vin.getOpcode() == ISTORE) { + LabelNode LnotBag = new LabelNode(); + int SAFE_REGISTER = 20; + mn.instructions.insert(vin, insns( + ALOAD(2), + INSTANCEOF("pm/c7/scout/screen/BagSlot"), + IFEQ(LnotBag), + ALOAD(2), + CHECKCAST("pm/c7/scout/screen/BagSlot"), + ASTORE(SAFE_REGISTER), + ALOAD(SAFE_REGISTER), + INVOKEVIRTUAL("pm/c7/scout/screen/BagSlot", "getX", "()I"), + ISTORE(vin.var - 1), + ALOAD(SAFE_REGISTER), + INVOKEVIRTUAL("pm/c7/scout/screen/BagSlot", "getY", "()I"), + ISTORE(vin.var), + LnotBag + )); + } + } + } + } + } + } + } + } + + private InsnList insns(AbstractInsnNode... insns) { + var li = new InsnList(); + for (var i : insns) li.add(i); + return li; + } + private static JumpInsnNode IFEQ(LabelNode var) { + return new JumpInsnNode(IFEQ, var); + } + private static VarInsnNode ALOAD(int var) { + return new VarInsnNode(ALOAD, var); + } + private static VarInsnNode ASTORE(int var) { + return new VarInsnNode(ASTORE, var); + } + private static TypeInsnNode INSTANCEOF(String desc) { + return new TypeInsnNode(INSTANCEOF, desc); + } + private static TypeInsnNode CHECKCAST(String desc) { + return new TypeInsnNode(CHECKCAST, desc); + } + private static MethodInsnNode INVOKEVIRTUAL(String owner, String name, String desc) { + return new MethodInsnNode(INVOKEVIRTUAL, owner, name, desc); + } + private static VarInsnNode ISTORE(int var) { + return new VarInsnNode(ISTORE, var); + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/InventoryScreenMixin.java b/src/main/java/pm/c7/scout/mixin/client/InventoryScreenMixin.java new file mode 100644 index 0000000..1520905 --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/InventoryScreenMixin.java @@ -0,0 +1,66 @@ +package pm.c7.scout.mixin.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.screen.ingame.AbstractInventoryScreen; +import net.minecraft.client.gui.screen.ingame.InventoryScreen; +import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.PlayerScreenHandler; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; + +@Environment(EnvType.CLIENT) +@Mixin(InventoryScreen.class) +public abstract class InventoryScreenMixin extends AbstractInventoryScreen implements RecipeBookProvider { + private InventoryScreenMixin() { + super(null, null, null); + } + + @Inject(method = "isClickOutsideBounds", at = @At("TAIL"), cancellable = true) + private void scout$adjustOutsideBounds(double mouseX, double mouseY, int left, int top, int button, CallbackInfoReturnable callbackInfo) { + if (this.client != null && this.client.player != null) { + ItemStack backStack = ScoutUtil.findBagItem(this.client.player, BagType.SATCHEL, false); + if (!backStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) backStack.getItem(); + int slots = bagItem.getSlotCount(); + int rows = (int) Math.ceil(slots / 9); + + if (mouseY < (top + this.backgroundHeight) + 8 + (18 * rows) && mouseY >= (top + this.backgroundHeight) && mouseX >= left && mouseY < (left + this.backgroundWidth)) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= left - (columns * 18) && mouseX < left && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + + ItemStack rightPouchStack = ScoutUtil.findBagItem(this.client.player, BagType.POUCH, true); + if (!rightPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) rightPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + int columns = (int) Math.ceil(slots / 3); + + if (mouseX >= (left + this.backgroundWidth) && mouseX < (left + this.backgroundWidth) + (columns * 18) && mouseY >= (top + this.backgroundHeight) - 90 && mouseY < (top + this.backgroundHeight) - 22) { + callbackInfo.setReturnValue(false); + } + } + } + } +} diff --git a/src/main/java/pm/c7/scout/mixin/client/RecipeBookWidgetMixin.java b/src/main/java/pm/c7/scout/mixin/client/RecipeBookWidgetMixin.java new file mode 100644 index 0000000..9e2543b --- /dev/null +++ b/src/main/java/pm/c7/scout/mixin/client/RecipeBookWidgetMixin.java @@ -0,0 +1,54 @@ +package pm.c7.scout.mixin.client; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; +import pm.c7.scout.item.BaseBagItem.BagType; + +// Lower priority to take priority over Better Recipe Book +@Environment(EnvType.CLIENT) +@Mixin(value = RecipeBookWidget.class, priority = 950) +public class RecipeBookWidgetMixin { + @Shadow + protected MinecraftClient client; + @Shadow + private int leftOffset; + + @Inject(method = "findLeftEdge", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true) + private void scout$modifyRecipeBookPosition(int width, int backgroundWidth, CallbackInfoReturnable callbackInfo, int x) { + if (this.client != null && this.client.player != null && this.isOpen()) { + ItemStack leftPouchStack = ScoutUtil.findBagItem(this.client.player, BagType.POUCH, false); + if (!leftPouchStack.isEmpty()) { + BaseBagItem bagItem = (BaseBagItem) leftPouchStack.getItem(); + int slots = bagItem.getSlotCount(); + + int columns = (int) Math.ceil(slots / 3); + + // Realign as best we can when "Keep crafting screens centered" is enabled in Better Recipe Book + if (this.leftOffset != 86) { + int diff = this.leftOffset - 86; + x -= diff; + } + + x += 18 * columns; + + callbackInfo.setReturnValue(x); + } + } + } + + @Shadow + public boolean isOpen() { + return false; + } +} diff --git a/src/main/java/pm/c7/scout/mixinsupport/ClassNodeTransformer.java b/src/main/java/pm/c7/scout/mixinsupport/ClassNodeTransformer.java new file mode 100644 index 0000000..94f351c --- /dev/null +++ b/src/main/java/pm/c7/scout/mixinsupport/ClassNodeTransformer.java @@ -0,0 +1,8 @@ +package pm.c7.scout.mixinsupport; + +import org.objectweb.asm.tree.ClassNode; + +public interface ClassNodeTransformer { + void transform(String name, ClassNode node); +} + diff --git a/src/main/java/pm/c7/scout/registry/ScoutItems.java b/src/main/java/pm/c7/scout/registry/ScoutItems.java new file mode 100644 index 0000000..497b7c0 --- /dev/null +++ b/src/main/java/pm/c7/scout/registry/ScoutItems.java @@ -0,0 +1,22 @@ +package pm.c7.scout.registry; + +import net.minecraft.item.Item; +import net.minecraft.util.Rarity; +import net.minecraft.util.registry.Registry; +import org.quiltmc.qsl.item.setting.api.QuiltItemSettings; +import pm.c7.scout.Scout; +import pm.c7.scout.ScoutUtil; +import pm.c7.scout.item.BaseBagItem; + +public class ScoutItems { + public static final Item TANNED_LEATHER = new Item(new QuiltItemSettings().group(Scout.ITEM_GROUP)); + public static final Item SATCHEL_STRAP = new Item(new QuiltItemSettings().group(Scout.ITEM_GROUP)); + public static final BaseBagItem SATCHEL = new BaseBagItem(new QuiltItemSettings().group(Scout.ITEM_GROUP).maxCount(1), ScoutUtil.MAX_SATCHEL_SLOTS / 2, BaseBagItem.BagType.SATCHEL); + public static final BaseBagItem UPGRADED_SATCHEL = new BaseBagItem(new QuiltItemSettings().group(Scout.ITEM_GROUP).maxCount(1).rarity(Rarity.RARE), ScoutUtil.MAX_SATCHEL_SLOTS, BaseBagItem.BagType.SATCHEL); + public static final BaseBagItem POUCH = new BaseBagItem(new QuiltItemSettings().group(Scout.ITEM_GROUP).maxCount(1), ScoutUtil.MAX_POUCH_SLOTS / 2, BaseBagItem.BagType.POUCH); + public static final BaseBagItem UPGRADED_POUCH = new BaseBagItem(new QuiltItemSettings().group(Scout.ITEM_GROUP).maxCount(1).rarity(Rarity.RARE), ScoutUtil.MAX_POUCH_SLOTS, BaseBagItem.BagType.POUCH); + + public static void init() { + Scout.AUTOREGISTRY.autoRegister(Registry.ITEM, ScoutItems.class, Item.class); + } +} diff --git a/src/main/java/pm/c7/scout/screen/BagSlot.java b/src/main/java/pm/c7/scout/screen/BagSlot.java new file mode 100644 index 0000000..35fbc4f --- /dev/null +++ b/src/main/java/pm/c7/scout/screen/BagSlot.java @@ -0,0 +1,107 @@ +package pm.c7.scout.screen; + +import net.minecraft.block.ShulkerBoxBlock; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; +import pm.c7.scout.config.ScoutConfigHandler; +import pm.c7.scout.item.BaseBagItem; + +public class BagSlot extends Slot { + private final int index; + public Inventory inventory; + private boolean enabled = false; + private int realX; + private int realY; + + public BagSlot(int index, int x, int y) { + super(null, index, x, y); + this.index = index; + this.realX = x; + this.realY = y; + } + + public void setInventory(Inventory inventory) { + this.inventory = inventory; + } + + public void setEnabled(boolean state) { + enabled = state; + } + + @Override + public boolean canInsert(ItemStack stack) { + if (stack.getItem() instanceof BaseBagItem) + return false; + + if (stack.getItem() instanceof BlockItem blockItem) { + if (blockItem.getBlock() instanceof ShulkerBoxBlock) + return (boolean) ScoutConfigHandler.getConfigValue("allowShulkers").value(); + } + + return enabled && inventory != null; + } + + @Override + public boolean canTakeItems(PlayerEntity playerEntity) { + return enabled && inventory != null; + } + + @Override + public boolean isEnabled() { + return enabled && inventory != null; + } + + @Override + public ItemStack getStack() { + return enabled && this.inventory != null ? this.inventory.getStack(this.index) : ItemStack.EMPTY; + } + + @Override + public void setStack(ItemStack stack) { + if (enabled && this.inventory != null) { + this.inventory.setStack(this.index, stack); + this.markDirty(); + } + } + + @Override + public void m_tfmituvd(ItemStack stack) { + if (enabled && this.inventory != null) { + this.inventory.setStack(this.index, stack); + this.markDirty(); + } + } + + @Override + public void markDirty() { + if (enabled && this.inventory != null) { + this.inventory.markDirty(); + } + } + + @Override + public ItemStack takeStack(int amount) { + return enabled && this.inventory != null ? this.inventory.removeStack(this.index, amount) : ItemStack.EMPTY; + } + + @Override + public int getMaxItemCount() { + return enabled && this.inventory != null ? this.inventory.getMaxCountPerStack() : 0; + } + + public int getX() { + return this.realX; + } + public int getY() { + return this.realY; + } + public void setX(int x) { + this.realX = x; + } + public void setY(int y) { + this.realY = y; + } +} diff --git a/src/main/resources/assets/emi/recipe/defaults/scout.json b/src/main/resources/assets/emi/recipe/defaults/scout.json new file mode 100644 index 0000000..bbfa28f --- /dev/null +++ b/src/main/resources/assets/emi/recipe/defaults/scout.json @@ -0,0 +1,10 @@ +{ + "recipes": [ + "scout:tanned_leather", + "scout:pouch", + "scout:satchel_strap", + "scout:upgraded_pouch", + "scout:upgraded_satchel", + "scout:satchel" + ] +} diff --git a/src/main/resources/assets/scout/icon.png b/src/main/resources/assets/scout/icon.png new file mode 100644 index 0000000..05d813e Binary files /dev/null and b/src/main/resources/assets/scout/icon.png differ diff --git a/src/main/resources/assets/scout/lang/en_us.json b/src/main/resources/assets/scout/lang/en_us.json new file mode 100644 index 0000000..c57fc21 --- /dev/null +++ b/src/main/resources/assets/scout/lang/en_us.json @@ -0,0 +1,14 @@ +{ + "lib39:enable_enhanced_lang": true, + "itemGroup.scout.itemgroup": "Scout", + "item.scout": { + "tanned_leather": "Tanned Leather", + "satchel_strap": "Satchel Strap", + "satchel": "Satchel", + "upgraded_satchel": "Upgraded Satchel", + "pouch": "Pouch", + "upgraded_pouch": "Upgraded Pouch" + }, + "trinkets.slot.legs.pouch": "Pouch", + "tooltip.scout.slots": "Holds {} items" +} diff --git a/src/main/resources/assets/scout/models/item/pouch.json b/src/main/resources/assets/scout/models/item/pouch.json new file mode 100644 index 0000000..8c391e8 --- /dev/null +++ b/src/main/resources/assets/scout/models/item/pouch.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/pouch" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/scout/models/item/satchel.json b/src/main/resources/assets/scout/models/item/satchel.json new file mode 100644 index 0000000..023d355 --- /dev/null +++ b/src/main/resources/assets/scout/models/item/satchel.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/satchel" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/scout/models/item/satchel_strap.json b/src/main/resources/assets/scout/models/item/satchel_strap.json new file mode 100644 index 0000000..2fa02eb --- /dev/null +++ b/src/main/resources/assets/scout/models/item/satchel_strap.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/satchel_strap" + } +} diff --git a/src/main/resources/assets/scout/models/item/tanned_leather.json b/src/main/resources/assets/scout/models/item/tanned_leather.json new file mode 100644 index 0000000..2342e52 --- /dev/null +++ b/src/main/resources/assets/scout/models/item/tanned_leather.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/tanned_leather" + } +} diff --git a/src/main/resources/assets/scout/models/item/upgraded_pouch.json b/src/main/resources/assets/scout/models/item/upgraded_pouch.json new file mode 100644 index 0000000..4ca8833 --- /dev/null +++ b/src/main/resources/assets/scout/models/item/upgraded_pouch.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/upgraded_pouch" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/scout/models/item/upgraded_satchel.json b/src/main/resources/assets/scout/models/item/upgraded_satchel.json new file mode 100644 index 0000000..6f83f6f --- /dev/null +++ b/src/main/resources/assets/scout/models/item/upgraded_satchel.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "scout:item/upgraded_satchel" + } +} \ No newline at end of file diff --git a/src/main/resources/assets/scout/textures/entity/satchel.png b/src/main/resources/assets/scout/textures/entity/satchel.png new file mode 100644 index 0000000..a6ffdbd Binary files /dev/null and b/src/main/resources/assets/scout/textures/entity/satchel.png differ diff --git a/src/main/resources/assets/scout/textures/entity/upgraded_satchel.png b/src/main/resources/assets/scout/textures/entity/upgraded_satchel.png new file mode 100644 index 0000000..ff85168 Binary files /dev/null and b/src/main/resources/assets/scout/textures/entity/upgraded_satchel.png differ diff --git a/src/main/resources/assets/scout/textures/gui/pouch_slot.png b/src/main/resources/assets/scout/textures/gui/pouch_slot.png new file mode 100644 index 0000000..29d5128 Binary files /dev/null and b/src/main/resources/assets/scout/textures/gui/pouch_slot.png differ diff --git a/src/main/resources/assets/scout/textures/gui/slots.png b/src/main/resources/assets/scout/textures/gui/slots.png new file mode 100644 index 0000000..211e5e9 Binary files /dev/null and b/src/main/resources/assets/scout/textures/gui/slots.png differ diff --git a/src/main/resources/assets/scout/textures/item/pouch.png b/src/main/resources/assets/scout/textures/item/pouch.png new file mode 100644 index 0000000..68266da Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/pouch.png differ diff --git a/src/main/resources/assets/scout/textures/item/satchel.png b/src/main/resources/assets/scout/textures/item/satchel.png new file mode 100644 index 0000000..a9ac5f7 Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/satchel.png differ diff --git a/src/main/resources/assets/scout/textures/item/satchel_strap.png b/src/main/resources/assets/scout/textures/item/satchel_strap.png new file mode 100644 index 0000000..191b0b1 Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/satchel_strap.png differ diff --git a/src/main/resources/assets/scout/textures/item/tanned_leather.png b/src/main/resources/assets/scout/textures/item/tanned_leather.png new file mode 100644 index 0000000..2ec25e9 Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/tanned_leather.png differ diff --git a/src/main/resources/assets/scout/textures/item/upgraded_pouch.png b/src/main/resources/assets/scout/textures/item/upgraded_pouch.png new file mode 100644 index 0000000..7551bc8 Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/upgraded_pouch.png differ diff --git a/src/main/resources/assets/scout/textures/item/upgraded_satchel.png b/src/main/resources/assets/scout/textures/item/upgraded_satchel.png new file mode 100644 index 0000000..9f9f70e Binary files /dev/null and b/src/main/resources/assets/scout/textures/item/upgraded_satchel.png differ diff --git a/src/main/resources/data/c/tags/items/leather.json b/src/main/resources/data/c/tags/items/leather.json new file mode 100644 index 0000000..8247c9a --- /dev/null +++ b/src/main/resources/data/c/tags/items/leather.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:leather" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/c/tags/items/string.json b/src/main/resources/data/c/tags/items/string.json new file mode 100644 index 0000000..90efbd8 --- /dev/null +++ b/src/main/resources/data/c/tags/items/string.json @@ -0,0 +1,6 @@ +{ + "replace": false, + "values": [ + "minecraft:string" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/scout/recipes/pouch.json b/src/main/resources/data/scout/recipes/pouch.json new file mode 100644 index 0000000..b40e078 --- /dev/null +++ b/src/main/resources/data/scout/recipes/pouch.json @@ -0,0 +1,19 @@ +{ + "type": "minecraft:crafting_shaped", + "lib39:discovered_via": "minecraft:leather", + "pattern": ["sls", "lbl", "sls"], + "key": { + "l": { + "tag": "c:leather" + }, + "s": { + "tag": "c:string" + }, + "b": { + "item": "minecraft:stone_button" + } + }, + "result": { + "item": "scout:pouch" + } +} diff --git a/src/main/resources/data/scout/recipes/satchel.json b/src/main/resources/data/scout/recipes/satchel.json new file mode 100644 index 0000000..62708e8 --- /dev/null +++ b/src/main/resources/data/scout/recipes/satchel.json @@ -0,0 +1,22 @@ +{ + "type": "minecraft:crafting_shaped", + "lib39:discovered_via": "scout:satchel_strap", + "pattern": ["SlS", "lbl", "sls"], + "key": { + "l": { + "item": "scout:tanned_leather" + }, + "s": { + "tag": "c:string" + }, + "b": { + "item": "minecraft:stone_button" + }, + "S": { + "item": "scout:satchel_strap" + } + }, + "result": { + "item": "scout:satchel" + } +} diff --git a/src/main/resources/data/scout/recipes/satchel_strap.json b/src/main/resources/data/scout/recipes/satchel_strap.json new file mode 100644 index 0000000..55f4684 --- /dev/null +++ b/src/main/resources/data/scout/recipes/satchel_strap.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shapeless", + "lib39:discovered_via": "scout:tanned_leather", + "ingredients": [ + { + "item": "scout:tanned_leather" + }, + { + "tag": "c:string" + }, + { + "item": "scout:tanned_leather" + } + ], + "result": { + "item": "scout:satchel_strap" + } +} diff --git a/src/main/resources/data/scout/recipes/tanned_leather.json b/src/main/resources/data/scout/recipes/tanned_leather.json new file mode 100644 index 0000000..c5d0bbf --- /dev/null +++ b/src/main/resources/data/scout/recipes/tanned_leather.json @@ -0,0 +1,10 @@ +{ + "type": "minecraft:campfire_cooking", + "lib39:discovered_via": "minecraft:leather", + "ingredient": { + "tag": "c:leather" + }, + "result": "scout:tanned_leather", + "experience": 0, + "cookingtime": 600 +} diff --git a/src/main/resources/data/scout/recipes/upgraded_pouch.json b/src/main/resources/data/scout/recipes/upgraded_pouch.json new file mode 100644 index 0000000..dad036e --- /dev/null +++ b/src/main/resources/data/scout/recipes/upgraded_pouch.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:smithing", + "lib39:discovered_via": "minecraft:diamond", + "base": { + "item": "scout:pouch" + }, + "addition": { + "item": "minecraft:diamond" + }, + "result": { + "item": "scout:upgraded_pouch" + } +} diff --git a/src/main/resources/data/scout/recipes/upgraded_satchel.json b/src/main/resources/data/scout/recipes/upgraded_satchel.json new file mode 100644 index 0000000..1fdd1f9 --- /dev/null +++ b/src/main/resources/data/scout/recipes/upgraded_satchel.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:smithing", + "lib39:discovered_via": "minecraft:diamond", + "base": { + "item": "scout:satchel" + }, + "addition": { + "item": "minecraft:diamond" + }, + "result": { + "item": "scout:upgraded_satchel" + } +} diff --git a/src/main/resources/data/trinkets/entities/scout.json b/src/main/resources/data/trinkets/entities/scout.json new file mode 100644 index 0000000..9cefa69 --- /dev/null +++ b/src/main/resources/data/trinkets/entities/scout.json @@ -0,0 +1,7 @@ +{ + "entities": ["player"], + "slots": [ + "chest/back", + "legs/pouch" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/trinkets/slots/legs/pouch.json b/src/main/resources/data/trinkets/slots/legs/pouch.json new file mode 100644 index 0000000..916aa05 --- /dev/null +++ b/src/main/resources/data/trinkets/slots/legs/pouch.json @@ -0,0 +1,5 @@ +{ + "replace": false, + "amount": 2, + "icon": "scout:gui/pouch_slot" +} \ No newline at end of file diff --git a/src/main/resources/data/trinkets/tags/items/chest/back.json b/src/main/resources/data/trinkets/tags/items/chest/back.json new file mode 100644 index 0000000..43aa8d1 --- /dev/null +++ b/src/main/resources/data/trinkets/tags/items/chest/back.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "scout:satchel", + "scout:upgraded_satchel" + ] +} \ No newline at end of file diff --git a/src/main/resources/data/trinkets/tags/items/legs/pouch.json b/src/main/resources/data/trinkets/tags/items/legs/pouch.json new file mode 100644 index 0000000..7e0791c --- /dev/null +++ b/src/main/resources/data/trinkets/tags/items/legs/pouch.json @@ -0,0 +1,7 @@ +{ + "replace": false, + "values": [ + "scout:pouch", + "scout:upgraded_pouch" + ] +} \ No newline at end of file diff --git a/src/main/resources/quilt.mod.json b/src/main/resources/quilt.mod.json new file mode 100644 index 0000000..4f8b66c --- /dev/null +++ b/src/main/resources/quilt.mod.json @@ -0,0 +1,91 @@ +{ + "schema_version": 1, + "quilt_loader": { + "group": "pm.c7.scout", + "id": "scout", + "version": "${version}", + "metadata": { + "name": "Scout", + "description": "A mod focused on \"physically\" extending the inventory (adding slots) through wearing various types of bags.", + "contributors": { + "Cynosphere": "Owner, Developer", + "Kat": "Original pouch texture", + "makamys": "Original idea" + }, + "contact": { + "sources": "https://gitdab.com/Cynosphere-mc/Scout" + }, + "icon": "assets/scout/icon.png" + }, + "intermediate_mappings": "net.fabricmc:intermediary", + "entrypoints": { + "init": "pm.c7.scout.Scout", + "client_init": "pm.c7.scout.client.ScoutClient", + "emi": "pm.c7.scout.client.compat.ScoutEmiPlugin" + }, + "depends": [ + { + "id": "quilt_loader", + "versions": ">=0.17.0-" + }, + { + "id": "quilted_fabric_api", + "versions": ">=4.0.0-" + }, + { + "id": "minecraft", + "versions": ">=${minecraft_version}" + }, + { + "id": "trinkets", + "versions": ">=${trinkets_version}" + }, + { + "id": "lib39-core", + "versions": ">=${lib39_version}" + }, + { + "id": "lib39-dessicant", + "versions": ">=${lib39_version}" + }, + { + "id": "emi", + "versions": ">=${emi_version}", + "optional": true + } + ], + "breaks": [ + { + "id": "roughlyenoughitems", + "versions": "*", + "reason": "API constantly rewritten for no reason, arrogant mod author, features stolen from EMI." + }, + { + "id": "inventoryprofilesnext", + "versions": "*", + "reason": "Crashes with no intent to fix on their end." + }, + { + "id": "infinitory", + "versions": "*", + "reason": "Infinite/Incompatible inventory mod." + }, + { + "id": "biginv", + "versions": "*", + "reason": "Infinite/Incompatible inventory mod." + }, + { + "id": "extrainv", + "versions": "*", + "reason": "Infinite/Incompatible inventory mod." + }, + { + "id": "inventory_backpack", + "versions": "*", + "reason": "Infinite/Incompatible inventory mod." + } + ] + }, + "mixin": "scout.mixins.json" +} diff --git a/src/main/resources/scout.mixins.json b/src/main/resources/scout.mixins.json new file mode 100644 index 0000000..ab9d864 --- /dev/null +++ b/src/main/resources/scout.mixins.json @@ -0,0 +1,10 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "pm.c7.scout.mixin", + "compatibilityLevel": "JAVA_17", + "plugin": "pm.c7.scout.ScoutMixin", + "injectors": { + "defaultRequire": 1 + } +}