package net.minecraft.client.renderer.item; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Optional; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperties; import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty; import net.minecraft.client.resources.model.ResolvableModel; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class RangeSelectItemModel implements ItemModel { private static final int LINEAR_SEARCH_THRESHOLD = 16; private final RangeSelectItemModelProperty property; private final float scale; private final float[] thresholds; private final ItemModel[] models; private final ItemModel fallback; RangeSelectItemModel(RangeSelectItemModelProperty property, float scale, float[] thresholds, ItemModel[] models, ItemModel fallback) { this.property = property; this.thresholds = thresholds; this.models = models; this.fallback = fallback; this.scale = scale; } private static int lastIndexLessOrEqual(float[] thresholds, float value) { if (thresholds.length < 16) { for (int i = 0; i < thresholds.length; i++) { if (thresholds[i] > value) { return i - 1; } } return thresholds.length - 1; } else { int ix = Arrays.binarySearch(thresholds, value); if (ix < 0) { int j = ~ix; return j - 1; } else { return ix; } } } @Override public void update( ItemStackRenderState renderState, ItemStack stack, ItemModelResolver itemModelResolver, ItemDisplayContext displayContext, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed ) { float f = this.property.get(stack, level, entity, seed) * this.scale; ItemModel itemModel; if (Float.isNaN(f)) { itemModel = this.fallback; } else { int i = lastIndexLessOrEqual(this.thresholds, f); itemModel = i == -1 ? this.fallback : this.models[i]; } itemModel.update(renderState, stack, itemModelResolver, displayContext, level, entity, seed); } @Environment(EnvType.CLIENT) public record Entry(float threshold, ItemModel.Unbaked model) { public static final Codec CODEC = RecordCodecBuilder.create( instance -> instance.group( Codec.FLOAT.fieldOf("threshold").forGetter(RangeSelectItemModel.Entry::threshold), ItemModels.CODEC.fieldOf("model").forGetter(RangeSelectItemModel.Entry::model) ) .apply(instance, RangeSelectItemModel.Entry::new) ); public static final Comparator BY_THRESHOLD = Comparator.comparingDouble(RangeSelectItemModel.Entry::threshold); } @Environment(EnvType.CLIENT) public record Unbaked(RangeSelectItemModelProperty property, float scale, List entries, Optional fallback) implements ItemModel.Unbaked { public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( RangeSelectItemModelProperties.MAP_CODEC.forGetter(RangeSelectItemModel.Unbaked::property), Codec.FLOAT.optionalFieldOf("scale", 1.0F).forGetter(RangeSelectItemModel.Unbaked::scale), RangeSelectItemModel.Entry.CODEC.listOf().fieldOf("entries").forGetter(RangeSelectItemModel.Unbaked::entries), ItemModels.CODEC.optionalFieldOf("fallback").forGetter(RangeSelectItemModel.Unbaked::fallback) ) .apply(instance, RangeSelectItemModel.Unbaked::new) ); @Override public MapCodec type() { return MAP_CODEC; } @Override public ItemModel bake(ItemModel.BakingContext context) { float[] fs = new float[this.entries.size()]; ItemModel[] itemModels = new ItemModel[this.entries.size()]; List list = new ArrayList(this.entries); list.sort(RangeSelectItemModel.Entry.BY_THRESHOLD); for (int i = 0; i < list.size(); i++) { RangeSelectItemModel.Entry entry = (RangeSelectItemModel.Entry)list.get(i); fs[i] = entry.threshold; itemModels[i] = entry.model.bake(context); } ItemModel itemModel = (ItemModel)this.fallback.map(unbaked -> unbaked.bake(context)).orElse(context.missingItemModel()); return new RangeSelectItemModel(this.property, this.scale, fs, itemModels, itemModel); } @Override public void resolveDependencies(ResolvableModel.Resolver resolver) { this.fallback.ifPresent(unbaked -> unbaked.resolveDependencies(resolver)); this.entries.forEach(entry -> entry.model.resolveDependencies(resolver)); } } }