diff --git a/src/main/java/fr/sushi/charmsnfabrics/client/CnFClient.java b/src/main/java/fr/sushi/charmsnfabrics/client/CnFClient.java index 1792c08..1dbef1a 100644 --- a/src/main/java/fr/sushi/charmsnfabrics/client/CnFClient.java +++ b/src/main/java/fr/sushi/charmsnfabrics/client/CnFClient.java @@ -4,11 +4,14 @@ import fr.sushi.charmsnfabrics.CharmsAndFabrics; import fr.sushi.charmsnfabrics.client.datagen.CnFBlockLootProvider; import fr.sushi.charmsnfabrics.client.datagen.CnFBlockTagsProvider; 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.renderer.FloralWorkbenchRenderer; import fr.sushi.charmsnfabrics.client.renderer.FlowerCrownRenderer; +import fr.sushi.charmsnfabrics.client.util.FlowersTint; import fr.sushi.charmsnfabrics.common.CnFRegistries; import net.minecraft.data.loot.LootTableProvider; +import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets; import net.neoforged.api.distmarker.Dist; 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.event.lifecycle.FMLClientSetupEvent; 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.IConfigScreenFactory; import net.neoforged.neoforge.data.event.GatherDataEvent; @@ -39,7 +44,8 @@ public class CnFClient private static void onClientSetup(final FMLClientSetupEvent event) { ICurioRenderer.register(CnFRegistries.Items.FLOWER_CROWN.get(), - FlowerCrownRenderer::new); + () -> new FlowerCrownRenderer( + FlowerCrownRenderer.createModel())); } @SubscribeEvent @@ -70,4 +76,32 @@ public class CnFClient LootContextParamSets.BLOCK)), lookup)); 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()); + // } } diff --git a/src/main/java/fr/sushi/charmsnfabrics/client/model/FlowerCrownItemModel.java b/src/main/java/fr/sushi/charmsnfabrics/client/model/FlowerCrownItemModel.java new file mode 100644 index 0000000..89934ca --- /dev/null +++ b/src/main/java/fr/sushi/charmsnfabrics/client/model/FlowerCrownItemModel.java @@ -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 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 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 bakeModel(ResourceLocation textureLocation, + int layer) + { + TextureAtlasSprite sprite = this.getSprite(textureLocation); + List bits = + UnbakedElementsHelper.createUnbakedItemElements(0, sprite); + return UnbakedElementsHelper.bakeElements(bits, $ -> sprite, + new ComposedModelState(BlockModelRotation.X0_Y0, + this.getTransform(layer))); + } + + private ItemModel bakeOutline(int layer) + { + List quads = + this.bakeModel(this.getOutlineLocation(layer), layer); + return new BlockModelWrapper(List.of(new FlowersTint(layer)), quads, + this.renderProperties); + } + + private ItemModel bakeFlower(int layer) + { + List models = new ArrayList<>(); + models.add(this.bakeOutline(layer)); + + List 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 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 MAP_CODEC = + RecordCodecBuilder.mapCodec(unbaked -> unbaked + .group(ResourceLocation.CODEC.fieldOf("base") + .forGetter(Unbaked::base)) + .apply(unbaked, Unbaked::new)); + + @Override + public MapCodec 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/fr/sushi/charmsnfabrics/client/renderer/FlowerCrownRenderer.java b/src/main/java/fr/sushi/charmsnfabrics/client/renderer/FlowerCrownRenderer.java index 8afa348..7bbdfd0 100644 --- a/src/main/java/fr/sushi/charmsnfabrics/client/renderer/FlowerCrownRenderer.java +++ b/src/main/java/fr/sushi/charmsnfabrics/client/renderer/FlowerCrownRenderer.java @@ -7,6 +7,7 @@ import fr.sushi.charmsnfabrics.client.model.FlowerCrownModel; import fr.sushi.charmsnfabrics.common.item.FlowerCrown; import net.minecraft.client.Minecraft; import net.minecraft.client.model.EntityModel; +import net.minecraft.client.model.geom.EntityModelSet; import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.entity.EntityRendererProvider; @@ -24,11 +25,15 @@ public class FlowerCrownRenderer implements ICurioRenderer { private final FlowerCrownModel model; - public FlowerCrownRenderer() + public FlowerCrownRenderer(FlowerCrownModel model) { - this.model = new FlowerCrownModel( - Minecraft.getInstance().getEntityModels() - .bakeLayer(CnFLayers.CROWN_LAYER)); + this.model = model; + } + + public static FlowerCrownModel createModel() + { + EntityModelSet modelSet = Minecraft.getInstance().getEntityModels(); + return new FlowerCrownModel(modelSet.bakeLayer(CnFLayers.CROWN_LAYER)); } @Override diff --git a/src/main/java/fr/sushi/charmsnfabrics/client/util/FlowersTint.java b/src/main/java/fr/sushi/charmsnfabrics/client/util/FlowersTint.java new file mode 100644 index 0000000..8fde5ec --- /dev/null +++ b/src/main/java/fr/sushi/charmsnfabrics/client/util/FlowersTint.java @@ -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 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 type() + { + return MAP_CODEC; + } +} diff --git a/src/main/java/fr/sushi/charmsnfabrics/common/CnFRegistries.java b/src/main/java/fr/sushi/charmsnfabrics/common/CnFRegistries.java index 33e439b..80f7d98 100644 --- a/src/main/java/fr/sushi/charmsnfabrics/common/CnFRegistries.java +++ b/src/main/java/fr/sushi/charmsnfabrics/common/CnFRegistries.java @@ -2,8 +2,10 @@ package fr.sushi.charmsnfabrics.common; import fr.sushi.charmsnfabrics.CharmsAndFabrics; 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.item.FlowerCrown; +import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; 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.DeferredRegister; +import java.util.List; import java.util.Set; import java.util.function.Supplier; @@ -33,8 +36,11 @@ public class CnFRegistries DeferredRegister.createItems(CharmsAndFabrics.MODID); public static final DeferredItem FLOWER_CROWN = ITEMS.registerItem("flower_crown", - (properties) -> new FlowerCrown( - properties.stacksTo(1))); + (properties) -> new FlowerCrown(properties.stacksTo(1) + .component( + DataComponents.FLOWER_CROWN_CONTENT, + new FlowerCrownContent( + List.of())))); /* BlockItems */ public static final DeferredItem FLORAL_WORKBENCH = ITEMS.registerItem("floral_workbench", @@ -100,11 +106,26 @@ public class CnFRegistries 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> + FLOWER_CROWN_CONTENT = + COMPONENT_TYPES.registerComponentType("flower_crown_content", + builder -> builder.persistent(FlowerCrownContent.CODEC) + .networkSynchronized( + FlowerCrownContent.STREAM_CODEC)); + } + public static void register(IEventBus bus) { Items.ITEMS.register(bus); Blocks.BLOCKS.register(bus); Tabs.CREATIVE_MODE_TABS.register(bus); Entities.BLOCK_ENTITY_TYPES.register(bus); + DataComponents.COMPONENT_TYPES.register(bus); } } diff --git a/src/main/java/fr/sushi/charmsnfabrics/common/data/FlowerCrownContent.java b/src/main/java/fr/sushi/charmsnfabrics/common/data/FlowerCrownContent.java new file mode 100644 index 0000000..a951a21 --- /dev/null +++ b/src/main/java/fr/sushi/charmsnfabrics/common/data/FlowerCrownContent.java @@ -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 flowers) +{ + public static final FlowerCrownContent EMPTY = + new FlowerCrownContent(List.of()); + public static final Codec CODEC = + ItemStack.CODEC.listOf().flatXmap((items) -> DataResult.success( + new FlowerCrownContent(items)), + content -> DataResult.success(content.flowers)); + public static final StreamCodec + STREAM_CODEC = ItemStack.STREAM_CODEC.apply(ByteBufCodecs.list()) + .map(FlowerCrownContent::new, + content -> content.flowers); + + public static int MAX_SIZE = 7; +} diff --git a/src/main/java/fr/sushi/charmsnfabrics/common/item/FlowerCrown.java b/src/main/java/fr/sushi/charmsnfabrics/common/item/FlowerCrown.java index 80bb504..3dd1fdf 100644 --- a/src/main/java/fr/sushi/charmsnfabrics/common/item/FlowerCrown.java +++ b/src/main/java/fr/sushi/charmsnfabrics/common/item/FlowerCrown.java @@ -1,14 +1,19 @@ package fr.sushi.charmsnfabrics.common.item; 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.world.entity.EquipmentSlot; -import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.InteractionResult; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; import top.theillusivec4.curios.api.SlotContext; import top.theillusivec4.curios.api.type.capability.ICurioItem; +import java.util.ArrayList; +import java.util.List; + public class FlowerCrown extends Item implements ICurioItem { public FlowerCrown(Properties properties) @@ -31,6 +36,35 @@ public class FlowerCrown extends Item implements ICurioItem public ResourceLocation getModelTexture() { 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 list = new ArrayList<>(content.flowers()); + list.add(offStack); + stack.set(CnFRegistries.DataComponents.FLOWER_CROWN_CONTENT.get(), + new FlowerCrownContent(list)); + + return InteractionResult.SUCCESS; } } diff --git a/src/main/resources/assets/charmsnfabrics/textures/flower/flower_large.png b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_large.png new file mode 100644 index 0000000..7f48c28 Binary files /dev/null and b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_large.png differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_large.png b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_large.png new file mode 100644 index 0000000..1bd724b Binary files /dev/null and b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_large.png differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_small.png b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_small.png new file mode 100644 index 0000000..94fcf0d Binary files /dev/null and b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_outline_small.png differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/flower/flower_small.png b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_small.png new file mode 100644 index 0000000..3f0646c Binary files /dev/null and b/src/main/resources/assets/charmsnfabrics/textures/flower/flower_small.png differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/gui/container/floral_workbench.png b/src/main/resources/assets/charmsnfabrics/textures/gui/container/floral_workbench.png deleted file mode 100644 index de8290c..0000000 Binary files a/src/main/resources/assets/charmsnfabrics/textures/gui/container/floral_workbench.png and /dev/null differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown.png b/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown.png index 7912290..a79300a 100644 Binary files a/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown.png and b/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown.png differ diff --git a/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown_old.png b/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown_old.png new file mode 100644 index 0000000..7912290 Binary files /dev/null and b/src/main/resources/assets/charmsnfabrics/textures/item/flower_crown_old.png differ diff --git a/src/main/resources/assets/minecraft/atlases/blocks.json b/src/main/resources/assets/minecraft/atlases/blocks.json new file mode 100644 index 0000000..6b18f8b --- /dev/null +++ b/src/main/resources/assets/minecraft/atlases/blocks.json @@ -0,0 +1,9 @@ +{ + "sources": [ + { + "type": "minecraft:directory", + "prefix": "flower/", + "source": "flower" + } + ] +} \ No newline at end of file