From 39f9f393db359f4d275064d44035432041d09daa Mon Sep 17 00:00:00 2001 From: Take Weiland Date: Fri, 16 Jul 2021 01:55:43 +0200 Subject: [PATCH] Add custom TextureAtlasSprite loaders (#7822) Adds a system for mods to specify custom texture loaders which can be requested by the resource pack through a metadata section in the .mcmeta file. Due to technical reasons, png file is still required for MC to identify a texture as existing, even if the loader doesn't use it. --- .../renderer/texture/AtlasTexture.java.patch | 9 ++ .../client/ForgeHooksClient.java | 17 +++ .../client/MinecraftForgeClient.java | 20 ++++ .../extensions/IForgeTextureAtlasSprite.java | 20 ++-- .../client/textures/ForgeTextureMetadata.java | 98 ++++++++++++++++++ .../textures/ITextureAtlasSpriteLoader.java | 59 +++++++++++ .../debug/client/CustomTASTest.java | 98 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + .../models/item/test_item.json | 6 ++ .../textures/item/test_item.png | Bin 0 -> 86 bytes .../textures/item/test_item.png.mcmeta | 5 + 11 files changed, 320 insertions(+), 14 deletions(-) create mode 100644 src/main/java/net/minecraftforge/client/textures/ForgeTextureMetadata.java create mode 100644 src/main/java/net/minecraftforge/client/textures/ITextureAtlasSpriteLoader.java create mode 100644 src/test/java/net/minecraftforge/debug/client/CustomTASTest.java create mode 100644 src/test/resources/assets/custom_tas_test/models/item/test_item.json create mode 100644 src/test/resources/assets/custom_tas_test/textures/item/test_item.png create mode 100644 src/test/resources/assets/custom_tas_test/textures/item/test_item.png.mcmeta diff --git a/patches/minecraft/net/minecraft/client/renderer/texture/AtlasTexture.java.patch b/patches/minecraft/net/minecraft/client/renderer/texture/AtlasTexture.java.patch index 952c087f70..3299f161cd 100644 --- a/patches/minecraft/net/minecraft/client/renderer/texture/AtlasTexture.java.patch +++ b/patches/minecraft/net/minecraft/client/renderer/texture/AtlasTexture.java.patch @@ -26,3 +26,12 @@ if (j1 < p_229220_4_) { field_147635_d.warn("{}: dropping miplevel from {} to {}, because of minimum power of two: {}", this.field_229214_j_, p_229220_4_, j1, i1); k1 = j1; +@@ -200,6 +_,8 @@ + + try (IResource iresource = p_229218_1_.func_199002_a(resourcelocation)) { + NativeImage nativeimage = NativeImage.func_195713_a(iresource.func_199027_b()); ++ TextureAtlasSprite customSprite = net.minecraftforge.client.ForgeHooksClient.loadTextureAtlasSprite(this, p_229218_1_, p_229218_2_, iresource, p_229218_3_, p_229218_4_, p_229218_6_, p_229218_7_, p_229218_5_, nativeimage); ++ if (customSprite != null) return customSprite; + return new TextureAtlasSprite(this, p_229218_2_, p_229218_5_, p_229218_3_, p_229218_4_, p_229218_6_, p_229218_7_, nativeimage); + } catch (RuntimeException runtimeexception) { + field_147635_d.error("Unable to parse metadata from {}", resourcelocation, runtimeexception); diff --git a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java index f401332452..173dbebfc8 100644 --- a/src/main/java/net/minecraftforge/client/ForgeHooksClient.java +++ b/src/main/java/net/minecraftforge/client/ForgeHooksClient.java @@ -61,6 +61,8 @@ import net.minecraft.fluid.FluidState; import net.minecraft.inventory.EquipmentSlotType; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.RecipeManager; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; import net.minecraft.util.Direction; import net.minecraft.util.Hand; import net.minecraft.util.MovementInput; @@ -84,6 +86,7 @@ import net.minecraftforge.client.event.*; import net.minecraftforge.client.event.sound.PlaySoundEvent; import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.animation.Animation; +import net.minecraftforge.client.textures.ForgeTextureMetadata; import net.minecraftforge.common.ForgeMod; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.model.TransformationHelper; @@ -822,4 +825,18 @@ public class ForgeHooksClient { return Optional.of(ForgeWorldTypeScreens.getDefaultGenerator()); } + + @Nullable + public static TextureAtlasSprite loadTextureAtlasSprite( + AtlasTexture atlasTexture, + IResourceManager resourceManager, TextureAtlasSprite.Info textureInfo, + IResource resource, + int atlasWidth, int atlasHeight, + int spriteX, int spriteY, int mipmapLevel, + NativeImage image + ) + { + ForgeTextureMetadata metadata = ForgeTextureMetadata.forResource(resource); + return metadata.getLoader() == null ? null : metadata.getLoader().load(atlasTexture, resourceManager, textureInfo, resource, atlasWidth, atlasHeight, spriteX, spriteY, mipmapLevel, image); + } } diff --git a/src/main/java/net/minecraftforge/client/MinecraftForgeClient.java b/src/main/java/net/minecraftforge/client/MinecraftForgeClient.java index 28ab7094b7..62f0ea00fd 100644 --- a/src/main/java/net/minecraftforge/client/MinecraftForgeClient.java +++ b/src/main/java/net/minecraftforge/client/MinecraftForgeClient.java @@ -23,7 +23,9 @@ import java.io.IOException; import java.util.BitSet; import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -45,6 +47,7 @@ import net.minecraft.resources.IResourceManager; import net.minecraft.util.ResourceLocation; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; +import net.minecraftforge.client.textures.ITextureAtlasSpriteLoader; public class MinecraftForgeClient { @@ -156,4 +159,21 @@ public class MinecraftForgeClient IResource iresource1 = resourceManager.getResource(resourceLocation); return NativeImage.read(iresource1.getInputStream()); } + + private static final Map textureAtlasSpriteLoaders = new ConcurrentHashMap<>(); + + /** + * Register a custom ITextureAtlasSprite loader. Call this method during {@link net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent}. + */ + public static void registerTextureAtlasSpriteLoader(ResourceLocation name, ITextureAtlasSpriteLoader loader) + { + textureAtlasSpriteLoaders.put(name, loader); + } + + @Nullable + public static ITextureAtlasSpriteLoader getTextureAtlasSpriteLoader(ResourceLocation name) + { + return textureAtlasSpriteLoaders.get(name); + } + } diff --git a/src/main/java/net/minecraftforge/client/extensions/IForgeTextureAtlasSprite.java b/src/main/java/net/minecraftforge/client/extensions/IForgeTextureAtlasSprite.java index 7614b3c374..eedc9a3f9b 100644 --- a/src/main/java/net/minecraftforge/client/extensions/IForgeTextureAtlasSprite.java +++ b/src/main/java/net/minecraftforge/client/extensions/IForgeTextureAtlasSprite.java @@ -30,34 +30,26 @@ import net.minecraft.util.ResourceLocation; public interface IForgeTextureAtlasSprite { + /** - * The result of this function determines is the below 'load' function is called, and the - * default vanilla loading code is bypassed completely. - * @param manager Main resource manager - * @param location File resource location - * @return True to use your own custom load code and bypass vanilla loading. + * @deprecated See {@link net.minecraftforge.client.textures.ITextureAtlasSpriteLoader} */ + @Deprecated default boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { return false; } /** - * Load the specified resource as this sprite's data. - * Returning false from this function will prevent this icon from being stitched onto the master texture. - * @param manager Main resource manager - * @param location File resource location - * @param textureGetter accessor for dependencies. All of them will be loaded before this one - * @return False to prevent this Icon from being stitched + * @deprecated See {@link net.minecraftforge.client.textures.ITextureAtlasSpriteLoader} */ + @Deprecated default boolean load(IResourceManager manager, ResourceLocation location, Function textureGetter) { return true; } - /** - * @return all textures that should be loaded before this texture. - */ + @Deprecated default Collection getDependencies() { return ImmutableList.of(); diff --git a/src/main/java/net/minecraftforge/client/textures/ForgeTextureMetadata.java b/src/main/java/net/minecraftforge/client/textures/ForgeTextureMetadata.java new file mode 100644 index 0000000000..976ac1dae4 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/textures/ForgeTextureMetadata.java @@ -0,0 +1,98 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.textures; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import net.minecraft.resources.IResource; +import net.minecraft.resources.data.IMetadataSectionSerializer; +import net.minecraft.util.JSONUtils; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.MinecraftForgeClient; + +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; + +/** + * The "forge" section of texture metadata files (.mcmeta). Currently used only to specify custom + * TextureAtlasSprite loaders. + * @see ITextureAtlasSpriteLoader + */ +public final class ForgeTextureMetadata +{ + + public static final ForgeTextureMetadata EMPTY = new ForgeTextureMetadata(null); + public static final IMetadataSectionSerializer SERIALIZER = new Serializer(); + + public static ForgeTextureMetadata forResource(IResource resource) + { + ForgeTextureMetadata metadata = resource.getMetadata(SERIALIZER); + return metadata == null ? EMPTY : metadata; + } + + @Nullable + private final ITextureAtlasSpriteLoader loader; + + public ForgeTextureMetadata(@Nullable ITextureAtlasSpriteLoader loader) + { + this.loader = loader; + } + + @Nullable + public ITextureAtlasSpriteLoader getLoader() + { + return loader; + } + + private static final class Serializer implements IMetadataSectionSerializer + { + + @Override + @Nonnull + public String getMetadataSectionName() + { + return "forge"; + } + + @Override + @Nonnull + public ForgeTextureMetadata fromJson(JsonObject json) + { + @Nullable + ITextureAtlasSpriteLoader loader; + if (json.has("loader")) + { + ResourceLocation loaderName = new ResourceLocation(JSONUtils.getAsString(json, "loader")); + loader = MinecraftForgeClient.getTextureAtlasSpriteLoader(loaderName); + if (loader == null) + { + throw new JsonSyntaxException("Unknown TextureAtlasSpriteLoader " + loaderName); + } + } + else + { + loader = null; + } + return new ForgeTextureMetadata(loader); + } + } + +} diff --git a/src/main/java/net/minecraftforge/client/textures/ITextureAtlasSpriteLoader.java b/src/main/java/net/minecraftforge/client/textures/ITextureAtlasSpriteLoader.java new file mode 100644 index 0000000000..a1ddabffb0 --- /dev/null +++ b/src/main/java/net/minecraftforge/client/textures/ITextureAtlasSpriteLoader.java @@ -0,0 +1,59 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.client.textures; + +import javax.annotation.Nonnull; + +import net.minecraft.client.renderer.texture.AtlasTexture; +import net.minecraft.client.renderer.texture.NativeImage; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; + +/** + * A loader for custom TextureAtlasSprite implementations.
+ * The loader can be specified in the corresponding .mcmeta file for a texture as follows: + *
+ * {
+ *   "forge": {
+ *     "loader": "examplemod:example_tas_loader"
+ *   }
+ * }
+ * 
+ * @see net.minecraftforge.client.MinecraftForgeClient#registerTextureAtlasSpriteLoader(ResourceLocation, ITextureAtlasSpriteLoader) + */ +public interface ITextureAtlasSpriteLoader +{ + + /** + * Load a TextureAtlasSprite for the given resource. + */ + @Nonnull + TextureAtlasSprite load( + AtlasTexture atlas, + IResourceManager resourceManager, TextureAtlasSprite.Info textureInfo, + IResource resource, + int atlasWidth, int atlasHeight, + int spriteX, int spriteY, int mipmapLevel, + NativeImage image + ); + +} diff --git a/src/test/java/net/minecraftforge/debug/client/CustomTASTest.java b/src/test/java/net/minecraftforge/debug/client/CustomTASTest.java new file mode 100644 index 0000000000..337262df26 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/client/CustomTASTest.java @@ -0,0 +1,98 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2021. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.client; + +import java.util.Random; + +import javax.annotation.Nonnull; + +import net.minecraft.client.renderer.texture.AtlasTexture; +import net.minecraft.client.renderer.texture.NativeImage; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.item.Item; +import net.minecraft.resources.IResource; +import net.minecraft.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.MinecraftForgeClient; +import net.minecraftforge.client.textures.ITextureAtlasSpriteLoader; +import net.minecraftforge.fml.RegistryObject; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; + +@Mod(CustomTASTest.MOD_ID) +public class CustomTASTest +{ + + private static final boolean enabled = true; + static final String MOD_ID = "custom_tas_test"; + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + private static final RegistryObject TEST_ITEM = ITEMS.register("test_item", () -> new Item(new Item.Properties())); + + public CustomTASTest() + { + if (enabled) + { + if (FMLLoader.getDist().isClient()) + { + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup); + } + ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); + } + } + + private void clientSetup(FMLClientSetupEvent event) + { + MinecraftForgeClient.registerTextureAtlasSpriteLoader(new ResourceLocation(MOD_ID, "tas_loader"), new TasLoader()); + } + + private static class TasLoader implements ITextureAtlasSpriteLoader + { + @Override + @Nonnull + public TextureAtlasSprite load(AtlasTexture atlas, IResourceManager resourceManager, TextureAtlasSprite.Info textureInfo, IResource resource, int atlasWidth, int atlasHeight, int spriteX, int spriteY, int mipmapLevel, NativeImage image) + { + return new TextureAtlasSprite( + atlas, textureInfo, mipmapLevel, atlasWidth, atlasHeight, spriteX, spriteY, image + ) + { + + private final Random random = new Random(); + + @Override + public boolean isAnimation() + { + return true; + } + + @Override + public void cycleFrames() + { + this.mainImage[0].fillRect(0, 0, 16, 16, 0xFF000000 | random.nextInt(0xFFFFFF)); + this.uploadFirstFrame(); + } + }; + } + } + +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 9f467775d7..d90d9ce691 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -124,3 +124,5 @@ license="LGPL v2.1" modId="entity_teleport_event_test" [[mods]] modId="text_linear_filtering_test" +[[mods]] + modId="custom_tas_test" \ No newline at end of file diff --git a/src/test/resources/assets/custom_tas_test/models/item/test_item.json b/src/test/resources/assets/custom_tas_test/models/item/test_item.json new file mode 100644 index 0000000000..0b8b342d5f --- /dev/null +++ b/src/test/resources/assets/custom_tas_test/models/item/test_item.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/handheld", + "textures": { + "layer0": "custom_tas_test:item/test_item" + } +} \ No newline at end of file diff --git a/src/test/resources/assets/custom_tas_test/textures/item/test_item.png b/src/test/resources/assets/custom_tas_test/textures/item/test_item.png new file mode 100644 index 0000000000000000000000000000000000000000..572dcf6822ca31caea90d173e707607563241c92 GIT binary patch literal 86 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`vYsxEAr_~TpPXIubP0l+XkKV5b(x literal 0 HcmV?d00001 diff --git a/src/test/resources/assets/custom_tas_test/textures/item/test_item.png.mcmeta b/src/test/resources/assets/custom_tas_test/textures/item/test_item.png.mcmeta new file mode 100644 index 0000000000..2c5786ad33 --- /dev/null +++ b/src/test/resources/assets/custom_tas_test/textures/item/test_item.png.mcmeta @@ -0,0 +1,5 @@ +{ + "forge": { + "loader": "custom_tas_test:tas_loader" + } +} \ No newline at end of file