commit aa1b254c5997d1d75d0d8cddef558575ba18cc7b Author: Shadowfacts Date: Wed Dec 19 19:39:10 2018 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a5fea3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# gradle + +.gradle/ +build/ +out/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# fabric + +run/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..83fe303 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SimpleMultipart + +SimpleMultipart is a WIP/experimental multipart API for use with Fabric and the Minecraft 1.14 snapshots. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5a81ee8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'fabric-loom' version '0.1.0-SNAPSHOT' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +archivesBaseName = "SimpleMultipart" +group = "net.shadowfacts.simplemultipart" +version = "0.1.0" + +minecraft { +} + +dependencies { + minecraft "com.mojang:minecraft:18w50a" + mappings "net.fabricmc:yarn:18w50a.9" + modCompile "net.fabricmc:fabric-loader:0.2.0.70" + + // Fabric API. This is technically optional, but you probably want it anyway. + modCompile "net.fabricmc:fabric:0.1.1.55" +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..deedc7f 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..b9e1d2c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 22 17:36:22 EDT 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9aa616c --- /dev/null +++ b/gradlew @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..683b201 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'http://maven.fabricmc.net/' + } + gradlePluginPortal() + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java b/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java new file mode 100644 index 0000000..c65e199 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java @@ -0,0 +1,67 @@ +package net.shadowfacts.simplemultipart; + +import net.fabricmc.api.ModInitializer; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.IdRegistry; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.loot.context.LootContextType; +import net.minecraft.world.loot.context.LootContextTypes; +import net.minecraft.world.loot.context.Parameter; +import net.minecraft.world.loot.context.Parameters; +import net.shadowfacts.simplemultipart.container.MultipartContainerEventHandler; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlock; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.lang.reflect.Method; +import java.util.function.Consumer; + +/** + * @author shadowfacts + */ +public class SimpleMultipart implements ModInitializer { + + public static final String MODID = "simplemultipart"; + + public static final Registry MULTIPART = createMultipartRegistry(); + + public static final Parameter MULTIPART_STATE_PARAMETER = new Parameter<>(new Identifier(MODID, "multipart_state")); + public static final LootContextType MULTIPART_LOOT_CONTEXT = createMultipartLootContextType(); + + public static final MultipartContainerBlock containerBlock = new MultipartContainerBlock(); + public static final BlockEntityType containerBlockEntity = createBlockEntityType(); + + @Override + public void onInitialize() { + Registry.register(Registry.BLOCK, new Identifier(MODID, "container"), containerBlock); + + MultipartContainerEventHandler.register(); + } + + private static Registry createMultipartRegistry() { + IdRegistry registry = new IdRegistry<>(); + Registry.REGISTRIES.register(new Identifier(MODID, "multipart"), registry); + return registry; + } + + private static BlockEntityType createBlockEntityType() { + BlockEntityType.Builder builder = BlockEntityType.Builder.create(MultipartContainerBlockEntity::new); + return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, "container"), builder.method_11034(null)); + } + + private static LootContextType createMultipartLootContextType() { + try { + Method register = LootContextTypes.class.getDeclaredMethod("register", String.class, Consumer.class); + register.setAccessible(true); + Consumer initializer = builder -> { + builder.require(MULTIPART_STATE_PARAMETER).require(Parameters.POSITION); + }; + return (LootContextType)register.invoke(null, MODID + ":multipart", initializer); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java new file mode 100644 index 0000000..3ace4ec --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java @@ -0,0 +1,22 @@ +package net.shadowfacts.simplemultipart.client; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public class MissingMultipartBakedModel implements MultipartBakedModel { + + @Override + public List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) { + return ImmutableList.of(); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java new file mode 100644 index 0000000..f8d1b48 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java @@ -0,0 +1,18 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public interface MultipartBakedModel { + + List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random); + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java new file mode 100644 index 0000000..2a5a9e7 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java @@ -0,0 +1,70 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelItemPropertyOverrideList; +import net.minecraft.client.render.model.json.ModelTransformations; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockState; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * @author shadowfacts + */ +public class MultipartContainerBakedModel implements BakedModel { + + @Override + public List getQuads(BlockState state, Direction side, Random random) { + if (!(state instanceof MultipartContainerBlockState)) { + return null; + } + MultipartContainerBlockState containerState = (MultipartContainerBlockState)state; + Map parts = containerState.getParts(); + MultipartModels models = SimpleMultipartClient.getMultipartModels(); + return parts.entrySet().stream() + .flatMap(entry -> { + MultipartBakedModel partModel = models.getModel(entry.getValue()); + return partModel.getQuads(entry.getValue(), entry.getKey(), side, random).stream(); + }) + .collect(Collectors.toList()); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean hasDepthInGui() { + return false; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getSprite() { + return MinecraftClient.getInstance().getSpriteAtlas().getSprite("blocks/stone"); + } + + @Override + public ModelTransformations getTransformations() { + return ModelTransformations.ORIGIN; + } + + @Override + public ModelItemPropertyOverrideList getItemPropertyOverrides() { + return ModelItemPropertyOverrideList.ORIGIN; + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java new file mode 100644 index 0000000..be9a8e8 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java @@ -0,0 +1,36 @@ +package net.shadowfacts.simplemultipart.client; + +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartModels { + + private final Map models = new IdentityHashMap<>(); +// private final BakedModelManager modelManager; + private final MultipartBakedModel missingModel = new MissingMultipartBakedModel(); + + public MultipartModels() { +// this.modelManager = modelManager; + } + + public MultipartBakedModel getMissingModel() { + return missingModel; + } + + public MultipartBakedModel getModel(MultipartState state) { + MultipartBakedModel model = models.get(state); + if (model == null) { + return getMissingModel(); + } + return model; + } + + public void register(MultipartState state, MultipartBakedModel model) { + models.put(state, model); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java b/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java new file mode 100644 index 0000000..b963270 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java @@ -0,0 +1,14 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; + +/** + * @author shadowfacts + */ +public interface RenderStateProvider { + + BlockState getStateForRendering(BlockState state, BlockPos pos, ExtendedBlockView world); + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java b/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java new file mode 100644 index 0000000..bd5af62 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java @@ -0,0 +1,26 @@ +package net.shadowfacts.simplemultipart.client; + +import net.fabricmc.api.ClientModInitializer; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class SimpleMultipartClient implements ClientModInitializer { + + private static MultipartModels multipartModels = new MultipartModels(); + + @Override + public void onInitializeClient() { +// multipartModels = new MultipartModels(MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelManager()); + } + + public static MultipartModels getMultipartModels() { + return multipartModels; + } + + public static void registerModel(MultipartState state, MultipartBakedModel model) { + getMultipartModels().register(state, model); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java new file mode 100644 index 0000000..21dffec --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java @@ -0,0 +1,71 @@ +package net.shadowfacts.simplemultipart.container; + +import net.fabricmc.fabric.block.FabricBlockSettings; +import net.minecraft.block.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.ExtendedBlockView; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.client.RenderStateProvider; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlock extends Block implements BlockEntityProvider, RenderStateProvider { + + public MultipartContainerBlock() { + super(FabricBlockSettings.of(Material.STONE).build()); + } + + @Override + public boolean activate(BlockState var1, World world, BlockPos pos, PlayerEntity player, Hand var5, Direction var6, float var7, float var8, float var9) { + if (player.isSneaking()) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + System.out.println(container.getParts()); + return true; + } else { + return false; + } + } + + @Override + public BlockState getStateForRendering(BlockState state, BlockPos pos, ExtendedBlockView world) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + Map parts = container.getParts(); + return new MultipartContainerBlockState(state, parts); + } + + @Override + public VoxelShape getBoundingShape(BlockState state, BlockView world, BlockPos pos) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + if (container == null) { + return VoxelShapes.empty(); + } + + VoxelShape shape = null; + for (Map.Entry e : container.getParts().entrySet()) { + VoxelShape partShape = e.getValue().getBoundingShape(e.getKey(), container); + shape = shape == null ? partShape : VoxelShapes.method_1084(shape, partShape); + } + return shape == null ? VoxelShapes.empty() : shape; + } + + @Override + public VoxelShape getRayTraceShape(BlockState var1, BlockView var2, BlockPos var3) { + return super.getRayTraceShape(var1, var2, var3); + } + + @Override + public MultipartContainerBlockEntity createBlockEntity(BlockView world) { + return new MultipartContainerBlockEntity(); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java new file mode 100644 index 0000000..e32accb --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java @@ -0,0 +1,134 @@ +package net.shadowfacts.simplemultipart.container; + +import com.google.common.collect.ImmutableMap; +import net.fabricmc.fabric.block.entity.ClientSerializable; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; +import net.shadowfacts.simplemultipart.util.MultipartHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlockEntity extends BlockEntity implements ClientSerializable { + + private Map parts = new HashMap<>(); + + public MultipartContainerBlockEntity() { + super(SimpleMultipart.containerBlockEntity); + } + + public ImmutableMap getParts() { + return ImmutableMap.copyOf(parts); + } + + public boolean hasPartInSlot(MultipartSlot slot) { + return parts.containsKey(slot); + } + + public boolean canInsert(MultipartState partState, MultipartSlot slot) { + if (hasPartInSlot(slot)) { + return false; + } + + // TODO: check bounding box intersections + + return true; + } + + public void insert(MultipartState partState, MultipartSlot slot) { + parts.put(slot, partState); + markDirty(); + world.scheduleBlockRender(pos); + } + + public MultipartState get(MultipartSlot slot) { + return parts.get(slot); + } + + public void remove(MultipartSlot slot) { + parts.remove(slot); + + if (parts.isEmpty()) { + world.setBlockState(pos, Blocks.AIR.getDefaultState()); + } + } + + public boolean breakPart(MultipartSlot slot) { + MultipartState state = get(slot); + if (state == null) { + return false; + } + + if (world instanceof ServerWorld) { + List drops = MultipartHelper.getDroppedStacks(state, (ServerWorld)world, pos); + drops.forEach(stack -> Block.dropStack(world, pos, stack)); + // TODO: don't drop if player is creative + } + + remove(slot); + + world.markDirty(pos, this); + world.scheduleBlockRender(pos); + BlockState blockState = world.getBlockState(pos); + world.updateListeners(pos, blockState, blockState, 3); + + return true; + } + + private CompoundTag partsToTag(CompoundTag tag) { + parts.forEach((slot, state) -> { + if (state != null) { + CompoundTag partStateTag = MultipartHelper.serializeMultipartState(state); + tag.put(slot.name(), partStateTag); + } + }); + return tag; + } + + private void partsFromTag(CompoundTag tag) { + parts.clear(); + for (MultipartSlot slot : MultipartSlot.values()) { + if (!(tag.containsKey(slot.name(), 10))) { + continue; + } + CompoundTag partStateTag = tag.getCompound(slot.name()); + MultipartState state = MultipartHelper.deserializeBlockState(partStateTag); + parts.put(slot, state); + } + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + partsToTag(tag); + return super.toTag(tag); + } + + @Override + public void fromTag(CompoundTag tag) { + super.fromTag(tag); + partsFromTag(tag); + } + + @Override + public CompoundTag toClientTag(CompoundTag tag) { + return partsToTag(tag); + } + + @Override + public void fromClientTag(CompoundTag tag) { + partsFromTag(tag); + world.scheduleBlockRender(pos); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java new file mode 100644 index 0000000..a8eca78 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java @@ -0,0 +1,24 @@ +package net.shadowfacts.simplemultipart.container; + +import net.minecraft.block.BlockState; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlockState extends BlockState { + + private Map parts; + + public MultipartContainerBlockState(BlockState delegate, Map parts) { + super(delegate.getBlock(), delegate.getEntries()); + this.parts = parts; + } + + public Map getParts() { + return parts; + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java new file mode 100644 index 0000000..a653a9e --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java @@ -0,0 +1,42 @@ +package net.shadowfacts.simplemultipart.container; + +import net.fabricmc.fabric.events.PlayerInteractionEvent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.util.MultipartHitResult; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.util.MultipartHelper; + +/** + * @author shadowfacts + */ +public class MultipartContainerEventHandler { + + public static void register() { + PlayerInteractionEvent.ATTACK_BLOCK.register(MultipartContainerEventHandler::handleBlockAttack); + } + + private static ActionResult handleBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) { + if (world.isRemote || world.getBlockState(pos).getBlock() != SimpleMultipart.containerBlock) { + return ActionResult.PASS; + } + + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + if (container == null) { + return ActionResult.FAILURE; + } + + MultipartHitResult hit = MultipartHelper.rayTrace(container, world, pos, player); + if (hit == null) { + return ActionResult.FAILURE; + } + + boolean success = container.breakPart(hit.partSlot); + return success ? ActionResult.SUCCESS : ActionResult.FAILURE; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java b/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java new file mode 100644 index 0000000..404656f --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java @@ -0,0 +1,79 @@ +package net.shadowfacts.simplemultipart.item; + +import net.minecraft.block.BlockState; +import net.minecraft.item.Item; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class ItemMultipart extends Item { + + protected Multipart part; + + public ItemMultipart(Multipart part) { + super(new Settings()); + this.part = part; + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + return place(new ItemPlacementContext(context)); + } + + protected ActionResult place(ItemPlacementContext context) { + MultipartContainerBlockEntity container = getOrCreateContainer(context.getWorld(), context.getPos()); + if (container == null) { + return ActionResult.FAILURE; + } + + MultipartSlot slot = getSlotForPlacement(container, context); + if (slot == null) { + return ActionResult.FAILURE; + } + + MultipartState partState = part.getStateForPlacement(slot, container); + if (!container.canInsert(partState, slot)) { + return ActionResult.FAILURE; + } + + container.insert(partState, slot); + + context.getItemStack().addAmount(-1); + + return ActionResult.SUCCESS; + } + + protected MultipartContainerBlockEntity getOrCreateContainer(World world, BlockPos pos) { + BlockState current = world.getBlockState(pos); + if (current.getBlock() == SimpleMultipart.containerBlock) { + return (MultipartContainerBlockEntity)world.getBlockEntity(pos); + } else if (current.isAir()) { + world.setBlockState(pos, SimpleMultipart.containerBlock.getDefaultState()); + return (MultipartContainerBlockEntity)world.getBlockEntity(pos); + } else { + return null; + } + } + + protected MultipartSlot getSlotForPlacement(MultipartContainerBlockEntity container, ItemPlacementContext context) { + MultipartSlot slot = MultipartSlot.fromClickedSide(context.getFacing()); + if (part.isValidSlot(slot) && !container.hasPartInSlot(slot)) { + return slot; + } + if (part.isValidSlot(MultipartSlot.CENTER) && !container.hasPartInSlot(MultipartSlot.CENTER)) { + return MultipartSlot.CENTER; + } + return null; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java new file mode 100644 index 0000000..0524608 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java @@ -0,0 +1,28 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import net.shadowfacts.simplemultipart.client.MultipartContainerBakedModel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.Map; + +/** + * @author shadowfacts + */ +@Mixin(BakedModelManager.class) +public class MixinBakedModelManager { + + @Redirect(method = "onResourceReload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader;getBakedModelMap()Ljava/util/Map;")) + public Map getBakedModelMap(ModelLoader loader) { + Map map = loader.getBakedModelMap(); + map.put(new ModelIdentifier("simplemultipart:container#"), new MultipartContainerBakedModel()); + return map; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java new file mode 100644 index 0000000..2fa2ff4 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java @@ -0,0 +1,49 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.RenderTypeBlock; +import net.minecraft.client.render.VertexBuffer; +import net.minecraft.client.render.block.BlockRenderManager; +import net.minecraft.client.render.block.BlockRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; +import net.shadowfacts.simplemultipart.client.RenderStateProvider; +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 java.util.Random; + +/** + * @author shadowfacts + */ +@Mixin(BlockRenderManager.class) +public abstract class MixinBlockRenderManager { + + @Shadow + private BlockRenderer renderer; + @Shadow + private Random field_4169; + + @Shadow + public abstract BakedModel getModel(BlockState var1); + + @Inject(at = @At("HEAD"), method = "method_3355", cancellable = true) + public void method_3355(BlockState state, BlockPos pos, ExtendedBlockView world, VertexBuffer buffer, Random random, CallbackInfoReturnable info) { + Block block = state.getBlock(); + if (state.getRenderType() == RenderTypeBlock.MODEL && block instanceof RenderStateProvider) { + RenderStateProvider provider = (RenderStateProvider)block; + BlockState renderState = provider.getStateForRendering(state, pos, world); + + BakedModel model = getModel(state); + boolean result = renderer.tesselate(world, model, renderState, pos, buffer, true, random, state.getPosRandom(pos)); + info.setReturnValue(result); + info.cancel(); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java new file mode 100644 index 0000000..1338427 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java @@ -0,0 +1,21 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.client.render.block.BiomeColors; +import net.minecraft.client.render.block.GrassColorHandler; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +/** + * @author shadowfacts + */ +@Mixin(BiomeColors.class) +public class MixinFuckYouBiomeColors { + + @Overwrite + public static int grassColorAt(ExtendedBlockView world, BlockPos pos) { + return GrassColorHandler.getColor(0.5D, 1.0D); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java new file mode 100644 index 0000000..68c70c8 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java @@ -0,0 +1,91 @@ +package net.shadowfacts.simplemultipart.multipart; + +import com.google.common.collect.ImmutableList; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.StateFactory; +import net.minecraft.util.Identifier; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.loot.LootSupplier; +import net.minecraft.world.loot.LootTables; +import net.minecraft.world.loot.context.LootContext; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; + +import java.util.List; + +/** + * @author shadowfacts + */ +public abstract class Multipart { + + private StateFactory stateFactory; + private MultipartState defaultState; + + private Identifier dropTableId; + + public Multipart() { + StateFactory.Builder builder = new StateFactory.Builder<>(this); + appendProperties(builder); + stateFactory = builder.build(MultipartState::new); + defaultState = stateFactory.getDefaultState(); + } + + protected void appendProperties(StateFactory.Builder builder) {} + + public MultipartState getDefaultState() { + return defaultState; + } + + public void setDefaultState(MultipartState defaultState) { + this.defaultState = defaultState; + } + + public StateFactory getStateFactory() { + return stateFactory; + } + + public boolean isValidSlot(MultipartSlot slot) { + return true; + } + + public MultipartState getStateForPlacement(MultipartSlot slot, MultipartContainerBlockEntity container) { + return getDefaultState(); + } + + /** + * Can be overridden, should only be called via {@link MultipartState#getStateForRendering} + */ + @Deprecated + public MultipartState getStateForRendering(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return state; + } + + /** + * Can be overridden, should only be called via {@link MultipartState#getBoundingShape} + */ + @Deprecated + public abstract VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container); + + public Identifier getDropTableId() { + if (dropTableId == null) { + Identifier id = SimpleMultipart.MULTIPART.getId(this); + dropTableId = new Identifier(id.getNamespace(), "multiparts/" + id.getPath()); + } + return dropTableId; + } + + @Deprecated + public List getDroppedStacks(MultipartState state, LootContext.Builder builder) { + Identifier dropTableId = getDropTableId(); + if (dropTableId == LootTables.EMPTY) { + return ImmutableList.of(); + } else { + LootContext context = builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, state).build(SimpleMultipart.MULTIPART_LOOT_CONTEXT); + ServerWorld world = context.getWorld(); + LootSupplier supplier = world.getServer().getLootManager().getSupplier(dropTableId); + return supplier.getDrops(context); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java new file mode 100644 index 0000000..8130e9b --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java @@ -0,0 +1,34 @@ +package net.shadowfacts.simplemultipart.multipart; + +import net.minecraft.util.math.Direction; + +/** + * @author shadowfacts + */ +public enum MultipartSlot { + TOP, + BOTTOM, + NORTH, + SOUTH, + EAST, + WEST, + CENTER; + + public static MultipartSlot fromClickedSide(Direction side) { + switch (side) { + case UP: + return BOTTOM; + case DOWN: + return TOP; + case NORTH: + return SOUTH; + case SOUTH: + return NORTH; + case EAST: + return WEST; + case WEST: + return EAST; + } + throw new RuntimeException("Unreachable: got direction outside of DUNSWE"); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java new file mode 100644 index 0000000..b0c9354 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java @@ -0,0 +1,41 @@ +package net.shadowfacts.simplemultipart.multipart; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.item.ItemStack; +import net.minecraft.state.AbstractPropertyContainer; +import net.minecraft.state.property.Property; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.loot.context.LootContext; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; + +import java.util.List; + +/** + * @author shadowfacts + */ +public class MultipartState extends AbstractPropertyContainer { + + public MultipartState(Multipart part, ImmutableMap, Comparable> properties) { + super(part, properties); + } + + public Multipart getMultipart() { + return owner; + } + + public MultipartState getStateForRendering(MultipartSlot slot, MultipartContainerBlockEntity container) { + //noinspection deprecation + return owner.getStateForRendering(this, slot, container); + } + + public VoxelShape getBoundingShape(MultipartSlot slot, MultipartContainerBlockEntity container) { + //noinspection deprecation + return owner.getBoundingShape(this, slot, container); + } + + public List getDroppedStacks(LootContext.Builder builder) { + //noinspection deprecated + return owner.getDroppedStacks(this, builder); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java new file mode 100644 index 0000000..46da156 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java @@ -0,0 +1,131 @@ +package net.shadowfacts.simplemultipart.util; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.PropertyContainer; +import net.minecraft.state.StateFactory; +import net.minecraft.state.property.Property; +import net.minecraft.util.HitResult; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.World; +import net.minecraft.world.loot.context.LootContext; +import net.minecraft.world.loot.context.Parameters; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.*; + +/** + * @author shadowfacts + */ +public class MultipartHelper { + + public static MultipartHitResult rayTrace(MultipartContainerBlockEntity container, World world, BlockPos pos, PlayerEntity player) { + // copied from BoatItem::use + float var6 = MathHelper.lerp(1.0F, player.prevPitch, player.pitch); + float var7 = MathHelper.lerp(1.0F, player.prevYaw, player.yaw); + double var8 = MathHelper.lerp(1.0D, player.prevX, player.x); + double var10 = MathHelper.lerp(1.0D, player.prevY, player.y) + (double)player.getEyeHeight(); + double var12 = MathHelper.lerp(1.0D, player.prevZ, player.z); + Vec3d start = new Vec3d(var8, var10, var12); + + float var15 = MathHelper.cos(-var7 * 0.017453292F - 3.1415927F); + float var16 = MathHelper.sin(-var7 * 0.017453292F - 3.1415927F); + float var17 = -MathHelper.cos(-var6 * 0.017453292F); + float var18 = MathHelper.sin(-var6 * 0.017453292F); + float var19 = var16 * var17; + float var21 = var15 * var17; + Vec3d end = start.add((double)var19 * 5.0D, (double)var18 * 5.0D, (double)var21 * 5.0D); + + return rayTrace(container, world, pos, start, end); + } + + public static MultipartHitResult rayTrace(MultipartContainerBlockEntity container, World world, BlockPos pos, Vec3d start, Vec3d end) { + return container.getParts().entrySet().stream() + .map(e -> { + VoxelShape shape = e.getValue().getBoundingShape(e.getKey(), container); + HitResult result = shape.rayTrace(start, end, pos); + return result == null ? null : new MultipartHitResult(result, e.getKey()); + }) + .filter(Objects::nonNull) + .min(Comparator.comparingDouble(hit -> hit.pos.subtract(start).lengthSquared())) + .orElse(null); + } + + public static List getDroppedStacks(MultipartState state, ServerWorld world, BlockPos pos) { + LootContext.Builder builder = new LootContext.Builder(world); + builder.setRandom(world.random); + builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, state); + builder.put(Parameters.POSITION, pos); + return state.getDroppedStacks(builder); + } + + public static CompoundTag serializeMultipartState(MultipartState state) { + CompoundTag tag = new CompoundTag(); + tag.putString("Name", SimpleMultipart.MULTIPART.getId(state.getMultipart()).toString()); + + ImmutableMap, Comparable> propertyMap = state.getEntries(); + if (!propertyMap.isEmpty()) { + CompoundTag propertyTag = new CompoundTag(); + + for (Map.Entry, Comparable> e : propertyMap.entrySet()) { + Property property = e.getKey(); + String str = getValueAsString(state, property); + propertyTag.putString(property.getName(), str); + } + + tag.put("Properties", propertyTag); + } + + return tag; + } + + private static , T extends Comparable> String getValueAsString(C state, Property property) { + return property.getValueAsString(state.get(property)); + } + + public static MultipartState deserializeBlockState(CompoundTag tag) { + if (!tag.containsKey("Name", 8)) { + return null; + } else { + Multipart part = SimpleMultipart.MULTIPART.get(new Identifier(tag.getString("Name"))); + MultipartState state = part.getDefaultState(); + + if (tag.containsKey("Properties", 10)) { + CompoundTag propertyTag = tag.getCompound("Properties"); + + StateFactory stateFactory = part.getStateFactory(); + + for (String propertyName : propertyTag.getKeys()) { + Property property = stateFactory.getProperty(propertyName); + + if (property != null) { + String valueStr = propertyTag.getString(propertyName); + state = withProperty(state, property, valueStr); + } + } + } + + return state; + } + } + + private static , T extends Comparable> C withProperty(C state, Property property, String valueString) { + Optional value = property.getValue(valueString); + if (!value.isPresent()) { + // TODO: logging + return state; + } + return state.with(property, value.get()); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java new file mode 100644 index 0000000..50ffe90 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java @@ -0,0 +1,32 @@ +package net.shadowfacts.simplemultipart.util; + +import net.minecraft.util.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; + +/** + * @author shadowfacts + */ +public class MultipartHitResult extends HitResult { + + public MultipartSlot partSlot; + + public MultipartHitResult(Vec3d pos, Direction side, BlockPos blockPos, MultipartSlot partSlot) { + super(pos, side, blockPos); + this.partSlot = partSlot; + } + + public MultipartHitResult(HitResult result, MultipartSlot partSlot) { + this(result.pos, result.side, result.getBlockPos(), partSlot); + if (result.type != Type.BLOCK) { + throw new IllegalArgumentException("Can't create a MultipartHitResult from a non BLOCK-type HitResult"); + } + } + + @Override + public String toString() { + return "HitResult{type=" + type + ", blockpos=" + getBlockPos() + ", f=" + side + ", pos=" + pos + ", partSlot=" + partSlot + '}'; + } +} diff --git a/src/main/resources/assets/simplemultipart/blockstates/container.json b/src/main/resources/assets/simplemultipart/blockstates/container.json new file mode 100644 index 0000000..243ebfb --- /dev/null +++ b/src/main/resources/assets/simplemultipart/blockstates/container.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "block/air" } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..565fda8 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "id": "simplemultipart", + "name": "Simple Multipart", + "description": "A simple multipart API for Fabric.", + "version": "0.1.0", + "side": "universal", + "initializers": [ + "net.shadowfacts.simplemultipart.SimpleMultipart", + "net.shadowfacts.simplemultipart.client.SimpleMultipartClient", + "net.shadowfacts.simplemultipart.test.MultipartTest" + ], + "requires": { + "fabric": "*" + }, + "mixins": { + "client": "simplemultipart.client.json", + "common": "simplemultipart.common.json" + } +} diff --git a/src/main/resources/simplemultipart.client.json b/src/main/resources/simplemultipart.client.json new file mode 100644 index 0000000..ff87340 --- /dev/null +++ b/src/main/resources/simplemultipart.client.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "net.shadowfacts.simplemultipart.mixin.client", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MixinFuckYouBiomeColors", + "MixinBakedModelManager", + "MixinBlockRenderManager" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/simplemultipart.common.json b/src/main/resources/simplemultipart.common.json new file mode 100644 index 0000000..89c2b25 --- /dev/null +++ b/src/main/resources/simplemultipart.common.json @@ -0,0 +1,10 @@ +{ + "required": true, + "package": "net.shadowfacts.simplemultipart.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java b/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java new file mode 100644 index 0000000..8e8a9fc --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java @@ -0,0 +1,25 @@ +package net.shadowfacts.simplemultipart.test; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class GreenMultipart extends Multipart { + + @Override + public boolean isValidSlot(MultipartSlot slot) { + return slot == MultipartSlot.NORTH; + } + + @Override + public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return VoxelShapes.cube(0, 0, 0, 1, 1, 1/16f); + } + +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java b/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java new file mode 100644 index 0000000..1df7959 --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java @@ -0,0 +1,35 @@ +package net.shadowfacts.simplemultipart.test; + +import net.fabricmc.api.ModInitializer; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.client.SimpleMultipartClient; +import net.shadowfacts.simplemultipart.item.ItemMultipart; + +/** + * @author shadowfacts + */ +public class MultipartTest implements ModInitializer { + + public static final String MODID = "multipart_test"; + + public static final RedMultipart red = new RedMultipart(); + public static final GreenMultipart green = new GreenMultipart(); + + public static final ItemMultipart redItem = new ItemMultipart(red); + public static final ItemMultipart greenItem = new ItemMultipart(green); + + @Override + public void onInitialize() { + Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "red"), red); + Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "green"), green); + + Registry.register(Registry.ITEM, new Identifier(MODID, "red"), redItem); + Registry.register(Registry.ITEM, new Identifier(MODID, "green"), greenItem); + + SimpleMultipartClient.registerModel(red.getDefaultState(), new TestMultipartModel(true)); + SimpleMultipartClient.registerModel(green.getDefaultState(), new TestMultipartModel(false)); + } + +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java b/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java new file mode 100644 index 0000000..0aea56c --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java @@ -0,0 +1,24 @@ +package net.shadowfacts.simplemultipart.test; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class RedMultipart extends Multipart { + + @Override + public boolean isValidSlot(MultipartSlot slot) { + return slot == MultipartSlot.BOTTOM; + } + + @Override + public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return VoxelShapes.cube(0, 0, 0, 1, 1/16f, 1); + } +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java b/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java new file mode 100644 index 0000000..e5ef09e --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java @@ -0,0 +1,64 @@ +package net.shadowfacts.simplemultipart.test; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.BakedQuadFactory; +import net.minecraft.client.render.model.ModelRotationContainer; +import net.minecraft.client.render.model.json.ModelElementFace; +import net.minecraft.client.render.model.json.ModelElementTexture; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.sortme.Vector3f; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.client.MultipartBakedModel; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public class TestMultipartModel implements MultipartBakedModel { + + private static final BakedQuadFactory QUAD_FACTORY = new BakedQuadFactory(); + + private boolean flag; + + public TestMultipartModel(boolean flag) { + this.flag = flag; + } + + @Override + public List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) { + if (side == null) { + return ImmutableList.of(createQuad()); + } + return ImmutableList.of(); + } + + private BakedQuad createQuad() { + ModelElementTexture elementTexture = new ModelElementTexture(new float[]{0, 0, 16, 16}, 0); + ModelElementFace face; + Vector3f from; + Vector3f to; + Direction side; + if (flag) { + from = new Vector3f(0, 0, 0); + to = new Vector3f(16, 1, 16); + side = Direction.UP; + face = new ModelElementFace(null, 0, "block/iron_block", elementTexture); + } else { + from = new Vector3f(0, 0, 0); + to = new Vector3f(16, 16, 1); + side = Direction.SOUTH; + face = new ModelElementFace(null, 0, "block/gold_block", elementTexture); + } + Sprite sprite = MinecraftClient.getInstance().getSpriteAtlas().getSprite(SpriteAtlasTexture.BLOCK_ATLAS_TEX); + ModelRotationContainer rotationContainer = new ModelRotationContainer() {}; + return QUAD_FACTORY.bake(from, to, face, sprite, side, rotationContainer, null, false); + } + +} diff --git a/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json b/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json new file mode 100644 index 0000000..6e224bb --- /dev/null +++ b/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json @@ -0,0 +1,14 @@ +{ + "type": "multipart:multipart", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "multipart_test:green" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json b/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json new file mode 100644 index 0000000..44a4d3c --- /dev/null +++ b/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json @@ -0,0 +1,14 @@ +{ + "type": "multipart:multipart", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "multipart_test:red" + } + ] + } + ] +} \ No newline at end of file