diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java.patch b/patches/minecraft/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java.patch index d6f144a888..4f92b7c933 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java.patch @@ -1,12 +1,10 @@ --- a/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java +++ b/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java -@@ -44,19 +_,20 @@ - if (itemstack.m_41720_() instanceof ArmorItem) { - ArmorItem armoritem = (ArmorItem)itemstack.m_41720_(); +@@ -46,17 +_,18 @@ if (armoritem.m_40402_() == p_117122_) { -+ p_117124_ = getArmorModelHook(p_117121_, itemstack, p_117122_, p_117124_); this.m_117386_().m_102872_(p_117124_); this.m_117125_(p_117124_, p_117122_); ++ net.minecraft.client.model.Model model = getArmorModelHook(p_117121_, itemstack, p_117122_, p_117124_); boolean flag = this.m_117128_(p_117122_); boolean flag1 = itemstack.m_41790_(); - if (armoritem instanceof DyeableArmorItem) { @@ -18,11 +16,11 @@ float f2 = (float)(i & 255) / 255.0F; - this.m_117106_(p_117119_, p_117120_, p_117123_, armoritem, flag1, p_117124_, flag, f, f1, f2, (String)null); - this.m_117106_(p_117119_, p_117120_, p_117123_, armoritem, flag1, p_117124_, flag, 1.0F, 1.0F, 1.0F, "overlay"); -+ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, p_117124_, f, f1, f2, this.getArmorResource(p_117121_, itemstack, p_117122_, null)); -+ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, p_117124_, 1.0F, 1.0F, 1.0F, this.getArmorResource(p_117121_, itemstack, p_117122_, "overlay")); ++ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, model, f, f1, f2, this.getArmorResource(p_117121_, itemstack, p_117122_, null)); ++ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, model, 1.0F, 1.0F, 1.0F, this.getArmorResource(p_117121_, itemstack, p_117122_, "overlay")); } else { - this.m_117106_(p_117119_, p_117120_, p_117123_, armoritem, flag1, p_117124_, flag, 1.0F, 1.0F, 1.0F, (String)null); -+ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, p_117124_, 1.0F, 1.0F, 1.0F, this.getArmorResource(p_117121_, itemstack, p_117122_, null)); ++ this.renderModel(p_117119_, p_117120_, p_117123_, flag1, model, 1.0F, 1.0F, 1.0F, this.getArmorResource(p_117121_, itemstack, p_117122_, null)); } } @@ -33,7 +31,7 @@ - VertexConsumer vertexconsumer = ItemRenderer.m_115184_(p_117108_, RenderType.m_110431_(this.m_117080_(p_117110_, p_117113_, p_117117_)), false, p_117111_); + renderModel(p_117107_, p_117108_, p_117109_, p_117111_, p_117112_, p_117114_, p_117115_, p_117116_, this.m_117080_(p_117110_, p_117113_, p_117117_)); + } -+ private void renderModel(PoseStack p_117107_, MultiBufferSource p_117108_, int p_117109_, boolean p_117111_, A p_117112_, float p_117114_, float p_117115_, float p_117116_, ResourceLocation armorResource) { ++ private void renderModel(PoseStack p_117107_, MultiBufferSource p_117108_, int p_117109_, boolean p_117111_, net.minecraft.client.model.Model p_117112_, float p_117114_, float p_117115_, float p_117116_, ResourceLocation armorResource) { + VertexConsumer vertexconsumer = ItemRenderer.m_115184_(p_117108_, RenderType.m_110431_(armorResource), false, p_117111_); p_117112_.m_7695_(p_117107_, vertexconsumer, p_117109_, OverlayTexture.f_118083_, p_117114_, p_117115_, p_117116_, 1.0F); } @@ -51,9 +49,9 @@ + /*=================================== FORGE START =========================================*/ + + /** -+ * Hook to allow item-sensitive armor model. for LayerBipedArmor. ++ * Hook to allow item-sensitive armor model. for HumanoidArmorLayer. + */ -+ protected A getArmorModelHook(T entity, ItemStack itemStack, EquipmentSlot slot, A model) { ++ protected net.minecraft.client.model.Model getArmorModelHook(T entity, ItemStack itemStack, EquipmentSlot slot, A model) { + return net.minecraftforge.client.ForgeHooksClient.getArmorModel(entity, itemStack, slot, model); + } + diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index cebb3b35ca..967ec41053 100755 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -26,6 +26,7 @@ import com.mojang.datafixers.util.Either; import net.minecraft.client.gui.chat.NarratorChatListener; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; +import net.minecraft.client.model.Model; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.builders.LayerDefinition; import net.minecraft.client.multiplayer.ClientLevel; @@ -279,10 +280,24 @@ public class ForgeHooksClient renderType.set(layer); } - public static > A getArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot slot, A _default) + public static Model getArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot slot, HumanoidModel _default) { - A model = RenderProperties.get(itemStack).getArmorModel(entityLiving, itemStack, slot, _default); - return model == null ? _default : model; + return RenderProperties.get(itemStack).getBaseArmorModel(entityLiving, itemStack, slot, _default); + } + + /** Copies humanoid model properties from the original model to another, used for armor models */ + @SuppressWarnings("unchecked") + public static void copyModelProperties(HumanoidModel original, HumanoidModel replacement) + { + // this function does not make use of the generic, so the unchecked cast should be safe + original.copyPropertiesTo((HumanoidModel)replacement); + replacement.head.visible = original.head.visible; + replacement.hat.visible = original.hat.visible; + replacement.body.visible = original.body.visible; + replacement.rightArm.visible = original.rightArm.visible; + replacement.leftArm.visible = original.leftArm.visible; + replacement.rightLeg.visible = original.rightLeg.visible; + replacement.leftLeg.visible = original.leftLeg.visible; } //This properly moves the domain, if provided, to the front of the string before concatenating diff --git a/src/main/java/net/minecraftforge/client/IItemRenderProperties.java b/src/main/java/net/minecraftforge/client/IItemRenderProperties.java index c5d2766945..03d7e9a2d3 100644 --- a/src/main/java/net/minecraftforge/client/IItemRenderProperties.java +++ b/src/main/java/net/minecraftforge/client/IItemRenderProperties.java @@ -22,12 +22,16 @@ package net.minecraftforge.client; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.Model; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public interface IItemRenderProperties { IItemRenderProperties DUMMY = new IItemRenderProperties() @@ -53,13 +57,40 @@ public interface IItemRenderProperties * @param itemStack The itemStack to render the model of * @param armorSlot The slot the armor is in * @param _default Original armor model. Will have attributes set. - * @return A ModelBiped to render instead of the default + * @return A HumanoidModel to render instead of the default, will have the relevant properties copied in {@link #getBaseArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel). + * Returning null will cause the default to render. + * @see #getBaseArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel) */ - default > A getArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot armorSlot, A _default) + @Nullable + default HumanoidModel getArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot armorSlot, HumanoidModel _default) { return null; } + /** + * Override this method to return a generic model rather than a {@link HumanoidModel}. More ideal for wrapping the original model or returning non-standard models like elytra wings. + * By default, this hook copies in the model properties from the default into the model returned by {@link #getArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel)}, + * so if you override this method you are responsible for copying properties you care about + * + * @param entityLiving The entity wearing the armor + * @param itemStack The itemStack to render the model of + * @param armorSlot The slot the armor is in + * @param _default Original armor model. Will have attributes set. + * @return A Model to render instead of the default + * @see #getArmorModel(LivingEntity, ItemStack, EquipmentSlot, HumanoidModel) + */ + @Nonnull + default Model getBaseArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot armorSlot, HumanoidModel _default) + { + HumanoidModel replacement = getArmorModel(entityLiving, itemStack, armorSlot, _default); + if (replacement != null && replacement != _default) + { + ForgeHooksClient.copyModelProperties(_default, replacement); + return replacement; + } + return _default; + } + /** * Called when the client starts rendering the HUD, for whatever item the player * currently has as a helmet. This is where pumpkins would render there overlay. diff --git a/src/test/java/net/minecraftforge/debug/client/CustomArmorModelTest.java b/src/test/java/net/minecraftforge/debug/client/CustomArmorModelTest.java new file mode 100644 index 0000000000..4301ab6ff8 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/CustomArmorModelTest.java @@ -0,0 +1,143 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.EndermanModel; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.Model; +import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ArmorItem; +import net.minecraft.world.item.ArmorMaterial; +import net.minecraft.world.item.ArmorMaterials; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Item.Properties; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.client.IItemRenderProperties; +import net.minecraftforge.common.util.Lazy; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.annotation.Nonnull; +import java.util.function.Consumer; +import java.util.function.Function; + +@Mod(CustomArmorModelTest.MOD_ID) +public class CustomArmorModelTest +{ + static final String MOD_ID = "custom_armor_model_test"; + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + // demonstrates custom non-humanoid model + private static final RegistryObject RED_LEGGINGS = ITEMS.register("red_leggings", () -> new TintedArmorItem(ArmorMaterials.DIAMOND, EquipmentSlot.LEGS, new Properties().stacksTo(1).tab(CreativeModeTab.TAB_MISC))); + // demonstrates the properties are copied from the vanilla model + private static final RegistryObject ENDERMAN_CHESTPLATE = ITEMS.register("enderman_chestplate", () -> new EndermanArmorItem(ArmorMaterials.GOLD, EquipmentSlot.CHEST, new Properties().stacksTo(1).tab(CreativeModeTab.TAB_MISC))); + private static final RegistryObject ENDERMAN_BOOTS = ITEMS.register("enderman_boots", () -> new EndermanArmorItem(ArmorMaterials.GOLD, EquipmentSlot.FEET, new Properties().stacksTo(1).tab(CreativeModeTab.TAB_MISC))); + + public CustomArmorModelTest() + { + final IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + ITEMS.register(modBus); + } + + private static class TintedArmorItem extends ArmorItem + { + public TintedArmorItem(ArmorMaterial material, EquipmentSlot slot, Properties props) + { + super(material, slot, props); + } + + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IItemRenderProperties() + { + @Override @Nonnull + public Model getBaseArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot armorSlot, HumanoidModel _default) + { + TintedArmorModel.INSTANCE.base = _default; + return TintedArmorModel.INSTANCE; + } + }); + } + } + + private static class EndermanArmorItem extends ArmorItem + { + public EndermanArmorItem(ArmorMaterial material, EquipmentSlot slot, Properties props) + { + super(material, slot, props); + } + + @Nullable + @Override + public String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String type) + { + return "textures/entity/enderman/enderman.png"; + } + + @Override + public void initializeClient(Consumer consumer) + { + consumer.accept(new IItemRenderProperties() + { + @Override + public HumanoidModel getArmorModel(LivingEntity entityLiving, ItemStack itemStack, EquipmentSlot armorSlot, HumanoidModel _default) + { + return TintedArmorModel.ENDERMAN.get(); + } + }); + } + } + + private static class TintedArmorModel extends Model + { + private static final TintedArmorModel INSTANCE = new TintedArmorModel(RenderType::entityCutoutNoCull); + private static final Lazy> ENDERMAN = Lazy.of(() -> new HumanoidModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(ModelLayers.ENDERMAN))); + + private HumanoidModel base; + private TintedArmorModel(Function renderTypeFunction) + { + super(renderTypeFunction); + } + + @Override + public void renderToBuffer(@Nonnull PoseStack poseStack, @Nonnull VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) + { + if (base != null) + { + base.renderToBuffer(poseStack, consumer, light, overlay, red, 0, 0, alpha); + } + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 53f90826ae..851825ee31 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -174,6 +174,8 @@ license="LGPL v2.1" modId="part_entity_test" [[mods]] modId="capabilities_test" +[[mods]] + modId="custom_armor_model_test" [[mods]] modId="duplicate_optional_tag_test" [[mods]]