Update armour rendering to use the armor material layers defined by the item.

Also fixes #269, #270
This commit is contained in:
Sollace 2024-05-04 00:03:28 +01:00
parent 8d385a3202
commit 3c44b00c1d
No known key found for this signature in database
GPG key ID: E52FACE7B5C773DB
2 changed files with 107 additions and 126 deletions

View file

@ -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<String, Identifier> cache = CacheBuilder.newBuilder()
private static final String CUSTOM_NONE = "none";
private final Cache<String, ArmourTexture> cache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.<String, Identifier>build();
.<String, ArmourTexture>build();
private final LoadingCache<String, ArmourTexture> 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<ArmourTexture> 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<Identifier, List<ArmorMaterial.Layer>> nonDyedLayers = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.build(CacheLoader.from(material -> List.of(new ArmorMaterial.Layer(material, "", false))));
private final LoadingCache<Identifier, List<ArmorMaterial.Layer>> 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<ArmorMaterial.Layer> 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<ArmourTexture> 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<ArmourTexture> 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;
}
}

View file

@ -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<T extends LivingEntity, M extends EntityModel<T> & PonyModel<T>> extends AbstractPonyFeature<T, M> {
@ -44,7 +46,7 @@ public class ArmourFeature<T extends LivingEntity, M extends EntityModel<T> & Po
public static <T extends LivingEntity, V extends PonyArmourModel<T>> void renderArmor(
Models<T, ? extends PonyModel<T>> 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<T extends LivingEntity, M extends EntityModel<T> & 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<PonyArmourModel<?>> 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<ArmorMaterial> material, ArmorTrim trim, ArmourLayer layer, boolean glint) {
private static VertexConsumer getTrimConsumer(VertexConsumerProvider provider, RegistryEntry<ArmorMaterial> 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()))
);
}
}