From 4b76f1c125a95d27325bcc2c88c4dd2a055958c5 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:08:19 -0800 Subject: [PATCH] Add UnbakedModelDeserializer --- .../loading/v1/UnbakedModelDeserializer.java | 46 +++++++ .../UnbakedModelDeserializerRegistry.java | 54 ++++++++ .../loading/UnbakedModelJsonDeserializer.java | 54 ++++++++ .../model/loading/BakedModelManagerMixin.java | 21 ++++ .../loading/JsonUnbakedModelAccessor.java | 31 +++++ .../model/loading/JsonUnbakedModelMixin.java | 35 ++++++ .../fabric-model-loading-api-v1.mixins.json | 2 + .../loading/UnbakedModelDeserializerTest.java | 115 ++++++++++++++++++ .../models/block/emerald_block.json | 13 ++ .../minecraft/blockstates/emerald_block.json | 7 ++ .../testmodClient/resources/fabric.mod.json | 3 +- 11 files changed, 380 insertions(+), 1 deletion(-) create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/UnbakedModelDeserializer.java create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelDeserializerRegistry.java create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelJsonDeserializer.java create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelAccessor.java create mode 100644 fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelMixin.java create mode 100644 fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/UnbakedModelDeserializerTest.java create mode 100644 fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/block/emerald_block.json create mode 100644 fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/blockstates/emerald_block.json diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/UnbakedModelDeserializer.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/UnbakedModelDeserializer.java new file mode 100644 index 0000000000..3dd4e23e88 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/api/client/model/loading/v1/UnbakedModelDeserializer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.model.loading.v1; + +import java.io.Reader; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; + +import net.fabricmc.fabric.impl.client.model.loading.UnbakedModelDeserializerRegistry; + +public interface UnbakedModelDeserializer { + static void register(Identifier id, UnbakedModelDeserializer deserializer) { + UnbakedModelDeserializerRegistry.register(id, deserializer); + } + + @Nullable + static UnbakedModelDeserializer get(Identifier id) { + return UnbakedModelDeserializerRegistry.get(id); + } + + static UnbakedModel deserialize(Reader reader) throws JsonParseException { + return UnbakedModelDeserializerRegistry.deserialize(reader); + } + + UnbakedModel deserialize(JsonObject jsonObject, JsonDeserializationContext context) throws RuntimeException; +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelDeserializerRegistry.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelDeserializerRegistry.java new file mode 100644 index 0000000000..60c224802b --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelDeserializerRegistry.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.model.loading; + +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.google.gson.JsonParseException; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; + +import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer; +import net.fabricmc.fabric.mixin.client.model.loading.JsonUnbakedModelAccessor; + +public class UnbakedModelDeserializerRegistry { + private static final Map DESERIALIZERS = new HashMap<>(); + + public static void register(Identifier id, UnbakedModelDeserializer deserializer) { + Objects.requireNonNull(id, "id cannot be null"); + Objects.requireNonNull(id, "deserializer cannot be null"); + + if (DESERIALIZERS.putIfAbsent(id, deserializer) != null) { + throw new IllegalArgumentException("UnbakedModelDeserializer with identifier '" + id + "' already registered"); + } + } + + public static UnbakedModelDeserializer get(Identifier id) { + Objects.requireNonNull(id, "id cannot be null"); + + return DESERIALIZERS.get(id); + } + + public static UnbakedModel deserialize(Reader reader) throws JsonParseException { + return JsonHelper.deserialize(JsonUnbakedModelAccessor.fabric_getGson(), reader, UnbakedModel.class); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelJsonDeserializer.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelJsonDeserializer.java new file mode 100644 index 0000000000..6956e17498 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/impl/client/model/loading/UnbakedModelJsonDeserializer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.model.loading; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; + +import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer; + +public class UnbakedModelJsonDeserializer implements JsonDeserializer { + private static final String TYPE_KEY = "fabric:type"; + + @Override + public UnbakedModel deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + if (jsonObject.has(TYPE_KEY)) { + Identifier id = Identifier.of(JsonHelper.getString(jsonObject, TYPE_KEY)); + UnbakedModelDeserializer deserializer = UnbakedModelDeserializer.get(id); + + if (deserializer == null) { + throw new JsonParseException("Cannot deserialize custom unbaked model of unknown type '" + id + "'"); + } + + return deserializer.deserialize(jsonObject, context); + } + + return context.deserialize(jsonElement, JsonUnbakedModel.class); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java index cc46cd6ad7..76fcaef9a4 100644 --- a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/BakedModelManagerMixin.java @@ -16,6 +16,7 @@ package net.fabricmc.fabric.mixin.client.model.loading; +import java.io.Reader; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -32,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @@ -40,11 +42,13 @@ import net.minecraft.client.render.model.BlockStatesLoader; import net.minecraft.client.render.model.ModelBaker; import net.minecraft.client.render.model.ReferencedModelsCollector; +import net.minecraft.client.render.model.json.JsonUnbakedModel; import net.minecraft.resource.ResourceManager; import net.minecraft.resource.ResourceReloader; import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.client.model.loading.v1.FabricBakedModelManager; +import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer; import net.fabricmc.fabric.impl.client.model.loading.BakedModelsHooks; import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingEventDispatcher; import net.fabricmc.fabric.impl.client.model.loading.ModelLoadingPluginManager; @@ -122,6 +126,23 @@ private Function hookModelBaking(Function function) }; } + // We want to redirect the JsonUnbakedModel.deserialize call, but its return type is JsonUnbakedModel, so we can't + // do that directly. + // Instead, cancel the original call and then modify the null value when it's being used to construct the Pair. + @Redirect(method = "method_65750(Ljava/util/Map$Entry;)Lcom/mojang/datafixers/util/Pair;", at = @At(value = "INVOKE", target = "net/minecraft/client/render/model/json/JsonUnbakedModel.deserialize(Ljava/io/Reader;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;")) + private static JsonUnbakedModel cancelVanillaDeserialize(Reader reader) { + return null; + } + + // Here we replace the null model with one produced by our own deserializer. + // The Pair's type is actually Pair, but since generics don't really exist, vanilla + // code doesn't explicitly cast the model to JsonUnbakedModel, and the enclosing method returns UnbakedModels per + // its return type, it's safe to return an UnbakedModel here. + @ModifyArg(method = "method_65750(Ljava/util/Map$Entry;)Lcom/mojang/datafixers/util/Pair;", at = @At(value = "INVOKE", target = "com/mojang/datafixers/util/Pair.of(Ljava/lang/Object;Ljava/lang/Object;)Lcom/mojang/datafixers/util/Pair;", remap = false), index = 1) + private static Object actuallyDeserializeModel(Object originalModel, @Local Reader reader) { + return UnbakedModelDeserializer.deserialize(reader); + } + @Inject(method = "upload", at = @At(value = "INVOKE_STRING", target = "net/minecraft/util/profiler/Profiler.swap(Ljava/lang/String;)V", args = "ldc=cache")) private void onUpload(CallbackInfo ci, @Local ModelBaker.BakedModels bakedModels) { extraModels = ((BakedModelsHooks) (Object) bakedModels).fabric_getExtraModels(); diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelAccessor.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelAccessor.java new file mode 100644 index 0000000000..c75067c1df --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelAccessor.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.model.loading; + +import com.google.gson.Gson; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.client.render.model.json.JsonUnbakedModel; + +@Mixin(JsonUnbakedModel.class) +public interface JsonUnbakedModelAccessor { + @Accessor("GSON") + static Gson fabric_getGson() { + throw new AssertionError(); + } +} diff --git a/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelMixin.java b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelMixin.java new file mode 100644 index 0000000000..4f362e7bc9 --- /dev/null +++ b/fabric-model-loading-api-v1/src/client/java/net/fabricmc/fabric/mixin/client/model/loading/JsonUnbakedModelMixin.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.model.loading; + +import com.google.gson.GsonBuilder; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.JsonUnbakedModel; + +import net.fabricmc.fabric.impl.client.model.loading.UnbakedModelJsonDeserializer; + +@Mixin(JsonUnbakedModel.class) +abstract class JsonUnbakedModelMixin { + @ModifyExpressionValue(method = "()V", at = @At(value = "NEW", target = "com/google/gson/GsonBuilder", remap = false)) + private static GsonBuilder addUnbakedModelAdapter(GsonBuilder builder) { + return builder.registerTypeHierarchyAdapter(UnbakedModel.class, new UnbakedModelJsonDeserializer()); + } +} diff --git a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json index f299bdb408..9c2a7cfe2d 100644 --- a/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json +++ b/fabric-model-loading-api-v1/src/client/resources/fabric-model-loading-api-v1.mixins.json @@ -4,6 +4,8 @@ "compatibilityLevel": "JAVA_21", "client": [ "BakedModelManagerMixin", + "JsonUnbakedModelAccessor", + "JsonUnbakedModelMixin", "ModelBakerBakedModelsMixin", "ModelBakerMixin", "ReferencedModelsCollectorMixin", diff --git a/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/UnbakedModelDeserializerTest.java b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/UnbakedModelDeserializerTest.java new file mode 100644 index 0000000000..6451758d5c --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/model/loading/UnbakedModelDeserializerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.model.loading; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSyntaxException; +import com.mojang.serialization.JsonOps; +import org.jetbrains.annotations.Nullable; + +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.ModelTextures; +import net.minecraft.client.render.model.UnbakedModel; +import net.minecraft.client.render.model.json.ModelTransformation; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.math.AffineTransformation; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedModelDeserializer; + +public class UnbakedModelDeserializerTest implements ClientModInitializer { + @Override + public void onInitializeClient() { + UnbakedModelDeserializer.register(ModelTestModClient.id("transformed"), TransformedModelDeserializer.INSTANCE); + } + + private static class TransformedModelDeserializer implements UnbakedModelDeserializer { + public static final TransformedModelDeserializer INSTANCE = new TransformedModelDeserializer(); + + @Override + public UnbakedModel deserialize(JsonObject jsonObject, JsonDeserializationContext context) throws JsonParseException { + JsonElement transformationElement = JsonHelper.getElement(jsonObject, "transformation"); + AffineTransformation transformation = AffineTransformation.ANY_CODEC.parse(JsonOps.INSTANCE, transformationElement).getOrThrow(); + + JsonElement parentElement = JsonHelper.getElement(jsonObject, "parent"); + + if (JsonHelper.isString(parentElement)) { + Identifier parentId = Identifier.of(parentElement.getAsString()); + return new TransformedUnbakedModel(transformation, parentId); + } else if (parentElement.isJsonObject()) { + UnbakedModel parent = context.deserialize(parentElement, UnbakedModel.class); + return new TransformedUnbakedModel(transformation, parent); + } else { + throw new JsonSyntaxException("parent must be string or object"); + } + } + } + + private static class TransformedUnbakedModel implements UnbakedModel { + private final AffineTransformation transformation; + @Nullable + private final Identifier parentId; + private UnbakedModel parent; + + private TransformedUnbakedModel(AffineTransformation transformation, Identifier parentId) { + this.transformation = transformation; + this.parentId = parentId; + } + + private TransformedUnbakedModel(AffineTransformation transformation, UnbakedModel parent) { + this.transformation = transformation; + parentId = null; + this.parent = parent; + } + + @Override + public void resolve(Resolver resolver) { + if (parentId != null) { + parent = resolver.resolve(parentId); + } + } + + @Override + public UnbakedModel getParent() { + return parent; + } + + @Override + public BakedModel bake(ModelTextures textures, Baker baker, ModelBakeSettings settings, boolean ambientOcclusion, boolean isSideLit, ModelTransformation transformation) { + settings = new SimpleModelBakeSettings(settings.getRotation().multiply(this.transformation), settings.isUvLocked()); + return parent.bake(textures, baker, settings, ambientOcclusion, isSideLit, transformation); + } + } + + private record SimpleModelBakeSettings(AffineTransformation transformation, boolean uvLocked) implements ModelBakeSettings { + @Override + public AffineTransformation getRotation() { + return transformation; + } + + @Override + public boolean isUvLocked() { + return uvLocked; + } + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/block/emerald_block.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/block/emerald_block.json new file mode 100644 index 0000000000..e1a60d9227 --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/fabric-model-loading-api-v1-testmod/models/block/emerald_block.json @@ -0,0 +1,13 @@ +{ + "fabric:type": "fabric-model-loading-api-v1-testmod:transformed", + "parent": "minecraft:block/emerald_block", + "transformation": { + "translation": [ 0, 0.5, 0 ], + "left_rotation": [ 0, 0, 0, 1 ], + "scale": [ 0.8, 1.2, 0.8 ], + "right_rotation": { + "angle": 1.57079632679, + "axis": [ 0, 0, 1 ] + } + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/blockstates/emerald_block.json b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/blockstates/emerald_block.json new file mode 100644 index 0000000000..7940d7e74b --- /dev/null +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/assets/minecraft/blockstates/emerald_block.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "fabric-model-loading-api-v1-testmod:block/emerald_block" + } + } +} diff --git a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json index 3291587c74..45a0772af7 100644 --- a/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json +++ b/fabric-model-loading-api-v1/src/testmodClient/resources/fabric.mod.json @@ -12,7 +12,8 @@ "entrypoints": { "client": [ "net.fabricmc.fabric.test.model.loading.ModelTestModClient", - "net.fabricmc.fabric.test.model.loading.PreparablePluginTest" + "net.fabricmc.fabric.test.model.loading.PreparablePluginTest", + "net.fabricmc.fabric.test.model.loading.UnbakedModelDeserializerTest" ] } }