From 3c44b00c1d2de5489f574795d6d567e42f64b639 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 4 May 2024 00:03:28 +0100 Subject: [PATCH] Update armour rendering to use the armor material layers defined by the item. Also fixes #269, #270 --- .../model/armour/ArmourTextureResolver.java | 148 ++++++++---------- .../render/entity/feature/ArmourFeature.java | 85 +++++----- 2 files changed, 107 insertions(+), 126 deletions(-) diff --git a/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java b/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java index 31a1274d..d7bca6e6 100644 --- a/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java +++ b/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java @@ -1,25 +1,22 @@ package com.minelittlepony.client.model.armour; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.texture.TextureManager; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.CustomModelDataComponent; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.EquipmentSlot; -import net.minecraft.item.ArmorItem; -import net.minecraft.item.ItemStack; +import net.minecraft.item.*; import net.minecraft.registry.Registries; +import net.minecraft.util.Colors; import net.minecraft.util.Identifier; -import com.google.common.base.Strings; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.google.common.cache.*; import com.minelittlepony.api.config.PonyConfig; +import com.minelittlepony.client.MineLittlePony; import com.minelittlepony.util.ResourceUtil; import org.jetbrains.annotations.Nullable; -import java.util.concurrent.ExecutionException; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * The default texture resolver used by Mine Little Pony. @@ -39,98 +36,85 @@ import java.util.concurrent.TimeUnit; public class ArmourTextureResolver { public static final ArmourTextureResolver INSTANCE = new ArmourTextureResolver(); - private final Cache cache = CacheBuilder.newBuilder() + private static final String CUSTOM_NONE = "none"; + + private final Cache cache = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) - .build(); + .build(); + private final LoadingCache layerCache = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(texture -> { + String[] parts = texture.split("#"); + if (!parts[1].equals(CUSTOM_NONE)) { + parts[0] = parts[0].replace(".png", parts[1] + ".png"); + } + List options = new ArrayList<>(); + ArmourTexture.resolveHumanOrPony(new Identifier(parts[0].replace("1", "inner").replace("2", "outer")), options); + ArmourTexture.resolveHumanOrPony(new Identifier(parts[0]), options); + ArmourTexture result = ArmourTexture.pick(options); + if (result == ArmourTexture.UNKNOWN) { + MineLittlePony.logger.warn("Could not identify correct texture to use for {}. Was none of: [" + System.lineSeparator() + "{}" + System.lineSeparator() + "]", texture, options.stream() + .map(ArmourTexture::texture) + .map(Identifier::toString) + .collect(Collectors.joining("," + System.lineSeparator()))); + return new ArmourTexture(new Identifier(parts[0]), ArmourVariant.LEGACY); + } + return result; + })); + private final LoadingCache> nonDyedLayers = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(material -> List.of(new ArmorMaterial.Layer(material, "", false)))); + private final LoadingCache> dyedLayers = CacheBuilder.newBuilder() + .expireAfterAccess(30, TimeUnit.SECONDS) + .build(CacheLoader.from(material -> List.of( + new ArmorMaterial.Layer(material, "", false), + new ArmorMaterial.Layer(material, "overlay", true) + ))); public void invalidate() { cache.invalidateAll(); } - public Identifier getTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, ArmourLayer layer, @Nullable String type) { - Identifier material = stack.getItem() instanceof ArmorItem armor - ? armor.getMaterial().getKey().get().getValue() - : Registries.ITEM.getId(stack.getItem()); - String custom = getCustom(stack); + public ArmourTexture getTexture(ItemStack stack, ArmourLayer layer, ArmorMaterial.Layer armorLayer) { + return layerCache.getUnchecked(armorLayer.getTexture(layer == ArmourLayer.OUTER) + "#" + getCustom(stack)); + } - try { - return cache.get(String.format("%s#%s#%s#%s", material, layer, type, custom), () -> { - String typed = Strings.nullToEmpty(type); - String extra = typed.isEmpty() ? "" : "_" + typed; - - Identifier texture; - - if (!"none".equals(custom)) { - texture = resolveNewOrOld(material, layer, custom + extra, typed); - if (texture != null) { - return texture; - } - } - - texture = resolveNewOrOld(material, layer, extra, typed); - if (texture != null) { - return texture; - } - - return TextureManager.MISSING_IDENTIFIER; - }); - } catch (ExecutionException ignored) { - return TextureManager.MISSING_IDENTIFIER; + public List getArmorLayers(ItemStack stack, int dyeColor) { + if (stack.getItem() instanceof ArmorItem armor) { + return armor.getMaterial().value().layers(); } + + return (dyeColor == Colors.WHITE ? nonDyedLayers : dyedLayers).getUnchecked(Registries.ITEM.getId(stack.getItem())); } private String getCustom(ItemStack stack) { - CustomModelDataComponent customModelData = stack.get(DataComponentTypes.CUSTOM_MODEL_DATA); - if (customModelData != null) { - return "custom_" + customModelData.value(); - } - return "none"; + int custom = stack.getOrDefault(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelDataComponent.DEFAULT).value(); + return custom == 0 ? "none" : String.valueOf(custom); } - @Nullable - private Identifier resolveNewOrOld(Identifier material, ArmourLayer layer, String extra, String type) { - Identifier texture = resolveHumanOrPony(ResourceUtil.format("%s:textures/models/armor/%s_layer_%s%s.png", material.getNamespace(), material.getPath(), layer, extra), type); + public record ArmourTexture(Identifier texture, ArmourVariant variant) { + public static final ArmourTexture UNKNOWN = new ArmourTexture(TextureManager.MISSING_IDENTIFIER, ArmourVariant.LEGACY); - if (texture != null) { - return texture; + public boolean validate() { + return texture != TextureManager.MISSING_IDENTIFIER && ResourceUtil.textureExists(texture); } - return resolveHumanOrPony(ResourceUtil.format("%s:textures/models/armor/%s_layer_%d%s.png", material.getNamespace(), material.getPath(), layer.getLegacyId(), extra), type); - } - - @Nullable - private Identifier resolveHumanOrPony(String res, String type) { - Identifier human = new Identifier(res); - - String domain = human.getNamespace(); - if ("minecraft".equals(domain)) { - domain = "minelittlepony"; // it's a vanilla armor. I provide these. + public static ArmourTexture pick(List options) { + return options.stream().filter(ArmourTexture::validate).findFirst().orElse(ArmourTexture.UNKNOWN); } - if (!PonyConfig.getInstance().disablePonifiedArmour.get()) { - - Identifier pony = new Identifier(domain, human.getPath().replace(".png", "_pony.png")); - - if (isValid(pony)) { - return pony; + @Nullable + private static void resolveHumanOrPony(Identifier human, List output) { + String domain = human.getNamespace(); + if (Identifier.DEFAULT_NAMESPACE.contentEquals(domain)) { + domain = "minelittlepony"; // it's a vanilla armor. I provide these. } + + if (!PonyConfig.getInstance().disablePonifiedArmour.get()) { + output.add(new ArmourTexture(new Identifier(domain, human.getPath().replace(".png", "_pony.png")), ArmourVariant.NORMAL)); + } + + output.add(new ArmourTexture(human, ArmourVariant.LEGACY)); } - - if (isValid(human)) { - return human; - } - - return null; - } - - private final boolean isValid(Identifier texture) { - return MinecraftClient.getInstance().getResourceManager().getResource(texture).isPresent(); - } - - public ArmourVariant getVariant(ArmourLayer layer, Identifier resolvedTexture) { - if (resolvedTexture.getPath().endsWith("_pony.png")) { - return ArmourVariant.NORMAL; - } - return ArmourVariant.LEGACY; } } 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 b484104e..700456f4 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 @@ -3,14 +3,15 @@ package com.minelittlepony.client.render.entity.feature; import com.minelittlepony.api.model.Models; import com.minelittlepony.api.model.PonyModel; import com.minelittlepony.client.model.armour.*; -import com.minelittlepony.client.render.ArmorRenderLayers; +import com.minelittlepony.client.model.armour.ArmourTextureResolver.ArmourTexture; import com.minelittlepony.client.render.PonyRenderContext; import com.minelittlepony.common.util.Color; +import java.util.*; + import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.*; import net.minecraft.client.render.entity.model.*; -import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.client.render.model.BakedModelManager; import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.SpriteAtlasTexture; @@ -22,7 +23,8 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.item.*; import net.minecraft.item.trim.ArmorTrim; import net.minecraft.registry.entry.RegistryEntry; -import net.minecraft.util.Identifier; +import net.minecraft.registry.tag.ItemTags; +import net.minecraft.util.Colors; public class ArmourFeature & PonyModel> extends AbstractPonyFeature { @@ -44,7 +46,7 @@ public class ArmourFeature & Po public static > void renderArmor( Models> pony, MatrixStack matrices, - VertexConsumerProvider renderContext, int light, T entity, + VertexConsumerProvider provider, int light, T entity, float limbDistance, float limbAngle, float age, float headYaw, float headPitch, EquipmentSlot armorSlot, ArmourLayer layer) { @@ -55,61 +57,56 @@ public class ArmourFeature & Po return; } - Identifier texture = ArmourTextureResolver.INSTANCE.getTexture(entity, stack, armorSlot, layer, null); - ArmourVariant variant = ArmourTextureResolver.INSTANCE.getVariant(layer, texture); - boolean glint = stack.hasGlint(); - pony.getArmourModel(stack, layer, variant) - .filter(m -> m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) - .ifPresent(model -> { - float red = 1; - float green = 1; - float blue = 1; + int color = stack.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(stack, -6265536) : Colors.WHITE; - DyedColorComponent colorComponent = stack.get(DataComponentTypes.DYED_COLOR); + Set> models = glint ? new HashSet<>() : null; - if (colorComponent != null) { - int color = colorComponent.rgb(); - red = Color.r(color); - green = Color.g(color); - blue = Color.b(color); + for (ArmorMaterial.Layer armorLayer : ArmourTextureResolver.INSTANCE.getArmorLayers(stack, color)) { + ArmourTexture layerTexture = ArmourTextureResolver.INSTANCE.getTexture(stack, layer, armorLayer); + + var m = pony.getArmourModel(stack, layer, layerTexture.variant()).orElse(null); + if (m != null && m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) { + float red = 1; + float green = 1; + float blue = 1; + if (armorLayer.isDyeable() && color != Colors.WHITE) { + red = Color.r(color); + green = Color.g(color); + blue = Color.b(color); + } + m.render(matrices, provider.getBuffer(RenderLayer.getArmorCutoutNoCull(layerTexture.texture())), light, OverlayTexture.DEFAULT_UV, red, green, blue, 1); + if (glint) { + models.add(m); + } } + } - model.render(matrices, getArmorConsumer(renderContext, texture, glint), light, OverlayTexture.DEFAULT_UV, red, green, blue, 1); + ArmorTrim trim = stack.get(DataComponentTypes.TRIM); - if (colorComponent != null) { - Identifier tex = ArmourTextureResolver.INSTANCE.getTexture(entity, stack, armorSlot, layer, "overlay"); - pony.getArmourModel(stack, layer, ArmourTextureResolver.INSTANCE.getVariant(layer, tex)) - .filter(m -> m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) - .ifPresent(m -> { - m.render(matrices, getArmorConsumer(renderContext, tex, false), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); - }); + if (trim != null && stack.getItem() instanceof ArmorItem armor) { + var m = pony.getArmourModel(stack, layer, ArmourVariant.TRIM).orElse(null); + if (m != null && m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) { + m.render(matrices, getTrimConsumer(provider, armor.getMaterial(), trim, layer), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); } + } - ArmorTrim trim = stack.get(DataComponentTypes.TRIM); - - if (trim != null && stack.getItem() instanceof ArmorItem armor) { - pony.getArmourModel(stack, layer, ArmourVariant.TRIM) - .filter(m -> m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) - .ifPresent(m -> { - m.render(matrices, getTrimConsumer(renderContext, armor.getMaterial(), trim, layer, glint), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); - }); + if (glint) { + VertexConsumer glintConsumer = provider.getBuffer(RenderLayer.getArmorEntityGlint()); + for (var m : models) { + m.render(matrices, glintConsumer, light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); } - }); + } } - private static VertexConsumer getArmorConsumer(VertexConsumerProvider provider, Identifier texture, boolean glint) { - return ItemRenderer.getArmorGlintConsumer(provider, ArmorRenderLayers.getArmorTranslucentNoCull(texture, false), false, glint); - } - - private static VertexConsumer getTrimConsumer(VertexConsumerProvider provider, RegistryEntry material, ArmorTrim trim, ArmourLayer layer, boolean glint) { + private static VertexConsumer getTrimConsumer(VertexConsumerProvider provider, RegistryEntry material, ArmorTrim trim, ArmourLayer layer) { SpriteAtlasTexture armorTrimsAtlas = MinecraftClient.getInstance().getBakedModelManager().getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); Sprite sprite = armorTrimsAtlas.getSprite( layer == ArmourLayer.INNER ? trim.getLeggingsModelId(material) : trim.getGenericModelId(material) ); - RenderLayer renderLayer = ArmorRenderLayers.getArmorTranslucentNoCull(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE, trim.getPattern().value().decal()); - - return sprite.getTextureSpecificVertexConsumer(ItemRenderer.getDirectItemGlintConsumer(provider, renderLayer, true, glint)); + return sprite.getTextureSpecificVertexConsumer( + provider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal())) + ); } }