Initial commit

This commit is contained in:
Shadowfacts 2018-12-19 19:39:10 -05:00
commit aa1b254c59
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
39 changed files with 1684 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# gradle
.gradle/
build/
out/
# idea
.idea/
*.iml
*.ipr
*.iws
# fabric
run/

121
LICENSE Normal file
View File

@ -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.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# SimpleMultipart
SimpleMultipart is a WIP/experimental multipart API for use with Fabric and the Minecraft 1.14 snapshots.

22
build.gradle Normal file
View File

@ -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"
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -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

169
gradlew vendored Executable file
View File

@ -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 "$@"

84
gradlew.bat vendored Normal file
View File

@ -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

10
settings.gradle Normal file
View File

@ -0,0 +1,10 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'http://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}

View File

@ -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> MULTIPART = createMultipartRegistry();
public static final Parameter<MultipartState> 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<MultipartContainerBlockEntity> containerBlockEntity = createBlockEntityType();
@Override
public void onInitialize() {
Registry.register(Registry.BLOCK, new Identifier(MODID, "container"), containerBlock);
MultipartContainerEventHandler.register();
}
private static Registry<Multipart> createMultipartRegistry() {
IdRegistry<Multipart> registry = new IdRegistry<>();
Registry.REGISTRIES.register(new Identifier(MODID, "multipart"), registry);
return registry;
}
private static BlockEntityType<MultipartContainerBlockEntity> createBlockEntityType() {
BlockEntityType.Builder<MultipartContainerBlockEntity> 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<LootContextType.Builder> 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);
}
}
}

View File

@ -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<BakedQuad> getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) {
return ImmutableList.of();
}
}

View File

@ -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<BakedQuad> getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random);
}

View File

@ -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<BakedQuad> getQuads(BlockState state, Direction side, Random random) {
if (!(state instanceof MultipartContainerBlockState)) {
return null;
}
MultipartContainerBlockState containerState = (MultipartContainerBlockState)state;
Map<MultipartSlot, MultipartState> 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;
}
}

View File

@ -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<MultipartState, MultipartBakedModel> 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);
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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<MultipartSlot, MultipartState> 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<MultipartSlot, MultipartState> 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();
}
}

View File

@ -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<MultipartSlot, MultipartState> parts = new HashMap<>();
public MultipartContainerBlockEntity() {
super(SimpleMultipart.containerBlockEntity);
}
public ImmutableMap<MultipartSlot, MultipartState> 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<ItemStack> 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);
}
}

View File

@ -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<MultipartSlot, MultipartState> parts;
public MultipartContainerBlockState(BlockState delegate, Map<MultipartSlot, MultipartState> parts) {
super(delegate.getBlock(), delegate.getEntries());
this.parts = parts;
}
public Map<MultipartSlot, MultipartState> getParts() {
return parts;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Identifier, BakedModel> getBakedModelMap(ModelLoader loader) {
Map<Identifier, BakedModel> map = loader.getBakedModelMap();
map.put(new ModelIdentifier("simplemultipart:container#"), new MultipartContainerBakedModel());
return map;
}
}

View File

@ -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<Boolean> 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();
}
}
}

View File

@ -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);
}
}

View File

@ -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<Multipart, MultipartState> stateFactory;
private MultipartState defaultState;
private Identifier dropTableId;
public Multipart() {
StateFactory.Builder<Multipart, MultipartState> builder = new StateFactory.Builder<>(this);
appendProperties(builder);
stateFactory = builder.build(MultipartState::new);
defaultState = stateFactory.getDefaultState();
}
protected void appendProperties(StateFactory.Builder<Multipart, MultipartState> builder) {}
public MultipartState getDefaultState() {
return defaultState;
}
public void setDefaultState(MultipartState defaultState) {
this.defaultState = defaultState;
}
public StateFactory<Multipart, MultipartState> 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<ItemStack> 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);
}
}
}

View File

@ -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");
}
}

View File

@ -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<Multipart, MultipartState> {
public MultipartState(Multipart part, ImmutableMap<Property<?>, 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<ItemStack> getDroppedStacks(LootContext.Builder builder) {
//noinspection deprecated
return owner.getDroppedStacks(this, builder);
}
}

View File

@ -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<ItemStack> 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<Property<?>, Comparable<?>> propertyMap = state.getEntries();
if (!propertyMap.isEmpty()) {
CompoundTag propertyTag = new CompoundTag();
for (Map.Entry<Property<?>, 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 <C extends PropertyContainer<C>, T extends Comparable<T>> String getValueAsString(C state, Property<T> 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<Multipart, MultipartState> 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 <C extends PropertyContainer<C>, T extends Comparable<T>> C withProperty(C state, Property<T> property, String valueString) {
Optional<T> value = property.getValue(valueString);
if (!value.isPresent()) {
// TODO: logging
return state;
}
return state.with(property, value.get());
}
}

View File

@ -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 + '}';
}
}

View File

@ -0,0 +1,5 @@
{
"variants": {
"": { "model": "block/air" }
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,13 @@
{
"required": true,
"package": "net.shadowfacts.simplemultipart.mixin.client",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinFuckYouBiomeColors",
"MixinBakedModelManager",
"MixinBlockRenderManager"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -0,0 +1,10 @@
{
"required": true,
"package": "net.shadowfacts.simplemultipart.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -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);
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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<BakedQuad> 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);
}
}

View File

@ -0,0 +1,14 @@
{
"type": "multipart:multipart",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "multipart_test:green"
}
]
}
]
}

View File

@ -0,0 +1,14 @@
{
"type": "multipart:multipart",
"pools": [
{
"rolls": 1,
"entries": [
{
"type": "minecraft:item",
"name": "multipart_test:red"
}
]
}
]
}