builder) {
+ this.builder = builder;
}
- /**
- * Registers a {@link ItemColor} instance for a set of blocks.
- *
- * @param itemColor The color provider
- * @param items The items
- */
- public void register(ItemColor itemColor, ItemLike... items) {
- itemColors.register(itemColor, items);
+ public void register(ColorResolver resolver) {
+ this.builder.add(resolver);
}
}
/**
- * Allows registration of custom {@link ColorResolver} implementations to be used with
- * {@link net.minecraft.world.level.BlockAndTintGetter#getBlockTint(BlockPos, ColorResolver)}.
+ * Fired for registering item color handlers.
+ *
+ * This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}
+ *
+ * @see ItemTintSource
+ * @see ItemTintSources
*/
- public static class ColorResolvers extends RegisterColorHandlersEvent {
- private final ImmutableList.Builder builder;
+ public static class ItemTintSources extends RegisterColorHandlersEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
@ApiStatus.Internal
- public ColorResolvers(ImmutableList.Builder builder) {
- this.builder = builder;
+ public ItemTintSources(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
}
- public void register(ColorResolver resolver) {
- this.builder.add(resolver);
+ public void register(ResourceLocation location, MapCodec extends ItemTintSource> source) {
+ this.idMapper.put(location, source);
}
}
}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java
new file mode 100644
index 00000000..8b76afe1
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import com.mojang.serialization.MapCodec;
+import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import net.neoforged.bus.api.Event;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event fired when special model renderers are registered.
+ *
+ * This event is fired during the model registration process for conditional item model properties.
+ * It is used to register property codecs which can be used to create custom conditional item model properties.
+ *
+ * This event is fired on the mod event bus.
+ */
+public class RegisterConditionalItemModelPropertyEvent extends Event implements IModBusEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
+
+ @ApiStatus.Internal
+ public RegisterConditionalItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
+ }
+
+ public void register(ResourceLocation location, MapCodec extends ConditionalItemModelProperty> source) {
+ this.idMapper.put(location, source);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java
new file mode 100644
index 00000000..0887e4b1
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import com.mojang.serialization.MapCodec;
+import net.minecraft.client.renderer.item.ItemModel;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import net.neoforged.bus.api.Event;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event fired when item models are registered.
+ *
+ * This event is fired during the model registration process for items.
+ * It is used to register custom item model codecs which can be used to create custom item models.
+ *
+ * This event is fired on the mod event bus.
+ */
+public class RegisterItemModelsEvent extends Event implements IModBusEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
+
+ @ApiStatus.Internal
+ public RegisterItemModelsEvent(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
+ }
+
+ public void register(ResourceLocation location, MapCodec extends ItemModel.Unbaked> source) {
+ this.idMapper.put(location, source);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java
new file mode 100644
index 00000000..9d6f6c68
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import java.util.Map;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.texture.TextureAtlas;
+import net.minecraft.client.resources.TextureAtlasHolder;
+import net.minecraft.client.resources.model.Material;
+import net.minecraft.client.resources.model.ModelManager;
+import net.minecraft.resources.ResourceLocation;
+import net.neoforged.bus.api.Event;
+import net.neoforged.bus.api.ICancellableEvent;
+import net.neoforged.fml.LogicalSide;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Fired for registering {@linkplain TextureAtlas texture atlases} that will be used with {@link Material} or
+ * other systems which retrieve the atlas via {@link Minecraft#getTextureAtlas(ResourceLocation)} or
+ * {@link ModelManager#getAtlas(ResourceLocation)}.
+ *
+ * If an atlas is registered via this event, then it must NOT be used through a {@link TextureAtlasHolder}.
+ *
+ * This event fires during startup when the {@link ModelManager} is constructed.
+ *
+ * This event is not {@linkplain ICancellableEvent cancellable}.
+ *
+ * This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.
+ */
+public class RegisterMaterialAtlasesEvent extends Event implements IModBusEvent {
+ private final Map atlases;
+
+ @ApiStatus.Internal
+ public RegisterMaterialAtlasesEvent(Map atlases) {
+ this.atlases = atlases;
+ }
+
+ /**
+ * Register a texture atlas with the given name and info location
+ *
+ * @param atlasLocation The name of the texture atlas
+ * @param atlasInfoLocation The location of the atlas info JSON relative to the {@code atlases} directory
+ */
+ public void register(ResourceLocation atlasLocation, ResourceLocation atlasInfoLocation) {
+ ResourceLocation oldAtlasInfoLoc = this.atlases.putIfAbsent(atlasLocation, atlasInfoLocation);
+ if (oldAtlasInfoLoc != null) {
+ throw new IllegalStateException(String.format(
+ "Duplicate registration of atlas: %s (old info: %s, new info: %s)",
+ atlasLocation,
+ oldAtlasInfoLoc,
+ atlasInfoLocation));
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java
new file mode 100644
index 00000000..28a71926
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import com.mojang.serialization.MapCodec;
+import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import net.neoforged.bus.api.Event;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event fired when special model renderers are registered.
+ *
+ * This event is fired during the model registration process for range select item model properties.
+ * It is used to register property codecs which can be used to create custom range select item model properties.
+ *
+ * This event is fired on the mod event bus.
+ */
+public class RegisterRangeSelectItemModelPropertyEvent extends Event implements IModBusEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
+
+ @ApiStatus.Internal
+ public RegisterRangeSelectItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
+ }
+
+ public void register(ResourceLocation location, MapCodec extends RangeSelectItemModelProperty> source) {
+ this.idMapper.put(location, source);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java
new file mode 100644
index 00000000..2db147ea
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import net.neoforged.bus.api.Event;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event fired when item model property selectors are registered.
+ *
+ * This event is fired during the model registration process for item model property selectors.
+ * It is used to register custom selector types which can be used to create custom item model property selectors.
+ *
+ * This event is fired on the mod event bus.
+ */
+public class RegisterSelectItemModelPropertyEvent extends Event implements IModBusEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
+
+ @ApiStatus.Internal
+ public RegisterSelectItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
+ }
+
+ public void register(ResourceLocation location, SelectItemModelProperty.Type, ?> source) {
+ this.idMapper.put(location, source);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java
new file mode 100644
index 00000000..b521657b
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.event;
+
+import com.mojang.serialization.MapCodec;
+import net.minecraft.client.renderer.special.SpecialModelRenderer;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.ExtraCodecs;
+import net.neoforged.bus.api.Event;
+import net.neoforged.fml.event.IModBusEvent;
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Event fired when special model renderers are registered.
+ *
+ * This event is fired during the model registration process for special item model renderers.
+ * It is used to register custom special item model renderer codecs which can be used to create custom special item model renderers.
+ *
+ * This event is fired on the mod event bus.
+ */
+public class RegisterSpecialModelRendererEvent extends Event implements IModBusEvent {
+ private final ExtraCodecs.LateBoundIdMapper> idMapper;
+
+ @ApiStatus.Internal
+ public RegisterSpecialModelRendererEvent(ExtraCodecs.LateBoundIdMapper> idMapper) {
+ this.idMapper = idMapper;
+ }
+
+ public void register(ResourceLocation location, MapCodec extends SpecialModelRenderer.Unbaked> source) {
+ this.idMapper.put(location, source);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java
index 479758d4..5d1fdb41 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java
@@ -10,7 +10,7 @@
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.ItemFrameRenderer;
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
-import net.minecraft.world.item.ItemStack;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.ICancellableEvent;
import net.neoforged.fml.LogicalSide;
@@ -30,7 +30,7 @@
* @see ItemFrameRenderer
*/
public class RenderItemInFrameEvent extends Event implements ICancellableEvent {
- private final ItemStack itemStack;
+ private final ItemStackRenderState itemStack;
private final ItemFrameRenderState frameRenderState;
private final ItemFrameRenderer> renderer;
private final PoseStack poseStack;
@@ -40,7 +40,7 @@ public class RenderItemInFrameEvent extends Event implements ICancellableEvent {
@ApiStatus.Internal
public RenderItemInFrameEvent(ItemFrameRenderState frameRenderState, ItemFrameRenderer> renderItemFrame, PoseStack poseStack,
MultiBufferSource multiBufferSource, int packedLight) {
- this.itemStack = frameRenderState.itemStack;
+ this.itemStack = frameRenderState.item;
this.frameRenderState = frameRenderState;
this.renderer = renderItemFrame;
this.poseStack = poseStack;
@@ -51,7 +51,7 @@ public RenderItemInFrameEvent(ItemFrameRenderState frameRenderState, ItemFrameRe
/**
* {@return the item stack being rendered}
*/
- public ItemStack getItemStack() {
+ public ItemStackRenderState getItemStackRenderState() {
return itemStack;
}
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java
index 5ecbefe3..d705fb15 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java
@@ -22,9 +22,6 @@
* Fired when a {@link LivingEntity} is rendered.
* See the two subclasses to listen for before and after rendering.
*
- * Despite this event's use of generic type parameters, this is not a {@link net.neoforged.bus.api.GenericEvent},
- * and should not be treated as such (such as using generic-specific listeners, which may cause a {@link ClassCastException}).
- *
* @param the living entity that is being rendered
* @param the model for the living entity
* @see RenderLivingEvent.Pre
@@ -99,11 +96,11 @@ public int getPackedLight() {
* Fired before an entity is rendered.
* This can be used to render additional effects or suppress rendering.
*
- * This event is {@linkplain ICancellableEvent cancelable}, and does not {@linkplain HasResult have a result}.
+ *
This event is {@linkplain ICancellableEvent cancelable}.
* If this event is cancelled, then the entity will not be rendered and the corresponding
* {@link RenderLivingEvent.Post} will not be fired.
*
- * This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
+ *
This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.
*
* @param the living entity that is being rendered
@@ -119,9 +116,9 @@ public Pre(S renderState, LivingEntityRenderer renderer, float partialT
/**
* Fired after an entity is rendered, if the corresponding {@link RenderLivingEvent.Post} is not cancelled.
*
- * This event is not {@linkplain ICancellableEvent cancelable}, and does not {@linkplain HasResult have a result}.
+ * This event is not {@linkplain ICancellableEvent cancelable}.
*
- * This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
+ *
This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.
*
* @param the living entity that was rendered
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java
index 6e6c501f..a33fc96f 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java
@@ -16,6 +16,7 @@
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.common.util.TriState;
import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
/**
* This event is fired before an entity renderer renders the nameplate of an entity.
@@ -26,14 +27,12 @@
*/
public abstract class RenderNameTagEvent extends Event {
private final EntityRenderState renderState;
- protected final Component originalContent;
private final EntityRenderer, ?> entityRenderer;
private final float partialTick;
@ApiStatus.Internal
- public RenderNameTagEvent(EntityRenderState renderState, Component content, EntityRenderer, ?> entityRenderer, float partialTick) {
+ public RenderNameTagEvent(EntityRenderState renderState, EntityRenderer, ?> entityRenderer, float partialTick) {
this.renderState = renderState;
- this.originalContent = content;
this.entityRenderer = entityRenderer;
this.partialTick = partialTick;
}
@@ -69,12 +68,16 @@ public float getPartialTick() {
*/
public static class CanRender extends RenderNameTagEvent {
private final Entity entity;
+ @Nullable
+ private final Component originalContent;
+ @Nullable
private Component content;
private TriState canRender = TriState.DEFAULT;
- public CanRender(Entity entity, EntityRenderState renderState, Component content, EntityRenderer, ?> entityRenderer, float partialTick) {
- super(renderState, content, entityRenderer, partialTick);
+ public CanRender(Entity entity, EntityRenderState renderState, @Nullable Component content, EntityRenderer, ?> entityRenderer, float partialTick) {
+ super(renderState, entityRenderer, partialTick);
this.entity = entity;
+ this.originalContent = content;
this.content = content;
}
@@ -88,6 +91,7 @@ public Entity getEntity() {
/**
* {@return the original text on the nameplate}
*/
+ @Nullable
public Component getOriginalContent() {
return this.originalContent;
}
@@ -121,6 +125,7 @@ public void setContent(Component contents) {
/**
* {@return the text on the nameplate that will be rendered}
*/
+ @Nullable
public Component getContent() {
return this.content;
}
@@ -137,12 +142,14 @@ public Component getContent() {
* @see EntityRenderer
*/
public static class DoRender extends RenderNameTagEvent implements ICancellableEvent {
+ private final Component content;
private final PoseStack poseStack;
private final MultiBufferSource multiBufferSource;
private final int packedLight;
public DoRender(EntityRenderState renderState, Component content, EntityRenderer, ?> entityRenderer, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight, float partialTick) {
- super(renderState, content, entityRenderer, partialTick);
+ super(renderState, entityRenderer, partialTick);
+ this.content = content;
this.poseStack = poseStack;
this.multiBufferSource = multiBufferSource;
this.packedLight = packedLight;
@@ -152,7 +159,7 @@ public DoRender(EntityRenderState renderState, Component content, EntityRenderer
* {@return the text on the nameplate}
*/
public Component getContent() {
- return this.originalContent;
+ return this.content;
}
/**
diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java
index b2fd3652..ff811395 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java
@@ -11,7 +11,6 @@
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.client.renderer.entity.state.PlayerRenderState;
-import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.ICancellableEvent;
import net.neoforged.fml.LogicalSide;
import net.neoforged.neoforge.common.NeoForge;
@@ -31,15 +30,20 @@ protected RenderPlayerEvent(PlayerRenderState renderState, PlayerRenderer render
super(renderState, renderer, partialTick, poseStack, multiBufferSource, packedLight);
}
+ @Override
+ public PlayerRenderer getRenderer() {
+ return (PlayerRenderer) super.getRenderer();
+ }
+
/**
* Fired before the player is rendered.
* This can be used for rendering additional effects or suppressing rendering.
*
- * This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain Event.HasResult have a result}.
+ *
This event is {@linkplain ICancellableEvent cancellable}.
* If this event is cancelled, then the player will not be rendered and the corresponding
* {@link RenderPlayerEvent.Post} will not be fired.
*
- * This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
+ *
This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.
*/
public static class Pre extends RenderPlayerEvent implements ICancellableEvent {
@@ -52,9 +56,9 @@ public Pre(PlayerRenderState renderState, PlayerRenderer renderer, float partial
/**
* Fired after the player is rendered, if the corresponding {@link RenderPlayerEvent.Pre} is not cancelled.
*
- * This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain Event.HasResult have a result}.
+ * This event is not {@linkplain ICancellableEvent cancellable}.
*
- * This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
+ *
This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus},
* only on the {@linkplain LogicalSide#CLIENT logical client}.
*/
public static class Post extends RenderPlayerEvent {
diff --git a/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java b/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java
index e0d20a71..5a4004a7 100644
--- a/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java
+++ b/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java
@@ -6,6 +6,7 @@
package net.neoforged.neoforge.client.event;
import net.minecraft.client.resources.sounds.SoundInstance;
+import net.minecraft.client.sounds.MusicInfo;
import net.minecraft.sounds.Music;
import net.neoforged.bus.api.Event;
import net.neoforged.bus.api.ICancellableEvent;
@@ -25,7 +26,7 @@
*
* Higher priorities would likely be better suited for biome-based or dimension-based musics, whereas lower priority is likely good for specific structures or situations.
*
- * This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.
+ * This event is {@linkplain ICancellableEvent cancellable}.
* If the event is canceled, then whatever the latest music set was will be used as the music.
*
* This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
@@ -33,11 +34,11 @@
*
*/
public class SelectMusicEvent extends Event implements ICancellableEvent {
- private @Nullable Music music;
- private final Music originalMusic;
+ private @Nullable MusicInfo music;
+ private final MusicInfo originalMusic;
private final @Nullable SoundInstance playingMusic;
- public SelectMusicEvent(Music music, @Nullable SoundInstance playingMusic) {
+ public SelectMusicEvent(MusicInfo music, @Nullable SoundInstance playingMusic) {
this.music = music;
this.originalMusic = music;
this.playingMusic = playingMusic;
@@ -46,7 +47,7 @@ public SelectMusicEvent(Music music, @Nullable SoundInstance playingMusic) {
/**
* {@return the original situational music that was selected}
*/
- public Music getOriginalMusic() {
+ public MusicInfo getOriginalMusic() {
return originalMusic;
}
@@ -62,7 +63,7 @@ public SoundInstance getPlayingMusic() {
* {@return the Music to be played, or {@code null} if any playing music should be cancelled}
*/
@Nullable
- public Music getMusic() {
+ public MusicInfo getMusic() {
return music;
}
@@ -71,7 +72,7 @@ public Music getMusic() {
* If this was {@code null} but on the next tick isn't, the music given will be immediately played.
*
*/
- public void setMusic(@Nullable Music newMusic) {
+ public void setMusic(@Nullable MusicInfo newMusic) {
this.music = newMusic;
}
@@ -79,7 +80,7 @@ public void setMusic(@Nullable Music newMusic) {
* Sets the music and then cancels the event so that other listeners will not be invoked.
* Note that listeners using {@link SubscribeEvent#receiveCanceled()} will still be able to override this, but by default they will not
*/
- public void overrideMusic(@Nullable Music newMusic) {
+ public void overrideMusic(@Nullable MusicInfo newMusic) {
this.music = newMusic;
this.setCanceled(true);
}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java
index eee572c5..25674945 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java
@@ -11,6 +11,7 @@
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.core.BlockPos;
@@ -86,7 +87,7 @@ default ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, M
}
/**
- * Gets an ordered list of {@link RenderType render types} to use when drawing this item.
+ * Gets the {@link RenderType render type} to use when drawing this item.
* All render types using the {@link com.mojang.blaze3d.vertex.DefaultVertexFormat#NEW_ENTITY} format are supported.
*
* This method will only be called on the models returned by {@link #getRenderPasses(ItemStack)}.
@@ -95,18 +96,20 @@ default ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, M
*
* @see #getRenderPasses(ItemStack)
*/
- default List getRenderTypes(ItemStack itemStack) {
- return List.of(RenderTypeHelper.getFallbackItemRenderType(itemStack, self()));
+ default RenderType getRenderType(ItemStack itemStack) {
+ return RenderTypeHelper.getFallbackItemRenderType(itemStack, self());
}
/**
* Gets an ordered list of baked models used to render this model as an item.
- * Each of those models' render types will be queried via {@link #getRenderTypes(ItemStack)}.
+ * Each of those models' render type will be queried via {@link #getRenderType(ItemStack)}.
*
* By default, returns the model itself.
*
- * @see #getRenderTypes(ItemStack)
+ * @see #getRenderType(ItemStack)
+ * @deprecated Please migrate to {@link ItemModel}s, or if this is not possible contact us at NeoForge.
*/
+ @Deprecated(forRemoval = true, since = "1.21.4")
default List getRenderPasses(ItemStack itemStack) {
return List.of(self());
}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java
index 9268c200..f6391f93 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java
@@ -8,7 +8,6 @@
import net.minecraft.client.Camera;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.DimensionSpecialEffects;
-import net.minecraft.client.renderer.LightTexture;
import org.joml.Matrix4f;
/**
@@ -42,7 +41,7 @@ default boolean renderSky(ClientLevel level, int ticks, float partialTick, Matri
*
* @return true to prevent vanilla snow and rain rendering
*/
- default boolean renderSnowAndRain(ClientLevel level, int ticks, float partialTick, LightTexture lightTexture, double camX, double camY, double camZ) {
+ default boolean renderSnowAndRain(ClientLevel level, int ticks, float partialTick, double camX, double camY, double camZ) {
return false;
}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java
index d6b1da00..3d4d365b 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java
@@ -143,7 +143,7 @@ default void blitInscribed(ResourceLocation texture, int x, int y, int boundsWid
if (centerX) x += (w - boundsWidth) / 2;
}
- self().blit(RenderType::guiTextured, texture, x, y, boundsWidth, boundsHeight, 0, 0, rectWidth, rectHeight, rectWidth, rectHeight);
+ self().blit(RenderType::guiTextured, texture, x, y, 0, 0, boundsWidth, boundsHeight, rectWidth, rectHeight, rectWidth, rectHeight);
}
// TODO: 1.20.2: do we need to fix these or can we just remove them?
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java
index 90d84c39..c0ed814b 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java
@@ -5,23 +5,40 @@
package net.neoforged.neoforge.client.extensions;
-import java.util.function.Function;
+import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelResourceLocation;
-import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.Nullable;
+/**
+ * An extension to {@link ModelBaker} that allows for custom model baking.
+ */
public interface IModelBakerExtension {
- @Nullable
- UnbakedModel getTopLevelModel(ModelResourceLocation location);
+ default ModelBaker self() {
+ return (ModelBaker) this;
+ }
- BakedModel bake(ResourceLocation location, ModelState state, Function sprites);
-
- BakedModel bakeUncached(UnbakedModel model, ModelState state, Function sprites);
+ /**
+ * Gets the unbaked model for the given location.
+ *
+ * @param location The location of the model
+ * @return The unbaked model, or null if not found
+ */
+ @Nullable
+ UnbakedModel getModel(ResourceLocation location);
- Function getModelTextureGetter();
+ /**
+ * Finds a sprite for the given slot name.
+ *
+ * @param slots The texture slots
+ * @param slotName The name of the slot
+ * @return The sprite, or a missing reference sprite if not found
+ */
+ default TextureAtlasSprite findSprite(TextureSlots slots, String slotName) {
+ Material material = slots.getMaterial(slotName);
+ return material != null ? self().sprites().get(material) : self().sprites().reportMissingReference(slotName);
+ }
}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java
new file mode 100644
index 00000000..1170a443
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.extensions;
+
+import net.minecraft.util.context.ContextKey;
+import net.neoforged.neoforge.client.renderstate.BaseRenderState;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Extension class for render state objects. Implemented by {@link BaseRenderState} for
+ * simple class extension.
+ */
+public interface IRenderStateExtension {
+ /**
+ * Gets the object associated with the given key.
+ *
+ * @param key Static key reference object
+ * @return The object associated with the key or null if the key is not present.
+ * @param Type of render data
+ */
+ @Nullable
+ T getRenderData(ContextKey key);
+
+ /**
+ * Sets the object associated with the given key. Key should be stored statically for later retrieval of the object.
+ *
+ * @param key Static key reference object
+ * @param data Object to store for custom rendering
+ * @param Type of render data
+ */
+ void setRenderData(ContextKey key, @Nullable T data);
+
+ /**
+ * Gets the value or throws an exception. Should be used in cases where the data must be present.
+ *
+ * @param key Static key reference object
+ * @return The data associate with the key
+ * @param Type of render data
+ */
+ default T getRenderDataOrThrow(ContextKey key) {
+ T data = getRenderData(key);
+ if (data == null) {
+ throw new IllegalStateException("No value associated for key " + key);
+ }
+ return data;
+ }
+
+ /**
+ * Gets the value or returns the default object if an object is not present
+ *
+ * @param key Static key reference object
+ * @param defaultVal Default value if an object is not present
+ * @return Value from the render data or the given default value if value is not present
+ * @param Type of render data
+ */
+ default T getRenderDataOrDefault(ContextKey key, T defaultVal) {
+ T data = getRenderData(key);
+ if (data == null) {
+ return defaultVal;
+ }
+ return data;
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java
new file mode 100644
index 00000000..0d528dd6
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.extensions;
+
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
+import net.minecraft.client.resources.model.BakedModel;
+import net.minecraft.client.resources.model.ModelBaker;
+import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.util.context.ContextKeySet;
+import net.minecraft.util.context.ContextMap;
+import net.neoforged.neoforge.client.model.ExtendedUnbakedModel;
+import net.neoforged.neoforge.client.model.NeoForgeModelProperties;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Extension type for the {@link UnbakedModel} interface.
+ */
+public interface IUnbakedModelExtension {
+ private UnbakedModel self() {
+ return (UnbakedModel) this;
+ }
+
+ /**
+ * {@code bake} override with additional context.
+ * Consider inheriting from {@link ExtendedUnbakedModel} which overrides the vanilla {@code bake} method.
+ *
+ * @param additionalProperties additional properties provided by NeoForge or mods
+ */
+ default BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) {
+ return self().bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms);
+ }
+
+ /**
+ * Appends additional properties for this model to the builder.
+ *
+ * This method will already have been called on the parent models.
+ * It can modify the properties added by a parent model and/or add its own.
+ * This ensures that the properties are merged across the model parent-child chain.
+ *
+ *
The context map containing all the properties will be passed as the last parameter to
+ * {@link #bake(TextureSlots, ModelBaker, ModelState, boolean, boolean, ItemTransforms, ContextMap)}.
+ *
+ * @see NeoForgeModelProperties
+ */
+ @ApiStatus.OverrideOnly
+ default void fillAdditionalProperties(ContextMap.Builder propertiesBuilder) {}
+
+ /**
+ * Resolves additional properties by walking the model child-parent chain and calling {@link #fillAdditionalProperties(ContextMap.Builder)}.
+ */
+ static ContextMap getTopAdditionalProperties(UnbakedModel topModel) {
+ var builder = new ContextMap.Builder();
+ fillAdditionalProperties(topModel, builder);
+ return builder.create(ContextKeySet.EMPTY);
+ }
+
+ private static void fillAdditionalProperties(@Nullable UnbakedModel model, ContextMap.Builder propertiesBuilder) {
+ if (model != null) {
+ fillAdditionalProperties(model.getParent(), propertiesBuilder);
+ model.fillAdditionalProperties(propertiesBuilder);
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java
index d9df521c..f53cfe25 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java
@@ -13,6 +13,7 @@
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.FogParameters;
import net.minecraft.client.renderer.FogRenderer;
+import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.ScreenEffectRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceLocation;
@@ -150,10 +151,10 @@ default ResourceLocation getRenderOverlayTexture(Minecraft mc) {
* @param mc the client instance
* @param poseStack the transformations representing the current rendering position
*/
- default void renderOverlay(Minecraft mc, PoseStack poseStack) {
+ default void renderOverlay(Minecraft mc, PoseStack poseStack, MultiBufferSource buffers) {
ResourceLocation texture = this.getRenderOverlayTexture(mc);
if (texture != null)
- ScreenEffectRenderer.renderFluid(mc, poseStack, texture);
+ ScreenEffectRenderer.renderFluid(mc, poseStack, buffers, texture);
}
/**
diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java
index 34018888..71032b91 100644
--- a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java
+++ b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java
@@ -6,14 +6,14 @@
package net.neoforged.neoforge.client.extensions.common;
import com.mojang.blaze3d.vertex.PoseStack;
-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.player.LocalPlayer;
-import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
import net.minecraft.client.renderer.entity.layers.EquipmentLayerRenderer;
-import net.minecraft.client.resources.model.BakedModel;
+import net.minecraft.client.resources.model.EquipmentClientInfo;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.ItemTags;
import net.minecraft.util.ARGB;
import net.minecraft.world.InteractionHand;
@@ -24,8 +24,6 @@
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
-import net.minecraft.world.item.equipment.EquipmentModel;
-import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.fml.LogicalSide;
import net.neoforged.neoforge.client.IArmPoseTransformer;
import org.jetbrains.annotations.Nullable;
@@ -96,15 +94,15 @@ default boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player,
* @param layerType The slot the item is in
* @param original The original armor model. Will have attributes set.
* @return A HumanoidModel to be rendered. Relevant properties are to be copied over by the caller.
- * @see #getGenericArmorModel(ItemStack, EquipmentModel.LayerType, Model)
+ * @see #getGenericArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model)
*/
- default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentModel.LayerType layerType, Model original) {
+ default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model original) {
return original;
}
/**
* Queries the armor model for this item when it's equipped. Useful in place of
- * {@link #getHumanoidArmorModel(ItemStack, EquipmentModel.LayerType, Model)} for wrapping the original
+ * {@link #getHumanoidArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model)} for wrapping the original
* model or returning anything non-standard.
*
* If you override this method you are responsible for copying any properties you care about from the original model.
@@ -113,9 +111,9 @@ default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentModel.LayerTyp
* @param layerType The slot the item is in
* @param original The original armor model. Will have attributes set.
* @return A Model to be rendered. Relevant properties must be copied over manually.
- * @see #getHumanoidArmorModel(ItemStack, EquipmentModel.LayerType, Model)
+ * @see #getHumanoidArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model)
*/
- default Model getGenericArmorModel(ItemStack itemStack, EquipmentModel.LayerType layerType, Model original) {
+ default Model getGenericArmorModel(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model original) {
Model replacement = getHumanoidArmorModel(itemStack, layerType, original);
if (replacement != original) {
// FIXME: equipment rendering deals with a plain Model now
@@ -155,18 +153,6 @@ default void setupModelAnimations(LivingEntity livingEntity, ItemStack itemStack
*/
default void renderHelmetOverlay(ItemStack stack, Player player, int width, int height, float partialTick) {}
- /**
- * Queries this item's renderer.
- *
- * Only used if {@link BakedModel#isCustomRenderer()} returns {@code true} or {@link BlockState#getRenderShape()}
- * returns {@link net.minecraft.world.level.block.RenderShape#ENTITYBLOCK_ANIMATED}.
- *
- * By default, returns vanilla's block entity renderer.
- */
- default BlockEntityWithoutLevelRenderer getCustomRenderer() {
- return Minecraft.getInstance().getItemRenderer().getBlockEntityRenderer();
- }
-
/**
* {@return Whether the item should bob when rendered in the world as an entity}
*
@@ -201,13 +187,13 @@ default boolean shouldSpreadAsEntity(ItemStack stack) {
* performance
* @return a custom color for the layer, in ARGB format, or 0 to skip rendering
*/
- default int getArmorLayerTintColor(ItemStack stack, EquipmentModel.Layer layer, int layerIdx, int fallbackColor) {
+ default int getArmorLayerTintColor(ItemStack stack, EquipmentClientInfo.Layer layer, int layerIdx, int fallbackColor) {
return EquipmentLayerRenderer.getColorForLayer(layer, fallbackColor);
}
/**
* Called once per render pass of equipped armor items, regardless of the number of layers; the return value of this
- * method is passed to {@link #getArmorLayerTintColor(ItemStack, EquipmentModel.Layer, int, int)} as
+ * method is passed to {@link #getArmorLayerTintColor(ItemStack, EquipmentClientInfo.Layer, int, int)} as
* the {@code fallbackColor} parameter.
*
* You can override this method for your custom armor item to provide an alternative default color for the item when
@@ -220,6 +206,24 @@ default int getDefaultDyeColor(ItemStack stack) {
return stack.is(ItemTags.DYEABLE) ? ARGB.opaque(DyedItemColor.getOrDefault(stack, 0)) : 0;
}
+ /**
+ * Called by RenderBiped and RenderPlayer to determine the armor texture that
+ * should be used for the currently equipped item. This will be called on
+ * stacks with the {@link DataComponents#EQUIPPABLE} component.
+ *
+ * Returning null from this function will use the default value.
+ *
+ * @param stack ItemStack for the equipped armor
+ * @param type The layer type of the armor
+ * @param layer The armor layer
+ * @param _default The default texture determined by the equipment renderer
+ * @return Path of texture to bind, or null to use default
+ */
+ @Nullable
+ default ResourceLocation getArmorTexture(ItemStack stack, EquipmentClientInfo.LayerType type, EquipmentClientInfo.Layer layer, ResourceLocation _default) {
+ return null;
+ }
+
enum FontContext {
/**
* Used to display the amount of items in the {@link ItemStack}.
diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java
index becd2d4c..3ad5d25f 100644
--- a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java
+++ b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java
@@ -11,6 +11,7 @@
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import com.electronwill.nightconfig.core.UnmodifiableConfig.Entry;
import com.google.common.collect.ImmutableList;
+import com.mojang.datafixers.util.Function4;
import com.mojang.realmsclient.RealmsMainScreen;
import com.mojang.serialization.Codec;
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
@@ -53,8 +54,6 @@
import net.minecraft.client.gui.screens.options.OptionsSubScreen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.resources.language.I18n;
-import net.minecraft.data.models.blockstates.PropertyDispatch.QuadFunction;
-import net.minecraft.data.models.blockstates.PropertyDispatch.TriFunction;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
@@ -87,9 +86,9 @@
*
As an entry point for your custom configuration screen that handles fetching your configs, matching {@link Type} to the current game, enforcing level and game restarts, etc.
* As a ready-made system but extensible that works out of the box with all configs that use the {@link ModConfigSpec} system and don't do anything overly weird with it.
*
- * For the former one, use the 3-argument constructor {@link #ConfigurationScreen(ModContainer, Screen, TriFunction)} and return your own screen from the TriFunction. For the latter,
+ * For the former one, use the 3-argument constructor {@link #ConfigurationScreen(ModContainer, Screen, Function4)} and return your own screen from the Function4. For the latter,
* use either the 2-argument constructor {@link #ConfigurationScreen(ModContainer, Screen)} if you don't need to extend the system, or the 3-argument one and return a subclass of
- * {@link ConfigurationSectionScreen} from the TriFunction.
+ * {@link ConfigurationSectionScreen} from the Function4.
*
* In any case, register your configuration screen in your client mod class like this:
*
@@ -260,7 +259,7 @@ public void finish() {
protected static final TranslationChecker translationChecker = new TranslationChecker();
protected final ModContainer mod;
- private final QuadFunction sectionScreen;
+ private final Function4 sectionScreen;
public RestartType needsRestart = RestartType.NONE;
// If there is only one config type (and it can be edited, we show that instantly on the way "down" and want to close on the way "up".
@@ -275,8 +274,7 @@ public ConfigurationScreen(final ModContainer mod, final Screen parent, Configur
this(mod, parent, (a, b, c, d) -> new ConfigurationSectionScreen(a, b, c, d, filter));
}
- @SuppressWarnings("resource")
- public ConfigurationScreen(final ModContainer mod, final Screen parent, QuadFunction sectionScreen) {
+ public ConfigurationScreen(final ModContainer mod, final Screen parent, Function4 sectionScreen) {
super(parent, Minecraft.getInstance().options, Component.translatable(translationChecker.check(mod.getModId() + ".configuration.title", LANG_PREFIX + "title"), mod.getModInfo().getDisplayName()));
this.mod = mod;
this.sectionScreen = sectionScreen;
@@ -1389,6 +1387,16 @@ protected void checkButtons() {
protected void updateWidgetNarration(final NarrationElementOutput pNarrationElementOutput) {
// TODO I have no idea. Help?
}
+
+ @Override
+ protected int contentHeight() {
+ return 0; // TODO 1.21.4 no idea
+ }
+
+ @Override
+ protected double scrollRate() {
+ return 4.0; // TODO 1.21.4 no idea
+ }
}
}
diff --git a/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java
index 77cf1328..741c3025 100644
--- a/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java
+++ b/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java
@@ -115,7 +115,7 @@ public static class LoadingEntryList extends ObjectSelectionList logoData = selectedMod.getLogoFile().map(logoFile -> {
- TextureManager tm = this.minecraft.getTextureManager();
- final Pack.ResourcesSupplier resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId())
- .orElse(ResourcePackLoader.getPackFor("neoforge").orElseThrow(() -> new RuntimeException("Can't find neoforge, WHAT!")));
- try (PackResources packResources = resourcePack.openPrimary(new PackLocationInfo("mod/" + selectedMod.getModId(), Component.empty(), PackSource.BUILT_IN, Optional.empty()))) {
- NativeImage logo = null;
- IoSupplier logoResource = packResources.getRootResource(logoFile.split("[/\\\\]"));
- if (logoResource != null)
- logo = NativeImage.read(logoResource.get());
- if (logo != null) {
-
- return Pair.of(tm.register("modlogo", new DynamicTexture(logo) {
- @Override
- public void upload() {
- this.bind();
- NativeImage td = this.getPixels();
- // Use custom "blur" value which controls texture filtering (nearest-neighbor vs linear)
- this.getPixels().upload(0, 0, 0, 0, 0, td.getWidth(), td.getHeight(), selectedMod.getLogoBlur(), false, false, false);
- }
- }), new Size2i(logo.getWidth(), logo.getHeight()));
- }
- } catch (IOException | IllegalArgumentException e) {}
- return Pair.of(null, new Size2i(0, 0));
- }).orElse(Pair.of(null, new Size2i(0, 0)));
+ Pair logoData;
+
+ if (selectedMod.getModId().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
+ logoData = Pair.of(LogoRenderer.MINECRAFT_LOGO, new Size2i(LogoRenderer.LOGO_TEXTURE_WIDTH, LogoRenderer.LOGO_TEXTURE_HEIGHT));
+ } else {
+ logoData = selectedMod.getLogoFile().map(logoFile -> {
+ TextureManager tm = this.minecraft.getTextureManager();
+ final Pack.ResourcesSupplier resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId())
+ .orElse(ResourcePackLoader.getPackFor("neoforge").orElseThrow(() -> new RuntimeException("Can't find neoforge, WHAT!")));
+ try (PackResources packResources = resourcePack.openPrimary(new PackLocationInfo("mod/" + selectedMod.getModId(), Component.empty(), PackSource.BUILT_IN, Optional.empty()))) {
+ NativeImage logo = null;
+ IoSupplier logoResource = packResources.getRootResource(logoFile.split("[/\\\\]"));
+ if (logoResource != null)
+ logo = NativeImage.read(logoResource.get());
+ if (logo != null) {
+ var textureId = ResourceLocation.fromNamespaceAndPath("neoforge", "modlogo");
+ tm.register(textureId, new DynamicTexture(logo) {
+ @Override
+ public void upload() {
+ this.bind();
+ NativeImage td = this.getPixels();
+ // Use custom "blur" value which controls texture filtering (nearest-neighbor vs linear)
+ // TODO 1.21.4 restore selectedMod.getLogoBlur() check
+ this.getPixels().upload(0, 0, 0, 0, 0, td.getWidth(), td.getHeight(), false);
+ }
+ });
+
+ return Pair.of(textureId, new Size2i(logo.getWidth(), logo.getHeight()));
+ }
+ } catch (IOException | IllegalArgumentException e) {}
+ return Pair.of(null, new Size2i(0, 0));
+ }).orElse(Pair.of(null, new Size2i(0, 0)));
+ }
lines.add(selectedMod.getDisplayName());
lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.version", MavenVersionTranslator.artifactVersionToString(selectedMod.getVersion())));
@@ -416,7 +426,7 @@ public void upload() {
lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.updateavailable", vercheck.url() == null ? "" : vercheck.url()).replace("\r\n", "\n"));
lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.license", selectedMod.getOwningFile().getLicense()).replace("\r\n", "\n"));
lines.add(null);
- lines.add(FMLTranslations.parseMessageWithFallback("fml.menu.mods.info.description." + selectedMod.getModId(), selectedMod::getDescription));
+ lines.add(FMLTranslations.getPattern("fml.menu.mods.info.description." + selectedMod.getModId(), selectedMod::getDescription));
/* Removed because people bitched that this information was misleading.
lines.add(null);
diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java
new file mode 100644
index 00000000..fb3cbe29
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.gui;
+
+import java.util.List;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import net.minecraft.ChatFormatting;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.components.ContainerObjectSelectionList;
+import net.minecraft.client.gui.components.CycleButton;
+import net.minecraft.client.gui.components.MultiLineTextWidget;
+import net.minecraft.client.gui.components.StringWidget;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.layouts.LayoutSettings;
+import net.minecraft.client.gui.layouts.LinearLayout;
+import net.minecraft.client.gui.narration.NarratableEntry;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.ExperimentsScreen;
+import net.minecraft.network.chat.CommonComponents;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.packs.repository.PackRepository;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+@ApiStatus.Internal
+public class ScrollableExperimentsScreen extends ExperimentsScreen {
+ private static final int DEFAULT_LIST_HEIGHT = 121;
+ private static final int ROW_PADDING = 4;
+ private static final int LIST_PADDING = ROW_PADDING + 34;
+ private static final int ENTRY_HEIGHT = 42;
+
+ @Nullable
+ private ExperimentSelectionList selectionList;
+ @Nullable
+ private LinearLayout listLayout;
+
+ public ScrollableExperimentsScreen(Screen parent, PackRepository packRepository, Consumer output) {
+ super(parent, packRepository, output);
+ }
+
+ @Override
+ protected void init() {
+ this.layout.addTitleHeader(TITLE, this.font);
+
+ LinearLayout contentLayout = this.layout.addToContents(LinearLayout.vertical(), LayoutSettings::alignVerticallyTop);
+ contentLayout.addChild(
+ new MultiLineTextWidget(INFO, this.font).setMaxWidth(MAIN_CONTENT_WIDTH),
+ settings -> settings.paddingBottom(15).alignHorizontallyCenter());
+
+ this.listLayout = contentLayout.addChild(LinearLayout.vertical());
+ this.selectionList = new ExperimentSelectionList(this.minecraft);
+ this.packs.forEach((pack, selected) -> selectionList.addEntry(new ExperimentSelectionList.ExperimentEntry(
+ getHumanReadableTitle(pack),
+ () -> this.packs.getBoolean(pack),
+ flag -> this.packs.put(pack, flag.booleanValue()),
+ pack.getDescription())));
+ this.listLayout.addChild(selectionList);
+
+ LinearLayout footerLayout = this.layout.addToFooter(LinearLayout.horizontal().spacing(8));
+ footerLayout.addChild(Button.builder(CommonComponents.GUI_DONE, btn -> this.onDone()).build());
+ footerLayout.addChild(Button.builder(CommonComponents.GUI_CANCEL, btn -> this.onClose()).build());
+
+ this.layout.visitWidgets(this::addRenderableWidget);
+ this.repositionElements();
+ }
+
+ @Override
+ protected void repositionElements() {
+ if (this.selectionList != null) {
+ // Reset list height to empirical default because layouts can't squish elements to fit...
+ this.selectionList.setHeight(DEFAULT_LIST_HEIGHT);
+ }
+ super.repositionElements();
+ if (this.selectionList != null && this.listLayout != null) {
+ this.selectionList.setHeight(this.layout.getContentHeight() - this.listLayout.getY());
+ this.selectionList.setPosition(this.listLayout.getX(), this.listLayout.getY());
+ this.selectionList.refreshScrollAmount();
+ }
+ }
+
+ private static class ExperimentSelectionList extends ContainerObjectSelectionList {
+ public ExperimentSelectionList(Minecraft mc) {
+ super(mc, ExperimentsScreen.MAIN_CONTENT_WIDTH + LIST_PADDING, DEFAULT_LIST_HEIGHT, 0, ENTRY_HEIGHT);
+ }
+
+ @Override
+ public int getRowWidth() {
+ return ExperimentsScreen.MAIN_CONTENT_WIDTH + ROW_PADDING;
+ }
+
+ @Override
+ protected int addEntry(ExperimentEntry entry) {
+ return super.addEntry(entry);
+ }
+
+ private static class ExperimentEntry extends ContainerObjectSelectionList.Entry {
+ private static final int BUTTON_WIDTH = 44;
+ private static final int TITLE_Y = 6;
+ private static final int DESCRIPTION_Y = 20;
+
+ private final StringWidget titleWidget;
+ private final MultiLineTextWidget descriptionWidget;
+ private final CycleButton button;
+ private final List children;
+
+ public ExperimentEntry(Component title, BooleanSupplier selectedSupplier, Consumer selectedSetter, Component description) {
+ this.titleWidget = new StringWidget(title, Minecraft.getInstance().font).alignLeft();
+ this.descriptionWidget = new MultiLineTextWidget(description.copy().withStyle(ChatFormatting.GRAY), Minecraft.getInstance().font)
+ .setMaxRows(2);
+ this.button = CycleButton.onOffBuilder(selectedSupplier.getAsBoolean())
+ .displayOnlyValue()
+ .withCustomNarration(btn -> CommonComponents.joinForNarration(title, btn.createDefaultNarrationMessage(), description))
+ .create(0, 0, BUTTON_WIDTH, Button.DEFAULT_HEIGHT, Component.empty(), (btn, val) -> selectedSetter.accept(val));
+ this.children = List.of(titleWidget, descriptionWidget, this.button);
+ }
+
+ @Override
+ public void render(GuiGraphics graphics, int entryIdx, int top, int left, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTick) {
+ this.titleWidget.setPosition(left, top + TITLE_Y);
+ this.descriptionWidget.setPosition(left, top + DESCRIPTION_Y);
+ this.descriptionWidget.setMaxWidth(entryWidth - this.button.getWidth());
+ this.button.setPosition(left + entryWidth - this.button.getWidth() - ROW_PADDING, top);
+
+ this.titleWidget.render(graphics, mouseX, mouseY, partialTick);
+ this.descriptionWidget.render(graphics, mouseX, mouseY, partialTick);
+ this.button.render(graphics, mouseX, mouseY, partialTick);
+ }
+
+ @Override
+ public List extends GuiEventListener> children() {
+ return this.children;
+ }
+
+ @Override
+ public List extends NarratableEntry> narratables() {
+ return this.children;
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java b/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java
index cd6626c3..831ac723 100644
--- a/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java
+++ b/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java
@@ -40,7 +40,7 @@ public ModListWidget(ModListScreen parent, int listWidth, int top, int bottom) {
}
@Override
- protected int getScrollbarPosition() {
+ protected int scrollBarX() {
return this.listWidth;
}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java
new file mode 100644
index 00000000..a00f3502
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import net.minecraft.client.data.models.model.TextureSlot;
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.resources.model.BakedModel;
+import net.minecraft.client.resources.model.ModelBaker;
+import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.SimpleBakedModel;
+import net.minecraft.util.context.ContextMap;
+import net.neoforged.neoforge.client.RenderTypeGroup;
+
+/**
+ * @deprecated Extend {@link ExtendedUnbakedModel} directly instead, and use {@link SimpleBakedModel.Builder} if appropriate.
+ */
+@Deprecated(forRemoval = true, since = "1.21.4")
+public abstract class AbstractSimpleUnbakedModel implements ExtendedUnbakedModel {
+ @Override
+ public BakedModel bake(TextureSlots slots,
+ ModelBaker baker,
+ ModelState state,
+ boolean useAmbientOcclusion,
+ boolean usesBlockLight,
+ ItemTransforms transforms,
+ ContextMap additionalProperties) {
+ TextureAtlasSprite particle = baker.findSprite(slots, TextureSlot.PARTICLE.getId());
+ var renderTypes = additionalProperties.getOrDefault(NeoForgeModelProperties.RENDER_TYPE, RenderTypeGroup.EMPTY);
+
+ IModelBuilder> builder = IModelBuilder.of(useAmbientOcclusion, usesBlockLight, isGui3d(),
+ transforms, particle, renderTypes);
+
+ addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms);
+
+ return builder.build();
+ }
+
+ @Override
+ public void resolveDependencies(Resolver p_387087_) {
+ //Has no dependencies
+ }
+
+ public abstract void addQuads(
+ IModelBuilder> builder,
+ TextureSlots slots,
+ ModelBaker baker,
+ ModelState state,
+ boolean useAmbientOcclusion,
+ boolean usesBlockLight,
+ ItemTransforms transforms);
+
+ protected boolean isGui3d() {
+ return true;
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java b/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java
deleted file mode 100644
index 01417638..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model;
-
-import com.mojang.blaze3d.vertex.PoseStack;
-import java.util.List;
-import net.minecraft.client.renderer.RenderType;
-import net.minecraft.client.renderer.block.model.BakedOverrides;
-import net.minecraft.client.renderer.block.model.BakedQuad;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
-import net.minecraft.util.RandomSource;
-import net.minecraft.world.item.ItemDisplayContext;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.level.BlockAndTintGetter;
-import net.minecraft.world.level.block.state.BlockState;
-import net.neoforged.neoforge.client.ChunkRenderTypeSet;
-import net.neoforged.neoforge.client.model.data.ModelData;
-import net.neoforged.neoforge.common.util.TriState;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Wrapper for {@link BakedModel} which delegates all operations to its parent.
- *
- * Useful for creating wrapper baked models which only override certain properties.
- */
-public abstract class BakedModelWrapper implements BakedModel {
- protected final T originalModel;
-
- public BakedModelWrapper(T originalModel) {
- this.originalModel = originalModel;
- }
-
- @Override
- public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) {
- return originalModel.getQuads(state, side, rand);
- }
-
- @Override
- public boolean useAmbientOcclusion() {
- return originalModel.useAmbientOcclusion();
- }
-
- @Override
- public TriState useAmbientOcclusion(BlockState state, ModelData data, RenderType renderType) {
- return originalModel.useAmbientOcclusion(state, data, renderType);
- }
-
- @Override
- public boolean isGui3d() {
- return originalModel.isGui3d();
- }
-
- @Override
- public boolean usesBlockLight() {
- return originalModel.usesBlockLight();
- }
-
- @Override
- public boolean isCustomRenderer() {
- return originalModel.isCustomRenderer();
- }
-
- @Override
- public TextureAtlasSprite getParticleIcon() {
- return originalModel.getParticleIcon();
- }
-
- @Override
- public ItemTransforms getTransforms() {
- return originalModel.getTransforms();
- }
-
- @Override
- public BakedOverrides overrides() {
- return originalModel.overrides();
- }
-
- @Override
- public BakedModel applyTransform(ItemDisplayContext cameraTransformType, PoseStack poseStack, boolean applyLeftHandTransform) {
- return originalModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform);
- }
-
- @Override
- public TextureAtlasSprite getParticleIcon(ModelData data) {
- return originalModel.getParticleIcon(data);
- }
-
- @Override
- public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) {
- return originalModel.getQuads(state, side, rand, extraData, renderType);
- }
-
- @Override
- public ModelData getModelData(BlockAndTintGetter level, BlockPos pos, BlockState state, ModelData modelData) {
- return originalModel.getModelData(level, pos, state, modelData);
- }
-
- @Override
- public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) {
- return originalModel.getRenderTypes(state, rand, data);
- }
-
- @Override
- public List getRenderTypes(ItemStack itemStack) {
- return originalModel.getRenderTypes(itemStack);
- }
-
- @Override
- public List getRenderPasses(ItemStack itemStack) {
- return originalModel.getRenderPasses(itemStack);
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java
new file mode 100644
index 00000000..1d61e20c
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
+import net.minecraft.client.resources.model.BakedModel;
+import net.minecraft.client.resources.model.ModelBaker;
+import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.util.context.ContextMap;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class DelegateUnbakedModel implements ExtendedUnbakedModel {
+ protected final UnbakedModel wrapped;
+
+ protected DelegateUnbakedModel(UnbakedModel wrapped) {
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) {
+ return this.wrapped.bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms, additionalProperties);
+ }
+
+ @Override
+ public void resolveDependencies(Resolver resolver) {
+ this.wrapped.resolveDependencies(resolver);
+ }
+
+ @Nullable
+ @Override
+ public Boolean getAmbientOcclusion() {
+ return this.wrapped.getAmbientOcclusion();
+ }
+
+ @Nullable
+ @Override
+ public GuiLight getGuiLight() {
+ return this.wrapped.getGuiLight();
+ }
+
+ @Nullable
+ @Override
+ public ItemTransforms getTransforms() {
+ return this.wrapped.getTransforms();
+ }
+
+ @Override
+ public TextureSlots.Data getTextureSlots() {
+ return this.wrapped.getTextureSlots();
+ }
+
+ @Nullable
+ @Override
+ public UnbakedModel getParent() {
+ return this.wrapped.getParent();
+ }
+
+ @Override
+ public void fillAdditionalProperties(ContextMap.Builder propertiesBuilder) {
+ this.wrapped.fillAdditionalProperties(propertiesBuilder);
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java b/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java
deleted file mode 100644
index b15c779a..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model;
-
-import com.google.common.collect.Maps;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonObject;
-import com.mojang.math.Transformation;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import net.minecraft.client.color.item.ItemColor;
-import net.minecraft.client.multiplayer.ClientLevel;
-import net.minecraft.client.renderer.RenderType;
-import net.minecraft.client.renderer.block.model.BakedOverrides;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.BlockModelRotation;
-import net.minecraft.client.resources.model.ItemModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.core.registries.BuiltInRegistries;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.util.GsonHelper;
-import net.minecraft.world.entity.LivingEntity;
-import net.minecraft.world.item.ItemStack;
-import net.minecraft.world.level.material.Fluid;
-import net.minecraft.world.level.material.Fluids;
-import net.neoforged.neoforge.client.ClientHooks;
-import net.neoforged.neoforge.client.NeoForgeRenderTypes;
-import net.neoforged.neoforge.client.RenderTypeGroup;
-import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import net.neoforged.neoforge.client.model.geometry.StandaloneGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper;
-import net.neoforged.neoforge.fluids.FluidUtil;
-import org.jetbrains.annotations.Nullable;
-import org.joml.Quaternionf;
-import org.joml.Vector3f;
-
-/**
- * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid.
- *
- * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask).
- * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting.
- *
- * Fluid tinting requires registering a separate {@link ItemColor}. An implementation is provided in {@link Colors}.
- *
- * @see Colors
- */
-public class DynamicFluidContainerModel implements IUnbakedGeometry {
- // Depth offsets to prevent Z-fighting
- private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf());
- private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf());
-
- private final Fluid fluid;
- private final boolean flipGas;
- private final boolean coverIsMask;
- private final boolean applyFluidLuminosity;
-
- private DynamicFluidContainerModel(Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) {
- this.fluid = fluid;
- this.flipGas = flipGas;
- this.coverIsMask = coverIsMask;
- this.applyFluidLuminosity = applyFluidLuminosity;
- }
-
- public static RenderTypeGroup getLayerRenderTypes(boolean unlit) {
- return new RenderTypeGroup(RenderType.translucent(), unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get());
- }
-
- /**
- * Returns a new ModelDynBucket representing the given fluid, but with the same
- * other properties (flipGas, tint, coverIsMask).
- */
- public DynamicFluidContainerModel withFluid(Fluid newFluid) {
- return new DynamicFluidContainerModel(newFluid, flipGas, coverIsMask, applyFluidLuminosity);
- }
-
- @Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- Material particleLocation = context.hasMaterial("particle") ? context.getMaterial("particle") : null;
- Material baseLocation = context.hasMaterial("base") ? context.getMaterial("base") : null;
- Material fluidMaskLocation = context.hasMaterial("fluid") ? context.getMaterial("fluid") : null;
- Material coverLocation = context.hasMaterial("cover") ? context.getMaterial("cover") : null;
-
- TextureAtlasSprite baseSprite = baseLocation != null ? spriteGetter.apply(baseLocation) : null;
- TextureAtlasSprite fluidSprite = fluid != Fluids.EMPTY ? spriteGetter.apply(ClientHooks.getBlockMaterial(IClientFluidTypeExtensions.of(fluid).getStillTexture())) : null;
- TextureAtlasSprite coverSprite = (coverLocation != null && (!coverIsMask || baseLocation != null)) ? spriteGetter.apply(coverLocation) : null;
-
- TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null;
-
- if (particleSprite == null) particleSprite = fluidSprite;
- if (particleSprite == null) particleSprite = baseSprite;
- if (particleSprite == null && !coverIsMask) particleSprite = coverSprite;
-
- // If the fluid is lighter than air, rotate 180deg to turn it upside down
- if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) {
- modelState = new SimpleModelState(
- modelState.getRotation().compose(
- new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null)));
- }
-
- // We need to disable GUI 3D and block lighting for this to render properly
- var itemContext = StandaloneGeometryBakingContext.builder(context).withGui3d(false).withUseBlockLight(false).build(ResourceLocation.fromNamespaceAndPath("neoforge", "dynamic_fluid_container"));
- var modelBuilder = CompositeModel.Baked.builder(itemContext, particleSprite, context.getTransforms());
-
- var normalRenderTypes = getLayerRenderTypes(false);
-
- if (baseLocation != null && baseSprite != null) {
- // Base texture
- var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite);
- var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState);
- modelBuilder.addQuads(normalRenderTypes, quads);
- }
-
- if (fluidMaskLocation != null && fluidSprite != null) {
- TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation);
- if (templateSprite != null) {
- // Fluid layer
- var transformedState = new SimpleModelState(modelState.getRotation().compose(FLUID_TRANSFORM), modelState.isUvLocked());
- var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask
- var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture
-
- var emissive = applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0;
- var renderTypes = getLayerRenderTypes(emissive);
- if (emissive) QuadTransformers.settingMaxEmissivity().processInPlace(quads);
-
- modelBuilder.addQuads(renderTypes, quads);
- }
- }
-
- if (coverSprite != null) {
- var sprite = coverIsMask ? baseSprite : coverSprite;
- if (sprite != null) {
- // Cover/overlay
- var transformedState = new SimpleModelState(modelState.getRotation().compose(COVER_TRANSFORM), modelState.isUvLocked());
- var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask
- var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture
- modelBuilder.addQuads(normalRenderTypes, quads);
- }
- }
-
- modelBuilder.setParticle(particleSprite);
-
- BakedModel bakedModel = modelBuilder.build();
- var bakedOverrides = new ContainedFluidOverrideHandler(new BakedOverrides(baker, overrides, spriteGetter), bakedModel, baker, itemContext, this);
- return new ItemModel.BakedModelWithOverrides(bakedModel, bakedOverrides);
- }
-
- public static final class Loader implements IGeometryLoader {
- public static final Loader INSTANCE = new Loader();
-
- private Loader() {}
-
- @Override
- public DynamicFluidContainerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
- if (!jsonObject.has("fluid"))
- throw new RuntimeException("Bucket model requires 'fluid' value.");
-
- ResourceLocation fluidName = ResourceLocation.parse(jsonObject.get("fluid").getAsString());
-
- Fluid fluid = BuiltInRegistries.FLUID.getValue(fluidName);
-
- boolean flip = GsonHelper.getAsBoolean(jsonObject, "flip_gas", false);
- boolean coverIsMask = GsonHelper.getAsBoolean(jsonObject, "cover_is_mask", true);
- boolean applyFluidLuminosity = GsonHelper.getAsBoolean(jsonObject, "apply_fluid_luminosity", true);
-
- // create new model with correct liquid
- return new DynamicFluidContainerModel(fluid, flip, coverIsMask, applyFluidLuminosity);
- }
- }
-
- private static final class ContainedFluidOverrideHandler extends BakedOverrides {
- private final Map cache = Maps.newHashMap(); // contains all the baked models since they'll never change
- private final BakedOverrides nested;
- private final BakedModel baseModel;
- private final ModelBaker baker;
- private final IGeometryBakingContext owner;
- private final DynamicFluidContainerModel parent;
-
- private ContainedFluidOverrideHandler(BakedOverrides nested, BakedModel baseModel, ModelBaker baker, IGeometryBakingContext owner, DynamicFluidContainerModel parent) {
- this.nested = nested;
- this.baseModel = baseModel;
- this.baker = baker;
- this.owner = owner;
- this.parent = parent;
- }
-
- @Override
- @Nullable
- public BakedModel findOverride(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) {
- BakedModel overridden = nested.findOverride(stack, level, entity, seed);
- if (overridden != null) return overridden;
-
- return FluidUtil.getFluidContained(stack)
- .map(fluidStack -> {
- Fluid fluid = fluidStack.getFluid();
- String name = BuiltInRegistries.FLUID.getKey(fluid).toString();
-
- if (!cache.containsKey(name)) {
- DynamicFluidContainerModel unbaked = this.parent.withFluid(fluid);
- BakedModel bakedModel = unbaked.bake(owner, baker, Material::sprite, BlockModelRotation.X0_Y0, List.of());
- cache.put(name, bakedModel);
- return bakedModel;
- }
-
- return cache.get(name);
- })
- // not a fluid item apparently
- .orElse(baseModel); // empty bucket
- }
- }
-
- public static class Colors implements ItemColor {
- @Override
- public int getColor(ItemStack stack, int tintIndex) {
- if (tintIndex != 1) return 0xFFFFFFFF;
- return FluidUtil.getFluidContained(stack)
- .map(fluidStack -> IClientFluidTypeExtensions.of(fluidStack.getFluid()).getTintColor(fluidStack))
- .orElse(0xFFFFFFFF);
- }
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java b/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java
index f3e1084e..b5128352 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java
@@ -5,13 +5,17 @@
package net.neoforged.neoforge.client.model;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import java.lang.reflect.Type;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Function;
import net.minecraft.client.renderer.block.model.BakedQuad;
-import net.minecraft.client.renderer.block.model.ItemOverride;
import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -20,34 +24,34 @@
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.SimpleBakedModel;
+import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.Direction;
import net.neoforged.neoforge.client.RenderTypeGroup;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import net.neoforged.neoforge.client.model.geometry.SimpleUnbakedGeometry;
import net.neoforged.neoforge.client.textures.UnitTextureAtlasSprite;
/**
* A completely empty model with no quads or texture dependencies.
*
- * You can access it as a {@link BakedModel}, an {@link IUnbakedGeometry} or an {@link IGeometryLoader}.
+ * You can access it as a {@link BakedModel}.
*/
-public class EmptyModel extends SimpleUnbakedGeometry {
+public class EmptyModel implements UnbakedModel, JsonDeserializer {
public static final BakedModel BAKED = new Baked();
public static final EmptyModel INSTANCE = new EmptyModel();
- public static final IGeometryLoader LOADER = (json, ctx) -> INSTANCE;
+ public static final UnbakedModelLoader LOADER = (object, context) -> INSTANCE;
private EmptyModel() {}
@Override
- protected void addQuads(IGeometryBakingContext owner, IModelBuilder> modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform) {
- // NO-OP
+ public BakedModel bake(TextureSlots p_386641_, ModelBaker p_250133_, ModelState p_119536_, boolean p_387129_, boolean p_388638_, ItemTransforms p_386911_) {
+ return BAKED;
}
@Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- return BAKED;
+ public void resolveDependencies(Resolver p_387087_) {}
+
+ @Override
+ public EmptyModel deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ return INSTANCE;
}
private static class Baked extends SimpleBakedModel {
diff --git a/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java b/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java
deleted file mode 100644
index db0e1957..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import com.mojang.math.Transformation;
-import java.lang.reflect.Type;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import net.minecraft.client.renderer.block.model.BlockElement;
-import net.minecraft.client.renderer.block.model.BlockElementFace;
-import net.minecraft.client.renderer.block.model.BlockFaceUV;
-import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.block.model.ItemTransform;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.resources.ResourceLocation;
-import net.minecraft.util.GsonHelper;
-import net.neoforged.neoforge.client.model.geometry.GeometryLoaderManager;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import net.neoforged.neoforge.common.util.TransformationHelper;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * A version of {@link BlockModel.Deserializer} capable of deserializing models with custom loaders, as well as other
- * changes introduced to the spec by Forge.
- */
-public class ExtendedBlockModelDeserializer extends BlockModel.Deserializer {
- public static final Gson INSTANCE = (new GsonBuilder())
- .registerTypeAdapter(BlockModel.class, new ExtendedBlockModelDeserializer())
- .registerTypeAdapter(BlockElement.class, new BlockElement.Deserializer())
- .registerTypeAdapter(BlockElementFace.class, new BlockElementFace.Deserializer())
- .registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer())
- .registerTypeAdapter(ItemTransform.class, new ItemTransform.Deserializer())
- .registerTypeAdapter(ItemTransforms.class, new ItemTransforms.Deserializer())
- .registerTypeAdapter(ItemOverride.class, new ItemOverride.Deserializer())
- .registerTypeAdapter(Transformation.class, new TransformationHelper.Deserializer())
- .create();
-
- @Override
- public BlockModel deserialize(JsonElement element, Type targetType, JsonDeserializationContext deserializationContext) throws JsonParseException {
- BlockModel model = super.deserialize(element, targetType, deserializationContext);
- JsonObject jsonobject = element.getAsJsonObject();
- IUnbakedGeometry> geometry = deserializeGeometry(deserializationContext, jsonobject);
-
- List elements = model.getElements();
- if (geometry != null) {
- elements.clear();
- model.customData.setCustomGeometry(geometry);
- }
-
- if (jsonobject.has("transform")) {
- JsonElement transform = jsonobject.get("transform");
- model.customData.setRootTransform(deserializationContext.deserialize(transform, Transformation.class));
- }
-
- if (jsonobject.has("render_type")) {
- var renderTypeHintName = GsonHelper.getAsString(jsonobject, "render_type");
- model.customData.setRenderTypeHint(ResourceLocation.parse(renderTypeHintName));
- }
-
- if (jsonobject.has("visibility")) {
- JsonObject visibility = GsonHelper.getAsJsonObject(jsonobject, "visibility");
- for (Map.Entry part : visibility.entrySet()) {
- model.customData.visibilityData.setVisibilityState(part.getKey(), part.getValue().getAsBoolean());
- }
- }
-
- return model;
- }
-
- @Nullable
- public static IUnbakedGeometry> deserializeGeometry(JsonDeserializationContext deserializationContext, JsonObject object) throws JsonParseException {
- if (!object.has("loader"))
- return null;
-
- ResourceLocation name;
- boolean optional;
- if (object.get("loader").isJsonObject()) {
- JsonObject loaderObj = object.getAsJsonObject("loader");
- name = ResourceLocation.parse(GsonHelper.getAsString(loaderObj, "id"));
- optional = GsonHelper.getAsBoolean(loaderObj, "optional", false);
- } else {
- name = ResourceLocation.parse(GsonHelper.getAsString(object, "loader"));
- optional = false;
- }
-
- var loader = GeometryLoaderManager.get(name);
- if (loader == null) {
- if (optional) {
- return null;
- }
- throw new JsonParseException(String.format(Locale.ENGLISH, "Model loader '%s' not found. Registered loaders: %s", name, GeometryLoaderManager.getLoaderList()));
- }
-
- return loader.read(object, deserializationContext);
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java
new file mode 100644
index 00000000..7c981e8f
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
+import net.minecraft.client.resources.model.BakedModel;
+import net.minecraft.client.resources.model.ModelBaker;
+import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.util.context.ContextMap;
+
+/**
+ * Base interface for unbaked models that wish to support the NeoForge-added {@code bake} method
+ * that receives {@linkplain #fillAdditionalProperties(ContextMap.Builder) additional properties}.
+ */
+public interface ExtendedUnbakedModel extends UnbakedModel {
+ @Deprecated
+ @Override
+ default BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms) {
+ return bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms, ContextMap.EMPTY);
+ }
+
+ // Re-abstract the extended version
+ @Override
+ BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties);
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java
index 51111b4d..a75207f3 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java
@@ -19,7 +19,10 @@
*
* Provides a generic base implementation via {@link #of(boolean, boolean, boolean, ItemTransforms, TextureAtlasSprite, RenderTypeGroup)}
* and a quad-collecting alternative via {@link #collecting(List)}.
+ *
+ * @deprecated Use {@link SimpleBakedModel.Builder} instead.
*/
+@Deprecated(forRemoval = true, since = "1.21.4")
public interface IModelBuilder> {
/**
* Creates a new model builder that uses the provided attributes in the final baked model.
@@ -38,10 +41,28 @@ static IModelBuilder> collecting(List quads) {
return new Collecting(quads);
}
+ /**
+ * Adds a face to the model that will be culled based on the provided facing.
+ *
+ * @param facing The facing
+ * @param quad The quad
+ * @return This builder
+ */
T addCulledFace(Direction facing, BakedQuad quad);
+ /**
+ * Adds a face to the model that will not be culled.
+ *
+ * @param quad The quad
+ * @return This builder
+ */
T addUnculledFace(BakedQuad quad);
+ /**
+ * Builds the model from the collected faces.
+ *
+ * @return The baked model
+ */
BakedModel build();
class Simple implements IModelBuilder {
diff --git a/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java b/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java
deleted file mode 100644
index 005c98f6..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import net.minecraft.client.renderer.RenderType;
-import net.minecraft.client.renderer.block.model.BakedOverrides;
-import net.minecraft.client.renderer.block.model.ItemModelGenerator;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.ItemModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.resources.ResourceLocation;
-import net.neoforged.neoforge.client.NeoForgeRenderTypes;
-import net.neoforged.neoforge.client.RenderTypeGroup;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Forge reimplementation of vanilla's {@link ItemModelGenerator}, i.e. builtin/generated models with some tweaks:
- * - Represented as {@link IUnbakedGeometry} so it can be baked as usual instead of being special-cased
- * - Not limited to an arbitrary number of layers (5)
- * - Support for per-layer render types
- */
-public class ItemLayerModel implements IUnbakedGeometry {
- @Nullable
- private ImmutableList textures;
- private final Int2ObjectMap layerData;
- private final Int2ObjectMap renderTypeNames;
-
- private ItemLayerModel(@Nullable ImmutableList textures, Int2ObjectMap layerData, Int2ObjectMap renderTypeNames) {
- this.textures = textures;
- this.layerData = layerData;
- this.renderTypeNames = renderTypeNames;
- }
-
- @Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- if (textures == null) {
- ImmutableList.Builder builder = ImmutableList.builder();
- for (int i = 0; context.hasMaterial("layer" + i); i++) {
- builder.add(context.getMaterial("layer" + i));
- }
- textures = builder.build();
- }
-
- TextureAtlasSprite particle = spriteGetter.apply(
- context.hasMaterial("particle") ? context.getMaterial("particle") : textures.get(0));
- var rootTransform = context.getRootTransform();
- if (!rootTransform.isIdentity())
- modelState = UnbakedGeometryHelper.composeRootTransformIntoModelState(modelState, rootTransform);
-
- var normalRenderTypes = new RenderTypeGroup(RenderType.translucent(), NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get());
- CompositeModel.Baked.Builder builder = CompositeModel.Baked.builder(context, particle, context.getTransforms());
- for (int i = 0; i < textures.size(); i++) {
- TextureAtlasSprite sprite = spriteGetter.apply(textures.get(i));
- var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite, this.layerData.get(i));
- var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, modelState);
- var renderTypeName = renderTypeNames.get(i);
- var renderTypes = renderTypeName != null ? context.getRenderType(renderTypeName) : null;
- builder.addQuads(renderTypes != null ? renderTypes : normalRenderTypes, quads);
- }
-
- BakedModel baked = builder.build();
- if (!overrides.isEmpty()) {
- baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter));
- }
- return baked;
- }
-
- public static final class Loader implements IGeometryLoader {
- public static final Loader INSTANCE = new Loader();
-
- @Override
- public ItemLayerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
- var renderTypeNames = new Int2ObjectOpenHashMap();
- if (jsonObject.has("render_types")) {
- var renderTypes = jsonObject.getAsJsonObject("render_types");
- for (Map.Entry entry : renderTypes.entrySet()) {
- var renderType = ResourceLocation.parse(entry.getKey());
- for (var layer : entry.getValue().getAsJsonArray())
- if (renderTypeNames.put(layer.getAsInt(), renderType) != null)
- throw new JsonParseException("Registered duplicate render type for layer " + layer);
- }
- }
-
- var emissiveLayers = new Int2ObjectArrayMap();
- if (jsonObject.has("neoforge_data")) {
- JsonObject forgeData = jsonObject.get("neoforge_data").getAsJsonObject();
- readLayerData(forgeData, "layers", renderTypeNames, emissiveLayers, false);
- }
- return new ItemLayerModel(null, emissiveLayers, renderTypeNames);
- }
-
- protected void readLayerData(JsonObject jsonObject, String name, Int2ObjectOpenHashMap renderTypeNames, Int2ObjectMap layerData, boolean logWarning) {
- if (!jsonObject.has(name)) {
- return;
- }
- var fullbrightLayers = jsonObject.getAsJsonObject(name);
- for (var entry : fullbrightLayers.entrySet()) {
- int layer = Integer.parseInt(entry.getKey());
- var data = ExtraFaceData.read(entry.getValue(), ExtraFaceData.DEFAULT);
- layerData.put(layer, data);
- }
- }
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java b/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java
new file mode 100644
index 00000000..43dd5a89
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import com.mojang.math.Transformation;
+import net.minecraft.client.renderer.block.model.BlockModel;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.util.context.ContextKey;
+import net.neoforged.neoforge.client.RenderTypeGroup;
+
+/**
+ * Properties that NeoForge adds for {@link BlockModel}s and {@link UnbakedModel}s.
+ */
+public final class NeoForgeModelProperties {
+ private NeoForgeModelProperties() {}
+
+ /**
+ * Root transform. For block models, this can be specified under the {@code transform} JSON key.
+ */
+ public static final ContextKey TRANSFORM = ContextKey.vanilla("transform");
+
+ /**
+ * Render type to use. For block models, this can be specified under the {@code render_type} JSON key.
+ */
+ public static final ContextKey RENDER_TYPE = ContextKey.vanilla("render_type");
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java b/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java
deleted file mode 100644
index 03e4d72b..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Maps;
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonObject;
-import com.mojang.blaze3d.vertex.PoseStack;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import net.minecraft.client.renderer.RenderType;
-import net.minecraft.client.renderer.block.model.BakedOverrides;
-import net.minecraft.client.renderer.block.model.BakedQuad;
-import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.ItemModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.client.resources.model.UnbakedModel;
-import net.minecraft.core.Direction;
-import net.minecraft.util.GsonHelper;
-import net.minecraft.util.RandomSource;
-import net.minecraft.world.item.ItemDisplayContext;
-import net.minecraft.world.level.block.state.BlockState;
-import net.neoforged.neoforge.client.ChunkRenderTypeSet;
-import net.neoforged.neoforge.client.model.data.ModelData;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * A model composed of multiple sub-models which are picked based on the {@link ItemDisplayContext} being used.
- */
-public class SeparateTransformsModel implements IUnbakedGeometry {
- private final BlockModel baseModel;
- private final ImmutableMap perspectives;
-
- public SeparateTransformsModel(BlockModel baseModel, ImmutableMap perspectives) {
- this.baseModel = baseModel;
- this.perspectives = perspectives;
- }
-
- @Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- BakedModel baked = new Baked(
- context.useAmbientOcclusion(), context.isGui3d(), context.useBlockLight(),
- spriteGetter.apply(context.getMaterial("particle")),
- baseModel.bake(baker, spriteGetter, modelState),
- ImmutableMap.copyOf(Maps.transformValues(perspectives, value -> value.bake(baker, spriteGetter, modelState))));
- if (!overrides.isEmpty()) {
- baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter));
- }
- return baked;
- }
-
- @Override
- public void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) {
- baseModel.resolveDependencies(modelGetter);
- perspectives.values().forEach(model -> model.resolveDependencies(modelGetter));
- }
-
- public static class Baked implements IDynamicBakedModel {
- private final boolean isAmbientOcclusion;
- private final boolean isGui3d;
- private final boolean isSideLit;
- private final TextureAtlasSprite particle;
- private final BakedModel baseModel;
- private final ImmutableMap perspectives;
-
- public Baked(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, BakedModel baseModel, ImmutableMap perspectives) {
- this.isAmbientOcclusion = isAmbientOcclusion;
- this.isGui3d = isGui3d;
- this.isSideLit = isSideLit;
- this.particle = particle;
- this.baseModel = baseModel;
- this.perspectives = perspectives;
- }
-
- @Override
- public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData data, @Nullable RenderType renderType) {
- return baseModel.getQuads(state, side, rand, data, renderType);
- }
-
- @Override
- public boolean useAmbientOcclusion() {
- return isAmbientOcclusion;
- }
-
- @Override
- public boolean isGui3d() {
- return isGui3d;
- }
-
- @Override
- public boolean usesBlockLight() {
- return isSideLit;
- }
-
- @Override
- public boolean isCustomRenderer() {
- return false;
- }
-
- @Override
- public TextureAtlasSprite getParticleIcon() {
- return particle;
- }
-
- @Override
- public ItemTransforms getTransforms() {
- return ItemTransforms.NO_TRANSFORMS;
- }
-
- @Override
- public BakedModel applyTransform(ItemDisplayContext cameraTransformType, PoseStack poseStack, boolean applyLeftHandTransform) {
- if (perspectives.containsKey(cameraTransformType)) {
- BakedModel p = perspectives.get(cameraTransformType);
- return p.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform);
- }
- return baseModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform);
- }
-
- @Override
- public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) {
- return baseModel.getRenderTypes(state, rand, data);
- }
- }
-
- public static final class Loader implements IGeometryLoader {
- public static final Loader INSTANCE = new Loader();
-
- private Loader() {}
-
- @Override
- public SeparateTransformsModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
- BlockModel baseModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(jsonObject, "base"), BlockModel.class);
-
- JsonObject perspectiveData = GsonHelper.getAsJsonObject(jsonObject, "perspectives");
-
- Map perspectives = new HashMap<>();
- for (ItemDisplayContext transform : ItemDisplayContext.values()) {
- if (perspectiveData.has(transform.getSerializedName())) {
- BlockModel perspectiveModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(perspectiveData, transform.getSerializedName()), BlockModel.class);
- perspectives.put(transform, perspectiveModel);
- }
- }
-
- return new SeparateTransformsModel(baseModel, ImmutableMap.copyOf(perspectives));
- }
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java
similarity index 74%
rename from src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java
rename to src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java
index 93fde9b7..b79dfa0c 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java
@@ -11,30 +11,29 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
+import com.mojang.math.Transformation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
-import java.util.function.Function;
+import net.minecraft.client.data.models.model.TextureSlot;
import net.minecraft.client.renderer.RenderType;
-import net.minecraft.client.renderer.block.model.BakedOverrides;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.ItemOverride;
import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.ItemModel;
-import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
+import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockAndTintGetter;
@@ -43,44 +42,44 @@
import net.neoforged.neoforge.client.RenderTypeGroup;
import net.neoforged.neoforge.client.model.data.ModelData;
import net.neoforged.neoforge.client.model.data.ModelProperty;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
-import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
-import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper;
import net.neoforged.neoforge.common.util.ConcatenatedListView;
import org.jetbrains.annotations.Nullable;
/**
* A model composed of several named children.
- *
- * These respect component visibility as specified in {@link IGeometryBakingContext} and can additionally be provided
- * with an item-specific render ordering, for multi-pass arrangements.
*/
-public class CompositeModel implements IUnbakedGeometry {
- private final ImmutableMap children;
+public class UnbakedCompositeModel implements UnbakedModel {
+ private final ImmutableMap children;
private final ImmutableList itemPasses;
+ private final Transformation rootTransform;
+ private final Map partVisibility;
- public CompositeModel(ImmutableMap children, ImmutableList itemPasses) {
+ public UnbakedCompositeModel(ImmutableMap children, ImmutableList itemPasses, Transformation rootTransform, Map partVisibility) {
this.children = children;
this.itemPasses = itemPasses;
+ this.rootTransform = rootTransform;
+ this.partVisibility = partVisibility;
}
@Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- Material particleLocation = context.getMaterial("particle");
- TextureAtlasSprite particle = spriteGetter.apply(particleLocation);
+ public BakedModel bake(TextureSlots slots,
+ ModelBaker baker,
+ ModelState state,
+ boolean useAmbientOcclusion,
+ boolean usesBlockLight,
+ ItemTransforms transforms) {
+ TextureAtlasSprite particle = baker.findSprite(slots, TextureSlot.PARTICLE.getId());
- var rootTransform = context.getRootTransform();
if (!rootTransform.isIdentity())
- modelState = UnbakedGeometryHelper.composeRootTransformIntoModelState(modelState, rootTransform);
+ state = UnbakedElementsHelper.composeRootTransformIntoModelState(state, rootTransform);
var bakedPartsBuilder = ImmutableMap.builder();
for (var entry : children.entrySet()) {
var name = entry.getKey();
- if (!context.isComponentVisible(name, true))
+ if (!partVisibility.getOrDefault(name, true))
continue;
var model = entry.getValue();
- bakedPartsBuilder.put(name, model.bake(baker, spriteGetter, modelState));
+ bakedPartsBuilder.put(name, baker.bake(model, state));
}
var bakedParts = bakedPartsBuilder.build();
@@ -92,36 +91,27 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio
itemPassesBuilder.add(model);
}
- BakedModel baked = new Baked(context.isGui3d(), context.useBlockLight(), context.useAmbientOcclusion(), particle, context.getTransforms(), bakedParts, itemPassesBuilder.build());
- if (!overrides.isEmpty()) {
- baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter));
- }
- return baked;
- }
-
- @Override
- public void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) {
- children.values().forEach(child -> child.resolveDependencies(modelGetter));
+ return new Baked(usesBlockLight, useAmbientOcclusion, particle, transforms, bakedParts, itemPassesBuilder.build());
}
@Override
- public Set getConfigurableComponentNames() {
- return children.keySet();
+ public void resolveDependencies(Resolver resolver) {
+ for (ResourceLocation path : children.values()) {
+ resolver.resolve(path);
+ }
}
public static class Baked implements IDynamicBakedModel {
private final boolean isAmbientOcclusion;
- private final boolean isGui3d;
private final boolean isSideLit;
private final TextureAtlasSprite particle;
private final ItemTransforms transforms;
private final ImmutableMap children;
private final ImmutableList itemPasses;
- public Baked(boolean isGui3d, boolean isSideLit, boolean isAmbientOcclusion, TextureAtlasSprite particle, ItemTransforms transforms, ImmutableMap children, ImmutableList itemPasses) {
+ public Baked(boolean isSideLit, boolean isAmbientOcclusion, TextureAtlasSprite particle, ItemTransforms transforms, ImmutableMap children, ImmutableList itemPasses) {
this.children = children;
this.isAmbientOcclusion = isAmbientOcclusion;
- this.isGui3d = isGui3d;
this.isSideLit = isSideLit;
this.particle = particle;
this.transforms = transforms;
@@ -133,7 +123,7 @@ public List getQuads(@Nullable BlockState state, @Nullable Direction
List> quadLists = new ArrayList<>();
for (Map.Entry entry : children.entrySet()) {
if (renderType == null || (state != null && entry.getValue().getRenderTypes(state, rand, data).contains(renderType))) {
- quadLists.add(entry.getValue().getQuads(state, side, rand, CompositeModel.Data.resolve(data, entry.getKey()), renderType));
+ quadLists.add(entry.getValue().getQuads(state, side, rand, Data.resolve(data, entry.getKey()), renderType));
}
}
return ConcatenatedListView.of(quadLists);
@@ -154,7 +144,7 @@ public boolean useAmbientOcclusion() {
@Override
public boolean isGui3d() {
- return isGui3d;
+ return true;
}
@Override
@@ -162,11 +152,6 @@ public boolean usesBlockLight() {
return isSideLit;
}
- @Override
- public boolean isCustomRenderer() {
- return false;
- }
-
@Override
public TextureAtlasSprite getParticleIcon() {
return particle;
@@ -181,7 +166,7 @@ public ItemTransforms getTransforms() {
public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) {
var sets = new ArrayList();
for (Map.Entry entry : children.entrySet())
- sets.add(entry.getValue().getRenderTypes(state, rand, CompositeModel.Data.resolve(data, entry.getKey())));
+ sets.add(entry.getValue().getRenderTypes(state, rand, Data.resolve(data, entry.getKey())));
return ChunkRenderTypeSet.union(sets);
}
@@ -195,10 +180,6 @@ public BakedModel getPart(String name) {
return children.get(name);
}
- public static Builder builder(IGeometryBakingContext owner, TextureAtlasSprite particle, ItemTransforms cameraTransforms) {
- return builder(owner.useAmbientOcclusion(), owner.isGui3d(), owner.useBlockLight(), particle, cameraTransforms);
- }
-
public static Builder builder(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, ItemTransforms cameraTransforms) {
return new Builder(isAmbientOcclusion, isGui3d, isSideLit, particle, cameraTransforms);
}
@@ -270,7 +251,7 @@ public BakedModel build() {
childrenBuilder.put("model_" + (i++), model);
itemPassesBuilder.add(model);
}
- return new Baked(isGui3d, isSideLit, isAmbientOcclusion, particle, transforms, childrenBuilder.build(), itemPassesBuilder.build());
+ return new Baked(isSideLit, isAmbientOcclusion, particle, transforms, childrenBuilder.build(), itemPassesBuilder.build());
}
}
}
@@ -325,16 +306,16 @@ public Data build() {
}
}
- public static final class Loader implements IGeometryLoader {
+ public static final class Loader implements UnbakedModelLoader {
public static final Loader INSTANCE = new Loader();
private Loader() {}
@Override
- public CompositeModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
+ public UnbakedCompositeModel read(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
List itemPasses = new ArrayList<>();
- ImmutableMap.Builder childrenBuilder = ImmutableMap.builder();
- readChildren(jsonObject, "children", deserializationContext, childrenBuilder, itemPasses);
+ ImmutableMap.Builder childrenBuilder = ImmutableMap.builder();
+ readChildren(jsonObject, "children", childrenBuilder, itemPasses);
var children = childrenBuilder.build();
if (children.isEmpty())
@@ -350,15 +331,34 @@ public CompositeModel read(JsonObject jsonObject, JsonDeserializationContext des
}
}
- return new CompositeModel(children, ImmutableList.copyOf(itemPasses));
+ final Map partVisibility;
+ if (jsonObject.has("visibility")) {
+ partVisibility = new HashMap<>();
+ JsonObject visibility = jsonObject.getAsJsonObject("visibility");
+ for (Map.Entry part : visibility.entrySet()) {
+ partVisibility.put(part.getKey(), part.getValue().getAsBoolean());
+ }
+ } else {
+ partVisibility = Collections.emptyMap();
+ }
+
+ final Transformation transformation;
+ if (jsonObject.has("transform")) {
+ transformation = BlockModel.GSON.fromJson(jsonObject.get("transform"), Transformation.class);
+ } else {
+ transformation = Transformation.identity();
+ }
+
+ return new UnbakedCompositeModel(children, ImmutableList.copyOf(itemPasses), transformation, partVisibility);
}
- private void readChildren(JsonObject jsonObject, String name, JsonDeserializationContext deserializationContext, ImmutableMap.Builder children, List itemPasses) {
+ private void readChildren(JsonObject jsonObject, String name, ImmutableMap.Builder children, List itemPasses) {
if (!jsonObject.has(name))
return;
var childrenJsonObject = jsonObject.getAsJsonObject(name);
for (Map.Entry entry : childrenJsonObject.entrySet()) {
- children.put(entry.getKey(), deserializationContext.deserialize(entry.getValue(), BlockModel.class));
+ ResourceLocation location = ResourceLocation.parse(entry.getValue().getAsString());
+ children.put(entry.getKey(), location);
itemPasses.add(entry.getKey()); // We can do this because GSON preserves ordering during deserialization
}
}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java
similarity index 64%
rename from src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java
rename to src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java
index e790cef4..5c5e67c5 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java
@@ -1,86 +1,38 @@
/*
- * Copyright (c) Forge Development LLC and contributors
+ * Copyright (c) NeoForged and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
-package net.neoforged.neoforge.client.model.geometry;
+package net.neoforged.neoforge.client.model;
import com.mojang.math.Transformation;
import java.util.ArrayList;
import java.util.BitSet;
-import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Function;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import net.minecraft.Util;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.BlockElement;
import net.minecraft.client.renderer.block.model.BlockElementFace;
import net.minecraft.client.renderer.block.model.BlockFaceUV;
-import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.FaceBakery;
import net.minecraft.client.renderer.block.model.ItemModelGenerator;
-import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
import net.minecraft.client.renderer.texture.SpriteContents;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.Material;
import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.Mth;
import net.neoforged.neoforge.client.ClientHooks;
-import net.neoforged.neoforge.client.model.ExtraFaceData;
-import net.neoforged.neoforge.client.model.IModelBuilder;
-import net.neoforged.neoforge.client.model.SimpleModelState;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
-/**
- * Helper for dealing with unbaked models and geometries.
- */
-public class UnbakedGeometryHelper {
- private static final ItemModelGenerator ITEM_MODEL_GENERATOR = new ItemModelGenerator();
- private static final FaceBakery FACE_BAKERY = new FaceBakery();
-
- /**
- * Explanation:
- * This takes anything that looks like a valid resourcepack texture location, and tries to extract a resourcelocation out of it.
- * 1. it will ignore anything up to and including an /assets/ folder,
- * 2. it will take the next path component as a namespace,
- * 3. it will match but skip the /textures/ part of the path,
- * 4. it will take the rest of the path up to but excluding the .png extension as the resource path
- * It's a best-effort situation, to allow model files exported by modelling software to be used without post-processing.
- * Example:
- * C:\Something\Or Other\src\main\resources\assets\mymodid\textures\item\my_thing.png
- * ........................................--------_______----------_____________----
- *
- * Result after replacing '\' to '/': mymodid:item/my_thing
- */
- private static final Pattern FILESYSTEM_PATH_TO_RESLOC = Pattern.compile("(?:.*[\\\\/]assets[\\\\/](?[a-z_-]+)[\\\\/]textures[\\\\/])?(?[a-z_\\\\/-]+)\\.png");
-
- /**
- * Resolves a material that may have been defined with a filesystem path instead of a proper {@link ResourceLocation}.
- *
- * The target atlas will always be {@link TextureAtlas#LOCATION_BLOCKS}.
- */
- public static Material resolveDirtyMaterial(@Nullable String tex, IGeometryBakingContext owner) {
- if (tex == null)
- return new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation());
- if (tex.startsWith("#"))
- return owner.getMaterial(tex);
-
- // Attempt to convert a common (windows/linux/mac) filesystem path to a ResourceLocation.
- // This makes no promises, if it doesn't work, too bad, fix your mtl file.
- Matcher match = FILESYSTEM_PATH_TO_RESLOC.matcher(tex);
- if (match.matches()) {
- String namespace = match.group("namespace");
- String path = match.group("path").replace("\\", "/");
- tex = namespace != null ? namespace + ":" + path : path;
- }
+public final class UnbakedElementsHelper {
+ private UnbakedElementsHelper() {}
- return new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.parse(tex));
- }
+ private static final ItemModelGenerator ITEM_MODEL_GENERATOR = new ItemModelGenerator();
/**
* @see #createUnbakedItemElements(int, TextureAtlasSprite, ExtraFaceData)
@@ -118,12 +70,13 @@ public static List createUnbakedItemMaskElements(int layerIndex, T
* The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up only the pixels the texture uses.
*/
public static List createUnbakedItemMaskElements(int layerIndex, TextureAtlasSprite sprite, @Nullable ExtraFaceData faceData) {
- var elements = createUnbakedItemElements(layerIndex, sprite, faceData);
- elements.remove(0); // Remove north and south faces
+ List elements = createUnbakedItemElements(layerIndex, sprite, faceData);
+ elements.removeFirst(); // Remove north and south faces
+ float expand = -sprite.uvShrinkRatio();
SpriteContents spriteContents = sprite.contents();
int width = spriteContents.width(), height = spriteContents.height();
- var bits = new BitSet(width * height);
+ BitSet bits = new BitSet(width * height);
// For every frame in the texture, mark all the opaque pixels (this is what vanilla does too)
spriteContents.getUniqueFrames().forEach(frame -> {
@@ -137,9 +90,8 @@ public static List createUnbakedItemMaskElements(int layerIndex, T
for (int y = 0; y < height; y++) {
int xStart = -1;
for (int x = 0; x < width; x++) {
- var opaque = bits.get(x + y * width);
- if (opaque == (xStart == -1)) // (opaque && -1) || (!opaque && !-1)
- {
+ boolean opaque = bits.get(x + y * width);
+ if (opaque == (xStart == -1)) { // (opaque && -1) || (!opaque && !-1)
if (xStart == -1) {
// We have found the start of a new segment, continue
xStart = x;
@@ -160,16 +112,31 @@ public static List createUnbakedItemMaskElements(int layerIndex, T
bits.clear(i + j * width);
// Create element
- elements.add(new BlockElement(
+ BlockElement element = new BlockElement(
new Vector3f(16 * xStart / (float) width, 16 - 16 * yEnd / (float) height, 7.5F),
new Vector3f(16 * x / (float) width, 16 - 16 * y / (float) height, 8.5F),
- Util.make(new HashMap<>(), map -> {
- for (Direction direction : Direction.values())
- map.put(direction, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0)));
- }),
+ Map.of(
+ Direction.NORTH, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0)),
+ Direction.SOUTH, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0))),
null,
true,
- 0));
+ 0);
+ // Expand coordinates to match the shrunk UVs of the front/back face on a standard generated model (done after to not affect the auto-generated UVs)
+ element.from.x = Mth.clamp(Mth.lerp(expand, element.from.x, 8F), 0F, 16F);
+ element.from.y = Mth.clamp(Mth.lerp(expand, element.from.y, 8F), 0F, 16F);
+ element.to.x = Mth.clamp(Mth.lerp(expand, element.to.x, 8F), 0F, 16F);
+ element.to.y = Mth.clamp(Mth.lerp(expand, element.to.y, 8F), 0F, 16F);
+ // Counteract sprite expansion to ensure pixel alignment
+ element.faces.forEach((dir, face) -> {
+ float[] uv = face.uv().uvs;
+ float centerU = (uv[0] + uv[0] + uv[2] + uv[2]) / 4.0F;
+ uv[0] = Mth.clamp(Mth.lerp(expand, uv[0], centerU), 0F, 16F);
+ uv[2] = Mth.clamp(Mth.lerp(expand, uv[2], centerU), 0F, 16F);
+ float centerV = (uv[1] + uv[1] + uv[3] + uv[3]) / 4.0F;
+ uv[1] = Mth.clamp(Mth.lerp(expand, uv[1], centerV), 0F, 16F);
+ uv[3] = Mth.clamp(Mth.lerp(expand, uv[3], centerV), 0F, 16F);
+ });
+ elements.add(element);
// Reset xStart
xStart = -1;
@@ -186,7 +153,7 @@ public static void bakeElements(IModelBuilder> builder, List ele
for (BlockElement element : elements) {
element.faces.forEach((side, face) -> {
var sprite = spriteGetter.apply(new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.parse(face.texture())));
- BakedQuad quad = BlockModel.bakeFace(element, face, sprite, side, modelState);
+ BakedQuad quad = SimpleBakedModel.bakeFace(element, face, sprite, side, modelState);
if (face.cullForDirection() == null)
builder.addUnculledFace(quad);
else
diff --git a/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java
new file mode 100644
index 00000000..0f518f18
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import net.minecraft.client.renderer.block.model.BlockModel;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
+import net.neoforged.neoforge.client.event.ModelEvent;
+import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
+
+/**
+ * A loader for custom {@linkplain UnbakedModel unbaked models}.
+ *
+ * If you do any caching, you should implement {@link ResourceManagerReloadListener} and register it with
+ * {@link RegisterClientReloadListenersEvent}.
+ *
+ * @see ModelEvent.RegisterLoaders
+ * @see RegisterClientReloadListenersEvent
+ */
+public interface UnbakedModelLoader {
+ /**
+ * Reads an unbaked model from the passed JSON object.
+ *
+ * The {@link JsonDeserializationContext} argument can be used to deserialize types that the system already understands.
+ * For example, {@code deserializationContext.deserialize(, Transformation.class)} to parse a transformation,
+ * or {@code deserializationContext.deserialize(, UnbakedModel.class)} to parse a nested model.
+ * The set of supported types can be found in the declaration of {@link BlockModel#GSON}.
+ */
+ T read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) throws JsonParseException;
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java
new file mode 100644
index 00000000..37e26215
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import java.io.Reader;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.stream.Collectors;
+import net.minecraft.client.renderer.block.model.BlockModel;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.util.GsonHelper;
+import net.neoforged.fml.ModLoader;
+import net.neoforged.neoforge.client.event.ModelEvent;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+
+public class UnbakedModelParser {
+ private static ImmutableMap> LOADERS;
+
+ @Nullable
+ public static UnbakedModelLoader> get(ResourceLocation name) {
+ return LOADERS.get(name);
+ }
+
+ @ApiStatus.Internal
+ public static void init() {
+ var loaders = new HashMap>();
+ ModLoader.postEventWrapContainerInModOrder(new ModelEvent.RegisterLoaders(loaders));
+ LOADERS = ImmutableMap.copyOf(loaders);
+ }
+
+ public static UnbakedModel parse(Reader reader) {
+ return GsonHelper.fromJson(BlockModel.GSON, reader, UnbakedModel.class);
+ }
+
+ @ApiStatus.Internal
+ public static final class Deserializer implements JsonDeserializer {
+ @Override
+ public UnbakedModel deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ if (!jsonElement.isJsonObject()) {
+ throw new JsonParseException("Expected object, got " + jsonElement);
+ } else {
+ JsonObject jsonObject = jsonElement.getAsJsonObject();
+
+ if (jsonObject.has("loader")) {
+ final ResourceLocation loader;
+ final boolean optional;
+ if (jsonObject.get("loader").isJsonObject()) {
+ JsonObject loaderObject = jsonObject.getAsJsonObject("loader");
+ loader = ResourceLocation.parse(GsonHelper.getAsString(loaderObject, "id"));
+ optional = GsonHelper.getAsBoolean(loaderObject, "optional", false);
+ } else {
+ loader = ResourceLocation.parse(GsonHelper.getAsString(jsonObject, "loader"));
+ optional = false;
+ }
+
+ var loaderInstance = UnbakedModelParser.get(loader);
+ if (loaderInstance != null) {
+ return loaderInstance.read(jsonObject, jsonDeserializationContext);
+ }
+ if (!optional) {
+ throw new JsonParseException("Unknown loader: " + loader + " (did you forget to register it?) Available loaders: " + LOADERS.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", ")));
+ }
+ }
+
+ return jsonDeserializationContext.deserialize(jsonObject, BlockModel.class);
+ }
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java
index a8717c55..7e088ea4 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java
@@ -10,7 +10,6 @@
import java.util.LinkedHashMap;
import java.util.Map;
import net.minecraft.resources.ResourceLocation;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
import net.neoforged.neoforge.common.data.ExistingFileHelper;
public abstract class CustomLoaderBuilder> {
diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java
index 9afe777b..90d619fd 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java
@@ -25,9 +25,9 @@
import net.minecraft.client.renderer.block.model.BlockElementFace;
import net.minecraft.client.renderer.block.model.BlockElementRotation;
import net.minecraft.client.renderer.block.model.BlockFaceUV;
-import net.minecraft.client.renderer.block.model.BlockModel.GuiLight;
import net.minecraft.client.renderer.block.model.ItemTransform;
import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
+import net.minecraft.client.resources.model.UnbakedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
@@ -61,7 +61,8 @@ public class ModelBuilder> extends ModelFile {
protected String renderType = null;
protected boolean ambientOcclusion = true;
- protected GuiLight guiLight = null;
+
+ protected UnbakedModel.GuiLight guiLight = null;
protected final List elements = new ArrayList<>();
@@ -185,7 +186,7 @@ public T ao(boolean ao) {
return self();
}
- public T guiLight(GuiLight light) {
+ public T guiLight(UnbakedModel.GuiLight light) {
this.guiLight = light;
return self();
}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java
index e39d3e8e..f52db6e3 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java
@@ -371,6 +371,10 @@ public T leaves(String name, ResourceLocation texture) {
return singleTexture(name, BLOCK_FOLDER + "/leaves", "all", texture);
}
+ public T flowerPotCross(String name, ResourceLocation plant) {
+ return singleTexture(name, BLOCK_FOLDER + "/flower_pot_cross", "plant", plant);
+ }
+
/**
* {@return a model builder that's not directly saved to disk. Meant for use in custom model loaders.}
*/
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java
deleted file mode 100644
index 02c53783..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import com.mojang.math.Transformation;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.resources.ResourceLocation;
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * A {@linkplain IGeometryBakingContext geometry baking context} that is bound to a {@link BlockModel}.
- *
- * Users should not be instantiating this themselves.
- */
-public class BlockGeometryBakingContext implements IGeometryBakingContext {
- public final BlockModel owner;
- public final VisibilityData visibilityData = new VisibilityData();
- @Nullable
- private IUnbakedGeometry> customGeometry;
- @Nullable
- private Transformation rootTransform;
- @Nullable
- private ResourceLocation renderTypeHint;
- private boolean gui3d = true;
-
- @ApiStatus.Internal
- public BlockGeometryBakingContext(BlockModel owner) {
- this.owner = owner;
- }
-
- @Override
- public String getModelName() {
- return owner.name;
- }
-
- public boolean hasCustomGeometry() {
- return getCustomGeometry() != null;
- }
-
- @Nullable
- public IUnbakedGeometry> getCustomGeometry() {
- return owner.parent != null && customGeometry == null ? owner.parent.customData.getCustomGeometry() : customGeometry;
- }
-
- public void setCustomGeometry(IUnbakedGeometry> geometry) {
- this.customGeometry = geometry;
- }
-
- @Override
- public boolean isComponentVisible(String part, boolean fallback) {
- return owner.parent != null && !visibilityData.hasCustomVisibility(part) ? owner.parent.customData.isComponentVisible(part, fallback) : visibilityData.isVisible(part, fallback);
- }
-
- @Override
- public boolean hasMaterial(String name) {
- return owner.hasTexture(name);
- }
-
- @Override
- public Material getMaterial(String name) {
- return owner.getMaterial(name);
- }
-
- @Override
- public boolean isGui3d() {
- return gui3d;
- }
-
- @Override
- public boolean useBlockLight() {
- return owner.getGuiLight().lightLikeBlock();
- }
-
- @Override
- public boolean useAmbientOcclusion() {
- return owner.hasAmbientOcclusion();
- }
-
- @Override
- public ItemTransforms getTransforms() {
- return owner.getTransforms();
- }
-
- @Override
- public Transformation getRootTransform() {
- if (rootTransform != null)
- return rootTransform;
- return owner.parent != null ? owner.parent.customData.getRootTransform() : Transformation.identity();
- }
-
- public void setRootTransform(Transformation rootTransform) {
- this.rootTransform = rootTransform;
- }
-
- @Nullable
- @Override
- public ResourceLocation getRenderTypeHint() {
- if (renderTypeHint != null)
- return renderTypeHint;
- return owner.parent != null ? owner.parent.customData.getRenderTypeHint() : null;
- }
-
- public void setRenderTypeHint(ResourceLocation renderTypeHint) {
- this.renderTypeHint = renderTypeHint;
- }
-
- public void setGui3d(boolean gui3d) {
- this.gui3d = gui3d;
- }
-
- public void copyFrom(BlockGeometryBakingContext other) {
- this.customGeometry = other.customGeometry;
- this.rootTransform = other.rootTransform;
- this.visibilityData.copyFrom(other.visibilityData);
- this.renderTypeHint = other.renderTypeHint;
- this.gui3d = other.gui3d;
- }
-
- public BakedModel bake(ModelBaker baker, Function bakedTextureGetter, ModelState modelTransform, List overrides) {
- IUnbakedGeometry> geometry = getCustomGeometry();
- if (geometry == null)
- throw new IllegalStateException("Can not use custom baking without custom geometry");
- return geometry.bake(this, baker, bakedTextureGetter, modelTransform, overrides);
- }
-
- public static class VisibilityData {
- private final Map data = new HashMap<>();
-
- public boolean hasCustomVisibility(String part) {
- return data.containsKey(part);
- }
-
- public boolean isVisible(String part, boolean fallback) {
- return data.getOrDefault(part, fallback);
- }
-
- public void setVisibilityState(String partName, boolean type) {
- data.put(partName, type);
- }
-
- public void copyFrom(VisibilityData visibilityData) {
- data.clear();
- data.putAll(visibilityData.data);
- }
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java
deleted file mode 100644
index 1382c821..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.HashMap;
-import java.util.stream.Collectors;
-import net.minecraft.resources.ResourceLocation;
-import net.neoforged.fml.ModLoader;
-import net.neoforged.neoforge.client.event.ModelEvent;
-import org.jetbrains.annotations.ApiStatus;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * Manager for {@linkplain IGeometryLoader geometry loaders}.
- *
- * Provides a lookup.
- */
-public final class GeometryLoaderManager {
- private static ImmutableMap> LOADERS;
- private static String LOADER_LIST;
-
- /**
- * Finds the {@link IGeometryLoader} for a given name, or null if not found.
- */
- @Nullable
- public static IGeometryLoader> get(ResourceLocation name) {
- return LOADERS.get(name);
- }
-
- /**
- * Retrieves a comma-separated list of all active loaders, for use in error messages.
- */
- public static String getLoaderList() {
- return LOADER_LIST;
- }
-
- @ApiStatus.Internal
- public static void init() {
- var loaders = new HashMap>();
- var event = new ModelEvent.RegisterGeometryLoaders(loaders);
- ModLoader.postEventWrapContainerInModOrder(event);
- LOADERS = ImmutableMap.copyOf(loaders);
- LOADER_LIST = loaders.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", "));
- }
-
- private GeometryLoaderManager() {}
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java
deleted file mode 100644
index 3b1950ce..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import com.mojang.math.Transformation;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.resources.ResourceLocation;
-import net.neoforged.neoforge.client.NamedRenderTypeManager;
-import net.neoforged.neoforge.client.RenderTypeGroup;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * The context in which a geometry is being baked, providing information such as lighting and
- * {@linkplain ItemTransforms transforms}, and allowing the user to create {@linkplain Material materials} and query
- * {@linkplain RenderTypeGroup render types}.
- *
- * @see StandaloneGeometryBakingContext
- * @see BlockGeometryBakingContext
- */
-public interface IGeometryBakingContext {
- /**
- * {@return the name of the model being baked for logging and caching purposes.}
- */
- String getModelName();
-
- /**
- * Checks if a material is present in the model.
- *
- * @param name The name of the material
- * @return true if the material is present, false otherwise
- */
- boolean hasMaterial(String name);
-
- /**
- * Resolves the final texture name, taking into account texture aliases and replacements.
- *
- * @param name The name of the material
- * @return The material, or the missing texture if not found
- */
- Material getMaterial(String name);
-
- /**
- * {@return true if this model should render in 3D in a GUI, false otherwise}
- */
- boolean isGui3d();
-
- /**
- * {@return true if block lighting should be used for this model, false otherwise}
- */
- boolean useBlockLight();
-
- /**
- * {@return true if per-vertex ambient occlusion should be used for this model, false otherwise}
- */
- boolean useAmbientOcclusion();
-
- /**
- * {@return the transforms for display in item form.}
- */
- ItemTransforms getTransforms();
-
- /**
- * {@return the root transformation to be applied to all variants of this model, regardless of item transforms.}
- */
- Transformation getRootTransform();
-
- /**
- * {@return a hint of the render type this model should use. Custom loaders may ignore this.}
- */
- @Nullable
- ResourceLocation getRenderTypeHint();
-
- /**
- * Queries the visibility of a component of this model.
- *
- * @param component The component for which to query visibility
- * @param fallback The default visibility if an override isn't found
- * @return The visibility of the component
- */
- boolean isComponentVisible(String component, boolean fallback);
-
- /**
- * {@return a {@link RenderTypeGroup} with the given name, or the empty group if not found.}
- */
- default RenderTypeGroup getRenderType(ResourceLocation name) {
- return NamedRenderTypeManager.get(name);
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java
deleted file mode 100644
index 67f2c7d0..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
-import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
-import net.neoforged.neoforge.client.event.ModelEvent;
-import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
-
-/**
- * A loader for custom {@linkplain IUnbakedGeometry model geometries}.
- *
- * If you do any caching, you should implement {@link ResourceManagerReloadListener} and register it with
- * {@link RegisterClientReloadListenersEvent}.
- *
- * @see ModelEvent.RegisterGeometryLoaders
- * @see RegisterClientReloadListenersEvent
- */
-public interface IGeometryLoader> {
- T read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) throws JsonParseException;
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java
deleted file mode 100644
index 79be568a..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import java.util.List;
-import java.util.Set;
-import java.util.function.Function;
-import net.minecraft.client.renderer.block.model.BlockModel;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.client.resources.model.UnbakedModel;
-
-/**
- * General interface for any model that can be baked, superset of vanilla {@link UnbakedModel}.
- *
- * Instances of this class ar usually created via {@link IGeometryLoader}.
- *
- * @see IGeometryLoader
- * @see IGeometryBakingContext
- */
-public interface IUnbakedGeometry> {
- BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides);
-
- /**
- * Resolve parents of nested {@link BlockModel}s which are later used in
- * {@link IUnbakedGeometry#bake(IGeometryBakingContext, ModelBaker, Function, ModelState, List)}
- * via {@link BlockModel#resolveDependencies(UnbakedModel.Resolver)}
- */
- default void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) {}
-
- /**
- * {@return a set of all the components whose visibility may be configured via {@link IGeometryBakingContext}}
- */
- default Set getConfigurableComponentNames() {
- return Set.of();
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java
deleted file mode 100644
index 1c8ea096..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import java.util.List;
-import java.util.function.Function;
-import net.minecraft.client.renderer.block.model.BakedQuad;
-import net.minecraft.client.renderer.block.model.ItemOverride;
-import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.BakedModel;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.client.resources.model.ModelBaker;
-import net.minecraft.client.resources.model.ModelState;
-import net.neoforged.neoforge.client.RenderTypeGroup;
-import net.neoforged.neoforge.client.model.IModelBuilder;
-
-/**
- * Base class for implementations of {@link IUnbakedGeometry} which do not wish to handle model creation themselves,
- * instead supplying {@linkplain BakedQuad baked quads} through a builder.
- */
-public abstract class SimpleUnbakedGeometry> implements IUnbakedGeometry {
- @Override
- public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) {
- TextureAtlasSprite particle = spriteGetter.apply(context.getMaterial("particle"));
-
- var renderTypeHint = context.getRenderTypeHint();
- var renderTypes = renderTypeHint != null ? context.getRenderType(renderTypeHint) : RenderTypeGroup.EMPTY;
- IModelBuilder> builder = IModelBuilder.of(context.useAmbientOcclusion(), context.useBlockLight(), context.isGui3d(),
- context.getTransforms(), particle, renderTypes);
-
- addQuads(context, builder, baker, spriteGetter, modelState);
-
- return builder.build();
- }
-
- protected abstract void addQuads(IGeometryBakingContext owner, IModelBuilder> modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform);
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java
deleted file mode 100644
index f983b777..00000000
--- a/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (c) Forge Development LLC and contributors
- * SPDX-License-Identifier: LGPL-2.1-only
- */
-
-package net.neoforged.neoforge.client.model.geometry;
-
-import com.google.common.base.Predicates;
-import com.mojang.math.Transformation;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
-import java.util.Map;
-import java.util.function.BiPredicate;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import net.minecraft.client.renderer.block.model.ItemTransforms;
-import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite;
-import net.minecraft.client.renderer.texture.TextureAtlas;
-import net.minecraft.client.resources.model.Material;
-import net.minecraft.resources.ResourceLocation;
-import org.jetbrains.annotations.Nullable;
-
-/**
- * A {@linkplain IGeometryBakingContext geometry baking context} that is not bound to block/item model loading.
- */
-public class StandaloneGeometryBakingContext implements IGeometryBakingContext {
- public static final ResourceLocation LOCATION = ResourceLocation.fromNamespaceAndPath("neoforge", "standalone");
-
- public static final StandaloneGeometryBakingContext INSTANCE = create(LOCATION);
-
- public static StandaloneGeometryBakingContext create(ResourceLocation modelName) {
- return builder().build(modelName);
- }
-
- public static StandaloneGeometryBakingContext create(Map textures) {
- return create(LOCATION, textures);
- }
-
- public static StandaloneGeometryBakingContext create(ResourceLocation modelName, Map textures) {
- return builder().withTextures(textures, MissingTextureAtlasSprite.getLocation()).build(modelName);
- }
-
- private final ResourceLocation modelName;
- private final Predicate materialCheck;
- private final Function materialLookup;
- private final boolean isGui3d;
- private final boolean useBlockLight;
- private final boolean useAmbientOcclusion;
- private final ItemTransforms transforms;
- private final Transformation rootTransform;
- @Nullable
- private final ResourceLocation renderTypeHint;
- private final BiPredicate visibilityTest;
-
- private StandaloneGeometryBakingContext(ResourceLocation modelName, Predicate materialCheck,
- Function materialLookup, boolean isGui3d,
- boolean useBlockLight, boolean useAmbientOcclusion,
- ItemTransforms transforms, Transformation rootTransform,
- @Nullable ResourceLocation renderTypeHint,
- BiPredicate visibilityTest) {
- this.modelName = modelName;
- this.materialCheck = materialCheck;
- this.materialLookup = materialLookup;
- this.isGui3d = isGui3d;
- this.useBlockLight = useBlockLight;
- this.useAmbientOcclusion = useAmbientOcclusion;
- this.transforms = transforms;
- this.rootTransform = rootTransform;
- this.renderTypeHint = renderTypeHint;
- this.visibilityTest = visibilityTest;
- }
-
- @Override
- public String getModelName() {
- return modelName.toString();
- }
-
- @Override
- public boolean hasMaterial(String name) {
- return materialCheck.test(name);
- }
-
- @Override
- public Material getMaterial(String name) {
- return materialLookup.apply(name);
- }
-
- @Override
- public boolean isGui3d() {
- return isGui3d;
- }
-
- @Override
- public boolean useBlockLight() {
- return useBlockLight;
- }
-
- @Override
- public boolean useAmbientOcclusion() {
- return useAmbientOcclusion;
- }
-
- @Override
- public ItemTransforms getTransforms() {
- return transforms;
- }
-
- @Override
- public Transformation getRootTransform() {
- return rootTransform;
- }
-
- @Nullable
- @Override
- public ResourceLocation getRenderTypeHint() {
- return renderTypeHint;
- }
-
- @Override
- public boolean isComponentVisible(String component, boolean fallback) {
- return visibilityTest.test(component, fallback);
- }
-
- public static Builder builder() {
- return new Builder();
- }
-
- public static Builder builder(IGeometryBakingContext parent) {
- return new Builder(parent);
- }
-
- public static final class Builder {
- private static final Material NO_MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation());
- private Predicate materialCheck = Predicates.alwaysFalse();
- private Function materialLookup = $ -> NO_MATERIAL;
- private boolean isGui3d = true;
- private boolean useBlockLight = true;
- private boolean useAmbientOcclusion = true;
- private ItemTransforms transforms = ItemTransforms.NO_TRANSFORMS;
- private Transformation rootTransform = Transformation.identity();
- @Nullable
- private ResourceLocation renderTypeHint;
- private BiPredicate visibilityTest = (c, def) -> def;
-
- private Builder() {}
-
- private Builder(IGeometryBakingContext parent) {
- this.materialCheck = parent::hasMaterial;
- this.materialLookup = parent::getMaterial;
- this.isGui3d = parent.isGui3d();
- this.useBlockLight = parent.useBlockLight();
- this.useAmbientOcclusion = parent.useAmbientOcclusion();
- this.transforms = parent.getTransforms();
- this.rootTransform = parent.getRootTransform();
- this.renderTypeHint = parent.getRenderTypeHint();
- this.visibilityTest = parent::isComponentVisible;
- }
-
- public Builder withTextures(Map textures, ResourceLocation defaultTexture) {
- return withTextures(TextureAtlas.LOCATION_BLOCKS, textures, defaultTexture);
- }
-
- public Builder withTextures(ResourceLocation atlasLocation, Map textures, ResourceLocation defaultTexture) {
- this.materialCheck = textures::containsKey;
- this.materialLookup = name -> new Material(atlasLocation, textures.getOrDefault(name, defaultTexture));
- return this;
- }
-
- public Builder withMaterials(Map materials, Material defaultMaterial) {
- this.materialCheck = materials::containsKey;
- this.materialLookup = name -> materials.getOrDefault(name, defaultMaterial);
- return this;
- }
-
- public Builder withGui3d(boolean isGui3d) {
- this.isGui3d = isGui3d;
- return this;
- }
-
- public Builder withUseBlockLight(boolean useBlockLight) {
- this.useBlockLight = useBlockLight;
- return this;
- }
-
- public Builder withUseAmbientOcclusion(boolean useAmbientOcclusion) {
- this.useAmbientOcclusion = useAmbientOcclusion;
- return this;
- }
-
- public Builder withTransforms(ItemTransforms transforms) {
- this.transforms = transforms;
- return this;
- }
-
- public Builder withRootTransform(Transformation rootTransform) {
- this.rootTransform = rootTransform;
- return this;
- }
-
- public Builder withRenderTypeHint(ResourceLocation renderTypeHint) {
- this.renderTypeHint = renderTypeHint;
- return this;
- }
-
- public Builder withVisibleComponents(Object2BooleanMap parts) {
- this.visibilityTest = parts::getOrDefault;
- return this;
- }
-
- public StandaloneGeometryBakingContext build(ResourceLocation modelName) {
- return new StandaloneGeometryBakingContext(modelName, materialCheck, materialLookup, isGui3d, useBlockLight, useAmbientOcclusion, transforms, rootTransform, renderTypeHint, visibilityTest);
- }
- }
-}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java b/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java
new file mode 100644
index 00000000..d2f69a1d
--- /dev/null
+++ b/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) NeoForged and contributors
+ * SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+package net.neoforged.neoforge.client.model.item;
+
+import com.mojang.math.Transformation;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.MapCodec;
+import com.mojang.serialization.codecs.RecordCodecBuilder;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import net.minecraft.client.color.item.Constant;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.item.BlockModelWrapper;
+import net.minecraft.client.renderer.item.ItemModel;
+import net.minecraft.client.renderer.item.ItemModelResolver;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.client.resources.model.BlockModelRotation;
+import net.minecraft.client.resources.model.Material;
+import net.minecraft.client.resources.model.ModelState;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.ItemDisplayContext;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.material.Fluid;
+import net.minecraft.world.level.material.Fluids;
+import net.neoforged.neoforge.client.ClientHooks;
+import net.neoforged.neoforge.client.NeoForgeRenderTypes;
+import net.neoforged.neoforge.client.RenderTypeGroup;
+import net.neoforged.neoforge.client.color.item.FluidContentsTint;
+import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions;
+import net.neoforged.neoforge.client.model.QuadTransformers;
+import net.neoforged.neoforge.client.model.SimpleModelState;
+import net.neoforged.neoforge.client.model.UnbakedCompositeModel;
+import net.neoforged.neoforge.client.model.UnbakedElementsHelper;
+import net.neoforged.neoforge.fluids.FluidStack;
+import net.neoforged.neoforge.fluids.FluidUtil;
+import org.jetbrains.annotations.Nullable;
+import org.joml.Quaternionf;
+import org.joml.Vector3f;
+
+/**
+ * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid.
+ *
+ * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask).
+ * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting.
+ */
+public class DynamicFluidContainerModel implements ItemModel {
+ // Depth offsets to prevent Z-fighting
+ private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf());
+ private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf());
+
+ private static RenderTypeGroup getLayerRenderTypes(boolean unlit) {
+ return new RenderTypeGroup(RenderType.translucent(), unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get());
+ }
+
+ private final Unbaked unbakedModel;
+ private final BakingContext bakingContext;
+ private final ItemTransforms itemTransforms;
+ private final Map cache = new IdentityHashMap<>(); // contains all the baked models since they'll never change
+
+ private DynamicFluidContainerModel(Unbaked unbakedModel, BakingContext bakingContext) {
+ this.unbakedModel = unbakedModel;
+ this.bakingContext = bakingContext;
+ // Source ItemTransforms from the base item model
+ var baseItemModel = bakingContext.blockModelBaker().getModel(ResourceLocation.withDefaultNamespace("item/generated"));
+ if (baseItemModel == null) {
+ throw new IllegalStateException("Failed to access item/generated model");
+ }
+ this.itemTransforms = baseItemModel.getTransforms();
+ }
+
+ private ItemModel bakeModelForFluid(Fluid fluid) {
+ var sprites = bakingContext.blockModelBaker().sprites();
+
+ Material particleLocation = unbakedModel.textures.particle.map(ClientHooks::getBlockMaterial).orElse(null);
+ Material baseLocation = unbakedModel.textures.base.map(ClientHooks::getBlockMaterial).orElse(null);
+ Material fluidMaskLocation = unbakedModel.textures.fluid.map(ClientHooks::getBlockMaterial).orElse(null);
+ Material coverLocation = unbakedModel.textures.cover.map(ClientHooks::getBlockMaterial).orElse(null);
+
+ TextureAtlasSprite baseSprite = baseLocation != null ? sprites.get(baseLocation) : null;
+ TextureAtlasSprite fluidSprite = fluid != Fluids.EMPTY ? sprites.get(ClientHooks.getBlockMaterial(IClientFluidTypeExtensions.of(fluid).getStillTexture())) : null;
+ TextureAtlasSprite coverSprite = (coverLocation != null && (!unbakedModel.coverIsMask || baseLocation != null)) ? sprites.get(coverLocation) : null;
+
+ TextureAtlasSprite particleSprite = particleLocation != null ? sprites.get(particleLocation) : null;
+
+ if (particleSprite == null) particleSprite = fluidSprite;
+ if (particleSprite == null) particleSprite = baseSprite;
+ if (particleSprite == null && !unbakedModel.coverIsMask) particleSprite = coverSprite;
+
+ // If the fluid is lighter than air, rotate 180deg to turn it upside down
+ ModelState state = BlockModelRotation.X0_Y0;
+ if (unbakedModel.flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) {
+ state = new SimpleModelState(
+ state.getRotation().compose(
+ new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null)));
+ }
+
+ // We need to disable GUI 3D and block lighting for this to render properly
+ var modelBuilder = UnbakedCompositeModel.Baked.builder(true, false, false, particleSprite, itemTransforms);
+
+ var normalRenderTypes = getLayerRenderTypes(false);
+
+ if (baseLocation != null) {
+ // Base texture
+ var unbaked = UnbakedElementsHelper.createUnbakedItemElements(0, baseSprite);
+ var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> baseSprite, state);
+ modelBuilder.addQuads(normalRenderTypes, quads);
+ }
+
+ if (fluidMaskLocation != null && fluidSprite != null) {
+ TextureAtlasSprite templateSprite = sprites.get(fluidMaskLocation);
+ // Fluid layer
+ var transformedState = new SimpleModelState(state.getRotation().compose(FLUID_TRANSFORM), state.isUvLocked());
+ var unbaked = UnbakedElementsHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask
+ var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture
+
+ var emissive = unbakedModel.applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0;
+ var renderTypes = getLayerRenderTypes(emissive);
+ if (emissive) QuadTransformers.settingMaxEmissivity().processInPlace(quads);
+
+ modelBuilder.addQuads(renderTypes, quads);
+ }
+
+ if (coverSprite != null) {
+ var sprite = unbakedModel.coverIsMask ? baseSprite : coverSprite;
+ // Cover/overlay
+ var transformedState = new SimpleModelState(state.getRotation().compose(COVER_TRANSFORM), state.isUvLocked());
+ var unbaked = UnbakedElementsHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask
+ var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture
+ modelBuilder.addQuads(normalRenderTypes, quads);
+ }
+
+ modelBuilder.setParticle(particleSprite);
+
+ return new BlockModelWrapper(modelBuilder.build(), List.of(new Constant(-1), FluidContentsTint.INSTANCE));
+ }
+
+ @Override
+ public void update(ItemStackRenderState renderState, ItemStack stack, ItemModelResolver modelResolver, ItemDisplayContext displayContext, @Nullable ClientLevel level, @Nullable LivingEntity entity, int p_387820_) {
+ var fluid = FluidUtil.getFluidContained(stack)
+ .map(FluidStack::getFluid)
+ // not a fluid item apparently
+ .orElse(unbakedModel.fluid);
+
+ cache.computeIfAbsent(fluid, this::bakeModelForFluid)
+ .update(renderState, stack, modelResolver, displayContext, level, entity, p_387820_);
+ }
+
+ public record Textures(
+ Optional particle,
+ Optional base,
+ Optional fluid,
+ Optional cover) {
+ public static final Codec CODEC = RecordCodecBuilder.create(
+ instance -> instance
+ .group(
+ ResourceLocation.CODEC.optionalFieldOf("particle").forGetter(Textures::particle),
+ ResourceLocation.CODEC.optionalFieldOf("base").forGetter(Textures::base),
+ ResourceLocation.CODEC.optionalFieldOf("fluid").forGetter(Textures::fluid),
+ ResourceLocation.CODEC.optionalFieldOf("cover").forGetter(Textures::cover))
+ .apply(instance, Textures::new))
+ .validate(textures -> {
+ if (textures.particle.isPresent() || textures.base.isPresent() || textures.fluid.isPresent() || textures.cover.isPresent()) {
+ return DataResult.success(textures);
+ }
+ return DataResult.error(() -> "Dynamic fluid container model requires at least one particle, base, fluid or cover texture.");
+ });
+ }
+
+ public record Unbaked(Textures textures, Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) implements ItemModel.Unbaked {
+
+ public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(
+ instance -> instance
+ .group(
+ Textures.CODEC.fieldOf("textures").forGetter(Unbaked::textures),
+ BuiltInRegistries.FLUID.byNameCodec().fieldOf("fluid").forGetter(Unbaked::fluid),
+ Codec.BOOL.optionalFieldOf("flip_gas", false).forGetter(Unbaked::flipGas),
+ Codec.BOOL.optionalFieldOf("cover_is_mask", true).forGetter(Unbaked::coverIsMask),
+ Codec.BOOL.optionalFieldOf("apply_fluid_luminosity", true).forGetter(Unbaked::applyFluidLuminosity))
+ .apply(instance, Unbaked::new));
+ @Override
+ public MapCodec extends ItemModel.Unbaked> type() {
+ return MAP_CODEC;
+ }
+
+ @Override
+ public ItemModel bake(BakingContext bakingContext) {
+ return new DynamicFluidContainerModel(this, bakingContext);
+ }
+
+ @Override
+ public void resolveDependencies(Resolver resolver) {
+ //No dependencies
+ }
+ }
+}
diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java b/src/main/java/net/neoforged/neoforge/client/model/item/package-info.java
similarity index 85%
rename from src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java
rename to src/main/java/net/neoforged/neoforge/client/model/item/package-info.java
index c5923aab..87c7c56d 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/item/package-info.java
@@ -6,7 +6,7 @@
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
-package net.neoforged.neoforge.client.model.renderable;
+package net.neoforged.neoforge.client.model.item;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.FieldsAreNonnullByDefault;
diff --git a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java
index 65d2b939..0bf9931d 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java
@@ -7,17 +7,21 @@
import com.google.common.collect.Maps;
import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
+import com.mojang.math.Transformation;
import java.io.FileNotFoundException;
+import java.util.HashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.block.model.BlockModel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.minecraft.util.GsonHelper;
-import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
+import net.neoforged.neoforge.client.model.UnbakedModelLoader;
/**
* A loader for {@link ObjModel OBJ models}.
@@ -25,7 +29,7 @@
* Allows the user to enable automatic face culling, toggle quad shading, flip UVs, render emissively and specify a
* {@link ObjMaterialLibrary material library} override.
*/
-public class ObjLoader implements IGeometryLoader, ResourceManagerReloadListener {
+public class ObjLoader implements UnbakedModelLoader, ResourceManagerReloadListener {
public static ObjLoader INSTANCE = new ObjLoader();
private final Map modelCache = Maps.newConcurrentMap();
@@ -40,7 +44,7 @@ public void onResourceManagerReload(ResourceManager resourceManager) {
}
@Override
- public ObjModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) {
+ public ObjModel read(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
if (!jsonObject.has("model"))
throw new JsonParseException("OBJ Loader requires a 'model' key that points to a valid .OBJ model.");
@@ -52,7 +56,21 @@ public ObjModel read(JsonObject jsonObject, JsonDeserializationContext deseriali
boolean emissiveAmbient = GsonHelper.getAsBoolean(jsonObject, "emissive_ambient", true);
String mtlOverride = GsonHelper.getAsString(jsonObject, "mtl_override", null);
- return loadModel(new ObjModel.ModelSettings(ResourceLocation.parse(modelLocation), automaticCulling, shadeQuads, flipV, emissiveAmbient, mtlOverride));
+ final Map partVisibility = new HashMap<>();
+ if (jsonObject.has("visibility")) {
+ JsonObject visibility = GsonHelper.getAsJsonObject(jsonObject, "visibility");
+ for (Map.Entry part : visibility.entrySet()) {
+ partVisibility.put(part.getKey(), part.getValue().getAsBoolean());
+ }
+ }
+
+ Transformation transformation = Transformation.identity();
+ if (jsonObject.has("transform")) {
+ JsonElement transform = jsonObject.get("transform");
+ transformation = BlockModel.GSON.fromJson(transform, Transformation.class);
+ }
+
+ return loadModel(new ObjModel.ModelSettings(ResourceLocation.parse(modelLocation), automaticCulling, shadeQuads, flipV, emissiveAmbient, mtlOverride, partVisibility, transformation));
}
public ObjModel loadModel(ObjModel.ModelSettings settings) {
diff --git a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java
index 25404e54..98625b04 100644
--- a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java
+++ b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java
@@ -7,40 +7,33 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import com.mojang.math.Transformation;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
import joptsimple.internal.Strings;
+import net.minecraft.client.data.models.model.TextureSlot;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.block.model.ItemTransforms;
+import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
-import net.minecraft.client.resources.model.Material;
+import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.client.resources.model.ModelState;
-import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.client.resources.model.SimpleBakedModel;
import net.minecraft.core.Direction;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.Mth;
+import net.minecraft.util.context.ContextMap;
import net.minecraft.world.phys.Vec2;
-import net.neoforged.neoforge.client.model.IModelBuilder;
-import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
-import net.neoforged.neoforge.client.model.geometry.SimpleUnbakedGeometry;
-import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper;
+import net.neoforged.neoforge.client.RenderTypeGroup;
+import net.neoforged.neoforge.client.model.ExtendedUnbakedModel;
+import net.neoforged.neoforge.client.model.NeoForgeModelProperties;
import net.neoforged.neoforge.client.model.pipeline.QuadBakingVertexConsumer;
-import net.neoforged.neoforge.client.model.renderable.CompositeRenderable;
-import net.neoforged.neoforge.client.textures.UnitTextureAtlasSprite;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.Nullable;
import org.joml.Vector3f;
@@ -52,7 +45,7 @@
* Supports positions, texture coordinates, normals and colors. The {@link ObjMaterialLibrary material library}
* has support for numerous features, including support for {@link ResourceLocation} textures (non-standard).
*/
-public class ObjModel extends SimpleUnbakedGeometry {
+public class ObjModel implements ExtendedUnbakedModel {
private static final Vector4f COLOR_WHITE = new Vector4f(1, 1, 1, 1);
private static final Vec2[] DEFAULT_COORDS = {
new Vec2(0, 0),
@@ -62,8 +55,6 @@ public class ObjModel extends SimpleUnbakedGeometry {
};
private final Map parts = Maps.newLinkedHashMap();
- private final Set rootComponentNames = Collections.unmodifiableSet(parts.keySet());
- private Set allComponentNames;
private final List positions = Lists.newArrayList();
private final List texCoords = Lists.newArrayList();
@@ -78,6 +69,8 @@ public class ObjModel extends SimpleUnbakedGeometry {
public final String mtlOverride;
public final ResourceLocation modelLocation;
+ public final Map partVisibility;
+ public final Transformation rootTransform;
private ObjModel(ModelSettings settings) {
this.modelLocation = settings.modelLocation;
@@ -86,6 +79,8 @@ private ObjModel(ModelSettings settings) {
this.flipV = settings.flipV;
this.emissiveAmbient = settings.emissiveAmbient;
this.mtlOverride = settings.mtlOverride;
+ this.partVisibility = settings.partVisibility;
+ this.rootTransform = settings.rootTransform;
}
public static ObjModel parse(ObjTokenizer tokenizer, ModelSettings settings) throws IOException {
@@ -292,26 +287,6 @@ static Vector4f parseVector4(String[] line) {
};
}
- @Override
- protected void addQuads(IGeometryBakingContext owner, IModelBuilder> modelBuilder, ModelBaker baker, Function