This gist represents a skeleton structure for overriding Items depending on their ModelTransformationMode. All code is free to use, with no attribution needed.
1.21.* includes functionality similar to this natively
| // Not required, but is very helpful. | |
| @Environment(EnvType.CLIENT) | |
| public class BooleanModelOverride implements ClampedModelPredicateProvider { | |
| public static ModelTransformationMode currentModelTransform = ModelTransformationMode.NONE; | |
| public static int offset = 1; | |
| int thisOffset = 0; | |
| public BooleanModelOverride(Function4<ItemStack, ClientWorld, LivingEntity, Integer, Boolean> p) { | |
| this.predicate = p; | |
| this.thisOffset = offset++; | |
| } | |
| public float execute(ItemStack s, ClientWorld w, LivingEntity e, int seed) { | |
| if (predicate == null) {return 0;} | |
| return predicate.apply(s, w, e, seed) ? 0.07991f * thisOffset : 0f; | |
| } | |
| public static boolean isRenderingInGUI(ItemStack stack) { | |
| return currentModelTransform == ModelTransformationMode.GUI && !isRenderingInHandAny(stack); | |
| } | |
| public static boolean isRenderingInHandFirst(ItemStack stack) { | |
| return currentModelTransform.isFirstPerson(); | |
| } | |
| public static boolean isRenderingInHandThird(ItemStack stack) { | |
| return currentModelTransform == ModelTransformationMode.THIRD_PERSON_LEFT_HAND || currentModelTransform == ModelTransformationMode.THIRD_PERSON_RIGHT_HAND; | |
| } | |
| public static boolean isRenderingInHandAny(ItemStack stack) { | |
| return isRenderingInHandFirst(stack) || isRenderingInHandThird(stack); | |
| } | |
| public static boolean isRenderingInFixedPos(ItemStack stack) { | |
| return currentModelTransform == ModelTransformationMode.FIXED; | |
| } | |
| public static boolean isRenderingAsDropped(ItemStack stack) { | |
| return currentModelTransform == ModelTransformationMode.GROUND; | |
| } | |
| public final Function4<ItemStack, ClientWorld, LivingEntity, Integer, Boolean> predicate; | |
| @Override | |
| public float unclampedCall(ItemStack stack, @Nullable ClientWorld world, @Nullable LivingEntity entity, int seed) { | |
| return execute(stack, world, entity, seed); | |
| } | |
| } |
| @Mixin(ItemRenderer.class) | |
| public abstract class ItemRendererMixin { | |
| @ModifyArg(method="renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", | |
| at=@At(value="INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderBakedItemModel(Lnet/minecraft/client/render/model/BakedModel;Lnet/minecraft/item/ItemStack;IILnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;)V")) | |
| private BakedModel injectedModel(BakedModel model, @Local ItemStack stack, @Local MatrixStack matrixStack, @Local ModelTransformationMode mode, @Local VertexConsumerProvider consumerProvider) { | |
| var m = model.getOverrides().apply(model,stack,MinecraftClient.getInstance().world, null,42); | |
| return m; | |
| } | |
| @Inject(at=@At("HEAD"), method= "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V") | |
| public void renderItem(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci, @Share("model") LocalRef<BakedModel> modelRef) { | |
| BooleanModelOverride.currentModelTransform = renderMode; | |
| } | |
| } |
| @Environment(EnvType.CLIENT) | |
| @Mixin(targets = "net.minecraft.client.render.model.json.ModelOverride$Deserializer") | |
| public class ModelOverrideDeserializerMixin { | |
| /** | |
| * Calculate a 64 bits hash by combining CRC32 with Adler32. | |
| * | |
| * @param bytes a byte array | |
| * @return a hash number | |
| */ | |
| public static long getHash64(byte[] bytes) { | |
| CRC32 crc32 = new CRC32(); | |
| Adler32 adl32 = new Adler32(); | |
| crc32.update(bytes); | |
| adl32.update(bytes); | |
| long crc = crc32.getValue(); | |
| long adl = adl32.getValue(); | |
| return ((crc << 32) | adl) + (crc << 8); | |
| } | |
| public static long getHash64(String s) { | |
| return getHash64(s.getBytes(StandardCharsets.UTF_8)); | |
| } | |
| // Unfortunately, I need to do this to allow different types. Sorry! | |
| @Inject(method="deserializeMinPropertyValues", at=@At("HEAD"), cancellable = true) | |
| private void onDeserializeMinPropertyValues(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir) { List<ModelOverride.Condition> conds = new ArrayList<>(); | |
| JsonObject jsonObject = JsonHelper.getObject(object, "predicate"); | |
| for (Map.Entry<String, JsonElement> stringJsonElementEntry : jsonObject.entrySet()) { | |
| if (JsonHelper.isBoolean(stringJsonElementEntry.getValue())) { | |
| boolean s = JsonHelper.asBoolean(stringJsonElementEntry.getValue(), stringJsonElementEntry.getKey()); | |
| float v = s ? 0.07991f : 0; | |
| conds.add(new ModelOverride.Condition(new Identifier(stringJsonElementEntry.getKey()), v)); | |
| continue; | |
| } else if (JsonHelper.isString(stringJsonElementEntry.getValue())) { | |
| float v = getHash64(JsonHelper.asString(stringJsonElementEntry.getValue(), stringJsonElementEntry.getKey())); | |
| conds.add(new ModelOverride.Condition(new Identifier(stringJsonElementEntry.getKey()), v)); | |
| continue; | |
| } | |
| conds.add(new ModelOverride.Condition(new Identifier(stringJsonElementEntry.getKey()), JsonHelper.asFloat(stringJsonElementEntry.getValue(), stringJsonElementEntry.getKey()))); | |
| i++; | |
| } | |
| cir.setReturnValue (conds); | |
| } | |
| } |
| public void onInitializeClient() { | |
| ... | |
| ModelPredicateProviderRegistry.register(new Identifier("is_hand_first"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandFirst(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_hand_third"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandThird(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_hand_any"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandAny(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_hand_first"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandFirst(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_hand_third"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandThird(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_hand_any"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInHandAny(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_gui"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInGUI(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_inventory"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInGUI(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_fixed"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInFixedPos(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_gui"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInGUI(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_inventory"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInGUI(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_fixed"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInFixedPos(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_in_item_frame"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingInFixedPos(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_dropped"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingAsDropped(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_ground"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingAsDropped(itemStack))); | |
| ModelPredicateProviderRegistry.register(new Identifier("is_on_ground"), new BooleanModelOverride((itemStack, clientWorld, livingEntity, integer) -> BooleanModelOverride.isRenderingAsDropped(itemStack))); | |
| } |
| { | |
| "credit": "Made with Blockbench", | |
| "parent": "namespace:item/my_item", | |
| "gui_light": "front", | |
| "overrides": [ | |
| {"predicate": {"is_gui": false, "is_hand_any": true, "swing_time": 0, "use_time": 0}, "model": "namespace:item/in_hand"}, | |
| {"predicate": {"is_gui": false, "is_hand_any": true, "use_time": 0.001}, "model": "namespace:item/in_hand_using"}, | |
| {"predicate": {"is_gui": false, "is_hand_any": true, "swing_time": 0.001, "use_time": 0}, "model": "namespace:item/in_hand_swinging"} | |
| ] | |
| } |