From e7d584101ddf9545ab926bd4f5cf7aa9c7dbe9af Mon Sep 17 00:00:00 2001 From: Lincoln-LM <73306575+Lincoln-LM@users.noreply.github.com> Date: Wed, 23 Jul 2025 04:59:34 -0600 Subject: [PATCH] feature: allow providing chunked animated texture files to bypass 16384px height limit --- .../customization/AnimatedTexture.java | 80 +++++++++++++------ .../customization/AnimationFrameMetadata.java | 14 ++++ .../seedqueue/customization/LockTexture.java | 15 +++- .../gui/wall/SeedQueueWallScreen.java | 34 ++++---- 4 files changed, 100 insertions(+), 43 deletions(-) create mode 100644 src/main/java/me/contaria/seedqueue/customization/AnimationFrameMetadata.java diff --git a/src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java b/src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java index 13adfb5a..fefa17d0 100644 --- a/src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java +++ b/src/main/java/me/contaria/seedqueue/customization/AnimatedTexture.java @@ -1,47 +1,77 @@ package me.contaria.seedqueue.customization; import me.contaria.seedqueue.SeedQueue; +import me.contaria.speedrunapi.util.IdentifierUtil; import net.minecraft.client.MinecraftClient; import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.util.Identifier; -import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.annotations.Nullable; public class AnimatedTexture { - private final Identifier id; - @Nullable - protected final AnimationResourceMetadata animation; - - protected AnimatedTexture(Identifier id) { - this.id = id; - AnimationResourceMetadata animation = null; - try { - animation = MinecraftClient.getInstance().getResourceManager().getResource(id).getMetadata(AnimationResourceMetadata.READER); - } catch (IOException e) { - SeedQueue.LOGGER.warn("Failed to read animation data for {}!", id, e); - } - this.animation = animation; - } + protected final List ids = new ArrayList<>(); + protected final List<@Nullable AnimationResourceMetadata> animations = new ArrayList<>(); + private int totalAnimationDuration = 0; - public Identifier getId() { - return this.id; + protected AnimatedTexture(List ids) { + for (Identifier id : ids) { + if (!MinecraftClient.getInstance().getResourceManager().containsResource(id)) { + continue; + } + try { + AnimationResourceMetadata animation = MinecraftClient.getInstance().getResourceManager().getResource(id).getMetadata(AnimationResourceMetadata.READER); + this.ids.add(id); + this.animations.add(animation); + if (animation == null) { + continue; + } + this.totalAnimationDuration += animation.getDefaultFrameTime() * animation.getFrameCount(); + } catch (IOException e) { + SeedQueue.LOGGER.warn("Failed to read animation data for {}!", id, e); + } + } + this.totalAnimationDuration = Math.max(1, this.totalAnimationDuration); // avoid division by zero } - public int getFrameIndex(int tick) { + public AnimationFrameMetadata getFrame(int tick) { // does not currently support setting frametime for individual frames // see AnimationFrameResourceMetadata#usesDefaultFrameTime - return this.animation != null ? this.animation.getFrameIndex((tick / this.animation.getDefaultFrameTime()) % this.animation.getFrameCount()) : 0; + int animationPosition = tick % this.totalAnimationDuration; + for (int i = 0; i < this.animations.size(); i++) { + AnimationResourceMetadata animation = this.animations.get(i); + if (animation == null) { + continue; + } + int duration = animation.getDefaultFrameTime() * animation.getFrameCount(); + if (animationPosition < duration) { + return new AnimationFrameMetadata(this.ids.get(i), animation.getFrameIndexSet().size(), animation.getFrameIndex(animationPosition / animation.getDefaultFrameTime())); + } + animationPosition -= duration; + } + return new AnimationFrameMetadata(this.ids.get(0), 1, 0); } - public int getIndividualFrameCount() { - return this.animation != null ? this.animation.getFrameIndexSet().size() : 1; + @Nullable + public static AnimatedTexture ofChunks(String namespace, String pathPrefix, String pathSuffix) { + List chunks = new ArrayList<>(); + Identifier chunk = IdentifierUtil.of(namespace, pathPrefix + pathSuffix); + do { + chunks.add(chunk); + } while (MinecraftClient.getInstance().getResourceManager().containsResource(chunk = IdentifierUtil.of(namespace, pathPrefix + "-" + chunks.size() + pathSuffix))); + return AnimatedTexture.of(chunks); } - public static @Nullable AnimatedTexture of(Identifier id) { - if (MinecraftClient.getInstance().getResourceManager().containsResource(id)) { - return new AnimatedTexture(id); + @Nullable + public static AnimatedTexture of(List ids) { + AnimatedTexture texture = new AnimatedTexture(ids); + if (texture.ids.isEmpty()) { + SeedQueue.LOGGER.warn("No valid animated textures found for identifiers: {}", ids); + return null; } - return null; + return texture; } } diff --git a/src/main/java/me/contaria/seedqueue/customization/AnimationFrameMetadata.java b/src/main/java/me/contaria/seedqueue/customization/AnimationFrameMetadata.java new file mode 100644 index 00000000..44a03d04 --- /dev/null +++ b/src/main/java/me/contaria/seedqueue/customization/AnimationFrameMetadata.java @@ -0,0 +1,14 @@ +package me.contaria.seedqueue.customization; + +import net.minecraft.util.Identifier; + +public class AnimationFrameMetadata { + public final Identifier animationId; + public final int animationFrameCount; + public final int frameIndex; + public AnimationFrameMetadata(Identifier animationId, int animationFrameCount, int frameIndex) { + this.animationId = animationId; + this.animationFrameCount = animationFrameCount; + this.frameIndex = frameIndex; + } +} diff --git a/src/main/java/me/contaria/seedqueue/customization/LockTexture.java b/src/main/java/me/contaria/seedqueue/customization/LockTexture.java index f450caa3..802501eb 100644 --- a/src/main/java/me/contaria/seedqueue/customization/LockTexture.java +++ b/src/main/java/me/contaria/seedqueue/customization/LockTexture.java @@ -3,25 +3,32 @@ import me.contaria.seedqueue.SeedQueue; import me.contaria.speedrunapi.util.IdentifierUtil; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.resource.metadata.AnimationResourceMetadata; import net.minecraft.client.texture.NativeImage; import net.minecraft.util.Identifier; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class LockTexture extends AnimatedTexture { private final int width; private final int height; - public LockTexture(Identifier id) throws IOException { - super(id); - try (NativeImage image = NativeImage.read(MinecraftClient.getInstance().getResourceManager().getResource(id).getInputStream())) { + public LockTexture(List ids) throws IOException { + super(ids); + try (NativeImage image = NativeImage.read(MinecraftClient.getInstance().getResourceManager().getResource(this.ids.get(0)).getInputStream())) { + AnimationResourceMetadata animation = this.animations.get(0); this.width = image.getWidth(); - this.height = image.getHeight() / (this.animation != null ? this.animation.getFrameIndexSet().size() : 1); + this.height = image.getHeight() / (animation != null ? animation.getFrameIndexSet().size() : 1); } } + public LockTexture(Identifier id) throws IOException { + this(Collections.singletonList(id)); + } + public double getAspectRatio() { return (double) this.width / this.height; } diff --git a/src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueWallScreen.java b/src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueWallScreen.java index 318b786c..e6fbc827 100644 --- a/src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueWallScreen.java +++ b/src/main/java/me/contaria/seedqueue/gui/wall/SeedQueueWallScreen.java @@ -12,6 +12,7 @@ import me.contaria.seedqueue.compat.SeedQueuePreviewProperties; import me.contaria.seedqueue.compat.SeedQueueSettingsCache; import me.contaria.seedqueue.customization.AnimatedTexture; +import me.contaria.seedqueue.customization.AnimationFrameMetadata; import me.contaria.seedqueue.customization.Layout; import me.contaria.seedqueue.customization.LockTexture; import me.contaria.seedqueue.debug.SeedQueueProfiler; @@ -47,10 +48,13 @@ public class SeedQueueWallScreen extends Screen { private static final Set WORLD_RENDERERS = new HashSet<>(); public static final Identifier CUSTOM_LAYOUT = IdentifierUtil.of("seedqueue", "wall/custom_layout.json"); - private static final Identifier WALL_BACKGROUND = IdentifierUtil.of("seedqueue", "textures/gui/wall/background.png"); - private static final Identifier WALL_OVERLAY = IdentifierUtil.of("seedqueue", "textures/gui/wall/overlay.png"); - private static final Identifier INSTANCE_BACKGROUND = IdentifierUtil.of("seedqueue", "textures/gui/wall/instance_background.png"); - private static final Identifier INSTANCE_OVERLAY = IdentifierUtil.of("seedqueue", "textures/gui/wall/instance_overlay.png"); + + public static final String TEXTURE_NAMESPACE = "seedqueue"; + private static final String WALL_BACKGROUND_PREFIX = "textures/gui/wall/background"; + private static final String WALL_OVERLAY_PREFIX = "textures/gui/wall/overlay"; + private static final String INSTANCE_BACKGROUND_PREFIX = "textures/gui/wall/instance_background"; + private static final String INSTANCE_OVERLAY_PREFIX = "textures/gui/wall/instance_overlay"; + public static final String TEXTURE_SUFFIX = ".png"; private static boolean renderingPreview; @@ -113,10 +117,10 @@ protected void init() { this.lockedPreviews = this.layout.locked != null ? new ArrayList<>() : null; this.preparingPreviews = new ArrayList<>(); this.lockTextures = LockTexture.createLockTextures(); - this.background = AnimatedTexture.of(WALL_BACKGROUND); - this.overlay = AnimatedTexture.of(WALL_OVERLAY); - this.instanceBackground = AnimatedTexture.of(INSTANCE_BACKGROUND); - this.instanceOverlay = AnimatedTexture.of(INSTANCE_OVERLAY); + this.background = AnimatedTexture.ofChunks(TEXTURE_NAMESPACE, WALL_BACKGROUND_PREFIX, TEXTURE_SUFFIX); + this.overlay = AnimatedTexture.ofChunks(TEXTURE_NAMESPACE, WALL_OVERLAY_PREFIX, TEXTURE_SUFFIX); + this.instanceBackground = AnimatedTexture.ofChunks(TEXTURE_NAMESPACE, INSTANCE_BACKGROUND_PREFIX, TEXTURE_SUFFIX); + this.instanceOverlay = AnimatedTexture.ofChunks(TEXTURE_NAMESPACE, INSTANCE_OVERLAY_PREFIX, TEXTURE_SUFFIX); } protected LockTexture getRandomLockTexture() { @@ -260,35 +264,37 @@ private void renderInstanceOverlay(Layout.Group group, MatrixStack matrices) { private void drawLock(MatrixStack matrices, Layout.Pos pos, LockTexture lock) { this.setOrtho(this.client.getWindow().getFramebufferWidth(), this.client.getWindow().getFramebufferHeight()); - this.client.getTextureManager().bindTexture(lock.getId()); + AnimationFrameMetadata frame = lock.getFrame(this.ticks); + this.client.getTextureManager().bindTexture(frame.animationId); DrawableHelper.drawTexture( matrices, pos.x, pos.y, 0.0f, - lock.getFrameIndex(this.ticks) * pos.height, + frame.frameIndex * pos.height, (int) Math.min(pos.width, pos.height * lock.getAspectRatio()), pos.height, (int) (pos.height * lock.getAspectRatio()), - pos.height * lock.getIndividualFrameCount() + pos.height * frame.animationFrameCount ); this.resetOrtho(); } @SuppressWarnings("SameParameterValue") private void drawAnimatedTexture(AnimatedTexture texture, MatrixStack matrices, int x, int y, int width, int height) { - this.client.getTextureManager().bindTexture(texture.getId()); + AnimationFrameMetadata frame = texture.getFrame(this.ticks); + this.client.getTextureManager().bindTexture(frame.animationId); RenderSystem.enableBlend(); DrawableHelper.drawTexture( matrices, x, y, 0.0f, - texture.getFrameIndex(this.ticks) * height, + frame.frameIndex * height, width, height, width, - height * texture.getIndividualFrameCount() + height * frame.animationFrameCount ); RenderSystem.disableBlend(); }