Compare commits

..

8 commits

Author SHA1 Message Date
SushiCannibale
ad6d36fecb feat: outline tinting
Some checks failed
Build / build (push) Has been cancelled
2025-08-14 17:43:00 +02:00
SushiCannibale
d393812448 fine tuned transforms
Some checks are pending
Build / build (push) Waiting to run
2025-08-14 12:25:41 +02:00
SushiCannibale
67ae714c64 feat: dynamic tinting
Some checks are pending
Build / build (push) Waiting to run
2025-08-13 19:19:50 +02:00
SushiCannibale
9bcabeb8f7 feat: flower_crown composite model
Some checks are pending
Build / build (push) Waiting to run
2025-08-12 22:14:54 +02:00
SushiCannibale
f73affdb2c feat: dynamic flower crown model + other thingsss
Some checks are pending
Build / build (push) Waiting to run
2025-08-11 23:57:31 +02:00
SushiCannibale
1f633e88be feat: flower color data component
Some checks failed
Build / build (push) Has been cancelled
2025-08-08 22:51:18 +02:00
SushiCannibale
3afde78414 feat: flower layers texture
Some checks are pending
Build / build (push) Waiting to run
2025-08-08 17:42:10 +02:00
SushiCannibale
a8990da272 feat: flower crown model type + layers baking (WIP)
Some checks failed
Build / build (push) Has been cancelled
2025-08-06 00:30:44 +02:00
15 changed files with 395 additions and 10 deletions

View file

@ -4,11 +4,14 @@ import fr.sushi.charmsnfabrics.CharmsAndFabrics;
import fr.sushi.charmsnfabrics.client.datagen.CnFBlockLootProvider; import fr.sushi.charmsnfabrics.client.datagen.CnFBlockLootProvider;
import fr.sushi.charmsnfabrics.client.datagen.CnFBlockTagsProvider; import fr.sushi.charmsnfabrics.client.datagen.CnFBlockTagsProvider;
import fr.sushi.charmsnfabrics.client.datagen.CnFModelProvider; import fr.sushi.charmsnfabrics.client.datagen.CnFModelProvider;
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.world.level.storage.loot.parameters.LootContextParamSets; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.neoforged.api.distmarker.Dist; import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.bus.api.SubscribeEvent;
@ -17,6 +20,8 @@ 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.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.ConfigurationScreen;
import net.neoforged.neoforge.client.gui.IConfigScreenFactory; import net.neoforged.neoforge.client.gui.IConfigScreenFactory;
import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.data.event.GatherDataEvent;
@ -39,7 +44,8 @@ public class CnFClient
private static void onClientSetup(final FMLClientSetupEvent event) private static void onClientSetup(final FMLClientSetupEvent event)
{ {
ICurioRenderer.register(CnFRegistries.Items.FLOWER_CROWN.get(), ICurioRenderer.register(CnFRegistries.Items.FLOWER_CROWN.get(),
FlowerCrownRenderer::new); () -> new FlowerCrownRenderer(
FlowerCrownRenderer.createModel()));
} }
@SubscribeEvent @SubscribeEvent
@ -70,4 +76,32 @@ public class CnFClient
LootContextParamSets.BLOCK)), lookup)); LootContextParamSets.BLOCK)), lookup));
event.createProvider(CnFBlockTagsProvider::new); event.createProvider(CnFBlockTagsProvider::new);
} }
@SubscribeEvent
private static void onRegisterItemModels(
final RegisterItemModelsEvent event)
{
event.register(
ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"flower_crown_loader"),
FlowerCrownItemModel.Unbaked.MAP_CODEC);
}
@SubscribeEvent
private static void onRegisterTinters(
RegisterColorHandlersEvent.ItemTintSources event)
{
event.register(
ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"flower_tint"), FlowersTint.MAP_CODEC);
}
// @SubscribeEvent
// private static void onRegisterStandaloneModels(
// ModelEvent.RegisterStandalone event)
// {
// event.register(new StandaloneModelKey<>(
// ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
// "item/flower")), StandaloneModelBaker.quadCollection());
// }
} }

View file

@ -0,0 +1,202 @@
package fr.sushi.charmsnfabrics.client.model;
import com.mojang.math.Transformation;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import fr.sushi.charmsnfabrics.CharmsAndFabrics;
import fr.sushi.charmsnfabrics.client.util.FlowersTint;
import fr.sushi.charmsnfabrics.common.CnFRegistries;
import fr.sushi.charmsnfabrics.common.data.FlowerCrownContent;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.BlockElement;
import net.minecraft.client.renderer.block.model.TextureSlots;
import net.minecraft.client.renderer.item.*;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
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.neoforged.neoforge.client.ClientHooks;
import net.neoforged.neoforge.client.model.ComposedModelState;
import net.neoforged.neoforge.client.model.UnbakedElementsHelper;
import org.jetbrains.annotations.Nullable;
import org.joml.Quaternionf;
import org.joml.Vector2f;
import org.joml.Vector3f;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FlowerCrownItemModel implements ItemModel
{
private static final Map<Integer, Vector2f> TRANSLATIONS = new HashMap<>();
/*
* Position the flower on the item model based on the layer (anticlockwise)
*/
static
{
TRANSLATIONS.put(0, new Vector2f(1, -4));
TRANSLATIONS.put(1, new Vector2f(5, -3));
TRANSLATIONS.put(2, new Vector2f(5, 2));
TRANSLATIONS.put(3, new Vector2f(2, 4));
TRANSLATIONS.put(4, new Vector2f(-3, 4));
TRANSLATIONS.put(5, new Vector2f(-5, 0));
TRANSLATIONS.put(6, new Vector2f(-4, -3));
}
private static final ModelDebugName DEBUG_NAME =
() -> "FlowerCrownItemModel";
private final Unbaked rawModel;
private final BakingContext context;
private ModelRenderProperties renderProperties;
public FlowerCrownItemModel(Unbaked rawModel, BakingContext context)
{
this.rawModel = rawModel;
this.context = context;
}
private Transformation getTransform(int layer)
{
Vector2f uv = TRANSLATIONS.get(layer);
return new Transformation(
new Vector3f(uv.x * 0.0625f, uv.y * 0.0625f, 0.0f),
new Quaternionf(), new Vector3f(1.001f), new Quaternionf());
}
private ResourceLocation getFlowerLocation(int layer)
{
return ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"flower/flower_" + (layer % 2 == 0 ? "large" : "small"));
}
private ResourceLocation getOutlineLocation(int layer)
{
return ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"flower/flower_outline_" +
(layer % 2 == 0 ? "large" : "small"));
}
private TextureAtlasSprite getSprite(ResourceLocation textureLocation)
{
SpriteGetter sprites = this.context.blockModelBaker().sprites();
Material material = ClientHooks.getBlockMaterial(textureLocation);
return sprites.get(material, DEBUG_NAME);
}
/**
* Bakes the base and updates the LayerRenderState quads
*/
private ItemModel bakeBase()
{
ModelBaker baker = this.context.blockModelBaker();
ResolvedModel resolvedModel = baker.getModel(this.rawModel.base);
TextureSlots slots = resolvedModel.getTopTextureSlots();
List<BakedQuad> quads = resolvedModel
.bakeTopGeometry(slots, baker, BlockModelRotation.X0_Y0)
.getAll();
this.renderProperties =
ModelRenderProperties.fromResolvedModel(baker, resolvedModel,
slots);
return new BlockModelWrapper(List.of(), quads, this.renderProperties);
}
private List<BakedQuad> bakeModel(ResourceLocation textureLocation,
int layer)
{
TextureAtlasSprite sprite = this.getSprite(textureLocation);
List<BlockElement> bits =
UnbakedElementsHelper.createUnbakedItemElements(0, sprite);
return UnbakedElementsHelper.bakeElements(bits, $ -> sprite,
new ComposedModelState(BlockModelRotation.X0_Y0,
this.getTransform(layer)));
}
private ItemModel bakeOutline(int layer)
{
List<BakedQuad> quads =
this.bakeModel(this.getOutlineLocation(layer), layer);
return new BlockModelWrapper(List.of(new FlowersTint(layer)), quads,
this.renderProperties);
}
private ItemModel bakeFlower(int layer)
{
List<ItemModel> models = new ArrayList<>();
models.add(this.bakeOutline(layer));
List<BakedQuad> quads =
this.bakeModel(this.getFlowerLocation(layer), layer);
models.add(
new BlockModelWrapper(List.of(), quads, this.renderProperties));
return new CompositeModel(models);
}
private ItemModel bakeFlowerCrown(FlowerCrownContent content)
{
List<ItemModel> models = new ArrayList<>();
models.add(this.bakeBase());
for (int i = 0; i < content.flowers().size(); i++)
{
if (content.flowers().get(i).isEmpty())
{
continue;
}
models.add(this.bakeFlower(i));
}
return new CompositeModel(models);
}
@Override
public void update(ItemStackRenderState renderState, ItemStack stack,
ItemModelResolver itemModelResolver,
ItemDisplayContext displayContext, @Nullable ClientLevel level,
@Nullable LivingEntity entity, int seed)
{
FlowerCrownContent comp = stack.get(
CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get());
if (comp != null)
{
this.bakeFlowerCrown(comp)
.update(renderState, stack, itemModelResolver, displayContext,
level, entity, seed);
}
}
public record Unbaked(ResourceLocation base) implements ItemModel.Unbaked
{
public static final MapCodec<Unbaked> MAP_CODEC =
RecordCodecBuilder.mapCodec(unbaked -> unbaked
.group(ResourceLocation.CODEC.fieldOf("base")
.forGetter(Unbaked::base))
.apply(unbaked, Unbaked::new));
@Override
public MapCodec<? extends ItemModel.Unbaked> type()
{
return MAP_CODEC;
}
@Override
public ItemModel bake(BakingContext context)
{
return new FlowerCrownItemModel(this, context);
}
@Override
public void resolveDependencies(Resolver resolver)
{
resolver.markDependency(this.base);
}
}
}

View file

@ -7,6 +7,7 @@ import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel;
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;
import net.minecraft.client.model.geom.EntityModelSet;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.EntityRendererProvider; import net.minecraft.client.renderer.entity.EntityRendererProvider;
@ -24,11 +25,15 @@ public class FlowerCrownRenderer implements ICurioRenderer
{ {
private final FlowerCrownModel model; private final FlowerCrownModel model;
public FlowerCrownRenderer() public FlowerCrownRenderer(FlowerCrownModel model)
{ {
this.model = new FlowerCrownModel( this.model = model;
Minecraft.getInstance().getEntityModels() }
.bakeLayer(CnFLayers.CROWN_LAYER));
public static FlowerCrownModel createModel()
{
EntityModelSet modelSet = Minecraft.getInstance().getEntityModels();
return new FlowerCrownModel(modelSet.bakeLayer(CnFLayers.CROWN_LAYER));
} }
@Override @Override

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.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(Items.BLUE_ORCHID))
{
return DyeColor.LIGHT_BLUE.getTextureDiffuseColor();
}
else if (flower.is(Items.LILY_OF_THE_VALLEY))
{
return DyeColor.WHITE.getTextureDiffuseColor();
}
else if (flower.is(Items.PINK_TULIP))
{
return DyeColor.MAGENTA.getTextureDiffuseColor();
}
}
return 0xFFFFFFFF;
}
@Override
public MapCodec<FlowersTint> type()
{
return MAP_CODEC;
}
}

View file

@ -2,8 +2,10 @@ 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.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.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceKey;
@ -22,6 +24,7 @@ import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredItem; import net.neoforged.neoforge.registries.DeferredItem;
import net.neoforged.neoforge.registries.DeferredRegister; import net.neoforged.neoforge.registries.DeferredRegister;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -33,8 +36,11 @@ public class CnFRegistries
DeferredRegister.createItems(CharmsAndFabrics.MODID); DeferredRegister.createItems(CharmsAndFabrics.MODID);
public static final DeferredItem<Item> FLOWER_CROWN = public static final DeferredItem<Item> FLOWER_CROWN =
ITEMS.registerItem("flower_crown", ITEMS.registerItem("flower_crown",
(properties) -> new FlowerCrown( (properties) -> new FlowerCrown(properties.stacksTo(1)
properties.stacksTo(1))); .component(
DataComponents.FLOWER_CROWN_CONTENT,
new FlowerCrownContent(
List.of()))));
/* BlockItems */ /* BlockItems */
public static final DeferredItem<BlockItem> FLORAL_WORKBENCH = public static final DeferredItem<BlockItem> FLORAL_WORKBENCH =
ITEMS.registerItem("floral_workbench", ITEMS.registerItem("floral_workbench",
@ -100,11 +106,26 @@ public class CnFRegistries
Set.of(Blocks.FLORAL_WORKBENCH.get()))); Set.of(Blocks.FLORAL_WORKBENCH.get())));
} }
public static class DataComponents
{
public static final DeferredRegister.DataComponents COMPONENT_TYPES =
DeferredRegister.createDataComponents(
Registries.DATA_COMPONENT_TYPE, CharmsAndFabrics.MODID);
public static final Supplier<DataComponentType<FlowerCrownContent>>
FLOWER_CROWN_CONTENT =
COMPONENT_TYPES.registerComponentType("flower_crown_content",
builder -> builder.persistent(FlowerCrownContent.CODEC)
.networkSynchronized(
FlowerCrownContent.STREAM_CODEC));
}
public static void register(IEventBus bus) public static void register(IEventBus bus)
{ {
Items.ITEMS.register(bus); Items.ITEMS.register(bus);
Blocks.BLOCKS.register(bus); Blocks.BLOCKS.register(bus);
Tabs.CREATIVE_MODE_TABS.register(bus); Tabs.CREATIVE_MODE_TABS.register(bus);
Entities.BLOCK_ENTITY_TYPES.register(bus); Entities.BLOCK_ENTITY_TYPES.register(bus);
DataComponents.COMPONENT_TYPES.register(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,14 +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.entity.EquipmentSlot; import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
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)
@ -33,4 +38,33 @@ public class FlowerCrown extends Item implements ICurioItem
return ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID, return ResourceLocation.fromNamespaceAndPath(CharmsAndFabrics.MODID,
"textures/models/accessory/flower_crown.png"); "textures/models/accessory/flower_crown.png");
} }
@Override
public InteractionResult useOn(UseOnContext context)
{
var player = context.getPlayer();
var hand = context.getHand();
ItemStack stack = player.getItemInHand(hand);
ItemStack offStack = player.getOffhandItem();
if (stack.isEmpty() || offStack.isEmpty())
{
return InteractionResult.PASS;
}
FlowerCrownContent content = stack.get(
CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get());
if (content == null ||
content.flowers().size() >= FlowerCrownContent.MAX_SIZE)
{
return InteractionResult.FAIL;
}
List<ItemStack> list = new ArrayList<>(content.flowers());
list.add(offStack);
stack.set(CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get(),
new FlowerCrownContent(list));
return InteractionResult.SUCCESS;
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 201 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

View file

@ -0,0 +1,9 @@
{
"sources": [
{
"type": "minecraft:directory",
"prefix": "flower/",
"source": "flower"
}
]
}