From 22f0cc59a2d3bc7900764e3916c670075ff9d35e Mon Sep 17 00:00:00 2001
From: Linnea Gräf <nea@nea.moe>
Date: Sun, 3 Nov 2024 01:24:24 +0100
Subject: 1.21.3 WIP

---
 .../firmament/mixins/CustomDurabilityBarPatch.java |  6 +-
 .../firmament/mixins/CustomModelBakerPatch.java    | 49 ---------------
 .../firmament/mixins/CustomModelEventPatch.java    | 18 +++---
 .../firmament/mixins/CustomSkullTexturePatch.java  |  9 ++-
 .../nea/firmament/mixins/DFUEntityIdFixPatch.java  |  1 +
 .../mixins/InjectCustomShaderPrograms.java         | 31 ----------
 .../firmament/mixins/MainWindowFirstLoadPatch.java | 31 ++++++++++
 .../nea/firmament/mixins/MixinHandledScreen.java   | 22 +++----
 .../mixins/ReplaceTextColorInHandledScreen.java    |  9 ++-
 .../nea/firmament/mixins/SlotUpdateListener.java   |  8 +--
 .../nea/firmament/mixins/WorldReadyEventPatch.java | 11 ++--
 .../mixins/WorldRenderLastEventPatch.java          | 46 +++++++++------
 .../mixins/accessor/AccessorGameRenderer.java      | 14 -----
 .../mixins/customgui/PatchHandledScreen.java       | 24 ++------
 .../custommodels/ApplyHeadModelInItemRenderer.java | 40 +++++++------
 .../custommodels/GlobalModelOverridePatch.java     | 18 +++---
 .../custommodels/HeadModelReplacerPatch.java       | 57 ++++++++++++++++++
 .../custommodels/ItemRendererTintContextPatch.java | 22 +++----
 .../custommodels/JsonUnbakedModelDataHolder.java   | 69 ++++++++++++++++------
 .../mixins/custommodels/PatchArmorTexture.java     | 28 ++++-----
 .../custommodels/PatchHeadFeatureRenderer.java     | 45 --------------
 .../custommodels/PatchLegacyArmorLayerSupport.java | 22 +++++++
 .../custommodels/PatchOverrideDeserializer.java    | 46 +++++++--------
 .../ProvideBakerToJsonUnbakedModelPatch.java       | 27 +++++++++
 .../custommodels/ReferenceCustomModelsPatch.java   | 37 ++++++++++++
 .../TestForFirmamentOverridePredicatesPatch.java   | 56 +++++++++---------
 26 files changed, 411 insertions(+), 335 deletions(-)
 delete mode 100644 src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
 delete mode 100644 src/main/java/moe/nea/firmament/mixins/InjectCustomShaderPrograms.java
 create mode 100644 src/main/java/moe/nea/firmament/mixins/MainWindowFirstLoadPatch.java
 delete mode 100644 src/main/java/moe/nea/firmament/mixins/accessor/AccessorGameRenderer.java
 create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java
 delete mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/PatchHeadFeatureRenderer.java
 create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
 create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java
 create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java

(limited to 'src/main/java/moe')

diff --git a/src/main/java/moe/nea/firmament/mixins/CustomDurabilityBarPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomDurabilityBarPatch.java
index 0f4d324..fde3580 100644
--- a/src/main/java/moe/nea/firmament/mixins/CustomDurabilityBarPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/CustomDurabilityBarPatch.java
@@ -14,7 +14,7 @@ import org.spongepowered.asm.mixin.injection.At;
 @Mixin(DrawContext.class)
 public class CustomDurabilityBarPatch {
     @WrapOperation(
-        method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
+        method = "drawItemBar",
         at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;isItemBarVisible()Z")
     )
     private boolean onIsItemBarVisible(
@@ -29,7 +29,7 @@ public class CustomDurabilityBarPatch {
         return barOverride.get() != null;
     }
 
-    @WrapOperation(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
+    @WrapOperation(method = "drawItemBar",
         at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItemBarStep()I"))
     private int overrideItemStep(
         ItemStack instance, Operation<Integer> original,
@@ -40,7 +40,7 @@ public class CustomDurabilityBarPatch {
         return original.call(instance);
     }
 
-    @WrapOperation(method = "drawItemInSlot(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V",
+    @WrapOperation(method = "drawItemBar",
         at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItemBarColor()I"))
     private int overrideItemColor(
         ItemStack instance, Operation<Integer> original,
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
deleted file mode 100644
index c1e359d..0000000
--- a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package moe.nea.firmament.mixins;
-
-import moe.nea.firmament.events.BakeExtraModelsEvent;
-import net.minecraft.client.render.model.ModelLoader;
-import net.minecraft.client.render.model.UnbakedModel;
-import net.minecraft.client.util.ModelIdentifier;
-import net.minecraft.util.Identifier;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import java.util.Map;
-
-@Mixin(ModelLoader.class)
-public abstract class CustomModelBakerPatch {
-
-    @Shadow
-    @Final
-    private Map<ModelIdentifier, UnbakedModel> modelsToBake;
-
-    @Shadow
-    protected abstract void loadItemModel(ModelIdentifier id);
-
-    @Shadow
-    abstract UnbakedModel getOrLoadModel(Identifier id);
-
-    @Shadow
-    protected abstract void add(ModelIdentifier id, UnbakedModel model);
-
-    @Unique
-    private void loadNonItemModel(ModelIdentifier identifier) {
-        UnbakedModel unbakedModel = this.getOrLoadModel(identifier.id());
-        this.add(identifier, unbakedModel);
-    }
-
-
-    @Inject(method = "bake", at = @At("HEAD"))
-    public void onBake(ModelLoader.SpriteGetter spliteGetter, CallbackInfo ci) {
-        BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::loadItemModel, this::loadNonItemModel));
-        modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel));
-//        modelsToBake.keySet().stream()
-//            .filter(it -> !it.id().getNamespace().equals("minecraft"))
-//            .forEach(it -> System.out.println("Non minecraft texture is being loaded: " + it));
-    }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java
index 61fc82e..e7207f4 100644
--- a/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/CustomModelEventPatch.java
@@ -7,6 +7,7 @@ import net.minecraft.client.render.item.ItemModels;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.BakedModelManager;
 import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
@@ -14,16 +15,15 @@ import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
+import java.util.Map;
+
 @Mixin(ItemModels.class)
 public class CustomModelEventPatch {
-    @Shadow
-    @Final
-    private BakedModelManager modelManager;
 
-    @Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
-    public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
-        var model = CustomItemModelEvent.getModel(stack, modelManager);
-        if (model != null)
-            cir.setReturnValue(model);
-    }
+	@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
+	public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
+		var model = CustomItemModelEvent.getModel(stack, (ItemModels) (Object) this);
+		if (model != null)
+			cir.setReturnValue(model);
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java b/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java
index 4b3f3c3..f3b616a 100644
--- a/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/CustomSkullTexturePatch.java
@@ -2,7 +2,6 @@
 
 package moe.nea.firmament.mixins;
 
-import com.mojang.authlib.GameProfile;
 import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
 import net.minecraft.block.SkullBlock;
 import net.minecraft.client.render.RenderLayer;
@@ -15,8 +14,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 @Mixin(SkullBlockEntityRenderer.class)
 public class CustomSkullTexturePatch {
-    @Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
-    private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
-        CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
-    }
+	@Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
+	private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
+		CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/DFUEntityIdFixPatch.java b/src/main/java/moe/nea/firmament/mixins/DFUEntityIdFixPatch.java
index da04ca2..717d404 100644
--- a/src/main/java/moe/nea/firmament/mixins/DFUEntityIdFixPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/DFUEntityIdFixPatch.java
@@ -17,6 +17,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 
 import java.util.Map;
 
+// TODO: rework this
 @Mixin(EntityIdFix.class)
 public abstract class DFUEntityIdFixPatch extends DataFix {
     @Shadow
diff --git a/src/main/java/moe/nea/firmament/mixins/InjectCustomShaderPrograms.java b/src/main/java/moe/nea/firmament/mixins/InjectCustomShaderPrograms.java
deleted file mode 100644
index 5306e42..0000000
--- a/src/main/java/moe/nea/firmament/mixins/InjectCustomShaderPrograms.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package moe.nea.firmament.mixins;
-
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import com.llamalad7.mixinextras.sugar.Local;
-import com.mojang.datafixers.util.Pair;
-import moe.nea.firmament.events.RegisterCustomShadersEvent;
-import net.minecraft.client.gl.ShaderProgram;
-import net.minecraft.client.render.GameRenderer;
-import net.minecraft.resource.ResourceFactory;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-import java.util.List;
-import java.util.function.Consumer;
-
-@Mixin(GameRenderer.class)
-public class InjectCustomShaderPrograms {
-
-    @Inject(method = "loadPrograms",
-        at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/GameRenderer;loadBlurPostProcessor(Lnet/minecraft/resource/ResourceFactory;)V",
-            shift = At.Shift.AFTER))
-    void addFirmamentShaders(
-        ResourceFactory resourceFactory, CallbackInfo ci,
-        @Local(index = 3) List<Pair<ShaderProgram, Consumer<ShaderProgram>>> list
-    ) {
-        var event = new RegisterCustomShadersEvent(list, resourceFactory);
-        RegisterCustomShadersEvent.Companion.publish(event);
-    }
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/MainWindowFirstLoadPatch.java b/src/main/java/moe/nea/firmament/mixins/MainWindowFirstLoadPatch.java
new file mode 100644
index 0000000..0a90b35
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/MainWindowFirstLoadPatch.java
@@ -0,0 +1,31 @@
+package moe.nea.firmament.mixins;
+
+import moe.nea.firmament.Firmament;
+import moe.nea.firmament.events.DebugInstantiateEvent;
+import net.minecraft.client.gui.LogoDrawer;
+import net.minecraft.client.gui.screen.TitleScreen;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(TitleScreen.class)
+public class MainWindowFirstLoadPatch {
+	@Unique
+	private static boolean hasInited = false;
+
+	@Inject(method = "<init>(ZLnet/minecraft/client/gui/LogoDrawer;)V", at = @At("RETURN"))
+	private void onCreate(boolean doBackgroundFade, LogoDrawer logoDrawer, CallbackInfo ci) {
+		if (!hasInited) {
+			try {
+				DebugInstantiateEvent.Companion.publish(new DebugInstantiateEvent());
+			} catch (Throwable t) {
+				Firmament.INSTANCE.getLogger().error("Failed to instantiate debug instances", t);
+				System.exit(1);
+				throw t;
+			}
+		}
+		hasInited = true;
+	}
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
index fd50c72..1034a12 100644
--- a/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/MixinHandledScreen.java
@@ -2,6 +2,9 @@
 
 package moe.nea.firmament.mixins;
 
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
 import moe.nea.firmament.events.*;
 import net.minecraft.client.gui.DrawContext;
 import net.minecraft.client.gui.screen.ingame.HandledScreen;
@@ -21,6 +24,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
+import java.util.Iterator;
+
 @Mixin(HandledScreen.class)
 public abstract class MixinHandledScreen<T extends ScreenHandler> {
 
@@ -90,15 +95,12 @@ public abstract class MixinHandledScreen<T extends ScreenHandler> {
 	}
 
 
-	@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILHARD)
-	public void onAfterDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) {
-		SlotRenderEvents.After event = new SlotRenderEvents.After(context, slot, mouseX, mouseY, delta);
-		SlotRenderEvents.After.Companion.publish(event);
-	}
-
-	@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V", shift = At.Shift.BEFORE), locals = LocalCapture.CAPTURE_FAILHARD)
-	public void onBeforeDrawSlot(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci, int i, int j, int k, Slot slot) {
-		SlotRenderEvents.Before event = new SlotRenderEvents.Before(context, slot, mouseX, mouseY, delta);
-		SlotRenderEvents.Before.Companion.publish(event);
+	@WrapOperation(method = "drawSlots", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V"))
+	public void onDrawSlots(HandledScreen instance, DrawContext context, Slot slot, Operation<Void> original) {
+		var before = new SlotRenderEvents.Before(context, slot);
+		SlotRenderEvents.Before.Companion.publish(before);
+		original.call(instance, context, slot);
+		var after = new SlotRenderEvents.After(context, slot);
+		SlotRenderEvents.After.Companion.publish(after);
 	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java
index 190fda0..c9fb073 100644
--- a/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/ReplaceTextColorInHandledScreen.java
@@ -20,12 +20,16 @@ import org.spongepowered.asm.mixin.injection.At;
 	AnvilScreen.class, BeaconScreen.class})
 public class ReplaceTextColorInHandledScreen {
 
+	// To my future self: double check those mixins, but don't be too concerned about errors. Some of the wrapopertions
+	// only apply in some of the specified subclasses.
+
 	@WrapOperation(
 		method = "drawForeground",
 		at = @At(
 			value = "INVOKE",
 			target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
-		expect = 0)
+		expect = 0,
+		require = 0)
 	private int replaceTextColorWithVariableShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
 		return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color), shadow);
 	}
@@ -35,7 +39,8 @@ public class ReplaceTextColorInHandledScreen {
 		at = @At(
 			value = "INVOKE",
 			target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"),
-		expect = 0)
+		expect = 0,
+		require = 0)
 	private int replaceTextColorWithShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, Operation<Integer> original) {
 		return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color));
 	}
diff --git a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java
index 6c854d4..06ecbd4 100644
--- a/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java
+++ b/src/main/java/moe/nea/firmament/mixins/SlotUpdateListener.java
@@ -23,14 +23,13 @@ public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
 
 	@Inject(
 		method = "onScreenHandlerSlotUpdate",
-		at = @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/TutorialManager;onSlotUpdate(Lnet/minecraft/item/ItemStack;)V"))
+		at = @At(value = "TAIL"))
 	private void onSingleSlotUpdate(
 		ScreenHandlerSlotUpdateS2CPacket packet,
 		CallbackInfo ci) {
 		var player = this.client.player;
 		assert player != null;
-		if (packet.getSyncId() == ScreenHandlerSlotUpdateS2CPacket.UPDATE_PLAYER_INVENTORY_SYNC_ID
-			|| packet.getSyncId() == 0) {
+		if (packet.getSyncId() == 0) {
 			PlayerInventoryUpdate.Companion.publish(new PlayerInventoryUpdate.Single(packet.getSlot(), packet.getStack()));
 		} else if (packet.getSyncId() == player.currentScreenHandler.syncId) {
 			ChestInventoryUpdateEvent.Companion.publish(
@@ -40,8 +39,7 @@ public abstract class SlotUpdateListener extends ClientCommonNetworkHandler {
 	}
 
 	@Inject(method = "onInventory",
-		at = @At(value = "INVOKE", target = "Lnet/minecraft/network/NetworkThreadUtils;forceMainThread(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;Lnet/minecraft/util/thread/ThreadExecutor;)V",
-			shift = At.Shift.AFTER))
+		at = @At("TAIL"))
 	private void onMultiSlotUpdate(InventoryS2CPacket packet, CallbackInfo ci) {
 		var player = this.client.player;
 		assert player != null;
diff --git a/src/main/java/moe/nea/firmament/mixins/WorldReadyEventPatch.java b/src/main/java/moe/nea/firmament/mixins/WorldReadyEventPatch.java
index c444f12..d4b8c9e 100644
--- a/src/main/java/moe/nea/firmament/mixins/WorldReadyEventPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/WorldReadyEventPatch.java
@@ -3,16 +3,17 @@
 package moe.nea.firmament.mixins;
 
 import moe.nea.firmament.events.WorldReadyEvent;
+import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.gui.screen.DownloadingTerrainScreen;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-@Mixin(DownloadingTerrainScreen.class)
+@Mixin(MinecraftClient.class)
 public class WorldReadyEventPatch {
-    @Inject(method = "close", at = @At("HEAD"))
-    public void onClose(CallbackInfo ci) {
-        WorldReadyEvent.Companion.publish(new WorldReadyEvent());
-    }
+	@Inject(method = "joinWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;setWorld(Lnet/minecraft/client/world/ClientWorld;)V", shift = At.Shift.AFTER))
+	public void onClose(CallbackInfo ci) {
+		WorldReadyEvent.Companion.publish(new WorldReadyEvent());
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java b/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java
index 2ff4560..847fb4d 100644
--- a/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/WorldRenderLastEventPatch.java
@@ -5,8 +5,10 @@ package moe.nea.firmament.mixins;
 import com.llamalad7.mixinextras.sugar.Local;
 import moe.nea.firmament.events.WorldRenderLastEvent;
 import net.minecraft.client.render.*;
+import net.minecraft.client.util.Handle;
+import net.minecraft.client.util.ObjectAllocator;
 import net.minecraft.client.util.math.MatrixStack;
-import net.minecraft.world.tick.TickManager;
+import net.minecraft.util.profiler.Profiler;
 import org.joml.Matrix4f;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
@@ -16,22 +18,30 @@ import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(WorldRenderer.class)
-public class WorldRenderLastEventPatch {
-    @Shadow
-    @Final
-    private BufferBuilderStorage bufferBuilders;
+public abstract class WorldRenderLastEventPatch {
+	@Shadow
+	@Final
+	private BufferBuilderStorage bufferBuilders;
 
-    @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/WorldRenderer;renderChunkDebugInfo(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/client/render/Camera;)V", shift = At.Shift.BEFORE))
-    public void onWorldRenderLast(
-        RenderTickCounter tickCounter, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer,
-        LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, Matrix4f matrix4f2,
-        CallbackInfo ci, @Local MatrixStack matrixStack
-    ) {
-        var event = new WorldRenderLastEvent(
-            matrixStack, tickCounter, renderBlockOutline,
-            camera, gameRenderer, lightmapTextureManager,
-            this.bufferBuilders.getEntityVertexConsumers()
-        );
-        WorldRenderLastEvent.Companion.publish(event);
-    }
+	@Shadow
+	@Final
+	private DefaultFramebufferSet framebufferSet;
+
+	@Shadow
+	protected abstract void checkEmpty(MatrixStack matrices);
+
+	@Inject(method = "method_62214", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiler/Profiler;pop()V", shift = At.Shift.AFTER))
+	public void onWorldRenderLast(Fog fog, RenderTickCounter tickCounter, Camera camera, Profiler profiler, Matrix4f matrix4f, Matrix4f matrix4f2, Handle handle, Handle handle2, Handle handle3, Handle handle4, boolean bl, Frustum frustum, Handle handle5, CallbackInfo ci) {
+		var imm = this.bufferBuilders.getEntityVertexConsumers();
+		var stack = new MatrixStack();
+		// TODO: pre-cancel this event if F1 is active
+		var event = new WorldRenderLastEvent(
+			stack, tickCounter,
+			camera,
+			imm
+		);
+		WorldRenderLastEvent.Companion.publish(event);
+		imm.draw();
+		checkEmpty(stack);
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorGameRenderer.java b/src/main/java/moe/nea/firmament/mixins/accessor/AccessorGameRenderer.java
deleted file mode 100644
index f5d2202..0000000
--- a/src/main/java/moe/nea/firmament/mixins/accessor/AccessorGameRenderer.java
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-package moe.nea.firmament.mixins.accessor;
-
-import net.minecraft.client.render.Camera;
-import net.minecraft.client.render.GameRenderer;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.gen.Invoker;
-
-@Mixin(GameRenderer.class)
-public interface AccessorGameRenderer {
-    @Invoker("getFov")
-    double getFov_firmament(Camera camera, float tickDelta, boolean changingFov);
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
index deac0a4..814f172 100644
--- a/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
+++ b/src/main/java/moe/nea/firmament/mixins/customgui/PatchHandledScreen.java
@@ -14,7 +14,6 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen;
 import net.minecraft.screen.ScreenHandler;
 import net.minecraft.screen.slot.Slot;
 import net.minecraft.text.Text;
-import net.minecraft.util.collection.DefaultedList;
 import org.jetbrains.annotations.Nullable;
 import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
@@ -88,29 +87,18 @@ public class PatchHandledScreen<T extends ScreenHandler> extends Screen implemen
 	}
 
 
-	@Unique
-	private Slot didBeforeSlotRender;
-
 	@WrapOperation(
-		method = "render",
+		method = "drawSlots",
 		at = @At(
 			value = "INVOKE",
-			target = "Lnet/minecraft/util/collection/DefaultedList;get(I)Ljava/lang/Object;"))
-	private Object beforeSlotRender(DefaultedList instance, int index, Operation<Object> original, @Local(argsOnly = true) DrawContext context) {
-		var slot = (Slot) original.call(instance, index);
+			target = "Lnet/minecraft/client/gui/screen/ingame/HandledScreen;drawSlot(Lnet/minecraft/client/gui/DrawContext;Lnet/minecraft/screen/slot/Slot;)V"))
+	private void beforeSlotRender(HandledScreen instance, DrawContext context, Slot slot, Operation<Void> original) {
 		if (override != null) {
-			didBeforeSlotRender = slot;
 			override.beforeSlotRender(context, slot);
 		}
-		return slot;
-	}
-
-	@Inject(method = "render",
-		at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/DefaultedList;size()I"))
-	private void afterSlotRender(DrawContext context, int mouseX, int mouseY, float delta, CallbackInfo ci) {
-		if (override != null && didBeforeSlotRender != null) {
-			override.afterSlotRender(context, didBeforeSlotRender);
-			didBeforeSlotRender = null;
+		original.call(instance, context, slot);
+		if (override != null) {
+			override.afterSlotRender(context, slot);
 		}
 	}
 
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
index 64b358f..dac65fe 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
@@ -1,33 +1,35 @@
 
 package moe.nea.firmament.mixins.custommodels;
 
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
 import com.llamalad7.mixinextras.sugar.Local;
+import com.llamalad7.mixinextras.sugar.ref.LocalRef;
 import moe.nea.firmament.features.texturepack.BakedModelExtra;
+import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.json.ModelTransformationMode;
-import net.minecraft.entity.LivingEntity;
+import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.item.ItemStack;
-import net.minecraft.world.World;
+import net.minecraft.item.ModelTransformationMode;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
 @Mixin(ItemRenderer.class)
 public class ApplyHeadModelInItemRenderer {
-    @WrapOperation(method = "renderItem(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;Lnet/minecraft/world/World;III)V",
-        at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;getModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)Lnet/minecraft/client/render/model/BakedModel;"))
-    private BakedModel applyHeadModel(ItemRenderer instance, ItemStack stack, World world, LivingEntity entity, int seed, Operation<BakedModel> original,
-                                      @Local(argsOnly = true) ModelTransformationMode modelTransformationMode) {
-        var model = original.call(instance, stack, world, entity, seed);
-        if (modelTransformationMode == ModelTransformationMode.HEAD
-            && model instanceof BakedModelExtra extra) {
-            var headModel = extra.getHeadModel_firmament();
-            if (headModel != null) {
-                model = headModel;
-            }
-        }
-        return model;
-    }
+	@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
+		at = @At("HEAD"))
+	private void applyHeadModel(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded,
+	                            MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay,
+	                            BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci,
+	                            @Local(argsOnly = true) LocalRef<BakedModel> modelMut
+	) {
+		var extra = BakedModelExtra.cast(model);
+		if (transformationMode == ModelTransformationMode.HEAD && extra != null) {
+			var headModel = extra.getHeadModel_firmament();
+			if (headModel != null) {
+				modelMut.set(headModel);
+			}
+		}
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java
index eee7557..c708862 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java
@@ -8,6 +8,7 @@ import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.entity.LivingEntity;
 import net.minecraft.item.ItemStack;
 import net.minecraft.world.World;
+import org.spongepowered.asm.mixin.Final;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
@@ -17,13 +18,14 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
 @Mixin(ItemRenderer.class)
 public abstract class GlobalModelOverridePatch {
 
-    @Shadow
-    public abstract ItemModels getModels();
+	@Shadow
+	@Final
+	private ItemModels models;
 
-    @Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
-    private void overrideGlobalModel(
-        ItemStack stack, World world, LivingEntity entity,
-        int seed, CallbackInfoReturnable<BakedModel> cir) {
-        CustomGlobalTextures.replaceGlobalModel(this.getModels(), stack, cir);
-    }
+	@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
+	private void overrideGlobalModel(
+		ItemStack stack, World world, LivingEntity entity,
+		int seed, CallbackInfoReturnable<BakedModel> cir) {
+		CustomGlobalTextures.replaceGlobalModel(this.models, stack, cir);
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java
new file mode 100644
index 0000000..26c331e
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/HeadModelReplacerPatch.java
@@ -0,0 +1,57 @@
+
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.BakedModelExtra;
+import net.minecraft.block.AbstractSkullBlock;
+import net.minecraft.block.Block;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.render.VertexConsumerProvider;
+import net.minecraft.client.render.entity.LivingEntityRenderer;
+import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
+import net.minecraft.client.render.entity.model.EntityModel;
+import net.minecraft.client.render.entity.model.ModelWithHead;
+import net.minecraft.client.render.entity.state.LivingEntityRenderState;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.entity.EquipmentSlot;
+import net.minecraft.item.BlockItem;
+import net.minecraft.item.ItemStack;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(HeadFeatureRenderer.class)
+public class HeadModelReplacerPatch<S extends LivingEntityRenderState, M extends EntityModel<S> & ModelWithHead> {
+	/**
+	 * This class serves to disable the replacing of head models with the vanilla block model. Vanilla first selects loads
+	 * the model containing the head model regularly in {@link LivingEntityRenderer#updateRenderState}, but then discards
+	 * the model in {@link HeadFeatureRenderer#render(MatrixStack, VertexConsumerProvider, int, LivingEntityRenderState, float, float)}
+	 * if it detects a skull block. This serves to disable that functionality if a head model override is present.
+	 */
+	@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
+		at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
+	private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local BakedModel bakedModel) {
+		var oldBlock = original.call(instance);
+		if (oldBlock instanceof AbstractSkullBlock) {
+			var extra = BakedModelExtra.cast(bakedModel);
+			if (extra != null && extra.getHeadModel_firmament() != null)
+				return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
+		}
+		return oldBlock;
+	}
+
+	/**
+	 * We disable the has model override, since texture packs get precedent to server data.
+	 */
+	@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
+		at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ArmorFeatureRenderer;hasModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/EquipmentSlot;)Z"))
+	private boolean replaceHasModel(ItemStack stack, EquipmentSlot slot, Operation<Boolean> original,
+	                                @Local BakedModel bakedModel) {
+		var extra = BakedModelExtra.cast(bakedModel);
+		if (extra != null && extra.getHeadModel_firmament() != null)
+			return false;
+		return original.call(stack, slot);
+	}
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java
index 5ed0fbe..8c5411b 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java
@@ -5,28 +5,30 @@ import moe.nea.firmament.features.texturepack.TintOverrides;
 import net.minecraft.client.render.VertexConsumerProvider;
 import net.minecraft.client.render.item.ItemRenderer;
 import net.minecraft.client.render.model.BakedModel;
-import net.minecraft.client.render.model.json.ModelTransformationMode;
 import net.minecraft.client.util.math.MatrixStack;
 import net.minecraft.item.ItemStack;
+import net.minecraft.item.ModelTransformationMode;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 import org.spongepowered.asm.mixin.injection.Inject;
 import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-@Mixin(ItemRenderer.class)
+@Mixin(value = ItemRenderer.class, priority = 1010)
 public class ItemRendererTintContextPatch {
-	@Inject(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/model/BakedModel;getTransformation()Lnet/minecraft/client/render/model/json/ModelTransformation;"), allow = 1)
-	private void onStartRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
-		if (model instanceof BakedModelExtra extra) {
+	@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
+		at = @At(value = "HEAD"), allow = 1)
+	private void onStartRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
+		var extra = BakedModelExtra.cast(model);
+		if (extra != null) {
 			TintOverrides.Companion.enter(extra.getTintOverrides_firmament());
 		}
 	}
 
-	@Inject(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/util/math/MatrixStack;pop()V"), allow = 1)
-	private void onEndRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) {
-		if (model instanceof BakedModelExtra extra) {
+	@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
+		at = @At("TAIL"), allow = 1)
+	private void onEndRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
+		var extra = BakedModelExtra.cast(model);
+		if (extra != null) {
 			TintOverrides.Companion.exit(extra.getTintOverrides_firmament());
 		}
 	}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java b/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java
index 20c69e2..a5bb34f 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java
@@ -1,11 +1,11 @@
 package moe.nea.firmament.mixins.custommodels;
 
-import com.google.gson.annotations.SerializedName;
 import com.llamalad7.mixinextras.injector.ModifyReturnValue;
 import com.llamalad7.mixinextras.sugar.Local;
 import moe.nea.firmament.features.texturepack.BakedModelExtra;
 import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
 import moe.nea.firmament.features.texturepack.TintOverrides;
+import moe.nea.firmament.util.ErrorUtil;
 import net.minecraft.client.render.model.BakedModel;
 import net.minecraft.client.render.model.Baker;
 import net.minecraft.client.render.model.ModelRotation;
@@ -18,15 +18,20 @@ import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.Unique;
 import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
 
-import java.util.Collection;
 import java.util.Objects;
 
 @Mixin(JsonUnbakedModel.class)
-public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
+public abstract class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
 	@Shadow
 	@Nullable
 	protected JsonUnbakedModel parent;
+
+	@Shadow
+	public abstract String toString();
+
 	@Unique
 	@Nullable
 	public Identifier headModel;
@@ -67,31 +72,59 @@ public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
 		return ((JsonUnbakedModelFirmExtra) this.parent).getHeadModel_firmament();
 	}
 
-	@ModifyReturnValue(method = "getModelDependencies", at = @At("RETURN"))
-	private Collection<Identifier> addDependencies(Collection<Identifier> original) {
+	@Inject(method = "resolve", at = @At("HEAD"))
+	private void addDependencies(UnbakedModel.Resolver resolver, CallbackInfo ci) {
 		var headModel = getHeadModel_firmament();
 		if (headModel != null) {
-			original.add(headModel);
+			resolver.resolve(headModel);
 		}
-		return original;
 	}
 
-	@ModifyReturnValue(
-		method = "bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
-		at = @At(value = "RETURN"))
-	private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
-		if (original instanceof BakedModelExtra extra) {
+	private void addExtraBakeInfo(BakedModel bakedModel, Baker baker) {
+		if (!this.toString().contains("minecraft") && this.toString().contains("crimson")) {
+			System.out.println("Found non minecraft model " + this);
+		}
+		var extra = BakedModelExtra.cast(bakedModel);
+		if (extra != null) {
 			var headModel = getHeadModel_firmament();
 			if (headModel != null) {
-				UnbakedModel unbakedModel = baker.getOrLoadModel(headModel);
-				extra.setHeadModel_firmament(
-					Objects.equals(unbakedModel, parent)
-						? null
-						: baker.bake(headModel, ModelRotation.X0_Y0));
+				extra.setHeadModel_firmament(baker.bake(headModel, ModelRotation.X0_Y0));
 			}
-			if (getTintOverrides_firmament().hasOverrides())
+			if (getTintOverrides_firmament().hasOverrides()) {
 				extra.setTintOverrides_firmament(getTintOverrides_firmament());
+			}
+		}
+	}
+
+	/**
+	 * @see ProvideBakerToJsonUnbakedModelPatch
+	 */
+	@Override
+	public void storeExtraBaker_firmament(@NotNull Baker baker) {
+		this.storedBaker = baker;
+	}
+
+	@Unique
+	private Baker storedBaker;
+
+	@ModifyReturnValue(
+		method = "bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
+		at = @At("RETURN"))
+	private BakedModel bakeExtraInfoWithoutBaker(BakedModel original) {
+		if (storedBaker != null) {
+			addExtraBakeInfo(original, storedBaker);
+			storedBaker = null;
 		}
 		return original;
 	}
+
+	@ModifyReturnValue(
+		method = {
+			"bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"
+		},
+		at = @At(value = "RETURN"))
+	private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
+		addExtraBakeInfo(original, baker);
+		return original;
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
index 7c6f69e..4468150 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
@@ -6,26 +6,24 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
 import com.llamalad7.mixinextras.sugar.Local;
 import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
 import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
-import net.minecraft.item.ArmorMaterial;
+import net.minecraft.component.type.EquippableComponent;
 import net.minecraft.item.ItemStack;
+import net.minecraft.util.Identifier;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.injection.At;
 
-import java.util.List;
+import java.util.Optional;
 
 @Mixin(ArmorFeatureRenderer.class)
 public class PatchArmorTexture {
-    @WrapOperation(
-        method = "renderArmor",
-        at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ArmorMaterial;layers()Ljava/util/List;"))
-    private List<ArmorMaterial.Layer> overrideLayers(
-        ArmorMaterial instance,
-        Operation<List<ArmorMaterial.Layer>> original,
-        @Local ItemStack itemStack
-    ) {
-        var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
-        if (overrides == null)
-            return original.call(instance);
-        return overrides;
-    }
+	@WrapOperation(
+		method = "renderArmor",
+		at = @At(value = "INVOKE", target = "Lnet/minecraft/component/type/EquippableComponent;model()Ljava/util/Optional;"))
+	private Optional<Identifier> overrideLayers(
+		EquippableComponent instance, Operation<Optional<Identifier>> original, @Local(argsOnly = true) ItemStack itemStack
+	) {
+		// TODO: check that all armour items are naturally equippable and have the equppable component. otherwise our call here will not be reached.
+		var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
+		return overrides.or(() -> original.call(instance));
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchHeadFeatureRenderer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchHeadFeatureRenderer.java
deleted file mode 100644
index 610a106..0000000
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchHeadFeatureRenderer.java
+++ /dev/null
@@ -1,45 +0,0 @@
-
-package moe.nea.firmament.mixins.custommodels;
-
-import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
-import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
-import com.llamalad7.mixinextras.sugar.Local;
-import moe.nea.firmament.features.texturepack.BakedModelExtra;
-import net.minecraft.block.AbstractSkullBlock;
-import net.minecraft.block.Block;
-import net.minecraft.block.Blocks;
-import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
-import net.minecraft.client.render.entity.model.EntityModel;
-import net.minecraft.client.render.item.HeldItemRenderer;
-import net.minecraft.entity.LivingEntity;
-import net.minecraft.item.BlockItem;
-import net.minecraft.item.ItemStack;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-
-@Mixin(HeadFeatureRenderer.class)
-public class PatchHeadFeatureRenderer<T extends LivingEntity, M extends EntityModel<T>> {
-
-    @Shadow
-    @Final
-    private HeldItemRenderer heldItemRenderer;
-
-    @WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/entity/LivingEntity;FFFFFF)V",
-        at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
-    private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local ItemStack itemStack, @Local(argsOnly = true) T entity) {
-        var oldBlock = original.call(instance);
-        if (oldBlock instanceof AbstractSkullBlock) {
-            var bakedModel = this.heldItemRenderer.itemRenderer
-                .getModel(itemStack, entity.getWorld(), entity, 0);
-            if (bakedModel instanceof BakedModelExtra extra && extra.getHeadModel_firmament() != null)
-                return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
-        }
-        return oldBlock;
-    }
-
-
-
-
-}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
new file mode 100644
index 0000000..8c0b3f8
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
@@ -0,0 +1,22 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
+import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
+import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
+import net.minecraft.item.equipment.EquipmentModel;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(EquipmentRenderer.class)
+public class PatchLegacyArmorLayerSupport {
+	@WrapOperation(method = "render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
+		at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/util/Identifier;)Lnet/minecraft/item/equipment/EquipmentModel;"))
+	private EquipmentModel patchModelLayers(EquipmentModelLoader instance, Identifier id, Operation<EquipmentModel> original) {
+		var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(id);
+		if (modelOverride != null) return modelOverride;
+		return original.call(instance, id);
+	}
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java
index 938d14d..abb1792 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchOverrideDeserializer.java
@@ -22,29 +22,29 @@ import java.util.Map;
 @Mixin(ModelOverride.Deserializer.class)
 public class PatchOverrideDeserializer {
 
-    @ModifyReturnValue(
-        method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
-        at = @At(value = "RETURN"))
-    private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
-        var originalData = (ModelOverrideData) original;
-        originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
-        return original;
-    }
+	@ModifyReturnValue(
+		method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
+		at = @At(value = "RETURN"))
+	private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
+		var originalData = (ModelOverrideData) (Object) original;
+		originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
+		return original;
+	}
 
-    @ModifyExpressionValue(
-        method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
-        at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
-    private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
-        if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
-        return original;
-    }
+	@ModifyExpressionValue(
+		method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
+		at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
+	private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
+		if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
+		return original;
+	}
 
-    @Inject(
-        method = "deserializeMinPropertyValues",
-        at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
-    )
-    private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
-                          @Local Map<Identifier, Float> maps) {
-        maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
-    }
+	@Inject(
+		method = "deserializeMinPropertyValues",
+		at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
+	)
+	private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
+	                      @Local Map<Identifier, Float> maps) {
+		maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
+	}
 }
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java
new file mode 100644
index 0000000..c1ac119
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ProvideBakerToJsonUnbakedModelPatch.java
@@ -0,0 +1,27 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
+import net.minecraft.client.render.model.BakedModel;
+import net.minecraft.client.render.model.Baker;
+import net.minecraft.client.render.model.ModelBakeSettings;
+import net.minecraft.client.render.model.json.JsonUnbakedModel;
+import net.minecraft.client.texture.Sprite;
+import net.minecraft.client.util.SpriteIdentifier;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.function.Function;
+
+/**
+ * @see JsonUnbakedModelDataHolder#storeExtraBaker_firmament
+ */
+@Mixin(targets = "net.minecraft.client.render.model.ModelBaker$BakerImpl")
+public abstract class ProvideBakerToJsonUnbakedModelPatch implements Baker {
+	@WrapOperation(method = "bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;"))
+	private BakedModel provideExtraBakerToModel(JsonUnbakedModel instance, Function<SpriteIdentifier, Sprite> function, ModelBakeSettings modelBakeSettings, boolean bl, Operation<BakedModel> original) {
+		((JsonUnbakedModelFirmExtra) instance).storeExtraBaker_firmament(this);
+		return original.call(instance, function, modelBakeSettings, bl);
+	}
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java
new file mode 100644
index 0000000..14c84ab
--- /dev/null
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java
@@ -0,0 +1,37 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import moe.nea.firmament.events.BakeExtraModelsEvent;
+import net.minecraft.client.render.model.BlockStatesLoader;
+import net.minecraft.client.render.model.ItemModel;
+import net.minecraft.client.render.model.ReferencedModelsCollector;
+import net.minecraft.client.render.model.UnbakedModel;
+import net.minecraft.client.util.ModelIdentifier;
+import net.minecraft.util.Identifier;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.Map;
+
+@Mixin(ReferencedModelsCollector.class)
+public abstract class ReferenceCustomModelsPatch {
+	@Shadow
+	protected abstract void addTopLevelModel(ModelIdentifier modelId, UnbakedModel model);
+
+	@Shadow
+	@Final
+	private Map<Identifier, UnbakedModel> inputs;
+
+	@Inject(method = "addBlockStates", at = @At("RETURN"))
+	private void addFirmamentReferencedModels(
+		BlockStatesLoader.BlockStateDefinition definition, CallbackInfo ci
+	) {
+		inputs.keySet().stream().filter(it->it.toString().contains("firm")).forEach(System.out::println);
+		BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(
+			(modelIdentifier, identifier) -> addTopLevelModel(modelIdentifier, new ItemModel(identifier))));
+
+	}
+}
diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java
index bc47d74..81ae3b8 100644
--- a/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java
+++ b/src/main/java/moe/nea/firmament/mixins/custommodels/TestForFirmamentOverridePredicatesPatch.java
@@ -17,33 +17,33 @@ import org.spongepowered.asm.mixin.injection.ModifyArg;
 @Mixin(ModelOverrideList.class)
 public class TestForFirmamentOverridePredicatesPatch {
 
-    @ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/List;)V",
-        at = @At(
-            value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
-        ))
-    public Object onInit(
-        Object element,
-        @Local ModelOverride modelOverride
-    ) {
-        var bakedOverride = (ModelOverrideList.BakedOverride) element;
-        ((BakedOverrideData) bakedOverride)
-            .setFirmamentOverrides(((ModelOverrideData) modelOverride).getFirmamentOverrides());
-        return element;
-    }
+	@ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Ljava/util/List;)V",
+		at = @At(
+			value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
+		))
+	public Object onInit(
+		Object element,
+		@Local ModelOverride modelOverride
+	) {
+		var bakedOverride = (ModelOverrideList.BakedOverride) element;
+		((BakedOverrideData) (Object) bakedOverride)
+			.setFirmamentOverrides(((ModelOverrideData) (Object) modelOverride).getFirmamentOverrides());
+		return element;
+	}
 
-    @ModifyExpressionValue(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
-    public boolean testFirmamentOverrides(boolean originalValue,
-                                          @Local ModelOverrideList.BakedOverride bakedOverride,
-                                          @Local(argsOnly = true) ItemStack stack) {
-        if (!originalValue) return false;
-        var overrideData = (BakedOverrideData) bakedOverride;
-        var overrides = overrideData.getFirmamentOverrides();
-        if (overrides == null) return true;
-        if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
-        for (FirmamentModelPredicate firmamentOverride : overrides) {
-            if (!firmamentOverride.test(stack))
-                return false;
-        }
-        return true;
-    }
+	@ModifyExpressionValue(method = "getModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
+	public boolean testFirmamentOverrides(boolean originalValue,
+	                                      @Local ModelOverrideList.BakedOverride bakedOverride,
+	                                      @Local(argsOnly = true) ItemStack stack) {
+		if (!originalValue) return false;
+		var overrideData = (BakedOverrideData) (Object) bakedOverride;
+		var overrides = overrideData.getFirmamentOverrides();
+		if (overrides == null) return true;
+		if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
+		for (FirmamentModelPredicate firmamentOverride : overrides) {
+			if (!firmamentOverride.test(stack))
+				return false;
+		}
+		return true;
+	}
 }
-- 
cgit