Rewrite armour texture resolution and add support for custom armour using the CustomModelData nbt tag.

This commit is contained in:
Sollace 2021-08-13 23:35:48 +02:00
parent aa97a81249
commit 46f171523a
5 changed files with 115 additions and 77 deletions

View file

@ -3,8 +3,16 @@ package com.minelittlepony.api.model.armour;
import net.minecraft.item.Item;
import net.minecraft.util.registry.Registry;
import com.minelittlepony.api.model.IModelWrapper;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.api.model.IModelWrapper;
import com.minelittlepony.api.pony.IPonyData;
/**
* Wrapper for an armour model and texture.
*
* @param <V> The type of the contained armour model.
*/
public interface IArmour<V extends IArmourModel> extends IModelWrapper {
/**
* Registers a custom armour for the supplied item.
@ -19,7 +27,10 @@ public interface IArmour<V extends IArmourModel> extends IModelWrapper {
/**
* Gets the armour model to render for the given layer.
* <p>
* Return null to preserve the default behaviour or override and return your custom model.
*/
@Nullable
V getModel(ArmourLayer layer);
/**
@ -30,4 +41,9 @@ public interface IArmour<V extends IArmourModel> extends IModelWrapper {
default IArmourTextureResolver getTextureResolver(IArmourTextureResolver defaultResolver) {
return defaultResolver;
}
@Override
default IArmour<V> applyMetadata(IPonyData meta) {
return this;
}
}

View file

@ -7,6 +7,11 @@ import net.minecraft.util.Identifier;
import org.jetbrains.annotations.Nullable;
/**
* A resolver for looking up the texture for a piece of armour.
* <p>
* This is for modders who want to override the default implementation found in {@link DefaultArmourTextureResolver}.
*/
public interface IArmourTextureResolver {
/**

View file

@ -1,14 +1,16 @@
package com.minelittlepony.client.model.armour;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.texture.TextureManager;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ArmorItem;
import net.minecraft.item.ItemStack;
import net.minecraft.resource.ResourceManager;
import net.minecraft.nbt.NbtElement;
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.minelittlepony.api.model.armour.ArmourLayer;
import com.minelittlepony.api.model.armour.ArmourVariant;
import com.minelittlepony.api.model.armour.IArmourTextureResolver;
@ -16,96 +18,109 @@ import com.minelittlepony.util.ResourceUtil;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* The default texture resolver used by Mine Little Pony.
* <p>
* The search order is as follows:
* <p>
* namespace:textures/models/armor/material_layer_[outer|inner](_overlay)(_custom_[0-9]+)_pony.png
* namespace:textures/models/armor/material_layer_[outer|inner](_overlay)(_custom_[0-9]+).png
* namespace:textures/models/armor/material_layer_[1|2](_overlay)(_custom_[0-9]+)_pony.png
* namespace:textures/models/armor/material_layer_[1|2](_overlay)(_custom_[0-9]+).png
* namespace:textures/models/armor/material_layer_[outer|inner](_overlay)_pony.png
* namespace:textures/models/armor/material_layer_[outer|inner](_overlay).png
* namespace:textures/models/armor/material_layer_[1|2](_overlay)_pony.png
* namespace:textures/models/armor/material_layer_[1|2](_overlay).png
* <p>
* - if namespace is "minecraft" will be rewritten to "minelittlepony"
* <p>
* For how modders can customise both the model and texture please refer to {@link IArmour} and {@link IArmourTextureResolver}.
*
* @see IArmour
* @see IArmourTextureResolver
*/
public class DefaultArmourTextureResolver implements IArmourTextureResolver {
private final Map<String, Identifier> HUMAN_ARMOUR = new HashMap<>();
private final Map<Identifier, Identifier> PONY_ARMOUR = new HashMap<>();
private final Cache<String, Identifier> cache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.<String, Identifier>build();
@Override
public Identifier getTexture(LivingEntity entity, ItemStack itemstack, EquipmentSlot slot, ArmourLayer layer, @Nullable String type) {
type = Strings.nullToEmpty(type);
public Identifier getTexture(LivingEntity entity, ItemStack stack, EquipmentSlot slot, ArmourLayer layer, @Nullable String type) {
Identifier material = new Identifier(((ArmorItem) stack.getItem()).getMaterial().getName());
String custom = getCustom(stack);
ArmorItem item = (ArmorItem) itemstack.getItem();
String texture = item.getMaterial().getName();
try {
return cache.get(String.format("%s#%s#%s#%s", material, layer, type, custom), () -> {
String typed = Strings.nullToEmpty(type);
String extra = typed.isEmpty() ? "" : "_" + typed;
String domain = "minecraft";
Identifier texture;
int idx = texture.indexOf(':');
if (idx > -1) {
domain = texture.substring(0, idx);
texture = texture.substring(idx + 1);
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;
}
}
private String getCustom(ItemStack stack) {
if (stack.hasTag() && stack.getTag().contains("CustomModelData", NbtElement.NUMBER_TYPE)) {
return "custom_" + stack.getTag().getInt("CustomModelData");
}
return "none";
}
@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);
if (texture != null) {
return texture;
}
String customType = type.isEmpty() ? "" : "_" + type;
String res = ResourceUtil.format("%s:textures/models/armor/%s_layer_%s%s.png", domain, texture, layer, customType);
String oldRes = ResourceUtil.format("%s:textures/models/armor/%s_layer_%d%s.png", domain, texture, layer.getLegacyId(), customType);
Identifier human = getArmorTexture(res, type);
Identifier pony = ponifyResource(human);
Identifier oldHuman = getArmorTexture(oldRes, type);
Identifier oldPony = ponifyResource(oldHuman);
return resolve(oldPony, pony, oldHuman, human);
return resolveHumanOrPony(ResourceUtil.format("%s:textures/models/armor/%s_layer_%d%s.png", material.getNamespace(), material.getPath(), layer.getLegacyId(), extra), type);
}
private Identifier resolve(Identifier... resources) {
// check resource packs for either texture.
@Nullable
private Identifier resolveHumanOrPony(String res, String type) {
Identifier human = new Identifier(res);
ResourceManager manager = MinecraftClient.getInstance().getResourceManager();
for (Identifier i : resources) {
if (manager.containsResource(i)) {
return i;
}
String domain = human.getNamespace();
if ("minecraft".equals(domain)) {
domain = "minelittlepony"; // it's a vanilla armor. I provide these.
}
return resources[resources.length - 1];
Identifier pony = new Identifier(domain, human.getPath().replace(".png", "_pony.png"));
if (isValid(pony)) {
return pony;
}
if (isValid(human)) {
return human;
}
return null;
}
private Identifier ponifyResource(Identifier human) {
return PONY_ARMOUR.computeIfAbsent(human, key -> {
String domain = key.getNamespace();
if ("minecraft".equals(domain)) {
domain = "minelittlepony"; // it's a vanilla armor. I provide these.
}
return new Identifier(domain, key.getPath().replace(".png", "_pony.png"));
});
}
private Identifier getArmorTexture(String def, String type) {
return HUMAN_ARMOUR.computeIfAbsent(def + "#" + type, s -> {
Identifier defId = new Identifier(def);
if (type.isEmpty() || type.equals(def)) {
return defId;
}
Identifier modId = new Identifier(type);
Path modPath = Paths.get(modId.getPath()).getParent();
if (modPath == null) {
return defId;
}
Path path = modPath.resolve(Paths.get(defId.getPath()).getFileName());
Identifier interemId = new Identifier(modId.getNamespace(), path.toString().replace('\\', '/'));
if (MinecraftClient.getInstance().getResourceManager().containsResource(interemId)) {
return interemId;
}
return modId;
});
private final boolean isValid(Identifier texture) {
return MinecraftClient.getInstance().getResourceManager().containsResource(texture);
}
@Override

View file

@ -62,6 +62,9 @@ public class ArmourFeature<T extends LivingEntity, M extends EntityModel<T> & IP
armour.applyMetadata(pony.getBody().getMetadata());
V model = armour.getModel(layer);
if (model == null) {
model = pony.<V>getArmor().getModel(layer);
}
if (model.prepareToRender(armorSlot, layer)) {
pony.getBody().copyAttributes(model);

View file

@ -21,5 +21,4 @@ public class VillagerPonyRenderer extends AbstractNpcRenderer<VillagerEntity, Vi
super.scale(villager, stack, ticks);
stack.scale(BASE_MODEL_SCALE, BASE_MODEL_SCALE, BASE_MODEL_SCALE);
}
}