From eeea8ee184a3384ba8d2c1cbce4128f267ee9332 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 4 May 2024 19:32:05 +0100 Subject: [PATCH] Fix turtle helmet and clear cached armour lookups when reloading resources --- .../minelittlepony/client/MineLittlePony.java | 1 + .../model/armour/ArmourTextureResolver.java | 124 +++++++++++------- .../render/entity/feature/ArmourFeature.java | 4 + .../com/minelittlepony/util/ResourceUtil.java | 15 ++- 4 files changed, 97 insertions(+), 47 deletions(-) diff --git a/src/main/java/com/minelittlepony/client/MineLittlePony.java b/src/main/java/com/minelittlepony/client/MineLittlePony.java index 49bfad77..2f2c1138 100644 --- a/src/main/java/com/minelittlepony/client/MineLittlePony.java +++ b/src/main/java/com/minelittlepony/client/MineLittlePony.java @@ -80,6 +80,7 @@ public class MineLittlePony implements ClientModInitializer { ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(ponyManager); ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(variatedTextures); + ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(ArmourTextureResolver.INSTANCE); // convert legacy pony skins SkinFilterCallback.EVENT.register(new LegacySkinConverter()); 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 d7bca6e6..1fb87572 100644 --- a/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java +++ b/src/main/java/com/minelittlepony/client/model/armour/ArmourTextureResolver.java @@ -1,22 +1,27 @@ package com.minelittlepony.client.model.armour; +import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; import net.minecraft.client.texture.TextureManager; import net.minecraft.component.DataComponentTypes; import net.minecraft.component.type.CustomModelDataComponent; import net.minecraft.item.*; import net.minecraft.registry.Registries; +import net.minecraft.resource.ResourceManager; import net.minecraft.util.Colors; import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + import com.google.common.cache.*; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; import com.minelittlepony.api.config.PonyConfig; import com.minelittlepony.client.MineLittlePony; import com.minelittlepony.util.ResourceUtil; -import org.jetbrains.annotations.Nullable; - import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * The default texture resolver used by Mine Little Pony. @@ -33,50 +38,72 @@ import java.util.stream.Collectors; * - the "minecraft" namespace is always replaced with "minelittlepony" *

*/ -public class ArmourTextureResolver { +public class ArmourTextureResolver implements IdentifiableResourceReloadListener { + public static final Identifier ID = MineLittlePony.id("armor_textures"); public static final ArmourTextureResolver INSTANCE = new ArmourTextureResolver(); - private static final String CUSTOM_NONE = "none"; + private static final Interner LAYER_INTERNER = Interners.newWeakInterner(); - private final Cache cache = CacheBuilder.newBuilder() + private final LoadingCache layerCache = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) - .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; + .build(CacheLoader.from(parameters -> { + return Stream.of(ArmourTexture.legacy(parameters.material().getTexture(parameters.layer() == ArmourLayer.OUTER))).flatMap(i -> { + if (parameters.layer() == ArmourLayer.OUTER) { + return Stream.of(i, ArmourTexture.legacy(parameters.material().getTexture(false))); + } + return Stream.of(i); + }).flatMap(i -> { + if (parameters.customModelId() != 0) { + return Stream.of(ArmourTexture.legacy(i.texture().withPath(p -> p.replace(".png", parameters.customModelId() + ".png"))), i); + } + return Stream.of(i); + }).flatMap(this::performLookup).findFirst().orElse(ArmourTexture.UNKNOWN); })); private final LoadingCache> nonDyedLayers = CacheBuilder.newBuilder() .expireAfterAccess(30, TimeUnit.SECONDS) - .build(CacheLoader.from(material -> List.of(new ArmorMaterial.Layer(material, "", false)))); + .build(CacheLoader.from(material -> List.of(LAYER_INTERNER.intern(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) + LAYER_INTERNER.intern(new ArmorMaterial.Layer(material, "", false)), + LAYER_INTERNER.intern(new ArmorMaterial.Layer(material, "overlay", true)) ))); + private Stream performLookup(ArmourTexture id) { + List options = Stream.of(id) + .flatMap(ArmourTexture::named) + .flatMap(ArmourTexture::ponify) + .toList(); + return options.stream().distinct() + .filter(ArmourTexture::validate) + .findFirst() + .or(() -> { + MineLittlePony.logger.warn("Could not identify correct texture to use for {}. Was none of: [" + System.lineSeparator() + "{}" + System.lineSeparator() + "]", id, options.stream() + .map(ArmourTexture::texture) + .map(Identifier::toString) + .collect(Collectors.joining("," + System.lineSeparator()))); + return Optional.empty(); + }).stream(); + } + public void invalidate() { - cache.invalidateAll(); + layerCache.invalidateAll(); + nonDyedLayers.invalidateAll(); + dyedLayers.invalidateAll(); + } + + @Override + public CompletableFuture reload(Synchronizer synchronizer, ResourceManager manager, Profiler prepareProfiler, Profiler applyProfiler, Executor prepareExecutor, Executor applyExecutor) { + return CompletableFuture.runAsync(this::invalidate, prepareExecutor).thenCompose(synchronizer::whenPrepared); + } + + @Override + public Identifier getFabricId() { + return ID; } public ArmourTexture getTexture(ItemStack stack, ArmourLayer layer, ArmorMaterial.Layer armorLayer) { - return layerCache.getUnchecked(armorLayer.getTexture(layer == ArmourLayer.OUTER) + "#" + getCustom(stack)); + return layerCache.getUnchecked(new ArmourParameters(layer, armorLayer, getCustom(stack))); } public List getArmorLayers(ItemStack stack, int dyeColor) { @@ -87,34 +114,39 @@ public class ArmourTextureResolver { return (dyeColor == Colors.WHITE ? nonDyedLayers : dyedLayers).getUnchecked(Registries.ITEM.getId(stack.getItem())); } - private String getCustom(ItemStack stack) { - int custom = stack.getOrDefault(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelDataComponent.DEFAULT).value(); - return custom == 0 ? "none" : String.valueOf(custom); + private int getCustom(ItemStack stack) { + return stack.getOrDefault(DataComponentTypes.CUSTOM_MODEL_DATA, CustomModelDataComponent.DEFAULT).value(); + } + + private record ArmourParameters(ArmourLayer layer, ArmorMaterial.Layer material, int customModelId) { + } public record ArmourTexture(Identifier texture, ArmourVariant variant) { - public static final ArmourTexture UNKNOWN = new ArmourTexture(TextureManager.MISSING_IDENTIFIER, ArmourVariant.LEGACY); + private static final Interner INTERNER = Interners.newWeakInterner(); + public static final ArmourTexture UNKNOWN = legacy(TextureManager.MISSING_IDENTIFIER); public boolean validate() { return texture != TextureManager.MISSING_IDENTIFIER && ResourceUtil.textureExists(texture); } - public static ArmourTexture pick(List options) { - return options.stream().filter(ArmourTexture::validate).findFirst().orElse(ArmourTexture.UNKNOWN); + public static ArmourTexture legacy(Identifier texture) { + return INTERNER.intern(new ArmourTexture(texture, ArmourVariant.LEGACY)); } - @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. - } + public static ArmourTexture modern(Identifier texture) { + return INTERNER.intern(new ArmourTexture(texture, ArmourVariant.NORMAL)); + } + public Stream named() { + return Stream.of(legacy(texture().withPath(p -> p.replace("1", "inner").replace("2", "outer"))), this); + } + + public Stream ponify() { if (!PonyConfig.getInstance().disablePonifiedArmour.get()) { - output.add(new ArmourTexture(new Identifier(domain, human.getPath().replace(".png", "_pony.png")), ArmourVariant.NORMAL)); + return Stream.of(this, modern(ResourceUtil.ponify(texture()))); } - - output.add(new ArmourTexture(human, ArmourVariant.LEGACY)); + return Stream.of(this); } } } 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 700456f4..c69cb902 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 @@ -66,6 +66,10 @@ public class ArmourFeature & Po for (ArmorMaterial.Layer armorLayer : ArmourTextureResolver.INSTANCE.getArmorLayers(stack, color)) { ArmourTexture layerTexture = ArmourTextureResolver.INSTANCE.getTexture(stack, layer, armorLayer); + if (layerTexture == ArmourTexture.UNKNOWN) { + continue; + } + 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; diff --git a/src/main/java/com/minelittlepony/util/ResourceUtil.java b/src/main/java/com/minelittlepony/util/ResourceUtil.java index 1bf9299e..957b3593 100644 --- a/src/main/java/com/minelittlepony/util/ResourceUtil.java +++ b/src/main/java/com/minelittlepony/util/ResourceUtil.java @@ -3,10 +3,11 @@ package com.minelittlepony.util; import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; +import com.minelittlepony.client.MineLittlePony; + import java.util.Optional; public final class ResourceUtil { - public static String format(String template, Object... args) { for (int i = 0; i < args.length; i++) { if (!(args[i] instanceof Number)) { @@ -29,4 +30,16 @@ public final class ResourceUtil { public static Optional verifyTexture(Identifier texture) { return textureExists(texture) ? Optional.of(texture) : Optional.empty(); } + + public static Identifier ponify(Identifier texture) { + String path = texture.getPath(); + if (path.endsWith("_pony.png")) { + return texture; + } + if (Identifier.DEFAULT_NAMESPACE.contentEquals(texture.getNamespace())) { + return MineLittlePony.id(path.replace(".png", "_pony.png")); // it's in the vanilla namespace, we provide these. + } + + return texture.withPath(p -> p.replace(".png", "_pony.png")); + } }