diff --git a/Common/src/main/java/dev/upcraft/sparkweave/api/client/event/CustomArmorRendererRegistryEvent.java b/Common/src/main/java/dev/upcraft/sparkweave/api/client/event/CustomArmorRendererRegistryEvent.java new file mode 100644 index 0000000..dbe2305 --- /dev/null +++ b/Common/src/main/java/dev/upcraft/sparkweave/api/client/event/CustomArmorRendererRegistryEvent.java @@ -0,0 +1,33 @@ +package dev.upcraft.sparkweave.api.client.event; + +import dev.upcraft.sparkweave.api.client.render.CustomArmorRenderer; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.ItemLike; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Objects; + +public class CustomArmorRendererRegistryEvent { + private static final HashMap RENDERERS = new HashMap<>(); + + public static void register(CustomArmorRenderer renderer, ItemLike... items) { + Objects.requireNonNull(renderer, "Custom armor renderer is null"); + + if(items.length == 0) + throw new IllegalArgumentException("Custom armor renderer registered, but no items are attached to it"); + + for(ItemLike item : items) { + Objects.requireNonNull(item.asItem(), "Armor item is null or doesn't exist"); + + if(RENDERERS.putIfAbsent(item.asItem(), renderer) != null) + throw new IllegalArgumentException("Custom armor renderer already exists for " + BuiltInRegistries.ITEM.getKey(item.asItem())); + } + } + + @Nullable + public static CustomArmorRenderer get(Item item) { + return RENDERERS.get(item); + } +} diff --git a/Common/src/main/java/dev/upcraft/sparkweave/api/client/render/CustomArmorRenderer.java b/Common/src/main/java/dev/upcraft/sparkweave/api/client/render/CustomArmorRenderer.java new file mode 100644 index 0000000..eef51cd --- /dev/null +++ b/Common/src/main/java/dev/upcraft/sparkweave/api/client/render/CustomArmorRenderer.java @@ -0,0 +1,25 @@ +package dev.upcraft.sparkweave.api.client.render; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.Model; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +@FunctionalInterface +public interface CustomArmorRenderer { + static void renderArmor(PoseStack matrices, MultiBufferSource vertexConsumers, int light, ItemStack stack, Model model, ResourceLocation texture) { + VertexConsumer vertexConsumer = ItemRenderer.getArmorFoilBuffer(vertexConsumers, RenderType.armorCutoutNoCull(texture), stack.hasFoil()); + + model.renderToBuffer(matrices, vertexConsumer, light, OverlayTexture.NO_OVERLAY); + } + + void render(PoseStack matrices, MultiBufferSource bufferSource, ItemStack stack, LivingEntity entity, EquipmentSlot slot, int light, HumanoidModel contextModel); +} diff --git a/Common/src/main/java/dev/upcraft/sparkweave/mixin/client/HumanoidArmorLayerMixin.java b/Common/src/main/java/dev/upcraft/sparkweave/mixin/client/HumanoidArmorLayerMixin.java new file mode 100644 index 0000000..d0dd954 --- /dev/null +++ b/Common/src/main/java/dev/upcraft/sparkweave/mixin/client/HumanoidArmorLayerMixin.java @@ -0,0 +1,33 @@ +package dev.upcraft.sparkweave.mixin.client; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.upcraft.sparkweave.api.client.event.CustomArmorRendererRegistryEvent; +import dev.upcraft.sparkweave.api.client.render.CustomArmorRenderer; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.entity.RenderLayerParent; +import net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer; +import net.minecraft.client.renderer.entity.layers.RenderLayer; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(HumanoidArmorLayer.class) +public abstract class HumanoidArmorLayerMixin extends RenderLayer> { + public HumanoidArmorLayerMixin(RenderLayerParent> parent) { super(parent); } + + @Inject(method = "renderArmorPiece", at = @At("HEAD"), cancellable = true) + private void disableDefaultArmorRendererForCustomArmor(PoseStack poseStack, MultiBufferSource bufferSource, LivingEntity entity, EquipmentSlot slot, int light, HumanoidModel model, CallbackInfo info) { + ItemStack stack = entity.getItemBySlot(slot); + CustomArmorRenderer renderer = CustomArmorRendererRegistryEvent.get(stack.getItem()); + + if(renderer != null) { + renderer.render(poseStack, bufferSource, stack, entity, slot, light, getParentModel()); + info.cancel(); + } + } +} diff --git a/Common/src/main/resources/sparkweave.mixins.json b/Common/src/main/resources/sparkweave.mixins.json index a09a32f..88729e6 100644 --- a/Common/src/main/resources/sparkweave.mixins.json +++ b/Common/src/main/resources/sparkweave.mixins.json @@ -1,16 +1,17 @@ { - "required": true, - "minVersion": "0.8", - "package": "dev.upcraft.sparkweave.mixin", - "plugin": "dev.upcraft.sparkweave.SparkweaveMixinConfigPlugin", - "refmap": "${mod_id}.refmap.json", - "compatibilityLevel": "JAVA_21", - "mixins": [ - ], - "client": [ - "client.MainMixin" - ], - "injectors": { - "defaultRequire": 1 + "required": true, + "minVersion": "0.8", + "package": "dev.upcraft.sparkweave.mixin", + "plugin": "dev.upcraft.sparkweave.SparkweaveMixinConfigPlugin", + "refmap": "${mod_id}.refmap.json", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "client": [ + "client.HumanoidArmorLayerMixin", + "client.MainMixin" + ], + "injectors": { + "defaultRequire": 1 } } diff --git a/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/SparkweaveTestmodClient.java b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/SparkweaveTestmodClient.java index 1adcbaf..b99ea10 100644 --- a/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/SparkweaveTestmodClient.java +++ b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/SparkweaveTestmodClient.java @@ -17,6 +17,8 @@ public class SparkweaveTestmodClient implements ClientEntryPoint { @Override public void onInitializeClient(ModContainer mod) { + RegisterLayerDefinitionsEvent.EVENT.register(event -> event.registerModelLayers(MageRobesModel.MODEL_LAYER, MageRobesModel::createBodyLayer)); + CustomArmorRendererRegistryEvent.register(new MageRobesRenderer(), Items.CHAINMAIL_HELMET, Items.CHAINMAIL_CHESTPLATE, Items.CHAINMAIL_LEGGINGS, Items.CHAINMAIL_BOOTS); } public static void onClientTickStart(Minecraft client) { diff --git a/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/models/MageRobesModel.java b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/models/MageRobesModel.java new file mode 100644 index 0000000..8da46cf --- /dev/null +++ b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/models/MageRobesModel.java @@ -0,0 +1,80 @@ +package dev.upcraft.sparkweave.testmod.client.models; + +import dev.upcraft.sparkweave.testmod.SparkweaveTestmod; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartNames; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.model.geom.builders.*; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.world.entity.LivingEntity; + +public class MageRobesModel extends HumanoidModel { + public static final ModelLayerLocation MODEL_LAYER = new ModelLayerLocation(SparkweaveTestmod.id("mage_robes"), "main"); + public final ModelPart closedHood; + public final ModelPart cloak; + public final ModelPart openHood; + public final ModelPart rightSleeve; + public final ModelPart leftSleeve; + public final ModelPart garb; + public final ModelPart belt; + public final ModelPart rightShoe; + public final ModelPart leftShoe; + + public MageRobesModel(ModelPart root) { + super(root, RenderType::armorCutoutNoCull); + closedHood = head.getChild("closedHood"); + cloak = body.getChild("cloak"); + openHood = cloak.getChild("openHood"); + rightSleeve = rightArm.getChild("rightSleeve"); + leftSleeve = leftArm.getChild("leftSleeve"); + garb = body.getChild("garb"); + belt = body.getChild("belt"); + rightShoe = rightLeg.getChild("rightShoe"); + leftShoe = leftLeg.getChild("leftShoe"); + } + + public static LayerDefinition createBodyLayer() { + MeshDefinition data = HumanoidModel.createMesh(CubeDeformation.NONE, 0); + PartDefinition head = data.getRoot().getChild(PartNames.HEAD); + PartDefinition body = data.getRoot().getChild(PartNames.BODY); + PartDefinition rightArm = data.getRoot().getChild(PartNames.RIGHT_ARM); + PartDefinition leftArm = data.getRoot().getChild(PartNames.LEFT_ARM); + PartDefinition rightLeg = data.getRoot().getChild(PartNames.RIGHT_LEG); + PartDefinition leftLeg = data.getRoot().getChild(PartNames.LEFT_LEG); + + PartDefinition closedHood = head.addOrReplaceChild("closedHood", CubeListBuilder.create().texOffs(0, 64).addBox(-4f, -8f, -4f, 8f, 8f, 8f, new CubeDeformation(0.3f)), PartPose.offset(0f, 0f, 0f)); + PartDefinition cube_r1 = closedHood.addOrReplaceChild("cube_r1", CubeListBuilder.create().texOffs(29, 78).addBox(-3f, 0.2f, 0f, 6f, 6f, 3f, new CubeDeformation(0.35f)), PartPose.offsetAndRotation(0f, -8f, -4f, -0.1745f, 0f, 0f)); + + PartDefinition cloak = body.addOrReplaceChild("cloak", CubeListBuilder.create(), PartPose.offset(0f, 0f, 0f)); + + PartDefinition cube_r2 = cloak.addOrReplaceChild("cube_r2", CubeListBuilder.create().texOffs(24, 91).addBox(-5f, -1f, 2f, 10f, 16f, 1f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(0f, 2f, 0f, 0.1745f, 0f, 0f)); + + PartDefinition openHood = cloak.addOrReplaceChild("openHood", CubeListBuilder.create(), PartPose.offset(0f, 0f, 0f)); + PartDefinition cube_r3 = openHood.addOrReplaceChild("cube_r3", CubeListBuilder.create().texOffs(0, 80).addBox(-5f, -4f, 0f, 10f, 4f, 7f, new CubeDeformation(0.3f)), PartPose.offsetAndRotation(0f, 2f, 0f, 0.1745f, 0f, 0f)); + + PartDefinition garb = body.addOrReplaceChild("garb", CubeListBuilder.create().texOffs(0, 91).addBox(-4f, -12f, -2f, 8f, 11f, 4f, new CubeDeformation(0.3f)), PartPose.offset(0f, 12f, 0f)); + PartDefinition belt = body.addOrReplaceChild("belt", CubeListBuilder.create().texOffs(41, 80).addBox(-3f, -3f, -2f, 6f, 3f, 1f, new CubeDeformation(0.2f)), PartPose.offset(0f, 12f, 0f)); + + PartDefinition backCover = garb.addOrReplaceChild("backCover", CubeListBuilder.create().texOffs(16, 108).addBox(-4f, 0f, 0f, 8f, 8f, 1f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(0f, -1f, 1f, 0.2618f, 0f, 0f)); + + PartDefinition rightCover = garb.addOrReplaceChild("rightCover", CubeListBuilder.create(), PartPose.offset(0f, 0f, 0f)); + PartDefinition cube_r4 = rightCover.addOrReplaceChild("cube_r4", CubeListBuilder.create().texOffs(50, 110).addBox(-1f, -1f, -2f, 1f, 8f, 4f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(-3f, 0f, 0f, 0f, 0f, 0.2618f)); + + PartDefinition leftCover = garb.addOrReplaceChild("leftCover", CubeListBuilder.create(), PartPose.offset(0f, 0f, 0f)); + PartDefinition cube_r5 = leftCover.addOrReplaceChild("cube_r5", CubeListBuilder.create().texOffs(48, 64).addBox(0f, -1f, -2f, 1f, 8f, 4f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(3f, 0f, 0f, 0f, 0f, -0.2618f)); + + PartDefinition rightSleeve = rightArm.addOrReplaceChild("rightSleeve", CubeListBuilder.create(), PartPose.offset(0f, 0f, 0f)); + PartDefinition cube_r6 = rightSleeve.addOrReplaceChild("cube_r6", CubeListBuilder.create().texOffs(0, 106).addBox(-3f, -2f, -2f, 4f, 10f, 4f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(0f, 0f, 0f, 0f, 0f, 0f)); + + PartDefinition leftSleeve = leftArm.addOrReplaceChild("leftSleeve", CubeListBuilder.create(), PartPose.offset(2f, 0f, 0f)); + PartDefinition cube_r7 = leftSleeve.addOrReplaceChild("cube_r7", CubeListBuilder.create().texOffs(32, 64).addBox(-1f, -2f, -2f, 4f, 10f, 4f, new CubeDeformation(0.29f)), PartPose.offsetAndRotation(-2f, 0f, 0f, 0f, 0f, 0f)); + + PartDefinition rightShoe = rightLeg.addOrReplaceChild("rightShoe", CubeListBuilder.create().texOffs(34, 110).addBox(-2f, 7f, -2f, 4f, 5f, 4f, new CubeDeformation(0.2f)), PartPose.offset(0f, 0f, 0f)); + + PartDefinition leftShoe = leftLeg.addOrReplaceChild("leftShoe", CubeListBuilder.create().texOffs(46, 90).addBox(-2f, 7f, -2f, 4f, 5f, 4f, new CubeDeformation(0.199f)), PartPose.offset(0.2f, 0f, 0f)); + + return LayerDefinition.create(data, 64, 128); + } +} diff --git a/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/renderers/MageRobesRenderer.java b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/renderers/MageRobesRenderer.java new file mode 100644 index 0000000..91ce8c8 --- /dev/null +++ b/Common/src/testmod/java/dev/upcraft/sparkweave/testmod/client/renderers/MageRobesRenderer.java @@ -0,0 +1,38 @@ +package dev.upcraft.sparkweave.testmod.client.renderers; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.upcraft.sparkweave.api.client.render.CustomArmorRenderer; +import dev.upcraft.sparkweave.testmod.SparkweaveTestmod; +import dev.upcraft.sparkweave.testmod.client.models.MageRobesModel; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.HumanoidModel; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; + +public class MageRobesRenderer implements CustomArmorRenderer { + private static final ResourceLocation TEXTURE = SparkweaveTestmod.id("textures/entity/armor/mage_robes.png"); + private MageRobesModel model; + + @Override + public void render(PoseStack matrices, MultiBufferSource bufferSource, ItemStack stack, LivingEntity entity, EquipmentSlot slot, int light, HumanoidModel contextModel) { + if(model == null) + model = new MageRobesModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(MageRobesModel.MODEL_LAYER)); + + contextModel.copyPropertiesTo(model); + model.setAllVisible(true); + model.openHood.visible = slot == EquipmentSlot.HEAD; + model.closedHood.visible = false; + model.cloak.visible = slot == EquipmentSlot.HEAD; + model.garb.visible = slot == EquipmentSlot.CHEST; + model.leftSleeve.visible = slot == EquipmentSlot.CHEST; + model.rightSleeve.visible = slot == EquipmentSlot.CHEST; + model.belt.visible = slot == EquipmentSlot.LEGS; + model.leftShoe.visible = slot == EquipmentSlot.FEET; + model.rightShoe.visible = slot == EquipmentSlot.FEET; + + CustomArmorRenderer.renderArmor(matrices, bufferSource, light, stack, model, TEXTURE); + } +} diff --git a/Common/src/testmod/resources/assets/sparkweave_testmod/textures/entity/armor/mage_robes.png b/Common/src/testmod/resources/assets/sparkweave_testmod/textures/entity/armor/mage_robes.png new file mode 100644 index 0000000..a47e24c Binary files /dev/null and b/Common/src/testmod/resources/assets/sparkweave_testmod/textures/entity/armor/mage_robes.png differ