diff --git a/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch b/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch index 2d8d0090603..6e9c950aae6 100644 --- a/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java.patch @@ -1,9 +1,19 @@ --- a/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java +++ b/net/minecraft/client/renderer/blockentity/SkullBlockRenderer.java -@@ -115,4 +_,14 @@ +@@ -57,7 +_,7 @@ + case PIGLIN -> new PiglinHeadModel(p_387840_.bakeLayer(ModelLayers.PIGLIN_HEAD)); + }); + } else { +- return null; ++ return net.neoforged.neoforge.client.ClientHooks.getModdedSkullModel(p_387840_, p_388801_); // Neo: Lookup model for modded skull types + } + } + +@@ -114,5 +_,15 @@ + p_389624_ != null ? p_389624_ : Minecraft.getInstance().getSkinManager().getInsecureSkin(p_389483_.gameProfile()).texture() ) : RenderType.entityCutoutNoCullZOffset(p_389624_ != null ? p_389624_ : SKIN_BY_TYPE.get(p_389566_)); - } ++ } + + @Override + public net.minecraft.world.phys.AABB getRenderBoundingBox(SkullBlockEntity blockEntity) { @@ -13,5 +23,5 @@ + return new net.minecraft.world.phys.AABB(pos.getX() - .75, pos.getY() - .35, pos.getZ() - .75, pos.getX() + 1.75, pos.getY() + 1.0, pos.getZ() + 1.75); + } + return BlockEntityRenderer.super.getRenderBoundingBox(blockEntity); -+ } + } } diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index 40d8b2c25e4..b19889061fe 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -53,6 +53,8 @@ import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.SkullModelBase; +import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.builders.LayerDefinition; import net.minecraft.client.multiplayer.ClientLevel; @@ -124,6 +126,7 @@ import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.SkullBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; @@ -695,6 +698,13 @@ public static void loadLayerDefinitions(ImmutableMap.Builder builder.put(k, v.get())); } + private static final Map> skullModelsByType = new HashMap<>(); + + @Nullable + public static SkullModelBase getModdedSkullModel(EntityModelSet modelSet, SkullBlock.Type type) { + return skullModelsByType.getOrDefault(type, set -> null).apply(modelSet); + } + private static final ResourceLocation ICON_SHEET = ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "textures/gui/icons.png"); public static void firePlayerLogin(MultiPlayerGameMode pc, LocalPlayer player, Connection networkManager) { @@ -982,6 +992,7 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou MenuScreens.init(); ModLoader.postEvent(new RegisterClientReloadListenersEvent(resourceManager)); ModLoader.postEvent(new EntityRenderersEvent.RegisterLayerDefinitions()); + ModLoader.postEvent(new EntityRenderersEvent.CreateSkullModels(skullModelsByType)); ModLoader.postEvent(new EntityRenderersEvent.RegisterRenderers()); ModLoader.postEvent(new RegisterRenderStateModifiersEvent()); ClientTooltipComponentManager.init(); diff --git a/src/main/java/net/neoforged/neoforge/client/event/EntityRenderersEvent.java b/src/main/java/net/neoforged/neoforge/client/event/EntityRenderersEvent.java index fc2d26c7ac9..d71fa9cf0d5 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/EntityRenderersEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/EntityRenderersEvent.java @@ -5,15 +5,16 @@ package net.neoforged.neoforge.client.event; -import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import net.minecraft.client.model.SkullModel; import net.minecraft.client.model.SkullModelBase; import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.builders.LayerDefinition; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; @@ -27,7 +28,6 @@ import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.block.SkullBlock; -import net.minecraft.world.level.block.SkullBlock.Type; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; import net.neoforged.bus.api.Event; @@ -195,43 +195,61 @@ public EntityRendererProvider.Context getContext() { } /** - * Fired for registering additional {@linkplain net.minecraft.client.model.SkullModelBase skull models} at the appropriate time. + * Fired for registering additional {@linkplain net.minecraft.client.model.SkullModelBase skull models}. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}, and does not have a result.

* *

This event is fired on the mod-specific event bus, * only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class CreateSkullModels extends EntityRenderersEvent { - private final ImmutableMap.Builder builder; - private final EntityModelSet entityModelSet; + private final Map> skullModels; @ApiStatus.Internal - public CreateSkullModels(ImmutableMap.Builder builder, EntityModelSet entityModelSet) { - this.builder = builder; - this.entityModelSet = entityModelSet; + public CreateSkullModels(Map> skullModels) { + this.skullModels = skullModels; } /** - * {@return the set of entity models} + * Registers a {@link SkullModel} for a skull block with the given {@link SkullBlock.Type}. + * + * @param type a unique skull type; an exception will be thrown if multiple mods register models + * for the same type or a mod tries to register a model for a vanilla type + * @param layerLocation the key that identifies the {@link LayerDefinition} used by the model + */ + public void registerSkullModel(SkullBlock.Type type, ModelLayerLocation layerLocation) { + this.registerSkullModel(type, layerLocation, SkullModel::new); + } + + /** + * Registers the entity model for a skull block with the given {@link SkullBlock.Type}. + * + * @param type a unique skull type; an exception will be thrown if multiple mods register models + * for the same type or a mod tries to register a model for a vanilla type + * @param layerLocation the key that identifies the {@link LayerDefinition} used by the model + * @param factory the factory to create the skull model instance, taking in the root {@link ModelPart} and + * returning the model. */ - public EntityModelSet getEntityModelSet() { - return entityModelSet; + public void registerSkullModel(SkullBlock.Type type, ModelLayerLocation layerLocation, Function factory) { + this.registerSkullModel(type, modelSet -> factory.apply(modelSet.bakeLayer(layerLocation))); } /** - * Registers the constructor for a skull block with the given {@link SkullBlock.Type}. - * These will be inserted into the maps used by the item, entity, and block model renderers at the appropriate - * time. + * Registers the entity model for a skull block with the given {@link SkullBlock.Type}. * - * @param type a unique skull type; an exception will be thrown later if multiple mods (including vanilla) - * register models for the same type - * @param model the skull model instance. A typical implementation will simply bake a model using - * {@link EntityModelSet#bakeLayer(ModelLayerLocation)} and pass it to the constructor for - * {@link SkullModel}. + * @param type a unique skull type; an exception will be thrown if multiple mods register models for + * the same type or a mod tries to register a model for a vanilla type + * @param factory the factory to create the skull model instance. A typical implementation will simply bake + * a model using {@link EntityModelSet#bakeLayer(ModelLayerLocation)} and pass it to the + * constructor for {@link SkullModel} */ - public void registerSkullModel(SkullBlock.Type type, SkullModelBase model) { - builder.put(type, model); + public void registerSkullModel(SkullBlock.Type type, Function factory) { + if (type instanceof SkullBlock.Types) { + throw new IllegalArgumentException("Cannot register skull model for vanilla skull type: " + type.getSerializedName()); + } + if (skullModels.putIfAbsent(type, factory) != null) { + throw new IllegalArgumentException("Factory already registered for provided skull type: " + type.getSerializedName()); + } } } } diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java index 7c4f41299c4..c6ae6c79df7 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/block/CustomHeadTest.java @@ -8,10 +8,12 @@ import net.minecraft.client.model.SkullModel; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.renderer.blockentity.SkullBlockRenderer; +import net.minecraft.client.renderer.special.SkullSpecialRenderer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; import net.minecraft.world.item.Rarity; @@ -32,6 +34,7 @@ import net.neoforged.fml.common.Mod; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.neoforge.client.event.EntityRenderersEvent; +import net.neoforged.neoforge.client.event.RegisterSpecialBlockModelRendererEvent; import net.neoforged.neoforge.common.util.Lazy; import net.neoforged.neoforge.event.BuildCreativeModeTabContentsEvent; import net.neoforged.neoforge.registries.DeferredBlock; @@ -53,7 +56,7 @@ public class CustomHeadTest { private static final DeferredRegister> BLOCK_ENTITIES = DeferredRegister.create(BuiltInRegistries.BLOCK_ENTITY_TYPE, MODID); private static final DeferredBlock BLAZE_HEAD = BLOCKS.registerBlock("blaze_head", props -> new CustomSkullBlock(SkullType.BLAZE, props), BlockBehaviour.Properties.of().strength(1.0F)); private static final DeferredBlock BLAZE_HEAD_WALL = BLOCKS.registerBlock("blaze_wall_head", props -> new CustomWallSkullBlock(SkullType.BLAZE, props.strength(1.0F).overrideLootTable(BLAZE_HEAD.get().getLootTable())), BlockBehaviour.Properties.of()); - private static final DeferredItem BLAZE_HEAD_ITEM = ITEMS.registerItem("blaze_head", props -> new StandingAndWallBlockItem(BLAZE_HEAD.get(), BLAZE_HEAD_WALL.get(), Direction.DOWN, props.rarity(Rarity.UNCOMMON))); + private static final DeferredItem BLAZE_HEAD_ITEM = ITEMS.registerItem("blaze_head", props -> new StandingAndWallBlockItem(BLAZE_HEAD.get(), BLAZE_HEAD_WALL.get(), Direction.DOWN, props.rarity(Rarity.UNCOMMON).equippableUnswappable(EquipmentSlot.HEAD))); private static final DeferredHolder, BlockEntityType> CUSTOM_SKULL = BLOCK_ENTITIES.register("custom_skull", () -> new BlockEntityType<>(CustomSkullBlockEntity::new, BLAZE_HEAD.get(), BLAZE_HEAD_WALL.get())); public CustomHeadTest(IEventBus modBus) { @@ -135,7 +138,12 @@ static void clientSetupEvent(FMLClientSetupEvent event) { @SubscribeEvent static void registerSkullModel(EntityRenderersEvent.CreateSkullModels event) { - event.registerSkullModel(SkullType.BLAZE, new SkullModel(event.getEntityModelSet().bakeLayer(ClientEvents.BLAZE_HEAD_LAYER))); + event.registerSkullModel(SkullType.BLAZE, ClientEvents.BLAZE_HEAD_LAYER); + } + + @SubscribeEvent + static void registerSpecialBlockRenderer(RegisterSpecialBlockModelRendererEvent event) { + event.register(BLAZE_HEAD.get(), new SkullSpecialRenderer.Unbaked(SkullType.BLAZE)); } } } diff --git a/tests/src/main/resources/assets/custom_head_test/items/blaze_head.json b/tests/src/main/resources/assets/custom_head_test/items/blaze_head.json new file mode 100644 index 00000000000..2c01f4dbc75 --- /dev/null +++ b/tests/src/main/resources/assets/custom_head_test/items/blaze_head.json @@ -0,0 +1,10 @@ +{ + "model": { + "type": "minecraft:special", + "base": "custom_head_test:item/blaze_head", + "model": { + "type": "minecraft:head", + "kind": "blaze" + } + } +} \ No newline at end of file