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; package com.minelittlepony.client.model.armour;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.texture.TextureManager; import net.minecraft.client.texture.TextureManager;
import net.minecraft.component.DataComponentTypes; import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.CustomModelDataComponent; import net.minecraft.component.type.CustomModelDataComponent;
import net.minecraft.entity.LivingEntity; import net.minecraft.item.*;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.ItemStack;
import net.minecraft.registry.Registries; import net.minecraft.registry.Registries;
import net.minecraft.util.Colors;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import com.google.common.base.Strings; import com.google.common.cache.*;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.minelittlepony.api.config.PonyConfig; import com.minelittlepony.api.config.PonyConfig;
import com.minelittlepony.client.MineLittlePony;
import com.minelittlepony.util.ResourceUtil; import com.minelittlepony.util.ResourceUtil;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.concurrent.ExecutionException; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* The default texture resolver used by Mine Little Pony. * The default texture resolver used by Mine Little Pony.
@ -39,98 +36,85 @@ import java.util.concurrent.TimeUnit;
public class ArmourTextureResolver { public class ArmourTextureResolver {
public static final ArmourTextureResolver INSTANCE = new 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) .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() { public void invalidate() {
cache.invalidateAll(); cache.invalidateAll();
} }
public Identifier getTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, ArmourLayer layer, @Nullable String type) { public ArmourTexture getTexture(ItemStack stack, ArmourLayer layer, ArmorMaterial.Layer armorLayer) {
Identifier material = stack.getItem() instanceof ArmorItem armor return layerCache.getUnchecked(armorLayer.getTexture(layer == ArmourLayer.OUTER) + "#" + getCustom(stack));
? armor.getMaterial().getKey().get().getValue() }
: Registries.ITEM.getId(stack.getItem());
String custom = getCustom(stack);
try { public List<ArmorMaterial.Layer> getArmorLayers(ItemStack stack, int dyeColor) {
return cache.get(String.format("%s#%s#%s#%s", material, layer, type, custom), () -> { if (stack.getItem() instanceof ArmorItem armor) {
String typed = Strings.nullToEmpty(type); return armor.getMaterial().value().layers();
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;
} }
return (dyeColor == Colors.WHITE ? nonDyedLayers : dyedLayers).getUnchecked(Registries.ITEM.getId(stack.getItem()));
} }
private String getCustom(ItemStack stack) { private String getCustom(ItemStack stack) {
CustomModelDataComponent customModelData = stack.get(DataComponentTypes.CUSTOM_MODEL_DATA); int custom = stack.getOrDefault(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelDataComponent.DEFAULT).value();
if (customModelData != null) { return custom == 0 ? "none" : String.valueOf(custom);
return "custom_" + customModelData.value();
}
return "none";
} }
@Nullable public record ArmourTexture(Identifier texture, ArmourVariant variant) {
private Identifier resolveNewOrOld(Identifier material, ArmourLayer layer, String extra, String type) { public static final ArmourTexture UNKNOWN = new ArmourTexture(TextureManager.MISSING_IDENTIFIER, ArmourVariant.LEGACY);
Identifier texture = resolveHumanOrPony(ResourceUtil.format("%s:textures/models/armor/%s_layer_%s%s.png", material.getNamespace(), material.getPath(), layer, extra), type);
if (texture != null) { public boolean validate() {
return texture; 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); public static ArmourTexture pick(List<ArmourTexture> options) {
} return options.stream().filter(ArmourTexture::validate).findFirst().orElse(ArmourTexture.UNKNOWN);
@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.
} }
if (!PonyConfig.getInstance().disablePonifiedArmour.get()) { @Nullable
private static void resolveHumanOrPony(Identifier human, List<ArmourTexture> output) {
Identifier pony = new Identifier(domain, human.getPath().replace(".png", "_pony.png")); String domain = human.getNamespace();
if (Identifier.DEFAULT_NAMESPACE.contentEquals(domain)) {
if (isValid(pony)) { domain = "minelittlepony"; // it's a vanilla armor. I provide these.
return pony;
} }
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.Models;
import com.minelittlepony.api.model.PonyModel; import com.minelittlepony.api.model.PonyModel;
import com.minelittlepony.client.model.armour.*; 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.client.render.PonyRenderContext;
import com.minelittlepony.common.util.Color; import com.minelittlepony.common.util.Color;
import java.util.*;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*; import net.minecraft.client.render.*;
import net.minecraft.client.render.entity.model.*; 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.render.model.BakedModelManager;
import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.Sprite;
import net.minecraft.client.texture.SpriteAtlasTexture; import net.minecraft.client.texture.SpriteAtlasTexture;
@ -22,7 +23,8 @@ import net.minecraft.entity.LivingEntity;
import net.minecraft.item.*; import net.minecraft.item.*;
import net.minecraft.item.trim.ArmorTrim; import net.minecraft.item.trim.ArmorTrim;
import net.minecraft.registry.entry.RegistryEntry; 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> { 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( public static <T extends LivingEntity, V extends PonyArmourModel<T>> void renderArmor(
Models<T, ? extends PonyModel<T>> pony, MatrixStack matrices, 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 limbDistance, float limbAngle,
float age, float headYaw, float headPitch, float age, float headYaw, float headPitch,
EquipmentSlot armorSlot, ArmourLayer layer) { EquipmentSlot armorSlot, ArmourLayer layer) {
@ -55,61 +57,56 @@ public class ArmourFeature<T extends LivingEntity, M extends EntityModel<T> & Po
return; return;
} }
Identifier texture = ArmourTextureResolver.INSTANCE.getTexture(entity, stack, armorSlot, layer, null);
ArmourVariant variant = ArmourTextureResolver.INSTANCE.getVariant(layer, texture);
boolean glint = stack.hasGlint(); boolean glint = stack.hasGlint();
pony.getArmourModel(stack, layer, variant) int color = stack.isIn(ItemTags.DYEABLE) ? DyedColorComponent.getColor(stack, -6265536) : Colors.WHITE;
.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;
DyedColorComponent colorComponent = stack.get(DataComponentTypes.DYED_COLOR); Set<PonyArmourModel<?>> models = glint ? new HashSet<>() : null;
if (colorComponent != null) { for (ArmorMaterial.Layer armorLayer : ArmourTextureResolver.INSTANCE.getArmorLayers(stack, color)) {
int color = colorComponent.rgb(); ArmourTexture layerTexture = ArmourTextureResolver.INSTANCE.getTexture(stack, layer, armorLayer);
red = Color.r(color);
green = Color.g(color); var m = pony.getArmourModel(stack, layer, layerTexture.variant()).orElse(null);
blue = Color.b(color); 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) { if (trim != null && stack.getItem() instanceof ArmorItem armor) {
Identifier tex = ArmourTextureResolver.INSTANCE.getTexture(entity, stack, armorSlot, layer, "overlay"); var m = pony.getArmourModel(stack, layer, ArmourVariant.TRIM).orElse(null);
pony.getArmourModel(stack, layer, ArmourTextureResolver.INSTANCE.getVariant(layer, tex)) if (m != null && m.poseModel(entity, limbAngle, limbDistance, age, headYaw, headPitch, armorSlot, layer, pony.body())) {
.filter(m -> 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);
.ifPresent(m -> {
m.render(matrices, getArmorConsumer(renderContext, tex, false), light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);
});
} }
}
ArmorTrim trim = stack.get(DataComponentTypes.TRIM); if (glint) {
VertexConsumer glintConsumer = provider.getBuffer(RenderLayer.getArmorEntityGlint());
if (trim != null && stack.getItem() instanceof ArmorItem armor) { for (var m : models) {
pony.getArmourModel(stack, layer, ArmourVariant.TRIM) m.render(matrices, glintConsumer, light, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1);
.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);
});
} }
}); }
} }
private static VertexConsumer getArmorConsumer(VertexConsumerProvider provider, Identifier texture, boolean glint) { private static VertexConsumer getTrimConsumer(VertexConsumerProvider provider, RegistryEntry<ArmorMaterial> material, ArmorTrim trim, ArmourLayer layer) {
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) {
SpriteAtlasTexture armorTrimsAtlas = MinecraftClient.getInstance().getBakedModelManager().getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE); SpriteAtlasTexture armorTrimsAtlas = MinecraftClient.getInstance().getBakedModelManager().getAtlas(TexturedRenderLayers.ARMOR_TRIMS_ATLAS_TEXTURE);
Sprite sprite = armorTrimsAtlas.getSprite( Sprite sprite = armorTrimsAtlas.getSprite(
layer == ArmourLayer.INNER ? trim.getLeggingsModelId(material) : trim.getGenericModelId(material) 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(
provider.getBuffer(TexturedRenderLayers.getArmorTrims(trim.getPattern().value().decal()))
return sprite.getTextureSpecificVertexConsumer(ItemRenderer.getDirectItemGlintConsumer(provider, renderLayer, true, glint)); );
} }
} }