diff --git a/common/src/main/java/org/embeddedt/modernfix/api/entrypoint/ModernFixClientIntegration.java b/common/src/main/java/org/embeddedt/modernfix/api/entrypoint/ModernFixClientIntegration.java index 3d49ff1c..d6310537 100644 --- a/common/src/main/java/org/embeddedt/modernfix/api/entrypoint/ModernFixClientIntegration.java +++ b/common/src/main/java/org/embeddedt/modernfix/api/entrypoint/ModernFixClientIntegration.java @@ -1,5 +1,11 @@ package org.embeddedt.modernfix.api.entrypoint; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBakery; +import net.minecraft.client.resources.model.ModelResourceLocation; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; + /** * Implement this interface in a mod class and add it to "modernfix:integration_v1" in your mod metadata file * to integrate with ModernFix's features. @@ -13,4 +19,19 @@ public interface ModernFixClientIntegration { */ default void onDynamicResourcesStatusChange(boolean enabled) { } + + /** + * Called to allow mods to observe the loading of a baked model and either make changes to it or wrap it with their + * own instance. + * + * @param location the ResourceLocation of the model (this may be a ModelResourceLocation) + * @param originalModel the original model + * @param bakery the model bakery - do not touch internal fields as they probably don't behave the way you expect + * with dynamic resources on + * @param textureGetter function to retrieve textures for this model + * @return the model which should actually be loaded for this resource location + */ + default BakedModel onBakedModelLoad(ModelResourceLocation location, UnbakedModel baseModel, BakedModel originalModel, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter textureGetter) { + return originalModel; + } } diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelBakeryMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelBakeryMixin.java index 03db8e91..9021a357 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelBakeryMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/dynamic_resources/ModelBakeryMixin.java @@ -6,6 +6,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.client.color.block.BlockColors; import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.BlockModelRotation; import net.minecraft.client.resources.model.BlockStateModelLoader; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelResourceLocation; @@ -14,7 +15,9 @@ import net.minecraft.core.DefaultedRegistry; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.profiling.ProfilerFiller; import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.ModernFixClient; import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; import org.embeddedt.modernfix.duck.IBlockStateModelLoader; import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.util.DynamicOverridableMap; @@ -129,6 +132,9 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery { ModernFix.LOGGER.error("Failed to load model " + location); model = bakedMissingModel; } + for(ModernFixClientIntegration integration : ModernFixClient.CLIENT_INTEGRATIONS) { + model = integration.onBakedModelLoad(location, prototype, model, BlockModelRotation.X0_Y0, (ModelBakery)(Object)this, this.textureGetter); + } } } } finally { diff --git a/common/src/main/resources/modernfix.accesswidener b/common/src/main/resources/modernfix.accesswidener index 1bda0f59..7610aad7 100644 --- a/common/src/main/resources/modernfix.accesswidener +++ b/common/src/main/resources/modernfix.accesswidener @@ -41,6 +41,9 @@ accessible field net/minecraft/client/renderer/block/model/BlockModel GSON Lcom/ accessible class net/minecraft/server/level/ChunkMap$DistanceManager accessible class net/minecraft/client/resources/model/ModelBakery$BakedCacheKey accessible method net/minecraft/client/resources/model/ModelBakery$BakedCacheKey (Lnet/minecraft/resources/ResourceLocation;Lcom/mojang/math/Transformation;Z)V +accessible class net/minecraft/client/resources/model/ModelBakery$ModelBakerImpl +accessible method net/minecraft/client/resources/model/ModelBakery$ModelBakerImpl (Lnet/minecraft/client/resources/model/ModelBakery;Lnet/minecraft/client/resources/model/ModelBakery$TextureGetter;Lnet/minecraft/client/resources/model/ModelResourceLocation;)V +accessible method net/minecraft/client/resources/model/ModelBakery getModel (Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/resources/model/UnbakedModel; accessible class net/minecraft/world/level/chunk/PalettedContainer$Data accessible field net/minecraft/server/MinecraftServer resources Lnet/minecraft/server/MinecraftServer$ReloadableResources; accessible class net/minecraft/server/MinecraftServer$ReloadableResources diff --git a/gradle.properties b/gradle.properties index 72c78867..2011f61a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ parchment_version=2024.07.07 refined_storage_version=4392788 jei_version=19.0.0.9 rei_version=13.0.678 -ctm_version=1.20.1-1.1.8+4 +ctm_version=1.21-1.2.0+2 kubejs_version=1902.6.0-build.142 rhino_version=1902.2.2-build.268 supported_minecraft_versions=1.21 diff --git a/neoforge/build.gradle b/neoforge/build.gradle index ba2a7a8a..85aaf99e 100644 --- a/neoforge/build.gradle +++ b/neoforge/build.gradle @@ -54,7 +54,7 @@ dependencies { modCompileOnly("me.shedaniel:RoughlyEnoughItems-forge:${rei_version}") { transitive false } modCompileOnly("dev.latvian.mods:kubejs-forge:${kubejs_version}") //modRuntimeOnly("curse.maven:ferritecore-429235:4441949") - modCompileOnly files("deps/ctm.jar") + modCompileOnly("team.chisel.ctm:CTM:${ctm_version}") modCompileOnly("curse.maven:supermartijncore-454372:4455391") modCompileOnly("vazkii.patchouli:Patchouli:1.19.2-77") diff --git a/neoforge/deps/ctm.jar b/neoforge/deps/ctm.jar deleted file mode 100644 index 38243093..00000000 Binary files a/neoforge/deps/ctm.jar and /dev/null differ diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/CTMModelBakeryAccessor.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/CTMModelBakeryAccessor.java new file mode 100644 index 00000000..5fa45eef --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/CTMModelBakeryAccessor.java @@ -0,0 +1,16 @@ +package org.embeddedt.modernfix.neoforge.mixin.perf.dynamic_resources.ctm; + +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBakery; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(ModelBakery.class) +@ClientOnlyMixin +public interface CTMModelBakeryAccessor { + @Accessor("bakedCache") + Map mfix$getBakedCache(); +} diff --git a/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java new file mode 100644 index 00000000..a7a87d65 --- /dev/null +++ b/neoforge/src/main/java/org/embeddedt/modernfix/neoforge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java @@ -0,0 +1,114 @@ +package org.embeddedt.modernfix.neoforge.mixin.perf.dynamic_resources.ctm; + +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.resources.model.*; +import net.minecraft.resources.ResourceLocation; +import org.embeddedt.modernfix.ModernFixClient; +import org.embeddedt.modernfix.annotation.ClientOnlyMixin; +import org.embeddedt.modernfix.annotation.RequiresMod; +import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; +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 team.chisel.ctm.CTM; +import team.chisel.ctm.api.model.IModelCTM; +import team.chisel.ctm.client.model.AbstractCTMBakedModel; +import team.chisel.ctm.client.model.ModelCTM; +import team.chisel.ctm.client.texture.IMetadataSectionCTM; +import team.chisel.ctm.client.util.ResourceUtil; +import team.chisel.ctm.client.util.TextureMetadataHandler; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.*; + +@Mixin(TextureMetadataHandler.class) +@RequiresMod("ctm") +@ClientOnlyMixin +public abstract class TextureMetadataHandlerMixin implements ModernFixClientIntegration { + + @Shadow(remap = false) @Nonnull protected abstract BakedModel wrap(UnbakedModel model, BakedModel object) throws IOException; + + @Shadow(remap = false) @Final private Multimap scrapedTextures; + + @Inject(method = "", at = @At("RETURN")) + private void subscribeDynamic(CallbackInfo ci) { + ModernFixClient.CLIENT_INTEGRATIONS.add(this); + } + + @Inject(method = { "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$BakingCompleted;)V", "onModelBake(Lnet/neoforged/neoforge/client/event/ModelEvent$ModifyBakingResult;)V" }, at = @At("HEAD"), cancellable = true, remap = false) + private void noIteration(CallbackInfo ci) { + ci.cancel(); + } + + @Override + public BakedModel onBakedModelLoad(ModelResourceLocation mrl, UnbakedModel rootModel, BakedModel baked, ModelState state, ModelBakery bakery, ModelBakery.TextureGetter getter) { + if (!(baked instanceof AbstractCTMBakedModel) && !baked.isCustomRenderer()) { + Deque dependencies = new ArrayDeque<>(); + Set seenModels = new HashSet<>(); + dependencies.push(mrl.id()); + seenModels.add(mrl.id()); + boolean shouldWrap = false; + Set> errors = new HashSet<>(); + // Breadth-first loop through dependencies, exiting as soon as a CTM texture is found, and skipping duplicates/cycles + while (!shouldWrap && !dependencies.isEmpty()) { + ResourceLocation dep = dependencies.pop(); + UnbakedModel model; + try { + model = dep == mrl.id() ? rootModel : bakery.getModel(dep); + } catch (Exception e) { + continue; + } + + Collection textures = Sets.newHashSet(scrapedTextures.get(dep)); + Collection newDependencies = model.getDependencies(); + for (Material tex : textures) { + IMetadataSectionCTM meta = null; + // Cache all dependent texture metadata + try { + meta = ResourceUtil.getMetadata(ResourceUtil.spriteToAbsolute(tex.texture())).orElse(null); // TODO, lazy + } catch (IOException e) {} // Fallthrough + if (meta != null) { + // At least one texture has CTM metadata, so we should wrap this model + shouldWrap = true; + } + } + + for (ResourceLocation newDep : newDependencies) { + if (seenModels.add(newDep)) { + dependencies.push(newDep); + } + } + } + if (shouldWrap) { + try { + baked = wrap(rootModel, baked); + handleInit(mrl, baked, bakery, getter); + dependencies.clear(); + } catch (IOException e) { + CTM.logger.error("Could not wrap model " + mrl + ". Aborting...", e); + } + } + } + return baked; + } + + private void handleInit(ModelResourceLocation key, BakedModel wrappedModel, ModelBakery bakery, ModelBakery.TextureGetter spriteGetter) { + if(wrappedModel instanceof AbstractCTMBakedModel baked) { + IModelCTM var10 = baked.getModel(); + if (var10 instanceof ModelCTM ctmModel) { + if (!ctmModel.isInitialized()) { + // Clear the baked cache as upstream CTM does + ((CTMModelBakeryAccessor)bakery).mfix$getBakedCache().clear(); + ModelBakery.ModelBakerImpl baker = bakery.new ModelBakerImpl(spriteGetter, key); + ctmModel.bake(baker, Material::sprite, BlockModelRotation.X0_Y0); + } + } + } + } +} \ No newline at end of file