From 4c09d8d774a5eaa89ab444af64d9f468a07d266f Mon Sep 17 00:00:00 2001 From: gigaherz Date: Sun, 22 May 2022 18:55:48 +0200 Subject: [PATCH] Make TransformType enum extensible, includes the possibility to specify a fallback type for cases where models don't specify the transform explicitly. (#8566) Makes it possible to add new perspectives. --- build.gradle | 1 + .../block/model/BlockModel.java.patch | 20 ++ .../block/model/ItemTransforms.java.patch | 141 ++++++++- src/generated_test/resources/.cache/cache | 5 +- .../blockstates/item_hanger.json | 19 ++ .../models/item/item_hanger.json | 6 + .../assets/minecraft/models/item/stick.json | 25 ++ .../models/item/separate_perspective.json | 2 +- .../client/model/ModelLoaderRegistry.java | 36 +-- .../model/SeparatePerspectiveModel.java | 29 +- .../model/generators/ItemModelProvider.java | 14 + .../client/model/generators/ModelBuilder.java | 29 +- .../SeparatePerspectiveModelBuilder.java | 3 +- src/main/resources/forge.exc | 3 +- .../debug/DataGeneratorTest.java | 17 +- .../client/model/CustomTransformTypeTest.java | 299 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + .../models/block/item_hanger.json | 3 + .../textures/item/item_hanger.png | Bin 0 -> 202 bytes 19 files changed, 593 insertions(+), 61 deletions(-) create mode 100644 src/generated_test/resources/assets/custom_transformtype_test/blockstates/item_hanger.json create mode 100644 src/generated_test/resources/assets/custom_transformtype_test/models/item/item_hanger.json create mode 100644 src/generated_test/resources/assets/minecraft/models/item/stick.json create mode 100644 src/test/java/net/minecraftforge/debug/client/model/CustomTransformTypeTest.java create mode 100644 src/test/resources/assets/custom_transformtype_test/models/block/item_hanger.json create mode 100644 src/test/resources/assets/custom_transformtype_test/textures/item/item_hanger.png diff --git a/build.gradle b/build.gradle index ff89ea172c..f82e69da7d 100644 --- a/build.gradle +++ b/build.gradle @@ -1043,6 +1043,7 @@ project(':forge') { '--mod', 'new_model_loader_test', '--mod', 'remove_tag_datagen_test', '--mod', 'tag_based_tool_types', + '--mod', 'custom_transformtype_test', '--output', rootProject.file('src/generated_test/resources/'), '--existing', sourceSets.main.resources.srcDirs[0], '--existing', sourceSets.test.resources.srcDirs[0] diff --git a/patches/minecraft/net/minecraft/client/renderer/block/model/BlockModel.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/model/BlockModel.java.patch index 82bd787264..6d2de87ca7 100644 --- a/patches/minecraft/net/minecraft/client/renderer/block/model/BlockModel.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/block/model/BlockModel.java.patch @@ -78,6 +78,26 @@ public boolean m_111477_(String p_111478_) { return !MissingTextureAtlasSprite.m_118071_().equals(this.m_111480_(p_111478_).m_119203_()); } +@@ -269,7 +_,18 @@ + ItemTransform itemtransform5 = this.m_111444_(ItemTransforms.TransformType.GUI); + ItemTransform itemtransform6 = this.m_111444_(ItemTransforms.TransformType.GROUND); + ItemTransform itemtransform7 = this.m_111444_(ItemTransforms.TransformType.FIXED); +- return new ItemTransforms(itemtransform, itemtransform1, itemtransform2, itemtransform3, itemtransform4, itemtransform5, itemtransform6, itemtransform7); ++ ++ var builder = com.google.common.collect.ImmutableMap.builder(); ++ for(ItemTransforms.TransformType type : ItemTransforms.TransformType.values()) { ++ if (type.isModded()) { ++ var transform = this.m_111444_(type); ++ if (transform != ItemTransform.f_111754_) { ++ builder.put(type, transform); ++ } ++ } ++ } ++ ++ return new ItemTransforms(itemtransform, itemtransform1, itemtransform2, itemtransform3, itemtransform4, itemtransform5, itemtransform6, itemtransform7, builder.build()); + } + + private ItemTransform m_111444_(ItemTransforms.TransformType p_111445_) { @@ -388,6 +_,8 @@ public boolean m_111526_() { return this == SIDE; diff --git a/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch index d525437e5c..294e1aefdf 100644 --- a/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/block/model/ItemTransforms.java.patch @@ -1,26 +1,143 @@ --- a/net/minecraft/client/renderer/block/model/ItemTransforms.java +++ b/net/minecraft/client/renderer/block/model/ItemTransforms.java -@@ -25,6 +_,7 @@ - this(ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_); - } +@@ -20,6 +_,7 @@ + public final ItemTransform f_111792_; + public final ItemTransform f_111793_; + public final ItemTransform f_111794_; ++ public final com.google.common.collect.ImmutableMap moddedTransforms; -+ @Deprecated - public ItemTransforms(ItemTransforms p_111807_) { - this.f_111787_ = p_111807_.f_111787_; - this.f_111788_ = p_111807_.f_111788_; -@@ -36,6 +_,7 @@ + private ItemTransforms() { + this(ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_, ItemTransform.f_111754_); +@@ -34,9 +_,16 @@ + this.f_111792_ = p_111807_.f_111792_; + this.f_111793_ = p_111807_.f_111793_; this.f_111794_ = p_111807_.f_111794_; ++ this.moddedTransforms = p_111807_.moddedTransforms; } + @Deprecated public ItemTransforms(ItemTransform p_111798_, ItemTransform p_111799_, ItemTransform p_111800_, ItemTransform p_111801_, ItemTransform p_111802_, ItemTransform p_111803_, ItemTransform p_111804_, ItemTransform p_111805_) { ++ this(p_111798_, p_111799_, p_111800_, p_111801_, p_111802_, p_111803_, p_111804_, p_111805_, com.google.common.collect.ImmutableMap.of()); ++ } ++ ++ public ItemTransforms(ItemTransform p_111798_, ItemTransform p_111799_, ItemTransform p_111800_, ItemTransform p_111801_, ItemTransform p_111802_, ItemTransform p_111803_, ItemTransform p_111804_, ItemTransform p_111805_, ++ com.google.common.collect.ImmutableMap moddedTransforms) { this.f_111787_ = p_111798_; this.f_111788_ = p_111799_; -@@ -47,6 +_,7 @@ + this.f_111789_ = p_111800_; +@@ -45,6 +_,7 @@ + this.f_111792_ = p_111803_; + this.f_111793_ = p_111804_; this.f_111794_ = p_111805_; ++ this.moddedTransforms = moddedTransforms; } -+ @Deprecated public ItemTransform m_111808_(ItemTransforms.TransformType p_111809_) { - switch(p_111809_) { - case THIRD_PERSON_LEFT_HAND: +@@ -66,7 +_,7 @@ + case FIXED: + return this.f_111794_; + default: +- return ItemTransform.f_111754_; ++ return moddedTransforms.getOrDefault(p_111809_, ItemTransform.f_111754_); + } + } + +@@ -94,7 +_,23 @@ + ItemTransform itemtransform5 = this.m_111815_(p_111822_, jsonobject, "gui"); + ItemTransform itemtransform6 = this.m_111815_(p_111822_, jsonobject, "ground"); + ItemTransform itemtransform7 = this.m_111815_(p_111822_, jsonobject, "fixed"); +- return new ItemTransforms(itemtransform1, itemtransform, itemtransform3, itemtransform2, itemtransform4, itemtransform5, itemtransform6, itemtransform7); ++ ++ var builder = com.google.common.collect.ImmutableMap.builder(); ++ for (TransformType type : TransformType.values()) { ++ if (type.isModded()) { ++ var transform = this.m_111815_(p_111822_, jsonobject, type.getSerializeName()); ++ var fallbackType = type; ++ while (transform == ItemTransform.f_111754_ && fallbackType.fallback != null) { ++ fallbackType = fallbackType.fallback; ++ transform = this.m_111815_(p_111822_, jsonobject, fallbackType.getSerializeName()); ++ } ++ if (transform != ItemTransform.f_111754_){ ++ builder.put(type, transform); ++ } ++ } ++ } ++ ++ return new ItemTransforms(itemtransform1, itemtransform, itemtransform3, itemtransform2, itemtransform4, itemtransform5, itemtransform6, itemtransform7, builder.build()); + } + + private ItemTransform m_111815_(JsonDeserializationContext p_111816_, JsonObject p_111817_, String p_111818_) { +@@ -103,19 +_,63 @@ + } + + @OnlyIn(Dist.CLIENT) +- public static enum TransformType { +- NONE, +- THIRD_PERSON_LEFT_HAND, +- THIRD_PERSON_RIGHT_HAND, +- FIRST_PERSON_LEFT_HAND, +- FIRST_PERSON_RIGHT_HAND, +- HEAD, +- GUI, +- GROUND, +- FIXED; ++ public static enum TransformType implements net.minecraftforge.common.IExtensibleEnum { ++ NONE("none"), ++ THIRD_PERSON_LEFT_HAND("thirdperson_lefthand"), ++ THIRD_PERSON_RIGHT_HAND("thirdperson_righthand"), ++ FIRST_PERSON_LEFT_HAND("firstperson_lefthand"), ++ FIRST_PERSON_RIGHT_HAND("firstperson_righthand"), ++ HEAD("head"), ++ GUI("gui"), ++ GROUND("ground"), ++ FIXED("fixed"); + + public boolean m_111841_() { + return this == FIRST_PERSON_LEFT_HAND || this == FIRST_PERSON_RIGHT_HAND; ++ } ++ ++ // Forge start ++ private final String serializeName; ++ private final boolean isModded; ++ @javax.annotation.Nullable ++ private final TransformType fallback; ++ ++ private TransformType(String name) { ++ serializeName = name; ++ isModded = false; ++ fallback = null; ++ } ++ ++ private TransformType(net.minecraft.resources.ResourceLocation serializeName) { ++ this.serializeName = java.util.Objects.requireNonNull(serializeName, "Modded TransformTypes must have a non-null serializeName").toString(); ++ isModded = true; ++ fallback = null; ++ } ++ ++ private TransformType(net.minecraft.resources.ResourceLocation serializeName, TransformType fallback) { ++ this.serializeName = java.util.Objects.requireNonNull(serializeName, "Modded TransformTypes must have a non-null serializeName").toString(); ++ isModded = true; ++ this.fallback = fallback; ++ } ++ ++ public boolean isModded() { ++ return isModded; ++ } ++ ++ @javax.annotation.Nullable ++ public TransformType fallback() { ++ return fallback; ++ } ++ ++ public String getSerializeName() { ++ return serializeName; ++ } ++ ++ public static TransformType create(String keyName, net.minecraft.resources.ResourceLocation serializeName) { ++ throw new IllegalStateException("Enum not extended!"); ++ } ++ public static TransformType create(String keyName, net.minecraft.resources.ResourceLocation serializeName, TransformType fallback) { ++ throw new IllegalStateException("Enum not extended!"); + } + } + } diff --git a/src/generated_test/resources/.cache/cache b/src/generated_test/resources/.cache/cache index d69d0affe8..18d1277855 100644 --- a/src/generated_test/resources/.cache/cache +++ b/src/generated_test/resources/.cache/cache @@ -1,3 +1,5 @@ +100d38c291c4b4dabbdec17d5eb512380507e9cb assets/custom_transformtype_test/blockstates/item_hanger.json +9349340d54ae91448ab1ae0bd42fcbff512470cc assets/custom_transformtype_test/models/item/item_hanger.json a9cf5409aca6b29f95eff8309254d1bd8ec98a21 assets/data_gen_test/lang/en_us.json 372b5ca364635d0d17d08c1402aed45578a52120 assets/data_gen_test/models/block/acacia_button.json d8ab540b855fa1df2249f9d09c0fa15a1b46a324 assets/data_gen_test/models/block/acacia_button_pressed.json @@ -76,10 +78,11 @@ bf2e445b48b024354a69138b20a4a4a8aa5d15d1 assets/minecraft/blockstates/oak_trapdo 9c4cc92efb78811e8d70a383a1a89eb75b69db04 assets/minecraft/blockstates/stone.json 570fdd86046df75a073b759ff7aaf4c4caf43115 assets/minecraft/blockstates/torch.json a012d6d92bab1c91913bd0f2dc0492a0fce916f2 assets/minecraft/blockstates/wall_torch.json +734ce54b91487abaf64d62bef91223d9c416dcff assets/minecraft/models/item/stick.json 0a1909376adb28354443930b18fb832ed577f5a0 assets/new_model_loader_test/blockstates/obj_block.json bab7cd04569c8aa63320772acdc3c91a5ceda14a assets/new_model_loader_test/models/block/obj_block.json 9fa44308a52fbc384d5befee4e117531642299fb assets/new_model_loader_test/models/item/item_layers.json -70c8c81f3a157e4d698f213d5c1a13c13eaa5157 assets/new_model_loader_test/models/item/separate_perspective.json +fff744b6ee36d8cffb5797389b00d096263583d9 assets/new_model_loader_test/models/item/separate_perspective.json 273e0ed992d227f09f1c83bc22d066fb68d03c84 assets/piston_event_test/blockstates/shiftonmove.json d2c8e860521c5e738ed0798337f5728d610825ff assets/piston_event_test/models/block/shiftonmove.json f51f026a75e27a0a53a76771d553c0e3385a2966 assets/piston_event_test/models/item/shiftonmove.json diff --git a/src/generated_test/resources/assets/custom_transformtype_test/blockstates/item_hanger.json b/src/generated_test/resources/assets/custom_transformtype_test/blockstates/item_hanger.json new file mode 100644 index 0000000000..2ee8e634d0 --- /dev/null +++ b/src/generated_test/resources/assets/custom_transformtype_test/blockstates/item_hanger.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "custom_transformtype_test:block/item_hanger" + }, + "facing=south": { + "model": "custom_transformtype_test:block/item_hanger", + "y": 180 + }, + "facing=west": { + "model": "custom_transformtype_test:block/item_hanger", + "y": 270 + }, + "facing=east": { + "model": "custom_transformtype_test:block/item_hanger", + "y": 90 + } + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/custom_transformtype_test/models/item/item_hanger.json b/src/generated_test/resources/assets/custom_transformtype_test/models/item/item_hanger.json new file mode 100644 index 0000000000..22017736bd --- /dev/null +++ b/src/generated_test/resources/assets/custom_transformtype_test/models/item/item_hanger.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "custom_transformtype_test:item/item_hanger" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/minecraft/models/item/stick.json b/src/generated_test/resources/assets/minecraft/models/item/stick.json new file mode 100644 index 0000000000..ce67a9f55f --- /dev/null +++ b/src/generated_test/resources/assets/minecraft/models/item/stick.json @@ -0,0 +1,25 @@ +{ + "parent": "minecraft:item/generated", + "display": { + "custom_transformtype_test:hanging": { + "rotation": [ + 62, + 147, + 40 + ], + "translation": [ + -2.25, + 1.5, + -0.25 + ], + "scale": [ + 0.48, + 0.48, + 0.48 + ] + } + }, + "textures": { + "layer0": "minecraft:item/stick" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/assets/new_model_loader_test/models/item/separate_perspective.json b/src/generated_test/resources/assets/new_model_loader_test/models/item/separate_perspective.json index fde0ecec20..75aa29b27c 100644 --- a/src/generated_test/resources/assets/new_model_loader_test/models/item/separate_perspective.json +++ b/src/generated_test/resources/assets/new_model_loader_test/models/item/separate_perspective.json @@ -8,7 +8,7 @@ "gui": { "parent": "minecraft:item/snowball" }, - "first_person_left_hand": { + "firstperson_lefthand": { "parent": "minecraft:item/bone" } } diff --git a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java index 14f8f36063..80f4408fad 100644 --- a/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java +++ b/src/main/java/net/minecraftforge/client/model/ModelLoaderRegistry.java @@ -214,18 +214,18 @@ public class ModelLoaderRegistry JsonObject transform = transformData.getAsJsonObject(); EnumMap transforms = Maps.newEnumMap(ItemTransforms.TransformType.class); - deserializeTRSR(context, transforms, transform, "thirdperson", ItemTransforms.TransformType.THIRD_PERSON_RIGHT_HAND); - deserializeTRSR(context, transforms, transform, "thirdperson_righthand", ItemTransforms.TransformType.THIRD_PERSON_RIGHT_HAND); - deserializeTRSR(context, transforms, transform, "thirdperson_lefthand", ItemTransforms.TransformType.THIRD_PERSON_LEFT_HAND); - - deserializeTRSR(context, transforms, transform, "firstperson", ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND); - deserializeTRSR(context, transforms, transform, "firstperson_righthand", ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND); - deserializeTRSR(context, transforms, transform, "firstperson_lefthand", ItemTransforms.TransformType.FIRST_PERSON_LEFT_HAND); - - deserializeTRSR(context, transforms, transform, "head", ItemTransforms.TransformType.HEAD); - deserializeTRSR(context, transforms, transform, "gui", ItemTransforms.TransformType.GUI); - deserializeTRSR(context, transforms, transform, "ground", ItemTransforms.TransformType.GROUND); - deserializeTRSR(context, transforms, transform, "fixed", ItemTransforms.TransformType.FIXED); + for (var type : ItemTransforms.TransformType.values()) + { + var fallbackType = type; + while (fallbackType.fallback() != null && !transform.has(fallbackType.getSerializeName())) { + fallbackType = fallbackType.fallback(); + } + if(transform.has(fallbackType.getSerializeName())) + { + Transformation t = context.deserialize(transform.remove(fallbackType.getSerializeName()), Transformation.class); + transforms.put(type, t.blockCenterToCorner()); + } + } int k = transform.entrySet().size(); if(transform.has("matrix")) k--; @@ -236,7 +236,8 @@ public class ModelLoaderRegistry if(transform.has("origin")) k--; if(k > 0) { - throw new JsonParseException("transform: allowed keys: 'thirdperson', 'firstperson', 'gui', 'head', 'matrix', 'translation', 'rotation', 'scale', 'post-rotation', 'origin'"); + throw new JsonParseException("transform: allowed keys: 'matrix', 'translation', 'rotation', 'scale', 'post-rotation', 'origin', " + + Arrays.stream(ItemTransforms.TransformType.values()).map(v -> "'" + v.getSerializeName() + "'").collect(Collectors.joining(", "))); } Transformation base = Transformation.identity(); if(!transform.entrySet().isEmpty()) @@ -248,15 +249,6 @@ public class ModelLoaderRegistry } } - private static void deserializeTRSR(JsonDeserializationContext context, EnumMap transforms, JsonObject transform, String name, ItemTransforms.TransformType itemCameraTransform) - { - if(transform.has(name)) - { - Transformation t = context.deserialize(transform.remove(name), Transformation.class); - transforms.put(itemCameraTransform, t.blockCenterToCorner()); - } - } - public static BakedModel bakeHelper(BlockModel blockModel, ModelBakery modelBakery, BlockModel otherModel, Function spriteGetter, ModelState modelTransform, ResourceLocation modelLocation, boolean guiLight3d) { BakedModel model; diff --git a/src/main/java/net/minecraftforge/client/model/SeparatePerspectiveModel.java b/src/main/java/net/minecraftforge/client/model/SeparatePerspectiveModel.java index bfdda1936d..468ac419cd 100644 --- a/src/main/java/net/minecraftforge/client/model/SeparatePerspectiveModel.java +++ b/src/main/java/net/minecraftforge/client/model/SeparatePerspectiveModel.java @@ -163,6 +163,7 @@ public class SeparatePerspectiveModel implements IModelGeometry PERSPECTIVES = ImmutableBiMap.builder() .put("none", ItemTransforms.TransformType.NONE) .put("third_person_left_hand", ItemTransforms.TransformType.THIRD_PERSON_LEFT_HAND) @@ -175,6 +176,14 @@ public class SeparatePerspectiveModel implements IModelGeometry BACKWARD_COMPATIBILITY = ImmutableBiMap.builder() + .put("third_person_left_hand", ItemTransforms.TransformType.THIRD_PERSON_LEFT_HAND) + .put("third_person_right_hand", ItemTransforms.TransformType.THIRD_PERSON_RIGHT_HAND) + .put("first_person_left_hand", ItemTransforms.TransformType.FIRST_PERSON_LEFT_HAND) + .put("first_person_right_hand", ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND) + .build(); + @Override public void onResourceManagerReload(final ResourceManager p_10758_) { @@ -187,17 +196,25 @@ public class SeparatePerspectiveModel implements IModelGeometry perspectives = ImmutableMap.builder(); - for(Map.Entry perspective : PERSPECTIVES.entrySet()) + Map perspectives = new HashMap<>(); + for(Map.Entry entry : BACKWARD_COMPATIBILITY.entrySet()) { - if (perspectiveData.has(perspective.getKey())) + if (perspectiveData.has(entry.getKey())) { - BlockModel perspectiveModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(perspectiveData, perspective.getKey()), BlockModel.class); - perspectives.put(perspective.getValue(), perspectiveModel); + BlockModel perspectiveModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(perspectiveData, entry.getKey()), BlockModel.class); + perspectives.put(entry.getValue(), perspectiveModel); + } + } + for(ItemTransforms.TransformType perspective : ItemTransforms.TransformType.values()) + { + if (perspectiveData.has(perspective.getSerializeName())) + { + BlockModel perspectiveModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(perspectiveData, perspective.getSerializeName()), BlockModel.class); + perspectives.put(perspective, perspectiveModel); } } - return new SeparatePerspectiveModel(baseModel, perspectives.build()); + return new SeparatePerspectiveModel(baseModel, ImmutableMap.copyOf(perspectives)); } } } diff --git a/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java b/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java index fcb3569459..3fb16fc275 100644 --- a/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java +++ b/src/main/java/net/minecraftforge/client/model/generators/ItemModelProvider.java @@ -8,6 +8,8 @@ package net.minecraftforge.client.model.generators; import javax.annotation.Nonnull; import net.minecraft.data.DataGenerator; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; import net.minecraftforge.common.data.ExistingFileHelper; /** @@ -20,6 +22,18 @@ public abstract class ItemModelProvider extends ModelProvider super(generator, modid, ITEM_FOLDER, ItemModelBuilder::new, existingFileHelper); } + public ItemModelBuilder basicItem(Item item) + { + return basicItem(item.getRegistryName()); + } + + public ItemModelBuilder basicItem(ResourceLocation item) + { + return getBuilder(item.toString()) + .parent(new ModelFile.UncheckedModelFile("item/generated")) + .texture("layer0", new ResourceLocation(item.getNamespace(), "item/" + item.getPath())); + } + @Nonnull @Override public String getName() { diff --git a/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java index 4081c71e8d..cee8769401 100644 --- a/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java +++ b/src/main/java/net/minecraftforge/client/model/generators/ModelBuilder.java @@ -214,10 +214,10 @@ public class ModelBuilder> extends ModelFile { root.addProperty("gui_light", this.guiLight.getSerializedName()); } - Map transforms = this.transforms.build(); + Map transforms = this.transforms.build(); if (!transforms.isEmpty()) { JsonObject display = new JsonObject(); - for (Entry e : transforms.entrySet()) { + for (Entry e : transforms.entrySet()) { JsonObject transform = new JsonObject(); ItemTransform vec = e.getValue(); if (vec.equals(ItemTransform.NO_TRANSFORM)) continue; @@ -230,7 +230,7 @@ public class ModelBuilder> extends ModelFile { if (!vec.scale.equals(ItemTransform.Deserializer.DEFAULT_SCALE)) { transform.add("scale", serializeVector3f(e.getValue().scale)); } - display.add(e.getKey().name, transform); + display.add(e.getKey().getSerializeName(), transform); } root.add("display", display); } @@ -599,6 +599,7 @@ public class ModelBuilder> extends ModelFile { } // Since vanilla doesn't keep the name in TransformType... + @Deprecated(forRemoval = true, since = "1.18.2") public enum Perspective { THIRDPERSON_RIGHT(TransformType.THIRD_PERSON_RIGHT_HAND, "thirdperson_righthand"), @@ -622,7 +623,21 @@ public class ModelBuilder> extends ModelFile { public class TransformsBuilder { - private final Map transforms = new LinkedHashMap<>(); + private final Map transforms = new LinkedHashMap<>(); + + /** + * Begin building a new transform for the given perspective. + * + * @param type the perspective to create or return the builder for + * @return the builder for the given perspective + * @throws NullPointerException if {@code type} is {@code null} + * + * @deprecated See {@link #transform(TransformType)} + */ + @Deprecated(forRemoval = true, since = "1.18.2") + public TransformVecBuilder transform(Perspective type) { + return transform(type.vanillaType); + } /** * Begin building a new transform for the given perspective. @@ -631,12 +646,12 @@ public class ModelBuilder> extends ModelFile { * @return the builder for the given perspective * @throws NullPointerException if {@code type} is {@code null} */ - public TransformVecBuilder transform(Perspective type) { + public TransformVecBuilder transform(TransformType type) { Preconditions.checkNotNull(type, "Perspective cannot be null"); return transforms.computeIfAbsent(type, TransformVecBuilder::new); } - Map build() { + Map build() { return this.transforms.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().build(), (k1, k2) -> { throw new IllegalArgumentException(); }, LinkedHashMap::new)); } @@ -649,7 +664,7 @@ public class ModelBuilder> extends ModelFile { private Vector3f translation = ItemTransform.Deserializer.DEFAULT_TRANSLATION.copy(); private Vector3f scale = ItemTransform.Deserializer.DEFAULT_SCALE.copy(); - TransformVecBuilder(Perspective type) { + TransformVecBuilder(TransformType type) { // param unused for functional match } diff --git a/src/main/java/net/minecraftforge/client/model/generators/loaders/SeparatePerspectiveModelBuilder.java b/src/main/java/net/minecraftforge/client/model/generators/loaders/SeparatePerspectiveModelBuilder.java index 967fb609d9..c953758247 100644 --- a/src/main/java/net/minecraftforge/client/model/generators/loaders/SeparatePerspectiveModelBuilder.java +++ b/src/main/java/net/minecraftforge/client/model/generators/loaders/SeparatePerspectiveModelBuilder.java @@ -42,9 +42,8 @@ public class SeparatePerspectiveModelBuilder> extends public SeparatePerspectiveModelBuilder perspective(ItemTransforms.TransformType perspective, T modelBuilder) { Preconditions.checkNotNull(perspective, "layer must not be null"); - Preconditions.checkArgument(SeparatePerspectiveModel.Loader.PERSPECTIVES.containsValue(perspective), "perspective is not included in SeparatePerspectiveModel. New mc version?"); Preconditions.checkNotNull(modelBuilder, "modelBuilder must not be null"); - childModels.put(SeparatePerspectiveModel.Loader.PERSPECTIVES.inverse().get(perspective), modelBuilder); + childModels.put(perspective.getSerializeName(), modelBuilder); return this; } diff --git a/src/main/resources/forge.exc b/src/main/resources/forge.exc index 1779550217..b2a0cfb08c 100644 --- a/src/main/resources/forge.exc +++ b/src/main/resources/forge.exc @@ -6,6 +6,7 @@ net/minecraft/client/renderer/ScreenEffectRenderer.getOverlayBlock(Lnet/minecraf net/minecraft/client/renderer/ShaderInstance.(Lnet/minecraft/server/packs/resources/ResourceProvider;Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/blaze3d/vertex/VertexFormat;)V=|p_173336_,shaderLocation,p_173338_ net/minecraft/client/renderer/block/BlockModelShaper.getTexture(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;=|p_110883_,level,pos net/minecraft/client/renderer/block/model/BlockModel.bakeVanilla(Lnet/minecraft/client/resources/model/ModelBakery;Lnet/minecraft/client/renderer/block/model/BlockModel;Ljava/util/function/Function;Lnet/minecraft/client/resources/model/ModelState;Lnet/minecraft/resources/ResourceLocation;Z)Lnet/minecraft/client/resources/model/BakedModel;=|p_111450_,p_111451_,p_111452_,p_111453_,p_111454_,p_111455_ +net/minecraft/client/renderer/block/model/ItemTransforms.(Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lnet/minecraft/client/renderer/block/model/ItemTransform;Lcom/google/common/collect/ImmutableMap;)V=|p_111798_,p_111799_,p_111800_,p_111801_,p_111802_,p_111803_,p_111804_,p_111805_,moddedTransforms net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$ChunkCompileTask.(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DZ)V=|p_194422_,pos,p_194423_,p_194424_ net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$RebuildTask.(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DLnet/minecraft/client/renderer/chunk/RenderChunkRegion;Z)V=|p_194426_,pos,p_194427_,p_194428_,p_194429_ net/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk$ResortTransparencyTask.(Lnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$RenderChunk;Lnet/minecraft/world/level/ChunkPos;DLnet/minecraft/client/renderer/chunk/ChunkRenderDispatcher$CompiledChunk;)V=|p_112888_,pos,p_112889_,p_112890_ @@ -58,4 +59,4 @@ net/minecraft/world/level/block/LiquidBlock.(Ljava/util/function/Supplier; net/minecraft/world/level/dimension/LevelStem.(Lnet/minecraft/core/Holder;Lnet/minecraft/world/level/chunk/ChunkGenerator;Z)V=|p_204519_,p_204520_,useServerSeed net/minecraft/world/level/entity/PersistentEntitySectionManager.addEntityWithoutEvent(Lnet/minecraft/world/level/entity/EntityAccess;Z)Z=|p_157539_,p_157540_ net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.processEntityInfos(Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate;Lnet/minecraft/world/level/LevelAccessor;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings;Ljava/util/List;)Ljava/util/List;=|template,p_215387_0_,p_215387_1_,p_215387_2_,p_215387_3_ -net/minecraft/world/level/storage/loot/LootPool.([Lnet/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer;[Lnet/minecraft/world/level/storage/loot/predicates/LootItemCondition;[Lnet/minecraft/world/level/storage/loot/functions/LootItemFunction;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Ljava/lang/String;)V=|p_165128_,p_165129_,p_165130_,p_165131_,p_165132_,name +net/minecraft/world/level/storage/loot/LootPool.([Lnet/minecraft/world/level/storage/loot/entries/LootPoolEntryContainer;[Lnet/minecraft/world/level/storage/loot/predicates/LootItemCondition;[Lnet/minecraft/world/level/storage/loot/functions/LootItemFunction;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Ljava/lang/String;)V=|p_165128_,p_165129_,p_165130_,p_165131_,p_165132_,name \ No newline at end of file diff --git a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java index 1b82d9c13f..3dd8df101c 100644 --- a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java +++ b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java @@ -92,7 +92,6 @@ import net.minecraftforge.client.model.generators.BlockStateProvider; import net.minecraftforge.client.model.generators.ConfiguredModel; import net.minecraftforge.client.model.generators.ItemModelProvider; import net.minecraftforge.client.model.generators.ModelBuilder; -import net.minecraftforge.client.model.generators.ModelBuilder.Perspective; import net.minecraftforge.client.model.generators.ModelFile; import net.minecraftforge.client.model.generators.ModelFile.UncheckedModelFile; import net.minecraftforge.client.model.generators.MultiPartBlockStateBuilder; @@ -753,27 +752,27 @@ public class DataGeneratorTest ModelFile block = models().getBuilder("block") .guiLight(GuiLight.SIDE) .transforms() - .transform(Perspective.GUI) + .transform(ItemTransforms.TransformType.GUI) .rotation(30, 225, 0) .scale(0.625f) .end() - .transform(Perspective.GROUND) + .transform(ItemTransforms.TransformType.GROUND) .translation(0, 3, 0) .scale(0.25f) .end() - .transform(Perspective.FIXED) + .transform(ItemTransforms.TransformType.FIXED) .scale(0.5f) .end() - .transform(Perspective.THIRDPERSON_RIGHT) + .transform(ItemTransforms.TransformType.THIRD_PERSON_RIGHT_HAND) .rotation(75, 45, 0) .translation(0, 2.5f, 0) .scale(0.375f) .end() - .transform(Perspective.FIRSTPERSON_RIGHT) + .transform(ItemTransforms.TransformType.FIRST_PERSON_RIGHT_HAND) .rotation(0, 45, 0) .scale(0.4f) .end() - .transform(Perspective.FIRSTPERSON_LEFT) + .transform(ItemTransforms.TransformType.FIRST_PERSON_LEFT_HAND) .rotation(0, 225, 0) .scale(0.4f) .end() @@ -1111,8 +1110,8 @@ public class DataGeneratorTest } else if (generatedDisplay != null) { // Both must be non-null ItemTransforms generatedTransforms = GSON.fromJson(generatedDisplay, ItemTransforms.class); ItemTransforms vanillaTransforms = GSON.fromJson(vanillaDisplay, ItemTransforms.class); - for (Perspective type : Perspective.values()) { - if (!generatedTransforms.getTransform(type.vanillaType).equals(vanillaTransforms.getTransform(type.vanillaType))) { + for (ItemTransforms.TransformType type : ItemTransforms.TransformType.values()) { + if (!generatedTransforms.getTransform(type).equals(vanillaTransforms.getTransform(type))) { ret.add("Model " + loc + " has transforms that differ from vanilla equivalent for perspective " + type.name()); return; } diff --git a/src/test/java/net/minecraftforge/debug/client/model/CustomTransformTypeTest.java b/src/test/java/net/minecraftforge/debug/client/model/CustomTransformTypeTest.java new file mode 100644 index 0000000000..d71d600230 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/model/CustomTransformTypeTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.debug.client.model; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.models.model.ModelLocationUtils; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.*; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.material.Material; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.EntityRenderersEvent; +import net.minecraftforge.client.model.generators.*; +import net.minecraftforge.common.data.ExistingFileHelper; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.forge.event.lifecycle.GatherDataEvent; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import org.jetbrains.annotations.Nullable; + +/* + * Test mod for the custom transform types feature. + * This test mod adds an item that should be held in the main hand, while another item is in the offhand. + * When right-clicked on a block, the item will "hang" the stack from the offhand slot on the wall. + * In addition, a replacement model for the stick is provided in the resources. + * If the feature is working, the stick will have a custom transform while hanging on walls, + * when compared to similar items such as a fishing rod. + * Editing that model json will reflect ingame after a resource reload (F3+T). + */ +@Mod(CustomTransformTypeTest.MODID) +public class CustomTransformTypeTest +{ + public static final String MODID = "custom_transformtype_test"; + + @Mod.EventBusSubscriber(value= Dist.CLIENT, modid = MODID, bus= Mod.EventBusSubscriber.Bus.MOD) + private static class RendererEvents + { + public static final ItemTransforms.TransformType HANGING = ItemTransforms.TransformType.create( + "custom_transformtype_test_hanging", + new ResourceLocation("custom_transformtype_test", "hanging") + ); + + @SubscribeEvent + public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) + { + event.registerBlockEntityRenderer(ITEM_HANGER_BE.get(), ItemHangerBlockEntityRenderer::new); + } + + private static class ItemHangerBlockEntityRenderer + implements BlockEntityRenderer + { + public ItemHangerBlockEntityRenderer(BlockEntityRendererProvider.Context context) + { + } + + @Override + public void render(ItemHangerBlockEntity blocken, float partialTicks, PoseStack poseStack, MultiBufferSource bufferSource, int packedLight, int overlayCoord) + { + var state = blocken.getBlockState(); + + if (!(state.getBlock() instanceof ItemHangerBlock)) return; + + poseStack.pushPose(); + + poseStack.translate(0.5,0.5,0.5); + poseStack.mulPose(state.getValue(ItemHangerBlock.FACING).getRotation()); + poseStack.translate(-0.5,-0.5,-0.5); + + ItemRenderer itemRenderer = Minecraft.getInstance().getItemRenderer(); + + var model = itemRenderer.getModel(blocken.heldItem, blocken.getLevel(), null, 0); + + itemRenderer.render(blocken.heldItem, HANGING, false, poseStack, bufferSource, packedLight, overlayCoord, model); + + poseStack.popPose(); + } + } + } + + public static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); + public static final DeferredRegister BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); + public static final DeferredRegister> BLOCK_ENTITY_TYPES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITIES, MODID); + + public static final RegistryObject + ITEM_HANGER_BLOCK = BLOCKS.register("item_hanger", () -> new ItemHangerBlock(BlockBehaviour.Properties.of(Material.WOOL).noCollission().noOcclusion().noDrops())); + public static final RegistryObject> + ITEM_HANGER_BE = BLOCK_ENTITY_TYPES.register("item_hanger", () -> BlockEntityType.Builder.of(ItemHangerBlockEntity::new, ITEM_HANGER_BLOCK.get()).build(null)); + public static final RegistryObject + ITEM_HANGER_ITEM = ITEMS.register("item_hanger", () -> new ItemHangerItem(ITEM_HANGER_BLOCK.get(), new Item.Properties().tab(CreativeModeTab.TAB_MISC))); + + public CustomTransformTypeTest() + { + var modBus = FMLJavaModLoadingContext.get().getModEventBus(); + modBus.addListener(this::gatherData); + BLOCKS.register(modBus); + BLOCK_ENTITY_TYPES.register(modBus); + ITEMS.register(modBus); + } + + public void gatherData(GatherDataEvent event) + { + DataGenerator gen = event.getGenerator(); + + if (event.includeClient()) + { + gen.addProvider(new ItemModels(gen, event.getExistingFileHelper())); + gen.addProvider(new BlockStateModels(gen, event.getExistingFileHelper())); + } + } + + public static class BlockStateModels extends BlockStateProvider + { + + public BlockStateModels(DataGenerator gen, ExistingFileHelper exFileHelper) + { + super(gen, MODID, exFileHelper); + } + + @Override + protected void registerStatesAndModels() + { + { + Block block = ITEM_HANGER_BLOCK.get(); + horizontalBlock(block, models().getExistingFile(ModelLocationUtils.getModelLocation(block))); + } + } + } + + public static class ItemModels extends ItemModelProvider + { + public ItemModels(DataGenerator generator, ExistingFileHelper existingFileHelper) + { + super(generator, MODID, existingFileHelper); + } + + @Override + protected void registerModels() + { + basicItem(ITEM_HANGER_ITEM.get()); + + basicItem(Items.STICK) + .transforms() + .transform(RendererEvents.HANGING) + .rotation(62, 180 - 33, 40) + .translation(-2.25f, 1.5f, -0.25f).scale(0.48f) + .end() + .end(); + } + } + + private static class ItemHangerBlock extends HorizontalDirectionalBlock implements EntityBlock + { + public ItemHangerBlock(BlockBehaviour.Properties properties) + { + super(properties); + registerDefaultState(getStateDefinition().any().setValue(FACING, Direction.NORTH)); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + builder.add(FACING); + } + + @Nullable + @Override + public BlockState getStateForPlacement(BlockPlaceContext ctx) + { + return defaultBlockState().setValue(FACING, ctx.getHorizontalDirection()); + } + + @Nullable + @Override + public BlockEntity newBlockEntity(BlockPos pos, BlockState state) + { + return new ItemHangerBlockEntity(pos, state); + } + + @Deprecated + @Override + public RenderShape getRenderShape(BlockState state) + { + return RenderShape.ENTITYBLOCK_ANIMATED; + } + } + + private static class ItemHangerBlockEntity extends BlockEntity + { + private ItemStack heldItem; + + public ItemHangerBlockEntity(BlockEntityType type, BlockPos blockPos, BlockState blockState) + { + super(type, blockPos, blockState); + } + + public ItemHangerBlockEntity(BlockPos blockPos, BlockState blockState) + { + this(ITEM_HANGER_BE.get(), blockPos, blockState); + } + + @Override + public CompoundTag getUpdateTag() + { + return saveWithoutMetadata(); + } + + @Nullable + @Override + public Packet getUpdatePacket() + { + return ClientboundBlockEntityDataPacket.create(this, BlockEntity::getUpdateTag); + } + + @Override + public void handleUpdateTag(CompoundTag tag) + { + load(tag); + } + + @Override + public void onDataPacket(Connection net, ClientboundBlockEntityDataPacket pkt) + { + handleUpdateTag(pkt.getTag()); + } + + @Override + protected void saveAdditional(CompoundTag tag) + { + var c = new CompoundTag(); + heldItem.save(c); + tag.put("item", c); + } + + @Override + public void load(CompoundTag tag) + { + var c = tag.getCompound("item"); + heldItem = ItemStack.of(c); + } + } + + private static class ItemHangerItem extends BlockItem + { + public ItemHangerItem(Block block, Item.Properties properties) + { + super(block, properties); + } + + @Override + protected boolean placeBlock(BlockPlaceContext ctx, BlockState state) + { + Player player = ctx.getPlayer(); + if (player == null) + return false; + var hand = ctx.getHand(); + if (hand != InteractionHand.MAIN_HAND) + { + return false; + } + if (!super.placeBlock(ctx, state)) + return false; + if (ctx.getLevel().getBlockEntity(ctx.getClickedPos()) instanceof ItemHangerBlockEntity be) + { + be.heldItem = player.getItemInHand(InteractionHand.OFF_HAND); + } + return true; + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 5dd3dbc55f..3ec9671b0e 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -220,5 +220,7 @@ license="LGPL v2.1" modId="valid_railshape_test" [[mods]] modId="player_negotiation_event_test" +[[mods]] + modId="custom_transformtype_test" # ADD ABOVE THIS LINE diff --git a/src/test/resources/assets/custom_transformtype_test/models/block/item_hanger.json b/src/test/resources/assets/custom_transformtype_test/models/block/item_hanger.json new file mode 100644 index 0000000000..7960c6d706 --- /dev/null +++ b/src/test/resources/assets/custom_transformtype_test/models/block/item_hanger.json @@ -0,0 +1,3 @@ +{ + "parent": "builtin/entity" +} \ No newline at end of file diff --git a/src/test/resources/assets/custom_transformtype_test/textures/item/item_hanger.png b/src/test/resources/assets/custom_transformtype_test/textures/item/item_hanger.png new file mode 100644 index 0000000000000000000000000000000000000000..5b32199dad9e27697874c6206fb8e632a9db0995 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1quc!FW#>#}Etuh($ literal 0 HcmV?d00001