From 81fc25c33d32aa3a8319cb9db096bbf497f185bc Mon Sep 17 00:00:00 2001 From: retucio Date: Mon, 20 Jul 2026 22:17:44 +0200 Subject: [PATCH] v5.5 --- cambios.md | 8 +- credits.txt | 4 +- gradle.properties | 2 +- .../sputnik/command/CommandManager.java | 1 + .../command/commands/PanoramaCommand.java | 265 ++++++++++++++++++ .../mixin/mixins/io/ScreenshotMixin.java | 9 +- .../me/retucio/sputnik/util/MiscUtil.java | 23 ++ .../resources/assets/sputnik/lang/en_us.json | 7 +- .../resources/assets/sputnik/lang/es_es.json | 5 + 9 files changed, 315 insertions(+), 9 deletions(-) create mode 100644 src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java diff --git a/cambios.md b/cambios.md index 712c18b..04dc1b4 100644 --- a/cambios.md +++ b/cambios.md @@ -1,4 +1,4 @@ -# v5.4.1 +# v5.5 ## novedades @@ -6,9 +6,13 @@ #### módulos nuevos: +#### comandos nuevos: + +- `$panorama`: construye un pack de texturas con una panorámica basada en tu posición actual. + ### arreglos / mejoras -- actualizado todos los links a GitHub para reflejar la migración a Codeberg. +- arreglado ScreenshotMixin, es decir, que ya no crasheará al tomar capturas ## problemas reconocidos diff --git a/credits.txt b/credits.txt index d9ebebe..43ffc49 100644 --- a/credits.txt +++ b/credits.txt @@ -10,6 +10,8 @@ elytra-recast: https://github.com/InLieuOfLuna/elytra-recast/ SpearHax: https://github.com/etianl/spearHax/ ProjectileTrajectoryPreview: https://github.com/maDU59/ProjectilesTrajectoryPreview DupersUnited public addon: https://discord.gg/dupes | https://dupers.wtf +Stardust: https://github.com/0xTas/stardust/ probablemente más pero como tengo demencia se me olvida -si quieres que te acredite o quite tu código de mi mod, mándame un mensaje en discord (username: retucio) y sin problemas :) \ No newline at end of file +si quieres que te acredite o quite tu código de mi mod, mándame un mensaje en discord (username: retucio) y sin problemas :) +también es probable que haya créditos sueltos en forma de comentarios donde haya usado código ajeno \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bab18dd..3514ceb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,6 +10,6 @@ discord_rpc_version=1.0.3 neutrino_version=1.1.0 jdk_version=25 -mod_version=5.4 +mod_version=5.5 maven_group=me.retucio archives_base_name=sputnik \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/command/CommandManager.java b/src/main/java/me/retucio/sputnik/command/CommandManager.java index cb8c10c..38c6e36 100644 --- a/src/main/java/me/retucio/sputnik/command/CommandManager.java +++ b/src/main/java/me/retucio/sputnik/command/CommandManager.java @@ -38,6 +38,7 @@ public class CommandManager { addCommand(new EnderChestCommand()); addCommand(new FriendCommand()); addCommand(new GarbageCleanerCommand()); + addCommand(new PanoramaCommand()); addCommand(new PeekCommand()); addCommand(new PluginsCommand()); addCommand(new PrefixCommand()); diff --git a/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java b/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java new file mode 100644 index 0000000..72cad26 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/command/commands/PanoramaCommand.java @@ -0,0 +1,265 @@ +package me.retucio.sputnik.command.commands; + + +import com.github.retucio.neutrino.EventListener; +import me.retucio.sputnik.Sputnik; +import me.retucio.sputnik.command.Command; +import me.retucio.sputnik.event.TickEvent; +import me.retucio.sputnik.util.ChatUtil; +import me.retucio.sputnik.util.MiscUtil; + +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.SharedConstants; +import net.minecraft.WorldVersion; +import net.minecraft.client.Screenshot; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.pack.PackFormat; +import net.minecraft.sounds.SoundEvents; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.arguments.StringArgumentType; + +import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.ThreadLocalRandom; + + +/** + * literalmente robado de aquí + */ + +public class PanoramaCommand extends Command { + + private int i = -1; + int timer = 10; + + boolean ready = false; + boolean warming = false; + + private float prevYaw; + private float prevPitch; + + private int prevWidth; + private int prevHeight; + + private boolean prevHudHidden; + + @Nullable Path dir = null; + + public PanoramaCommand() { + super("panorama", Component.translatable("sputnik.commands.panorama.desc").getString()); + } + + private void takeScreenshot() { + if (dir == null) return; + + Screenshot.grab( + dir.toFile(), + "panorama_" + i + ".png", + mc.gameRenderer.mainRenderTarget(), + 1, msg -> Sputnik.LOGGER.info(msg.getString()) + ); + + i++; + warming = false; + } + + private void begin(String name) { + if (mc.player == null) return; + + dir = FabricLoader.getInstance().getGameDir().resolve(Sputnik.MOD_ID + "/panoramas/" + name); + dir.toFile().mkdirs(); + + i = 0; + warming = false; + + prevYaw = mc.player.getYRot(); + prevPitch = mc.player.getXRot(); + prevWidth = mc.getWindow().getWidth(); + prevHeight = mc.getWindow().getHeight(); + prevHudHidden = mc.gui.hud.isHidden(); + + mc.getWindow().setWidth(4096); + mc.getWindow().setHeight(4096); + mc.gameRenderer.mainRenderTarget().resize(4096, 4096); + } + + private void assembleResourcePack() { + if (dir == null) return; + + Path resourcePacks = FabricLoader.getInstance().getGameDir().resolve("resourcepacks"); + Path screenshotsFolder = FabricLoader.getInstance().getGameDir().resolve(Sputnik.MOD_ID + "/panoramas/" + dir.getFileName() + "/screenshots"); + Path customBaseFolder = resourcePacks.resolve(dir.getFileName()); + Path customPackFolder = customBaseFolder.resolve("assets/minecraft/textures/gui/title/background/"); + customPackFolder.toFile().mkdirs(); + + String mcMeta = + """ + { + "pack": + { + "min_format": [65, 0], + "max_format": [%d, 0], + "description": "%s" + } + } + """.formatted( + getPackVersion(), + Component.translatable("sputnik.commands.panorama.packdesc").getString() + ); + + try { + int luckyNumber = ThreadLocalRandom.current().nextInt(6); + Files.walkFileTree(screenshotsFolder, new SimpleFileVisitor<>() { + public @NonNull FileVisitResult visitFile(@NonNull Path file, @NonNull BasicFileAttributes attrs) { + MiscUtil.copy(file.toFile(), customPackFolder.resolve(file.getFileName()).toFile()); + + // pack.png random de alguna de los screenshots + if (file.getFileName().toString().contains(String.valueOf(luckyNumber))) { + MiscUtil.copy(file.toFile(), customBaseFolder.resolve("pack.png").toFile()); + } + + return FileVisitResult.CONTINUE; + } + }); + + File packMeta = customBaseFolder.resolve("pack.mcmeta").toFile(); + if (!packMeta.createNewFile()) { + ChatUtil.error(Component.translatable("sputnik.commands.panorama.errorassemble").getString()); + return; + } + Files.write(packMeta.toPath(), mcMeta.getBytes()); + + // eliminar el efecto vignette feo de cojones que usa Minecraft por defecto + BufferedImage transparentOverlay = new BufferedImage(16, 128, BufferedImage.TYPE_INT_ARGB); + File overlayImage = customPackFolder.resolve("panorama_overlay.png").toFile(); + ImageIO.write(transparentOverlay, "PNG", overlayImage); + } catch (Exception e) { + ChatUtil.error("error de i/o"); + Sputnik.LOGGER.error(e); + } + + ready = false; + if (mc.player != null) { + mc.player.playSound(SoundEvents.AMETHYST_BLOCK_CHIME, 1f, 1f); + ChatUtil.info(Component.translatable("sputnik.commands.panorama.ready").getString()); + } + } + + @EventListener + private void onTick(TickEvent.Post event) { + if (mc.player == null || dir == null) return; + + if (timer > 0) { + --timer; + return; + } else { + timer = 10; + } + + if (ready) { + assembleResourcePack(); + } + if (i < 0) return; + + switch (i) { + case 0 -> { + if (!warming) { + mc.gameRenderer.mainCamera().enablePanoramicMode(); + mc.gameRenderer.setRenderBlockOutline(false); + mc.execute(() -> { + synchronized (mc.levelRenderer) { + mc.levelRenderer.notifyAll(); + } + }); + if (!mc.gui.hud.isHidden()) mc.gui.hud.toggle(); + mc.player.setYRot(prevYaw); + mc.player.setXRot(0f); + warming = true; + } else takeScreenshot(); + } + case 1 -> { + if (!warming) { + mc.player.setYRot((prevYaw + 90f) % 360f); + mc.player.setXRot(0f); + warming = true; + } else takeScreenshot(); + } + case 2 -> { + if (!warming) { + mc.player.setYRot((prevYaw + 180f) % 360f); + mc.player.setXRot(0f); + warming = true; + } else takeScreenshot(); + } + case 3 -> { + if (!warming) { + mc.player.setYRot((prevYaw - 90f) % 360f); + mc.player.setXRot(0f); + warming = true; + } else takeScreenshot(); + } + case 4 -> { + if (!warming) { + mc.player.setYRot(prevYaw); + mc.player.setXRot(-90f); + warming = true; + } else takeScreenshot(); + } + default -> { + if (!warming) { + mc.player.setYRot(prevYaw); + mc.player.setXRot(90f); + warming = true; + } else { + takeScreenshot(); + i = -1; + mc.player.setYRot(prevYaw); + mc.player.setXRot(prevPitch); + mc.gameRenderer.mainCamera().disablePanoramicMode(); + mc.gameRenderer.setRenderBlockOutline(true); + mc.getWindow().setWidth(prevWidth); + mc.getWindow().setHeight(prevHeight); + if (mc.gui.hud.isHidden() != prevHudHidden) mc.gui.hud.toggle(); + mc.gameRenderer.mainRenderTarget().resize(prevWidth, prevHeight); + mc.execute(() -> { + synchronized (mc.levelRenderer) { + mc.levelRenderer.notifyAll(); + } + }); + + timer = 100; // esperar a que las capturas se escriban + ready = true; + ChatUtil.info(Component.translatable("sputnik.commands.panorama.finalizing").getString()); + } + } + } + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then( + argument("name", StringArgumentType.word()).executes(ctx -> { + String name = ctx.getArgument("name", String.class); + begin(name); + return SUCCESS; + }) + ); + } + + public static int getPackVersion() { + WorldVersion version = SharedConstants.getCurrentVersion(); + PackFormat format = version.packVersion(PackType.CLIENT_RESOURCES); + return format.major(); + } +} \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/io/ScreenshotMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/io/ScreenshotMixin.java index 8ed8d5a..a520914 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/io/ScreenshotMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/io/ScreenshotMixin.java @@ -5,6 +5,7 @@ import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.misc.ScreenshotPlus; import net.minecraft.client.Minecraft; import net.minecraft.client.Screenshot; +import net.minecraft.network.chat.Component; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -19,14 +20,14 @@ import java.util.function.Consumer; @Mixin(Screenshot.class) public abstract class ScreenshotMixin { - @Inject(method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"), cancellable = true) - private static void sendActionsMessage(NativeImage image, File file, Consumer callback, CallbackInfo ci) { + @Inject(method = "lambda$grab$3", at = @At(value = "INVOKE", target = "Ljava/util/function/Consumer;accept(Ljava/lang/Object;)V"), cancellable = true) + private static void sendActionsMessage(NativeImage image, File file, Consumer callback, CallbackInfo ci) { ScreenshotPlus screenshotPlus = getScreenshotPlus(); if (screenshotPlus.isEnabled()) ci.cancel(); } - @Inject(method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;writeToFile(Ljava/io/File;)V"), cancellable = true) - private static void interceptScreenshotFile(NativeImage image, File file, Consumer callback, CallbackInfo ci) throws IOException { + @Inject(method = "lambda$grab$3", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;writeToFile(Ljava/io/File;)V"), cancellable = true) + private static void interceptScreenshotFile(NativeImage image, File file, Consumer callback, CallbackInfo ci) throws IOException { ScreenshotPlus screenshotPlus = getScreenshotPlus(); // aunque el módulo esté apagado, pasar las referencias para poder usar los comandos para las capturas diff --git a/src/main/java/me/retucio/sputnik/util/MiscUtil.java b/src/main/java/me/retucio/sputnik/util/MiscUtil.java index 25dcb1f..4096e48 100644 --- a/src/main/java/me/retucio/sputnik/util/MiscUtil.java +++ b/src/main/java/me/retucio/sputnik/util/MiscUtil.java @@ -1,6 +1,7 @@ package me.retucio.sputnik.util; import com.github.retucio.neutrino.EventListener; +import me.retucio.sputnik.Sputnik; import me.retucio.sputnik.event.TickEvent; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.client.Hud; @@ -8,10 +9,12 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.server.IntegratedServer; import net.minecraft.core.Vec3i; import net.minecraft.world.phys.Vec3; +import org.apache.commons.io.IOUtils; import org.joml.Vector3d; import org.joml.Vector3f; import org.lwjgl.glfw.GLFW; +import java.io.*; import java.time.Instant; import java.time.LocalTime; import java.time.ZoneOffset; @@ -110,6 +113,26 @@ public class MiscUtil { } + public static void copy(File from, File to) { + try (InputStream in = new FileInputStream(from); + OutputStream out = new FileOutputStream(to)) { + in.transferTo(out); + } catch (IOException e) { + Sputnik.LOGGER.error("error copying from file \"{}\" to file \"{}\".", from.getName(), to.getName(), e); + } + } + + public static void copy(InputStream in, File to) { + try (OutputStream out = new FileOutputStream(to)) { + in.transferTo(out); + } catch (IOException e) { + Sputnik.LOGGER.error("error writing to file \"{}\".", to.getName(), e); + } finally { + IOUtils.closeQuietly(in); + } + } + + public static Vec3 vec3dOf(Vector3f v) { return new Vec3(v.x, v.y, v.z); } diff --git a/src/main/resources/assets/sputnik/lang/en_us.json b/src/main/resources/assets/sputnik/lang/en_us.json index faa4f0f..f74c38f 100644 --- a/src/main/resources/assets/sputnik/lang/en_us.json +++ b/src/main/resources/assets/sputnik/lang/en_us.json @@ -29,8 +29,13 @@ "sputnik.commands.friend.removed": "%s is not your friend anymore :(", "sputnik.commands.gc.collected": "garbage collected", "sputnik.commands.gc.desc": "frees up ram using the garbage collector (gc)", + "sputnik.commands.panorama.desc": "creates a panorama resource pack based on your current position", + "sputnik.commands.panorama.errorassemble": "failed to assemble custom resource pack! does a pack of that name already exist?", + "sputnik.commands.panorama.packdesc": "generated by Sputnik", + "sputnik.commands.panorama.ready": "resource pack ready!", + "sputnik.commands.panorama.finalizing": "finalizing resource pack...", "sputnik.commands.peek.desc": "peeks at another player's inventory", - "sputnik.commands.plugins.calmdown": "calm dooown", + "sputnik.commands.plugins.calmdown": "calm dooowwnnn", "sputnik.commands.plugins.desc": "looks for the plugins the server has", "sputnik.commands.plugins.erroranalyzingcommands": "error analyzing commands: %s", "sputnik.commands.plugins.namespacesfound": "%s command namespace(s) found:", diff --git a/src/main/resources/assets/sputnik/lang/es_es.json b/src/main/resources/assets/sputnik/lang/es_es.json index 3799f9b..3871f41 100644 --- a/src/main/resources/assets/sputnik/lang/es_es.json +++ b/src/main/resources/assets/sputnik/lang/es_es.json @@ -29,6 +29,11 @@ "sputnik.commands.friend.removed": "%s ya no es tu amigo :(", "sputnik.commands.gc.collected": "basura recolectada", "sputnik.commands.gc.desc": "limpia la ram mediante el recolector de basura (gc)", + "sputnik.commands.panorama.desc": "crea un pack de textures con la panorámica basada en tu posición actual", + "sputnik.commands.panorama.errorassemble": "no se pudo crear el pack; existe uno ya con ese nombre?", + "sputnik.commands.panorama.packdesc": "generado por Sputnik", + "sputnik.commands.panorama.ready": "pack listo!", + "sputnik.commands.panorama.finalizing": "finalizando pack...", "sputnik.commands.peek.desc": "cotillea el inventario de otro jugador", "sputnik.commands.plugins.calmdown": "calmaaa", "sputnik.commands.plugins.desc": "busca los plugins que tenga el server",