package net.minecraft.client.renderer.block.model; import com.google.common.collect.Lists; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMaps; import it.unimi.dsi.fastutil.objects.ObjectIterator; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.renderer.block.model.TextureSlots.Data.Builder; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelDebugName; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class TextureSlots { public static final TextureSlots EMPTY = new TextureSlots(Map.of()); private static final char REFERENCE_CHAR = '#'; private final Map resolvedValues; TextureSlots(Map resolvedValues) { this.resolvedValues = resolvedValues; } @Nullable public Material getMaterial(String name) { if (isTextureReference(name)) { name = name.substring(1); } return (Material)this.resolvedValues.get(name); } private static boolean isTextureReference(String name) { return name.charAt(0) == '#'; } public static TextureSlots.Data parseTextureMap(JsonObject json, ResourceLocation atlas) { Builder builder = new Builder(); for (Entry entry : json.entrySet()) { parseEntry(atlas, (String)entry.getKey(), ((JsonElement)entry.getValue()).getAsString(), builder); } return builder.build(); } private static void parseEntry(ResourceLocation atlas, String name, String material, Builder builder) { if (isTextureReference(material)) { builder.addReference(name, material.substring(1)); } else { ResourceLocation resourceLocation = ResourceLocation.tryParse(material); if (resourceLocation == null) { throw new JsonParseException(material + " is not valid resource location"); } builder.addTexture(name, new Material(atlas, resourceLocation)); } } @Environment(EnvType.CLIENT) public record Data(Map values) { public static final TextureSlots.Data EMPTY = new TextureSlots.Data(Map.of()); } @Environment(EnvType.CLIENT) record Reference(String target) implements TextureSlots.SlotContents { } @Environment(EnvType.CLIENT) public static class Resolver { private static final Logger LOGGER = LogUtils.getLogger(); private final List entries = new ArrayList(); public TextureSlots.Resolver addLast(TextureSlots.Data data) { this.entries.addLast(data); return this; } public TextureSlots.Resolver addFirst(TextureSlots.Data data) { this.entries.addFirst(data); return this; } public TextureSlots resolve(ModelDebugName name) { if (this.entries.isEmpty()) { return TextureSlots.EMPTY; } else { Object2ObjectMap object2ObjectMap = new Object2ObjectArrayMap<>(); Object2ObjectMap object2ObjectMap2 = new Object2ObjectArrayMap<>(); for (TextureSlots.Data data : Lists.reverse(this.entries)) { data.values.forEach((string, slotContents) -> { switch (slotContents) { case TextureSlots.Value value: object2ObjectMap2.remove(string); object2ObjectMap.put(string, value.material()); break; case TextureSlots.Reference reference: object2ObjectMap.remove(string); object2ObjectMap2.put(string, reference); break; default: throw new MatchException(null, null); } }); } if (object2ObjectMap2.isEmpty()) { return new TextureSlots(object2ObjectMap); } else { boolean bl = true; while (bl) { bl = false; ObjectIterator> objectIterator = Object2ObjectMaps.fastIterator( object2ObjectMap2 ); while (objectIterator.hasNext()) { it.unimi.dsi.fastutil.objects.Object2ObjectMap.Entry entry = (it.unimi.dsi.fastutil.objects.Object2ObjectMap.Entry)objectIterator.next(); Material material = object2ObjectMap.get(((TextureSlots.Reference)entry.getValue()).target); if (material != null) { object2ObjectMap.put((String)entry.getKey(), material); objectIterator.remove(); bl = true; } } } if (!object2ObjectMap2.isEmpty()) { LOGGER.warn( "Unresolved texture references in {}:\n{}", name.debugName(), object2ObjectMap2.entrySet() .stream() .map(entryx -> "\t#" + (String)entryx.getKey() + "-> #" + ((TextureSlots.Reference)entryx.getValue()).target + "\n") .collect(Collectors.joining()) ); } return new TextureSlots(object2ObjectMap); } } } } @Environment(EnvType.CLIENT) public sealed interface SlotContents permits TextureSlots.Value, TextureSlots.Reference { } @Environment(EnvType.CLIENT) record Value(Material material) implements TextureSlots.SlotContents { } }