diff --git a/fabric/src/main/java/com/noxcrew/noxesium/feature/model/CustomItemOverrides.java b/fabric/src/main/java/com/noxcrew/noxesium/feature/model/CustomItemOverrides.java index b2148660..2fa184af 100644 --- a/fabric/src/main/java/com/noxcrew/noxesium/feature/model/CustomItemOverrides.java +++ b/fabric/src/main/java/com/noxcrew/noxesium/feature/model/CustomItemOverrides.java @@ -11,18 +11,22 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemStack; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; /** * A complete re-implementation of the item overrides system. We do diverge slightly from vanilla behaviour because - * it doesn't really make sense. Vanilla matches the first entry where the value is at or below the target value. We only - * match the custom model data exactly the first time, and then do it like vanilla. + * it doesn't really make sense. Vanilla's algorithm goes as follows: + * - Start iterating over all overrides bottom up + * - Find the first override that is exceeded by the values, e.g. if the bottom model has custom model data 5, an item + * with custom model data 6 will match that override. Even if another override exists for model data 6. + *

+ * Instead, this patch uses a hashmap for custom model data, if and only if, such a hashmap will not cause any differences + * to vanilla behaviour. */ public class CustomItemOverrides extends ItemOverrides { @@ -40,10 +44,26 @@ public class CustomItemOverrides extends ItemOverrides { private List overrides; /** - * All models that are directly determined by a single custom model data. + * All models that are directly determined by a single custom model data in the correct order. */ private Map customModelDatas; + /** + * All optional model ranges. + */ + private List> optionalRanges; + + /** + * The lowest valid custom model data integer. No new values below this are entered as any such + * values would use a different custom model. + */ + private Integer lowestCustomModelData; + + /** + * The highest valid custom model data integer. Any values above this use this value. + */ + private Integer highestCustomModelData; + public CustomItemOverrides() { super(); } @@ -72,12 +92,13 @@ public CustomItemOverrides(ModelBaker modelBaker, BlockModel blockModel, List(); for (var i = list.size() - 1; i >= 0; --i) { var override = list.get(i); - // Micro-optmization: cache the model in case it's used multiple times per model, we ditch this hashmap after - // this constructor anyway and it only stores references. But if we had to make the model multiple times it'd be + // Micro-optimization: cache the model in case it's used multiple times per model, we ditch this hashmap after + // this constructor anyway, and it only stores references. But if we had to make the model multiple times it'd be // much more costly! var bakedmodel = modelCache.computeIfAbsent(override.getModel(), (t) -> bakeModel(modelBaker, blockModel, override)); var properties = override.getPredicates().map((predicate) -> { @@ -93,12 +114,20 @@ public CustomItemOverrides(ModelBaker modelBaker, BlockModel blockModel, List highestCustomModelData) { + highestCustomModelData = value; + } } - continue; + } else { + // Indicate that there is a non-custom model data override! + canOptimize = false; } // Add this model to the overrides list @@ -107,12 +136,39 @@ public CustomItemOverrides(ModelBaker modelBaker, BlockModel blockModel, List lastPair = null; + var copy = new ArrayList<>(new HashMap<>(customModelDatas).entrySet()); + copy.sort(Map.Entry.comparingByKey()); + for (var pair : copy) { + if (lastPair != null) { + // Go over each index between these two pairs + var distance = (pair.getKey() - 1) - (lastPair.getKey() + 1); + if (distance > 0) { + if (optionalRanges == null) { + optionalRanges = new ArrayList<>(); + } + optionalRanges.add(Triple.of(lastPair.getKey() + 1, pair.getKey() - 1, lastPair.getValue())); + } + } + lastPair = pair; + } + } + } } @Nullable @Override public BakedModel resolve(BakedModel fallback, ItemStack itemStack, @Nullable ClientLevel clientLevel, @Nullable LivingEntity livingEntity, int i) { - // Try test individual overrides first, these do not include any sole custom models + // Try to test individual overrides if possible if (overrides != null) { var item = itemStack.getItem(); @@ -136,10 +192,7 @@ public BakedModel resolve(BakedModel fallback, ItemStack itemStack, @Nullable Cl return bakedmodel == null ? fallback : bakedmodel; } } - } - - // Try test sole custom model data - if (customModelDatas != null) { + } else if (customModelDatas != null) { // Determine the custom model of the item as fast as possible (this code is called a lot per tick if there's many models) var customTag = itemStack.getTag(); if (customTag == null) return fallback; @@ -147,7 +200,32 @@ public BakedModel resolve(BakedModel fallback, ItemStack itemStack, @Nullable Cl if (customModelData == null || customModelData.getId() > 6) return fallback; var numericTag = (NumericTag) customModelData; var id = numericTag.getAsInt(); - return customModelDatas.getOrDefault(id, fallback); + + // Snap to the highest valid id + if (highestCustomModelData != null && id > highestCustomModelData) { + id = highestCustomModelData; + } + + // If the id is below the minimum we will never find a hit and + // we simply return the fallback + if (lowestCustomModelData != null && id < lowestCustomModelData) { + return fallback; + } + + // Try to get the exact model from the cache + var model = customModelDatas.get(id); + if (model != null) { + return model; + } + + if (optionalRanges != null) { + // We fall somewhere in between, let's assess the ranges + for (var triple : optionalRanges) { + if (id >= triple.getLeft() && id <= triple.getMiddle()) { + return triple.getRight(); + } + } + } } return fallback; } diff --git a/gradle.properties b/gradle.properties index 25e56670..1884844c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ loader_version=0.14.22 #Fabric api fabric_version=0.89.2+1.20.2 # Mod Properties -mod_version=1.0.4 +mod_version=1.0.5 # Mod dependencies sodium = mc1.20.2-0.5.3