From 3284669e245ea023f952bf10e662fab349e65eac Mon Sep 17 00:00:00 2001 From: Sollace Date: Tue, 17 Dec 2024 14:37:06 +0100 Subject: [PATCH] (Backport) Add support for rendering modded armor # Conflicts: # src/main/java/com/minelittlepony/client/model/armour/PonifiedEquipmentRenderer.java # src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java --- .../minelittlepony/api/config/PonyConfig.java | 9 +++ .../render/entity/feature/ArmourFeature.java | 66 +++++++++++++++++++ .../client/util/render/MatrixStackUtil.java | 19 ++++++ .../assets/minelittlepony/lang/en_us.json | 1 + 4 files changed, 95 insertions(+) create mode 100644 src/main/java/com/minelittlepony/client/util/render/MatrixStackUtil.java 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/render/entity/feature/ArmourFeature.java b/src/main/java/com/minelittlepony/client/render/entity/feature/ArmourFeature.java index 0c9f9569..c58b67b5 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 @@ -8,9 +8,17 @@ import com.minelittlepony.common.util.Color; import java.util.*; +import com.minelittlepony.api.config.PonyConfig; +import com.minelittlepony.api.model.*; +import com.minelittlepony.client.model.ClientPonyModel; +import com.minelittlepony.client.util.render.MatrixStackUtil; + +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.model.*; import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.component.DataComponentTypes; import net.minecraft.entity.EquipmentSlot; @@ -18,8 +26,16 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.item.*; import net.minecraft.item.trim.ArmorTrim; import net.minecraft.util.Colors; +import net.minecraft.util.Unit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ArmourFeature & PonyModel> extends AbstractPonyFeature { + private static final Logger LOGGER = LogManager.getLogger("PonifiedEquipmentRenderer"); + + private static boolean FABRIC_API_FAILURE; + public ArmourFeature(PonyRenderContext context, BakedModelManager bakery) { super(context); } @@ -56,6 +72,17 @@ public class ArmourFeature & Po continue; } + if (!FABRIC_API_FAILURE && PonyConfig.getInstance().enableFabricModelsApiSupport.get()) { + try { + if (FabricArmorRendererInvoker.renderArmor(stack, pony, matrices, provider, light, entity, armorSlot)) { + continue; + } + } catch (Throwable t) { + LOGGER.error("Failure calling fabric armor rendering api", t); + FABRIC_API_FAILURE = true; + } + } + float glintAlpha = plugin.getGlintAlpha(armorSlot, stack); boolean glint = glintAlpha > 0; int color = plugin.getDyeColor(armorSlot, stack); @@ -118,4 +145,43 @@ public class ArmourFeature & Po plugin.onArmourRendered(entity, matrices, provider, armorSlot, layer, ArmourRendererPlugin.ArmourType.ARMOUR); } + + + 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, T entity, + EquipmentSlot armorSlot) { + ArmorRenderer renderer = ArmorRendererRegistryImpl.get(stack.getItem()); + + if (renderer != null && !FAILING_RENDERERS.containsKey(renderer)) { + MatrixStack isolation = MatrixStackUtil.pushIsolation(matrices); + try { + isolation.push(); + models.body().transform(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",