feat: dynamic tinting
Some checks are pending
Build / build (push) Waiting to run

This commit is contained in:
SushiCannibale 2025-08-13 19:19:50 +02:00
parent 9bcabeb8f7
commit 67ae714c64
8 changed files with 144 additions and 69 deletions

View file

@ -8,6 +8,7 @@ import fr.sushi.charmsnfabrics.client.model.FlowerCrownItemModel;
import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel; import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel;
import fr.sushi.charmsnfabrics.client.renderer.FloralWorkbenchRenderer; import fr.sushi.charmsnfabrics.client.renderer.FloralWorkbenchRenderer;
import fr.sushi.charmsnfabrics.client.renderer.FlowerCrownRenderer; import fr.sushi.charmsnfabrics.client.renderer.FlowerCrownRenderer;
import fr.sushi.charmsnfabrics.client.util.FlowersTint;
import fr.sushi.charmsnfabrics.common.CnFRegistries; import fr.sushi.charmsnfabrics.common.CnFRegistries;
import net.minecraft.data.loot.LootTableProvider; import net.minecraft.data.loot.LootTableProvider;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -19,6 +20,7 @@ import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent; import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.client.event.EntityRenderersEvent; import net.neoforged.neoforge.client.event.EntityRenderersEvent;
import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent;
import net.neoforged.neoforge.client.event.RegisterItemModelsEvent; import net.neoforged.neoforge.client.event.RegisterItemModelsEvent;
import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.ConfigurationScreen;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
@ -76,7 +78,8 @@ public class CnFClient
} }
@SubscribeEvent @SubscribeEvent
private static void onRegisterItemModels(final RegisterItemModelsEvent event) private static void onRegisterItemModels(
final RegisterItemModelsEvent event)
{ {
event.register( event.register(
ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID, ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
@ -84,6 +87,15 @@ public class CnFClient
FlowerCrownItemModel.Unbaked.MAP_CODEC); FlowerCrownItemModel.Unbaked.MAP_CODEC);
} }
@SubscribeEvent
private static void onRegisterTinters(
RegisterColorHandlersEvent.ItemTintSources event)
{
event.register(
ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"flower_tint"), FlowersTint.MAP_CODEC);
}
// @SubscribeEvent // @SubscribeEvent
// private static void onRegisterStandaloneModels( // private static void onRegisterStandaloneModels(
// ModelEvent.RegisterStandalone event) // ModelEvent.RegisterStandalone event)

View file

@ -4,8 +4,9 @@ import com.mojang.math.Transformation;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import fr.sushi.charmsnfabrics.CharmsAndFabrics; import fr.sushi.charmsnfabrics.CharmsAndFabrics;
import fr.sushi.charmsnfabrics.client.util.FlowersTint;
import fr.sushi.charmsnfabrics.common.CnFRegistries; import fr.sushi.charmsnfabrics.common.CnFRegistries;
import fr.sushi.charmsnfabrics.common.data.SavedColors; import fr.sushi.charmsnfabrics.common.data.FlowerCrownContent;
import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BakedQuad;
@ -16,7 +17,6 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*; import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.client.ClientHooks; import net.neoforged.neoforge.client.ClientHooks;
@ -35,8 +35,6 @@ import java.util.Map;
public class FlowerCrownItemModel implements ItemModel public class FlowerCrownItemModel implements ItemModel
{ {
/* TODO: Center flower0&1 textures and play with transform to get them at the right place for every layer */
private static final Map<Integer, Transformation> TRANSFORMS = private static final Map<Integer, Transformation> TRANSFORMS =
new HashMap<>(); new HashMap<>();
@ -117,30 +115,33 @@ public class FlowerCrownItemModel implements ItemModel
return new BlockModelWrapper(List.of(), quads, this.renderProperties); return new BlockModelWrapper(List.of(), quads, this.renderProperties);
} }
private ItemModel bakeLayer(int layer, DyeColor color) private ItemModel bakeLayer(int layer, ItemStack flower)
{ {
SpriteGetter sprites = this.context.blockModelBaker().sprites(); SpriteGetter sprites = this.context.blockModelBaker().sprites();
Material material = Material material =
ClientHooks.getBlockMaterial(this.getFlowerLocation(layer)); ClientHooks.getBlockMaterial(this.getFlowerLocation(layer));
TextureAtlasSprite sprite = sprites.get(material, DEBUG_NAME); TextureAtlasSprite sprite = sprites.get(material, DEBUG_NAME);
/* Create all the bits that makes the model */
List<BlockElement> bits = List<BlockElement> bits =
UnbakedElementsHelper.createUnbakedItemElements(0, sprite); UnbakedElementsHelper.createUnbakedItemElements(0, sprite);
List<BakedQuad> quads = List<BakedQuad> quads =
UnbakedElementsHelper.bakeElements(bits, $ -> sprite, UnbakedElementsHelper.bakeElements(bits, $ -> sprite,
new ComposedModelState(BlockModelRotation.X0_Y0, new ComposedModelState(BlockModelRotation.X0_Y0,
TRANSFORMS.get(layer))); TRANSFORMS.get(layer)));
return new BlockModelWrapper(List.of(), quads, this.renderProperties); return new BlockModelWrapper(List.of(new FlowersTint(layer)), quads,
this.renderProperties);
} }
private ItemModel composeModel(SavedColors colors) private ItemModel composeModel(FlowerCrownContent colors)
{ {
List<ItemModel> models = new ArrayList<>(); List<ItemModel> models = new ArrayList<>();
models.add(this.bakeBase()); models.add(this.bakeBase());
for (int i = 0; i < colors.flowers().size(); i++) for (int i = 0; i < colors.flowers().size(); i++)
{
if (!colors.flowers().get(i).isEmpty())
{ {
models.add(this.bakeLayer(i, colors.flowers().get(i))); models.add(this.bakeLayer(i, colors.flowers().get(i)));
} }
}
return new CompositeModel(models); return new CompositeModel(models);
} }
@ -150,11 +151,11 @@ public class FlowerCrownItemModel implements ItemModel
ItemDisplayContext displayContext, @Nullable ClientLevel level, ItemDisplayContext displayContext, @Nullable ClientLevel level,
@Nullable LivingEntity entity, int seed) @Nullable LivingEntity entity, int seed)
{ {
SavedColors comp = FlowerCrownContent comp =
stack.get(CnFRegistries.DataComponents.SAVED_FLOWERS.get()); stack.get(CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get());
if (comp == null) if (comp == null)
{ {
comp = new SavedColors(List.of()); comp = new FlowerCrownContent(List.of());
} }
this.composeModel(comp) this.composeModel(comp)
.update(renderState, stack, itemModelResolver, displayContext, .update(renderState, stack, itemModelResolver, displayContext,

View file

@ -4,7 +4,6 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import fr.sushi.charmsnfabrics.client.CnFLayers; import fr.sushi.charmsnfabrics.client.CnFLayers;
import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel; import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel;
import fr.sushi.charmsnfabrics.common.data.SavedColors;
import fr.sushi.charmsnfabrics.common.item.FlowerCrown; import fr.sushi.charmsnfabrics.common.item.FlowerCrown;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.model.EntityModel; import net.minecraft.client.model.EntityModel;
@ -15,8 +14,6 @@ import net.minecraft.client.renderer.entity.EntityRendererProvider;
import net.minecraft.client.renderer.entity.ItemRenderer; import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.renderer.entity.RenderLayerParent; import net.minecraft.client.renderer.entity.RenderLayerParent;
import net.minecraft.client.renderer.entity.state.LivingEntityRenderState; import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
import net.minecraft.client.renderer.item.BlockModelWrapper;
import net.minecraft.client.renderer.item.ItemModel;
import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;

View file

@ -0,0 +1,54 @@
package fr.sushi.charmsnfabrics.client.util;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import fr.sushi.charmsnfabrics.common.CnFRegistries;
import fr.sushi.charmsnfabrics.common.data.FlowerCrownContent;
import net.minecraft.client.color.item.ItemTintSource;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.tags.ItemTags;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.item.DyeColor;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public record FlowersTint(int layer) implements ItemTintSource
{
public static final MapCodec<FlowersTint> MAP_CODEC =
RecordCodecBuilder.mapCodec(instance -> instance
.group(Codec.INT.fieldOf("layer")
.forGetter(FlowersTint::layer))
.apply(instance, FlowersTint::new));
@Override
public int calculate(ItemStack stack, @Nullable ClientLevel level,
@Nullable LivingEntity entity)
{
FlowerCrownContent data =
stack.get(CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get());
if (data != null)
{
ItemStack flower = data.flowers().get(this.layer);
if (flower.is(ItemTags.FLOWERS)) {
}
if (flower.is(Items.POPPY))
{
return DyeColor.RED.getTextureDiffuseColor();
}
else if (flower.is(Items.DANDELION))
{
return DyeColor.YELLOW.getTextureDiffuseColor();
}
}
return 0xFFFFFFFF;
}
@Override
public MapCodec<FlowersTint> type()
{
return MAP_CODEC;
}
}

View file

@ -2,7 +2,7 @@ package fr.sushi.charmsnfabrics.common;
import fr.sushi.charmsnfabrics.CharmsAndFabrics; import fr.sushi.charmsnfabrics.CharmsAndFabrics;
import fr.sushi.charmsnfabrics.common.block.FloralWorkbench; import fr.sushi.charmsnfabrics.common.block.FloralWorkbench;
import fr.sushi.charmsnfabrics.common.data.SavedColors; import fr.sushi.charmsnfabrics.common.data.FlowerCrownContent;
import fr.sushi.charmsnfabrics.common.entities.block.FloralWorkbenchBlockEntity; import fr.sushi.charmsnfabrics.common.entities.block.FloralWorkbenchBlockEntity;
import fr.sushi.charmsnfabrics.common.item.FlowerCrown; import fr.sushi.charmsnfabrics.common.item.FlowerCrown;
import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponentType;
@ -38,8 +38,8 @@ public class CnFRegistries
ITEMS.registerItem("flower_crown", ITEMS.registerItem("flower_crown",
(properties) -> new FlowerCrown(properties.stacksTo(1) (properties) -> new FlowerCrown(properties.stacksTo(1)
.component( .component(
DataComponents.SAVED_FLOWERS, DataComponents.FLOWER_CROWN_CONTENT,
new SavedColors( new FlowerCrownContent(
List.of())))); List.of()))));
/* BlockItems */ /* BlockItems */
public static final DeferredItem<BlockItem> FLORAL_WORKBENCH = public static final DeferredItem<BlockItem> FLORAL_WORKBENCH =
@ -112,12 +112,12 @@ public class CnFRegistries
DeferredRegister.createDataComponents( DeferredRegister.createDataComponents(
Registries.DATA_COMPONENT_TYPE, CharmsAndFabrics.MODID); Registries.DATA_COMPONENT_TYPE, CharmsAndFabrics.MODID);
public static final Supplier<DataComponentType<SavedColors>> public static final Supplier<DataComponentType<FlowerCrownContent>>
SAVED_FLOWERS = FLOWER_CROWN_CONTENT =
COMPONENT_TYPES.registerComponentType("saved_flowers", COMPONENT_TYPES.registerComponentType("flower_crown_content",
builder -> builder.persistent(SavedColors.CODEC) builder -> builder.persistent(FlowerCrownContent.CODEC)
.networkSynchronized( .networkSynchronized(
SavedColors.STREAM_CODEC)); FlowerCrownContent.STREAM_CODEC));
} }
public static void register(IEventBus bus) public static void register(IEventBus bus)

View file

@ -0,0 +1,26 @@
package fr.sushi.charmsnfabrics.common.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.ItemStack;
import java.util.List;
public record FlowerCrownContent(List<ItemStack> flowers)
{
public static final FlowerCrownContent EMPTY =
new FlowerCrownContent(List.of());
public static final Codec<FlowerCrownContent> CODEC =
ItemStack.CODEC.listOf().flatXmap((items) -> DataResult.success(
new FlowerCrownContent(items)),
content -> DataResult.success(content.flowers));
public static final StreamCodec<RegistryFriendlyByteBuf, FlowerCrownContent>
STREAM_CODEC = ItemStack.STREAM_CODEC.apply(ByteBufCodecs.list())
.map(FlowerCrownContent::new,
content -> content.flowers);
public static int MAX_SIZE = 7;
}

View file

@ -1,24 +0,0 @@
package fr.sushi.charmsnfabrics.common.data;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import net.minecraft.network.codec.ByteBufCodecs;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.item.DyeColor;
import java.util.List;
public record SavedColors(List<DyeColor> flowers)
{
public static final Codec<SavedColors> CODEC = RecordCodecBuilder.create(
instance -> instance
.group(DyeColor.CODEC.listOf().fieldOf("flowers")
.forGetter(SavedColors::flowers))
.apply(instance, SavedColors::new));
public static final StreamCodec<ByteBuf, SavedColors> STREAM_CODEC =
StreamCodec.composite(
DyeColor.STREAM_CODEC.apply(ByteBufCodecs.list(256)),
SavedColors::flowers, SavedColors::new);
}

View file

@ -1,12 +1,19 @@
package fr.sushi.charmsnfabrics.common.item; package fr.sushi.charmsnfabrics.common.item;
import fr.sushi.charmsnfabrics.CharmsAndFabrics; import fr.sushi.charmsnfabrics.CharmsAndFabrics;
import fr.sushi.charmsnfabrics.common.CnFRegistries;
import fr.sushi.charmsnfabrics.common.data.FlowerCrownContent;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.context.UseOnContext;
import top.theillusivec4.curios.api.SlotContext; import top.theillusivec4.curios.api.SlotContext;
import top.theillusivec4.curios.api.type.capability.ICurioItem; import top.theillusivec4.curios.api.type.capability.ICurioItem;
import java.util.ArrayList;
import java.util.List;
public class FlowerCrown extends Item implements ICurioItem public class FlowerCrown extends Item implements ICurioItem
{ {
public FlowerCrown(Properties properties) public FlowerCrown(Properties properties)
@ -32,25 +39,27 @@ public class FlowerCrown extends Item implements ICurioItem
"textures/models/accessory/flower_crown.png"); "textures/models/accessory/flower_crown.png");
} }
// @Override @Override
// public InteractionResult useOn(UseOnContext context) public InteractionResult useOn(UseOnContext context)
// { {
// var player = context.getPlayer(); var player = context.getPlayer();
// var level = context.getLevel(); var hand = context.getHand();
// var hand = context.getHand();
// ItemStack stack = player.getItemInHand(hand);
// ItemStack stack = player.getItemInHand(hand); ItemStack offStack = player.getOffhandItem();
// SavedColors comp =
// stack.get(CnFRegistries.DataComponents.SAVED_FLOWERS.get()); FlowerCrownContent content = stack.get(
// CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get());
// stack.set(CnFRegistries.DataComponents.SAVED_FLOWERS.get(), if (content == null ||
// new SavedColors(List.of(DyeColor.PURPLE, DyeColor.LIME))); content.flowers().size() >= FlowerCrownContent.MAX_SIZE)
// if (comp != null && !comp.flowers().isEmpty()) {
// { return InteractionResult.FAIL;
// player.displayClientMessage(Component.literal( }
// comp.flowers().stream().map(DyeColor::toString) List<ItemStack> list = new ArrayList<>(content.flowers());
// .collect(Collectors.joining(" "))), false); list.add(offStack);
// } stack.set(CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get(),
// return InteractionResult.SUCCESS; new FlowerCrownContent(list));
// }
return InteractionResult.SUCCESS;
}
} }