diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererHuman.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererHuman.java
index 7d72ad752..d91656156 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererHuman.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererHuman.java
@@ -7,6 +7,7 @@ import net.minecraft.core.util.helper.MathHelper;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.joml.Math;
+import org.useless.dragonfly.data.entity.mojang.EntityGeometryMojangData;
 import org.useless.dragonfly.models.entity.StaticEntityModel;
 
 public class MobRendererHuman extends MobRendererBiped<MobHuman> {
@@ -40,8 +41,10 @@ public class MobRendererHuman extends MobRendererBiped<MobHuman> {
 
     @Override
     protected @Nullable StaticEntityModel getActiveModel(@NotNull final MobHuman entity) {
-        final MobRendererPlayer.Specials specials = MobRendererPlayer.Specials.getSpecialsForMob(entity);
-        if (specials != null) return getModel(specials.mainModelReference);
+        if (entity.playerUUID != null) {
+            var overrideModel = EntityGeometryMojangData.Cache.getPlayerModelOverride(entity.playerUUID, EntityGeometryMojangData.PlayerModelOverrideComponent.MAIN);
+            if (overrideModel != null) return overrideModel;
+        }
         return entity.slimModel ? getModel("main.slim") : getModel("main");
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
index 8e781275c..af1e997ee 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererPlayer.java
@@ -17,7 +17,6 @@ import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.render.tileentity.TileEntityRenderer;
 import net.minecraft.core.block.entity.TileEntity;
 import net.minecraft.core.block.motion.CarriedBlock;
-import net.minecraft.core.entity.Mob;
 import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.player.gamemode.Gamemodes;
 import net.minecraft.core.util.helper.MathHelper;
@@ -25,13 +24,15 @@ import net.minecraft.core.world.BlocksContainer;
 import net.minecraft.core.world.pos.TilePos;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import org.useless.dragonfly.data.entity.mojang.EntityGeometryMojangData;
 import org.useless.dragonfly.models.entity.BoneTransform;
 import org.useless.dragonfly.models.entity.StaticEntityModel;
 
 import java.util.UUID;
 
 public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
-
+    private static final @NotNull UUID UUID_MELON = UUID.fromString("9af822f9-ed7c-4afc-be28-2bd5b8c40b50");
+    private static final @NotNull UUID UUID_WATERMELON = UUID.fromString("f9b18145-2be6-4817-8afb-a36e91369d2a");
 
     private BlocksContainer container = null;
 
@@ -40,12 +41,12 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
     }
 
     protected @NotNull StaticEntityModel getActiveModel(final @NotNull Player player, final boolean onlyArms) {
-        final Specials specials = Specials.getSpecialsForPlayer(player);
+        var overrideModel = EntityGeometryMojangData.Cache.getPlayerModelOverride(player.uuid,
+            onlyArms ? EntityGeometryMojangData.PlayerModelOverrideComponent.ARMS : EntityGeometryMojangData.PlayerModelOverrideComponent.MAIN);
+        if (overrideModel != null) return overrideModel;
         if (onlyArms) {
-            if (specials != null && specials.armsModelReference != null) return getModel(specials.armsModelReference);
             return player.slimModel ? getModel("arms.slim") : getModel("arms");
         } else {
-            if (specials != null) return getModel(specials.mainModelReference);
             return player.slimModel ? getModel("main.slim") : getModel("main");
         }
     }
@@ -201,7 +202,7 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
             GLRenderer.modelM4f().scale(0.6f, 0.5f, 0.6f);
         }
 
-        if (Specials.getSpecialsForPlayer(entity) == Specials.MELON || Specials.getSpecialsForPlayer(entity) == Specials.WATERMELON) {
+        if (entity.uuid.equals(UUID_MELON) || entity.uuid.equals(UUID_WATERMELON)) {
             GLRenderer.modelM4f().scale(0.9f, 0.9f, 0.9f);
         }
 
@@ -261,8 +262,8 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
 
     @Override
     protected @Nullable StaticEntityModel getActiveModel(@NotNull final Player entity) {
-        final Specials specials = Specials.getSpecialsForPlayer(entity);
-        if (specials != null) return getModel(specials.mainModelReference);
+        var overrideModel = EntityGeometryMojangData.Cache.getPlayerModelOverride(entity.uuid, EntityGeometryMojangData.PlayerModelOverrideComponent.MAIN);
+        if (overrideModel != null) return overrideModel;
         return entity.slimModel ? getModel("main.slim") : getModel("main");
     }
 
@@ -296,43 +297,4 @@ public class MobRendererPlayer extends MobRendererBipedArmored<Player> {
     public void loadEntityTexture(final @NotNull Player entity) {
         bindDownloadableTexture(entity.skinURL, entity.getEntityTexture(), entity.slimModel ? PlayerSkinParser.instanceAlex : PlayerSkinParser.instanceSteve);
     }
-
-    public enum Specials {
-        SHIGN(UUID.fromString("49f6ea39-30b2-4441-b07c-7bfe0beeb61e"), "ShignBright", "main.shign", null),
-        THA_BOAR(UUID.fromString("f73086f5-d583-408b-a65c-d2dbdd9afaae"), "thaboar", "main.boar", null),
-        MELON(UUID.fromString("9af822f9-ed7c-4afc-be28-2bd5b8c40b50"), "MelonMojito", "main.melon", "arms.melon"),
-        WATERMELON(UUID.fromString("f9b18145-2be6-4817-8afb-a36e91369d2a"), "WatermelonMojito", "main.melon", "arms.melon");
-
-
-        public final @NotNull UUID uuid;
-        public final @NotNull String username;
-        public final @NotNull String mainModelReference;
-        public final @Nullable String armsModelReference;
-
-        Specials(final @NotNull UUID uuid, final @NotNull String username, final @NotNull String mainModelReference, @Nullable String armsModelReference) {
-            this.uuid = uuid;
-            this.username = username;
-            this.mainModelReference = mainModelReference;
-            this.armsModelReference = armsModelReference;
-        }
-
-        public static @Nullable Specials getSpecialsForPlayer(final @NotNull Player player) {
-            if (!GameSettings.DEV_MODELS.value || !player.playerConfig.devModelEnabled())
-                return null;
-            if (player.uuid.equals(SHIGN.uuid) || player.username.equals(SHIGN.username)) return SHIGN;
-            if (player.uuid.equals(THA_BOAR.uuid) || player.username.equals(THA_BOAR.username)) return THA_BOAR;
-            if (player.uuid.equals(MELON.uuid) || player.username.equals(MELON.username)) return MELON;
-            if (player.uuid.equals(WATERMELON.uuid) || player.username.equals(WATERMELON.username)) return WATERMELON;
-            return null;
-        }
-
-        public static @Nullable Specials getSpecialsForMob(final @NotNull Mob mob) {
-            if (!GameSettings.DEV_MODELS.value) return null;
-            if (mob.nickname.equals(SHIGN.username)) return SHIGN;
-            if (mob.nickname.equals(THA_BOAR.username)) return THA_BOAR;
-            if (mob.nickname.equals(MELON.username)) return MELON;
-            if (mob.nickname.equals(WATERMELON.username)) return WATERMELON;
-            return null;
-        }
-    }
 }
diff --git a/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java b/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
index b8b725172..42e315884 100644
--- a/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
+++ b/game/client/src/main/java/org/useless/dragonfly/data/entity/mojang/EntityGeometryMojangData.java
@@ -9,9 +9,11 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParseException;
 import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
 import com.google.gson.JsonSerializationContext;
 import com.google.gson.JsonSerializer;
 import com.mojang.logging.LogUtils;
+import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 import net.minecraft.client.render.texturepack.TexturePack;
 import net.minecraft.client.render.texturepack.TexturePackList;
 import net.minecraft.core.util.HardIllegalArgumentException;
@@ -29,13 +31,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class EntityGeometryMojangData implements EntityModelData {
     public static final @NotNull String CURRENT_VERSION = "1.8.0";
@@ -201,9 +197,68 @@ public class EntityGeometryMojangData implements EntityModelData {
     }
 
     public static class Cache {
-        protected static final Map<String, EntityGeometryMojangData> modelDataCache = new HashMap<>();
+        protected static final Map<String, EntityGeometryMojangData> modelDataCache = new Object2ObjectOpenHashMap<>();
+        protected static final @NotNull Map<@NotNull UUID, @NotNull PlayerModelOverrides> playerModelOverrides = new Object2ObjectOpenHashMap<>();
+        protected static final @NotNull Map<@NotNull UUID, @NotNull Map<@NotNull PlayerModelOverrideComponent, @Nullable StaticEntityModel>> playerModelOverridesCache = new Object2ObjectOpenHashMap<>();
 
-        protected static final @NotNull Map<@NotNull NamespaceID, @NotNull Map<@NotNull String, @NotNull Pair<String, Double>>> localModelMappings = new HashMap<>();
+        protected static final @NotNull Map<@NotNull NamespaceID, @NotNull Map<@NotNull String, @NotNull Pair<String, Double>>> localModelMappings = new Object2ObjectOpenHashMap<>();
+
+        public static @Nullable StaticEntityModel getPlayerModelOverride(@NotNull UUID uuid, @NotNull PlayerModelOverrideComponent component) {
+            var cached = playerModelOverridesCache.get(uuid);
+            if (cached != null) {
+                if (cached.containsKey(component)) {
+                    return cached.get(component);
+                }
+            }
+            PlayerModelOverrides overrides = playerModelOverrides.get(uuid);
+            if (overrides == null) return null;
+            StaticEntityModel model = switch (component) {
+                case MAIN -> {
+                    Pair<String, Double> pair = overrides.mainOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case ARMS -> {
+                    Pair<String, Double> pair = overrides.armsOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case CAPE -> {
+                    Pair<String, Double> pair = overrides.capeOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case HELMET -> {
+                    Pair<String, Double> pair = overrides.helmetOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case CHEST_PLATE -> {
+                    Pair<String, Double> pair = overrides.chestPlateOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case LEGGINGS -> {
+                    Pair<String, Double> pair = overrides.leggingsOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+                case BOOTS -> {
+                    Pair<String, Double> pair = overrides.bootsOverride();
+                    if (pair == null) yield null;
+                    yield getModel(pair.getLeft(), pair.getRight());
+                }
+            };
+            Map<@NotNull PlayerModelOverrideComponent, @Nullable StaticEntityModel> map;
+            if (!playerModelOverridesCache.containsKey(uuid)) {
+                map = new Object2ObjectOpenHashMap<>();
+                playerModelOverridesCache.put(uuid, map);
+            } else {
+                map = playerModelOverridesCache.get(uuid);
+            }
+            map.put(component, model);
+            return model;
+        }
 
         public static @NotNull StaticEntityModel getFallbackModel() {
             return new EntityGeometryMojangData.Builder(1, 1)
@@ -270,6 +325,8 @@ public class EntityGeometryMojangData implements EntityModelData {
 
         public static void reload(@NotNull final TexturePackList packList) {
             modelDataCache.clear();
+            playerModelOverrides.clear();
+            playerModelOverridesCache.clear();
 
             final Collection<TexturePack> packs = new ArrayList<>();
             packs.add(packList.getDefaultTexturePack());
@@ -287,6 +344,25 @@ public class EntityGeometryMojangData implements EntityModelData {
                             modelPaths.add(e.getAsString());
                         }
                     }
+                    if (manifestObject.has("custom_player_models")) {
+                        final JsonObject overrides = manifestObject.getAsJsonObject("custom_player_models");
+                        for (Map.Entry<String, JsonElement> entry : overrides.entrySet()) {
+                            try {
+                                UUID uuid = UUID.fromString(entry.getKey());
+                                JsonObject playerOverrides = entry.getValue().getAsJsonObject();
+                                var oMain = extractOverridePair(playerOverrides, "main");
+                                var oArms = extractOverridePair(playerOverrides, "arms");
+                                var oCape = extractOverridePair(playerOverrides, "cape");
+                                var oHelmet = extractOverridePair(playerOverrides, "armor.helmet");
+                                var oChestPlate = extractOverridePair(playerOverrides, "armor.chestplate");
+                                var oLeggings = extractOverridePair(playerOverrides, "armor.leggings");
+                                var oBoots = extractOverridePair(playerOverrides, "armor.boots");
+                                playerModelOverrides.put(uuid, new PlayerModelOverrides(uuid, oMain, oArms, oCape, oBoots, oLeggings, oChestPlate, oHelmet));
+                            } catch (Exception exception) {
+                                LOGGER.error("Error when parsing UUID for string '{}', skipping player model override for '{}'!", entry.getKey(), entry.getKey());
+                            }
+                        }
+                    }
                     if (manifestObject.has("renderers")) {
                         final JsonObject renderersObj = manifestObject.getAsJsonObject("renderers");
                         for (final Map.Entry<String, JsonElement> entry : renderersObj.entrySet()) {
@@ -332,4 +408,52 @@ public class EntityGeometryMojangData implements EntityModelData {
             }
         }
     }
+
+    private static @Nullable Pair<@NotNull String, @NotNull Double> extractOverridePair(@NotNull JsonObject jsonObject, @NotNull String key) {
+        if (!jsonObject.has(key)) return null;
+        JsonElement overrideElement = jsonObject.get(key);
+        if (overrideElement.isJsonPrimitive()) {
+            JsonPrimitive primitive = overrideElement.getAsJsonPrimitive();
+            if (primitive.isString()) {
+                return Pair.of(primitive.getAsString(), 0d);
+            }
+        } else if (overrideElement.isJsonObject()) {
+            JsonObject object = overrideElement.getAsJsonObject();
+            String modelId = object.has("model") ? object.get("model").getAsString() : null;
+            double inflation = object.has("inflation") ? object.get("inflation").getAsDouble() : 0;
+            if (modelId != null) {
+                return Pair.of(modelId, inflation);
+            } else {
+                LOGGER.error("Player model override entry missing model reference! Skipping!");
+            }
+        }
+        LOGGER.error("Unsupported player model override type '{}'! Skipping!", overrideElement.getClass());
+        return null;
+    }
+
+    public record PlayerModelOverrides(
+            @NotNull UUID uuid,
+            @Nullable Pair<@NotNull String, @NotNull Double> mainOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> armsOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> capeOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> bootsOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> leggingsOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> chestPlateOverride,
+            @Nullable Pair<@NotNull String, @NotNull Double> helmetOverride
+    ) {}
+
+    public enum PlayerModelOverrideComponent {
+        MAIN("main"),
+        ARMS("arms"),
+        CAPE("cape"),
+        HELMET("armor.helmet"),
+        CHEST_PLATE("armor.chestplate"),
+        LEGGINGS("armor.leggings"),
+        BOOTS("armor.boots");
+
+        public final @NotNull String refString;
+        PlayerModelOverrideComponent(@NotNull String refString) {
+            this.refString = refString;
+        }
+    }
 }
diff --git a/game/client/src/main/resources/assets/minecraft/models/entity/models.json b/game/client/src/main/resources/assets/minecraft/models/entity/models.json
index 74a55e415..3149837b7 100644
--- a/game/client/src/main/resources/assets/minecraft/models/entity/models.json
+++ b/game/client/src/main/resources/assets/minecraft/models/entity/models.json
@@ -25,6 +25,22 @@
         "/assets/minecraft/models/entity/sign/sign.geo.json",
         "/assets/minecraft/models/entity/flag/flag.geo.json"
     ],
+    "custom_player_models": {
+        "9af822f9-ed7c-4afc-be28-2bd5b8c40b50": {
+            "main": "geometry.player.base.melon",
+            "arms": "geometry.player.base.melon.arms"
+        },
+        "f9b18145-2be6-4817-8afb-a36e91369d2a": {
+            "main": "geometry.player.base.melon",
+            "arms": "geometry.player.base.melon.arms"
+        },
+        "f73086f5-d583-408b-a65c-d2dbdd9afaae": {
+            "main": "geometry.player.base.boar"
+        },
+        "49f6ea39-30b2-4441-b07c-7bfe0beeb61e": {
+            "main": "geometry.player.base.shign"
+        }
+    },
     "renderers": {
         "minecraft:spider": {
             "main":"geometry.spider"
@@ -94,10 +110,6 @@
         "minecraft:player": {
             "main":"geometry.player.base.steve",
             "main.slim":"geometry.player.base.alex",
-            "main.shign":"geometry.player.base.shign",
-            "main.boar":"geometry.player.base.boar",
-            "main.melon":"geometry.player.base.melon",
-            "arms.melon":"geometry.player.base.melon.arms",
             "arms":"geometry.player.base.steve.arms",
             "arms.slim":"geometry.player.base.alex.arms",
             "cape":"geometry.player.cape",
@@ -108,10 +120,6 @@
         },
         "minecraft:human": {
             "main":"geometry.player.base.steve",
-            "main.slim":"geometry.player.base.alex",
-            "main.shign":"geometry.player.base.shign",
-            "main.boar":"geometry.player.base.boar",
-            "main.melon":"geometry.player.base.melon",
             "arms":"geometry.player.base.steve.arms",
             "arms.slim":"geometry.player.base.alex.arms",
             "cape":"geometry.player.cape"
diff --git a/game/core/src/main/java/net/minecraft/core/data/DataLoader.java b/game/core/src/main/java/net/minecraft/core/data/DataLoader.java
index 342dd06a4..6e329553e 100644
--- a/game/core/src/main/java/net/minecraft/core/data/DataLoader.java
+++ b/game/core/src/main/java/net/minecraft/core/data/DataLoader.java
@@ -142,10 +142,18 @@ public class DataLoader {
             String recipeTypeId = recipeJson.get("type").getAsString();
             String recipeId = recipeJson.get("name").getAsString();
             Class<? extends RecipeEntryBase<?,?,?>> recipeEntryClass = Registries.RECIPE_TYPES.getItem(recipeTypeId);
-            if(Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)){
+            if(recipeEntryClass == null){
+                throw new NullPointerException("No recipe class registered for type '" + recipeTypeId + "'!");
+            }
+            if (!Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)) {
+                throw new IllegalStateException("Recipe type '" + recipeTypeId + "' is not serializable! (does not implement HasJsonAdapter.)");
+            } else {
                 try {
                     RecipeEntryBase<?,?,?> recipeEntryPrototype = recipeEntryClass.getConstructor().newInstance();
                     RecipeJsonAdapter<?> adapter = ((HasJsonAdapter)recipeEntryPrototype).getAdapter();
+                    if(adapter == null){
+                        throw new NullPointerException("No JSON adapter registered for recipe type '" + recipeTypeId + "'!");
+                    }
                     if(!usedAdapters.contains(adapter)){
                         builder.registerTypeAdapter(recipeEntryClass,adapter);
                         usedAdapters.add(adapter);
@@ -179,10 +187,18 @@ public class DataLoader {
             String recipeTypeId = recipeJson.get("type").getAsString();
             String recipeId = recipeJson.get("name").getAsString();
             Class<? extends RecipeEntryBase<?,?,?>> recipeEntryClass = Registries.RECIPE_TYPES.getItem(recipeTypeId);
-            if(Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)){
+            if(recipeEntryClass == null){
+                throw new NullPointerException("No recipe class registered for type '" + recipeTypeId + "'!");
+            }
+            if (!Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)) {
+                throw new IllegalStateException("Recipe type '" + recipeTypeId + "' is not serializable! (does not implement HasJsonAdapter.)");
+            } else {
                 try {
                     RecipeEntryBase<?,?,?> recipeEntryPrototype = recipeEntryClass.getConstructor().newInstance();
                     RecipeJsonAdapter<?> adapter = ((HasJsonAdapter)recipeEntryPrototype).getAdapter();
+                    if(adapter == null){
+                        throw new NullPointerException("No JSON adapter registered for recipe type '" + recipeTypeId + "'!");
+                    }
                     if(!usedAdapters.contains(adapter)){
                         builder.registerTypeAdapter(recipeEntryClass,adapter);
                         usedAdapters.add(adapter);
@@ -214,10 +230,18 @@ public class DataLoader {
         String recipeTypeId = recipeJson.get("type").getAsString();
         String recipeId = recipeJson.get("name").getAsString();
         Class<? extends RecipeEntryBase<?,?,?>> recipeEntryClass = Registries.RECIPE_TYPES.getItem(recipeTypeId);
-        if(Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)) {
+        if(recipeEntryClass == null){
+            throw new NullPointerException("No recipe class registered for type '" + recipeTypeId + "'!");
+        }
+        if (!Arrays.asList(recipeEntryClass.getInterfaces()).contains(HasJsonAdapter.class)) {
+            throw new IllegalStateException("Recipe type '" + recipeTypeId + "' is not serializable! (does not implement HasJsonAdapter.)");
+        } else {
             try {
                 RecipeEntryBase<?, ?, ?> recipeEntryPrototype = recipeEntryClass.getConstructor().newInstance();
                 RecipeJsonAdapter<?> adapter = ((HasJsonAdapter) recipeEntryPrototype).getAdapter();
+                if(adapter == null){
+                    throw new NullPointerException("No JSON adapter registered for recipe type '" + recipeTypeId + "'!");
+                }
                 builder.registerTypeAdapter(recipeEntryClass, adapter);
             } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                      NoSuchMethodException e) {
diff --git a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java
index b19956171..0c44fe617 100644
--- a/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java
+++ b/game/core/src/main/java/net/minecraft/core/data/registry/recipe/RecipeRegistry.java
@@ -62,6 +62,20 @@ public class RecipeRegistry extends Registry<RecipeNamespace> {
         return Collections.unmodifiableList(recipes);
     }
 
+    public <T extends RecipeEntryBase<?,?,?>> List<T> getAllRecipesOfType(Class<T> clazz){
+        List<T> recipes = new ArrayList<>();
+        for (RecipeNamespace recipeNamespace : this) {
+            for (RecipeGroup<? extends RecipeEntryBase<?,?,?>> recipeGroup : recipeNamespace) {
+                for (RecipeEntryBase<?,?,?> recipeEntry : recipeGroup) {
+                    if (clazz.isAssignableFrom(recipeEntry.getClass())) {
+                        recipes.add(clazz.cast(recipeEntry));
+                    }
+                }
+            }
+        }
+        return Collections.unmodifiableList(recipes);
+    }
+
     public List<RecipeEntryBase<?,?,?>> getAllSerializableRecipes(){
         ArrayList<RecipeEntryBase<?,?,?>> recipes = new ArrayList<>();
         for (RecipeNamespace recipeNamespace : this) {
diff --git a/game/core/src/main/java/net/minecraft/core/entity/monster/MobHuman.java b/game/core/src/main/java/net/minecraft/core/entity/monster/MobHuman.java
index 3b31309c0..9d0ce57fc 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/monster/MobHuman.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/monster/MobHuman.java
@@ -1,19 +1,29 @@
 package net.minecraft.core.entity.monster;
 
+import net.minecraft.core.Global;
 import net.minecraft.core.entity.Entity;
 import com.mojang.nbt.tags.CompoundTag;
 import net.minecraft.core.util.helper.GetMonsterSkinUrlThread;
 import net.minecraft.core.world.World;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.UUID;
 
 
 public class MobHuman extends MobMonster
 {
+    public @Nullable String skinUrl;
+    public @Nullable UUID playerUUID;
+    public boolean slimModel = false;
+
     public MobHuman(World world)
     {
         super(world);
 
-        new GetMonsterSkinUrlThread(this);
+        if (!Global.isServer) {
+            new GetMonsterSkinUrlThread(this);
+        }
     }
 
     @Override
@@ -27,7 +37,9 @@ public class MobHuman extends MobMonster
     {
         super.onLabelled();
 
-        new GetMonsterSkinUrlThread(this);
+        if (!Global.isServer) {
+            new GetMonsterSkinUrlThread(this);
+        }
     }
 
     @Override
@@ -35,9 +47,8 @@ public class MobHuman extends MobMonster
     {
         super.readAdditionalSaveData(tag);
 
-        new GetMonsterSkinUrlThread(this);
+        if (!Global.isServer) {
+            new GetMonsterSkinUrlThread(this);
+        }
     }
-
-    public String skinUrl;
-    public boolean slimModel = false;
 }
diff --git a/game/core/src/main/java/net/minecraft/core/util/collection/Pair.java b/game/core/src/main/java/net/minecraft/core/util/collection/Pair.java
index a93c6754c..d0eed0550 100644
--- a/game/core/src/main/java/net/minecraft/core/util/collection/Pair.java
+++ b/game/core/src/main/java/net/minecraft/core/util/collection/Pair.java
@@ -1,5 +1,7 @@
 package net.minecraft.core.util.collection;
 
+import java.util.Objects;
+
 public class Pair<A, B> {
     private final A left;
     private final B right;
@@ -32,6 +34,13 @@ public class Pair<A, B> {
         return getRight() != null ? getRight().equals(pair.getRight()) : pair.getRight() == null;
     }
 
+    @Override
+    public int hashCode() {
+        int result = Objects.hashCode(this.left);
+        result = 31 * result + Objects.hashCode(this.right);
+        return result;
+    }
+
     @Override
     public String toString() {
         return "Pair{" +
diff --git a/game/core/src/main/java/net/minecraft/core/util/helper/GetMonsterSkinUrlThread.java b/game/core/src/main/java/net/minecraft/core/util/helper/GetMonsterSkinUrlThread.java
index 570c875a8..029041d2c 100644
--- a/game/core/src/main/java/net/minecraft/core/util/helper/GetMonsterSkinUrlThread.java
+++ b/game/core/src/main/java/net/minecraft/core/util/helper/GetMonsterSkinUrlThread.java
@@ -6,81 +6,84 @@ import com.b100.json.element.JsonObject;
 import com.b100.utils.StringUtils;
 import com.mojang.logging.LogUtils;
 import net.minecraft.core.entity.monster.MobHuman;
+import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
 
 import java.util.Base64;
 import java.util.UUID;
 
-public class GetMonsterSkinUrlThread extends Thread{
-	private static final Logger LOGGER = LogUtils.getLogger();
-	private static int counter = 0;
-
-	private static final String urlUUID = "https://api.mojang.com/users/profiles/minecraft/";
-	private static final String urlSkin = "https://sessionserver.mojang.com/session/minecraft/profile/";
-
-	private static final JsonParser jsonParser = new JsonParser();
-
-	private MobHuman monster;
-	private UUID uuid;
-
-	public GetMonsterSkinUrlThread(MobHuman monster) {
-		this.monster = monster;
-		
-		setDaemon(true);
-		setName("GetSkinUrlThread " + counter++);
-		
-		start();
-	}
-	
-	@Override
-	public void run() {
-		String name = monster.nickname;
-		if(name == null || name.isEmpty()) return;
-
-		//  Attempt to get the skin 3 times before failing
-		String string = null;
-		for (int i = 0; i < 3; i++) {
-			string = getSkinObject(name);
-			if (string != null) break;
-			try {
-				sleep(5000);
-			} catch (InterruptedException e) {
-				break;
-			}
-		}
-		if (string == null) return;
-		
-		JsonObject object = jsonParser.parse(string);
-		JsonArray properties = object.getArray("properties");
-		JsonObject textureProperty = properties.query((e) -> e.getAsObject().getString("name").equalsIgnoreCase("textures")).getAsObject();
-		JsonObject texturesObject = jsonParser.parse(decodeBase64(textureProperty.getString("value"))).getObject("textures");
-		if(texturesObject.has("SKIN")) {
-			monster.skinUrl = texturesObject.getObject("SKIN").getString("url");
-			if (texturesObject.getObject("SKIN").has("metadata")) {
-				if (texturesObject.getObject("SKIN").getObject("metadata").getString("model").equals("slim")) {
-					monster.slimModel = true;
-				}
-			}
-		}
-
-        LOGGER.info("Skin URL: {}", monster.skinUrl);
-	}
-	
-	public static String decodeBase64(String string) {
-		return new String(Base64.getDecoder().decode(string));
-	}
-	private String getSkinObject(String name){
-		String string;
-		this.uuid = UUIDHelper.getUUIDFromName(name);
-    if(uuid == null) return null;
+public class GetMonsterSkinUrlThread extends Thread {
+    private static final Logger LOGGER = LogUtils.getLogger();
+    private static int counter = 0;
+
+    private static final String urlUUID = "https://api.mojang.com/users/profiles/minecraft/";
+    private static final String urlSkin = "https://sessionserver.mojang.com/session/minecraft/profile/";
+
+    private static final JsonParser jsonParser = new JsonParser();
+
+    private final MobHuman monster;
+    private @Nullable UUID uuid;
+
+    public GetMonsterSkinUrlThread(MobHuman monster) {
+        this.monster = monster;
+
+        setDaemon(true);
+        setName("GetSkinUrlThread " + counter++);
+
+        start();
+    }
+
+    @Override
+    public void run() {
+        String name = this.monster.nickname;
+        if (name.isEmpty()) return;
+
+        //  Attempt to get the skin 3 times before failing
+        String string = null;
+        for (int i = 0; i < 3; i++) {
+            string = getSkinObject(name);
+            if (string != null) break;
+            try {
+                sleep(5000);
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+        if (string == null) return;
+
+        JsonObject object = jsonParser.parse(string);
+        JsonArray properties = object.getArray("properties");
+        JsonObject textureProperty = properties.query((e) -> e.getAsObject().getString("name").equalsIgnoreCase("textures")).getAsObject();
+        JsonObject texturesObject = jsonParser.parse(decodeBase64(textureProperty.getString("value"))).getObject("textures");
+        if (texturesObject.has("SKIN")) {
+            this.monster.skinUrl = texturesObject.getObject("SKIN").getString("url");
+            this.monster.playerUUID = this.uuid;
+            if (texturesObject.getObject("SKIN").has("metadata")) {
+                if (texturesObject.getObject("SKIN").getObject("metadata").getString("model").equals("slim")) {
+                    this.monster.slimModel = true;
+                }
+            }
+        }
+
+        LOGGER.info("Skin URL: {}", this.monster.skinUrl);
+    }
+
+    public static String decodeBase64(String string) {
+        return new String(Base64.getDecoder().decode(string));
+    }
+
+    private String getSkinObject(String name) {
+        String string;
+        this.uuid = UUIDHelper.getUUIDFromName(name);
+        if (this.uuid == null) return null;
 
         LOGGER.info("Loading Skin for Player {}...", name);
-		try {
-			string = StringUtils.getWebsiteContentAsString(urlSkin + uuid);
-		}catch (Exception e) {
-            LOGGER.warn("Invalid UUID {}, or can't connect to the Mojang API.", uuid);
-			return null;
-		}
-		return string;
-	}
+        try {
+            string = StringUtils.getWebsiteContentAsString(urlSkin + this.uuid);
+        } catch (Exception e) {
+            LOGGER.warn("Invalid UUID {}, or can't connect to the Mojang API.", this.uuid);
+            return null;
+        }
+        return string;
+    }
 }
diff --git a/game/core/src/main/resources/lang/en_US/tile.lang b/game/core/src/main/resources/lang/en_US/tile.lang
index 4e481d3c7..37c497bc0 100644
--- a/game/core/src/main/resources/lang/en_US/tile.lang
+++ b/game/core/src/main/resources/lang/en_US/tile.lang
@@ -951,7 +951,7 @@ tile.sign.wall.planks.oak.painted.orange.desc=An wooden sign placed on a wall.
 tile.sign.wall.planks.oak.painted.white.name=White Wooden Wall Sign
 tile.sign.wall.planks.oak.painted.white.desc=An wooden sign placed on a wall.
 
-tile.sign.post.planks.oak.painted.name=Mystery Wooden Chest
+tile.sign.post.planks.oak.painted.name=Mystery Wooden Signpost
 tile.sign.post.planks.oak.painted.desc=How did you get this?!
 tile.sign.post.planks.oak.painted.black.name=Black Wooden Signpost
 tile.sign.post.planks.oak.painted.black.desc=A free-standing wooden sign.
