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"} | |
] | |
} |