From c8a28027efb080c946d94474fbbcfdeb97571017 Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 17 Dec 2024 13:46:08 +0100 Subject: [PATCH] Add support for rendering modded armor --- plugins/ShowMeYourPonies | 2 +- .../minelittlepony/api/config/PonyConfig.java | 9 ++ .../armour/PonifiedEquipmentRenderer.java | 1 - .../render/entity/feature/ArmourFeature.java | 85 +++++++++++++++++-- .../client/util/render/MatrixStackUtil.java | 19 +++++ .../assets/minelittlepony/lang/en_us.json | 1 + 6 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/minelittlepony/client/util/render/MatrixStackUtil.java diff --git a/plugins/ShowMeYourPonies b/plugins/ShowMeYourPonies index 1ae97d06..913c3568 160000 --- a/plugins/ShowMeYourPonies +++ b/plugins/ShowMeYourPonies @@ -1 +1 @@ -Subproject commit 1ae97d064e7579d94a93f2784473dc7ce0e1e670 +Subproject commit 913c35681b09cfbab0bf5def678db3407f5834bb diff --git a/src/main/java/com/minelittlepony/api/config/PonyConfig.java b/src/main/java/com/minelittlepony/api/config/PonyConfig.java index b5d5d933..72656e40 100644 --- a/src/main/java/com/minelittlepony/api/config/PonyConfig.java +++ b/src/main/java/com/minelittlepony/api/config/PonyConfig.java @@ -77,6 +77,15 @@ public class PonyConfig extends Config { .addComment("Disables certain easter eggs and secrets (party pooper)") .addComment("Turning this off may help with compatibility in some cases"); + public final Setting enableFabricModelsApiSupport = value("settings", "enableFabricModelsApiSupport", false) + .addComment("Enables rendering of modded armour registered via the fabric api") + .addComment("Note that since any armour registered in this way is designed to work for the human model, pieces may not fit exactly.") + .addComment("i.e. Anything that goes on the plyer's backs needs to be rotated by the mod developer when rendered on a pony") + .addComment("Developers: To know if you're being rendered on a pony model, check the renderstate or model class with") + .addComment(" state instanceof com.minelittlepony.api.model.PonyModel$AttributedHolder or model instanceof com.minelittlepony.api.model.PonyModel") + .addComment(" Note that the matrix stack your receieve is pre-transformed for the body part your model is attached to, so if you intend to call model.transform(state, part, matrices)") + .addComment(" with a different part (ie BACK) you must pop the stack to revert to the previous first."); + public final Setting horseButton = value("horseButton", VisibilityMode.AUTO) .addComment("Whether to show the mine little pony settings button on the main menu") .addComment("AUTO (default) - only show when HDSkins is not installed") diff --git a/src/main/java/com/minelittlepony/client/model/armour/PonifiedEquipmentRenderer.java b/src/main/java/com/minelittlepony/client/model/armour/PonifiedEquipmentRenderer.java index f932a3ce..485e7ed5 100644 --- a/src/main/java/com/minelittlepony/client/model/armour/PonifiedEquipmentRenderer.java +++ b/src/main/java/com/minelittlepony/client/model/armour/PonifiedEquipmentRenderer.java @@ -23,7 +23,6 @@ import com.minelittlepony.api.model.Models; import com.minelittlepony.client.model.AbstractPonyModel; import com.minelittlepony.client.model.ClientPonyModel; import com.minelittlepony.client.render.entity.state.PonyRenderState; - import java.util.*; public class PonifiedEquipmentRenderer extends EquipmentRenderer { diff --git a/src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java b/src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java index aef9a312..85a3bc05 100644 --- a/src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java +++ b/src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java @@ -1,13 +1,20 @@ package com.minelittlepony.client.render.entity.feature; -import com.minelittlepony.api.model.Models; +import com.minelittlepony.api.config.PonyConfig; +import com.minelittlepony.api.model.*; import com.minelittlepony.client.model.ClientPonyModel; import com.minelittlepony.client.model.armour.*; import com.minelittlepony.client.render.PonyRenderContext; import com.minelittlepony.client.render.entity.state.PonyRenderState; +import com.minelittlepony.client.util.render.MatrixStackUtil; +import java.util.*; + +import net.fabricmc.fabric.api.client.rendering.v1.ArmorRenderer; +import net.fabricmc.fabric.impl.client.rendering.ArmorRendererRegistryImpl; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.equipment.EquipmentModelLoader; +import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.EquippableComponent; @@ -15,7 +22,10 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.item.*; import net.minecraft.item.equipment.EquipmentModel; +import net.minecraft.util.Unit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.Nullable; public class ArmourFeature< @@ -23,6 +33,9 @@ public class ArmourFeature< S extends PonyRenderState, M extends ClientPonyModel > extends AbstractPonyFeature { + private static final Logger LOGGER = LogManager.getLogger("PonifiedEquipmentRenderer"); + + private static boolean FABRIC_API_FAILURE; private final PonifiedEquipmentRenderer equipmentRenderer; @@ -58,17 +71,77 @@ public class ArmourFeature< ArmourRendererPlugin plugin = ArmourRendererPlugin.INSTANCE.get(); for (ItemStack stack : plugin.getArmorStacks(entity, armorSlot, layerType, ArmourRendererPlugin.ArmourType.ARMOUR)) { - EquippableComponent equippableComponent = stack.get(DataComponentTypes.EQUIPPABLE); - - if (hasModel(equippableComponent, armorSlot)) { - equipmentRenderer.render(armorSlot, layerType, equippableComponent.model().orElseThrow(), entity, models, stack, matrices, vertices, light); - } + render(armorSlot, layerType, entity, models, stack, matrices, vertices, light, equipmentRenderer); } plugin.onArmourRendered(entity, matrices, vertices, armorSlot, layerType, ArmourRendererPlugin.ArmourType.ARMOUR); } + private static > void render( + EquipmentSlot slot, + EquipmentModel.LayerType layerType, + S entity, + Models models, + ItemStack stack, + MatrixStack matrices, + VertexConsumerProvider vertices, + int light, PonifiedEquipmentRenderer equipmentRenderer + ) { + if (!FABRIC_API_FAILURE && PonyConfig.getInstance().enableFabricModelsApiSupport.get()) { + try { + if (FabricArmorRendererInvoker.renderArmor(stack, models, matrices, vertices, light, entity, slot, layerType)) { + return; + } + } catch (Throwable t) { + LOGGER.error("Failure calling fabric armor rendering api", t); + FABRIC_API_FAILURE = true; + } + } + EquippableComponent equippableComponent = stack.get(DataComponentTypes.EQUIPPABLE); + if (hasModel(equippableComponent, slot)) { + equipmentRenderer.render(slot, layerType, equippableComponent.model().orElseThrow(), entity, models, stack, matrices, vertices, light, null); + } + } + private static boolean hasModel(@Nullable EquippableComponent component, EquipmentSlot slot) { return component != null && component.model().isPresent() && component.slot() == slot; } + + private static final class FabricArmorRendererInvoker { + private static final Map FAILING_RENDERERS = new WeakHashMap<>(); + + @SuppressWarnings({"rawtypes", "unchecked"}) + private static > boolean renderArmor( + ItemStack stack, + Models models, MatrixStack matrices, + VertexConsumerProvider vertices, int light, S entity, + EquipmentSlot armorSlot, EquipmentModel.LayerType layerType) { + ArmorRenderer renderer = ArmorRendererRegistryImpl.get(stack.getItem()); + + if (renderer != null && !FAILING_RENDERERS.containsKey(renderer)) { + MatrixStack isolation = MatrixStackUtil.pushIsolation(matrices); + try { + isolation.push(); + models.body().transform(entity, getBodyPart(armorSlot), isolation); + renderer.render(isolation, vertices, stack, entity, armorSlot, light, (BipedEntityModel)models.body()); + isolation.pop(); + } catch (Throwable t) { + LOGGER.error("Exception occured whilst rendering custom armor via fabric api. Renderer {} has been disabled", renderer, t); + FAILING_RENDERERS.put(renderer, Unit.INSTANCE); + } finally { + MatrixStackUtil.popIsolation(); + } + return true; + } + return false; + } + + private static BodyPart getBodyPart(EquipmentSlot slot) { + return switch (slot) { + case HEAD -> BodyPart.HEAD; + case CHEST, BODY -> BodyPart.BODY; + case LEGS, FEET, MAINHAND, OFFHAND -> BodyPart.LEGS; + }; + } + } } diff --git a/src/main/java/com/minelittlepony/client/util/render/MatrixStackUtil.java b/src/main/java/com/minelittlepony/client/util/render/MatrixStackUtil.java new file mode 100644 index 00000000..e23df238 --- /dev/null +++ b/src/main/java/com/minelittlepony/client/util/render/MatrixStackUtil.java @@ -0,0 +1,19 @@ +package com.minelittlepony.client.util.render; + +import net.minecraft.client.util.math.MatrixStack; + +public class MatrixStackUtil { + static final MatrixStack ISOLATION_STACK = new MatrixStack(); + + public static MatrixStack pushIsolation(MatrixStack matrices) { + ISOLATION_STACK.peek().getNormalMatrix().set(matrices.peek().getNormalMatrix()); + ISOLATION_STACK.peek().getPositionMatrix().set(matrices.peek().getPositionMatrix()); + return ISOLATION_STACK; + } + + public static void popIsolation() { + while (!ISOLATION_STACK.isEmpty()) { + ISOLATION_STACK.pop(); + } + } +} diff --git a/src/main/resources/assets/minelittlepony/lang/en_us.json b/src/main/resources/assets/minelittlepony/lang/en_us.json index 220fa574..4541c4b2 100644 --- a/src/main/resources/assets/minelittlepony/lang/en_us.json +++ b/src/main/resources/assets/minelittlepony/lang/en_us.json @@ -14,6 +14,7 @@ "minelp.options.skins.hdskins.open": "Open HD Skins", "minelp.options.skins.hdskins.disabled": "HD Skins is not installed\n\nThe HD Skins mod is required to upload skins from in-game and to use custom skin servers.\n\nIf you cannot use that you will have to go to www.minecraft.net to upload your skin there.", "minelp.options.snuzzles": "Show Snuzzles", + "minelp.options.enablefabricmodelsapisupport": "(Experimental) Show Modded Armor", "minelp.options.fillycam": "Filly Cam", "minelp.options.showscale": "Show-accurate scaling", "minelp.options.fpsmagic": "Magic in first-person",