diff --git a/common/build.gradle b/common/build.gradle index 38bc6a85..b8c30714 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,6 +20,8 @@ dependencies { } modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}" + + modCompileOnly fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version) // Remove the next line if you don't want to depend on the API // modApi "me.shedaniel:architectury:${rootProject.architectury_version}" } diff --git a/common/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelProvider.java b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelProvider.java index 3c5eb68f..8117f367 100644 --- a/common/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelProvider.java +++ b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/DynamicModelProvider.java @@ -43,6 +43,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import org.embeddedt.modernfix.ModernFix; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.Reader; import java.lang.ref.WeakReference; @@ -50,6 +51,7 @@ import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -97,6 +99,9 @@ public class DynamicModelProvider { private final Map mrlModelOverrides = new ConcurrentHashMap<>(); private final Map itemStackModelOverrides = new ConcurrentHashMap<>(); private final Map standaloneModelOverrides = new ConcurrentHashMap<>(); + private final Map unbakedBlockStateModelOverrides = new ConcurrentHashMap<>(); + + private final List pluginList = new ArrayList<>(); private static final boolean DEBUG_DYNAMIC_MODEL_LOADING = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); @@ -129,6 +134,12 @@ public class DynamicModelProvider { this.itemModelGenerator = new ItemModelGenerator(); this.missingModel = this.bakeModel(this.unbakedMissingModel, () -> "missing"); this.missingItemModel = new MissingItemModel(this.missingModel); + try { + Class.forName("net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin"); + pluginList.add(new FabricDynamicModelHandler(this)); + } catch(Exception ignored) { + // Fabric API likely not present + } } public BakedModel getMissingBakedModel() { @@ -329,7 +340,21 @@ public class DynamicModelProvider { ModernFix.LOGGER.error("Failed to load blockstate definition {} from pack '{}'", location, resource.sourcePackId(), e); } } - return Optional.of(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel)); + var loadedModels = new HashMap<>(BlockStateModelLoader.loadBlockStateDefinitionStack(location, stateDefinition, loadedDefinitions, this.unbakedMissingModel).models()); + if (!pluginList.isEmpty()) { + loadedModels.replaceAll((mrl, oldModel) -> { + UnbakedBlockStateModel ubm = oldModel.model(); + for (var plugin : pluginList) { + ubm = plugin.modifyBlockModelOnLoad(ubm, mrl, oldModel.state()); + } + if (ubm == oldModel.model()) { + return oldModel; + } else { + return new BlockStateModelLoader.LoadedModel(oldModel.state(), ubm); + } + }); + } + return Optional.of(new BlockStateModelLoader.LoadedModels(loadedModels)); } private BakedModel bakeModel(UnbakedModel model, ModelDebugName name) { @@ -364,15 +389,18 @@ public class DynamicModelProvider { if (location.variant().equals("standalone") || location.variant().equals("fabric_resource")) { return this.loadStandaloneModel(location.id()); } else { - var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id()); - Optional unbakedModelOpt = optLoadedModels.map(loadedModels -> { - var loadedModel = loadedModels.models().get(location); - if(loadedModel != null) { - return loadedModel.model(); - } else { - return null; - } - }); + Optional unbakedModelOpt = Optional.ofNullable(this.unbakedBlockStateModelOverrides.get(location)); + if (unbakedModelOpt.isEmpty()) { + var optLoadedModels = this.loadedStateDefinitions.getUnchecked(location.id()); + unbakedModelOpt = optLoadedModels.map(loadedModels -> { + var loadedModel = loadedModels.models().get(location); + if(loadedModel != null) { + return loadedModel.model(); + } else { + return null; + } + }); + } return unbakedModelOpt.map(unbakedModel -> { return this.bakeModel(unbakedModel, location::toString); }); @@ -389,12 +417,14 @@ public class DynamicModelProvider { }); } - private Optional loadBlockModel(ResourceLocation location) { + private Optional loadBlockModelDefault(ResourceLocation location) { if (DEBUG_DYNAMIC_MODEL_LOADING) { ModernFix.LOGGER.info("Loading block model '{}'", location); } if (location.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) { return Optional.of(this.itemModelGenerator); + } else if (location.equals(MissingBlockModel.LOCATION)) { + return Optional.of(this.unbakedMissingModel); } var resource = this.resourceManager.getResource(ResourceLocation.fromNamespaceAndPath(location.getNamespace(), "models/" + location.getPath() + ".json")); if(resource.isPresent()) { @@ -411,6 +441,15 @@ public class DynamicModelProvider { } } + private Optional loadBlockModel(ResourceLocation location) { + Optional value = loadBlockModelDefault(location); + for (var plugin : this.pluginList) { + value = plugin.modifyModelOnLoad(value, location); + } + return value; + } + + private Optional loadClientItemProperties(ResourceLocation location) { if (DEBUG_DYNAMIC_MODEL_LOADING) { ModernFix.LOGGER.info("Loading client item '{}'", location); @@ -460,6 +499,10 @@ public class DynamicModelProvider { return this.loadedStandaloneModels.getUnchecked(location).orElse(this.missingModel); } + public void addUnbakedBlockStateOverride(ModelResourceLocation location, UnbakedBlockStateModel model) { + this.unbakedBlockStateModelOverrides.put(location, model); + } + private class DynamicBaker implements ModelBaker { private final ModelDebugName modelDebugName; @@ -469,7 +512,7 @@ public class DynamicModelProvider { @Override public BakedModel bake(ResourceLocation location, ModelState transform) { - return DynamicModelProvider.this.loadBlockModel(location).map(unbakedModel -> { + return DynamicModelProvider.this.loadedBlockModels.getUnchecked(location).map(unbakedModel -> { DynamicModelProvider.this.resolver.clearResolver(); unbakedModel.resolveDependencies(DynamicModelProvider.this.resolver); return UnbakedModel.bakeWithTopModelValues(unbakedModel, this, transform); @@ -526,4 +569,9 @@ public class DynamicModelProvider { public interface ModelManagerExtension { DynamicModelProvider mfix$getModelProvider(); } + + public interface DynamicModelPlugin { + Optional modifyModelOnLoad(Optional model, ResourceLocation id); + UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state); + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/dynamicresources/FabricDynamicModelHandler.java b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/FabricDynamicModelHandler.java new file mode 100644 index 00000000..184a512e --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/dynamicresources/FabricDynamicModelHandler.java @@ -0,0 +1,133 @@ +package org.embeddedt.modernfix.dynamicresources; + +import net.fabricmc.fabric.api.client.model.loading.v1.BlockStateResolver; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelLoadingPlugin; +import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.client.renderer.block.BlockModelShaper; +import net.minecraft.client.renderer.block.model.UnbakedBlockStateModel; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import org.embeddedt.modernfix.ModernFix; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class FabricDynamicModelHandler implements DynamicModelProvider.DynamicModelPlugin { + private final List pluginList; + + // Borrowed from Fabric API, this dispatching logic is extremely trivial + + private static final ResourceLocation[] MODEL_MODIFIER_PHASES = new ResourceLocation[] { ModelModifier.OVERRIDE_PHASE, ModelModifier.DEFAULT_PHASE, ModelModifier.WRAP_PHASE, ModelModifier.WRAP_LAST_PHASE }; + + private final Event onLoadModifiers = EventFactory.createWithPhases(ModelModifier.OnLoad.class, modifiers -> (model, context) -> { + for (ModelModifier.OnLoad modifier : modifiers) { + try { + model = modifier.modifyModelOnLoad(model, context); + } catch (Exception exception) { + ModernFix.LOGGER.error("Failed to modify unbaked model on load", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + + private final Event onLoadBlockModifiers = EventFactory.createWithPhases(ModelModifier.OnLoadBlock.class, modifiers -> (model, context) -> { + for (ModelModifier.OnLoadBlock modifier : modifiers) { + try { + model = modifier.modifyModelOnLoad(model, context); + } catch (Exception exception) { + ModernFix.LOGGER.error("Failed to modify unbaked block model on load", exception); + } + } + + return model; + }, MODEL_MODIFIER_PHASES); + + public FabricDynamicModelHandler(DynamicModelProvider provider) { + this.pluginList = ModelLoadingPlugin.getAll(); + var context = new PluginContext(provider); + for (var plugin : this.pluginList) { + plugin.initialize(context); + } + context.fireResolvers(); + } + + @Override + public Optional modifyModelOnLoad(Optional model, ResourceLocation id) { + return Optional.ofNullable(this.onLoadModifiers.invoker().modifyModelOnLoad(model.orElse(null), () -> id)); + } + + @Override + public UnbakedBlockStateModel modifyBlockModelOnLoad(UnbakedBlockStateModel model, ModelResourceLocation id, BlockState state) { + return this.onLoadBlockModifiers.invoker().modifyModelOnLoad(model, new ModelModifier.OnLoadBlock.Context() { + @Override + public ModelResourceLocation id() { + return id; + } + + @Override + public BlockState state() { + return state; + } + }); + } + + private class PluginContext implements ModelLoadingPlugin.Context { + private final DynamicModelProvider provider; + private final Map resolvers = new HashMap<>(); + + private PluginContext(DynamicModelProvider provider) { + this.provider = provider; + } + + @Override + public void addModels(ResourceLocation... ids) { + /* no-op on dynamic model loader */ + } + + @Override + public void addModels(Collection ids) { + /* no-op on dynamic model loader */ + } + + @Override + public void registerBlockStateResolver(Block block, BlockStateResolver resolver) { + resolvers.put(block, resolver); + } + + public void fireResolvers() { + resolvers.forEach((block, resolver) -> { + resolver.resolveBlockStates(new BlockStateResolver.Context() { + @Override + public Block block() { + return block; + } + + @Override + public void setModel(BlockState state, UnbakedBlockStateModel model) { + provider.addUnbakedBlockStateOverride(BlockModelShaper.stateToModelLocation(state), model); + } + }); + }); + } + + @Override + public Event modifyModelOnLoad() { + return onLoadModifiers; + } + + @Override + public Event modifyBlockModelOnLoad() { + return onLoadBlockModifiers; + } + } +} diff --git a/fabric/build.gradle b/fabric/build.gradle index 288b9f67..14e6b112 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -36,13 +36,10 @@ dependencies { modCompileOnly(fabricApi.module("fabric-model-loading-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modCompileOnly(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modCompileOnly(fabricApi.module("fabric-data-generation-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } + modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false } + modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}" if(project.use_fabric_api_at_runtime.toBoolean()) { - modImplementation("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false } - modImplementation "curse.maven:spark-361579:${rootProject.spark_version}" modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' } - } else { - modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false } - modCompileOnly "curse.maven:spark-361579:${rootProject.spark_version}" } // Remove the next line if you don't want to depend on the API