This commit is contained in:
retucio committed 2026-07-20 22:17:44 +02:00
1 parent 81b043270b
commit 81fc25c33d
9 files changed
+315 -9

No files matched your search

+6 -2
View File
@@ -1,4 +1,4 @@
# v5.4.1 # v5.5
## novedades ## novedades
@@ -6,9 +6,13 @@
#### módulos nuevos: #### módulos nuevos:
#### comandos nuevos:
- `$panorama`: construye un pack de texturas con una panorámica basada en tu posición actual.
### arreglos / mejoras ### 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 ## problemas reconocidos
+3 -1
View File
@@ -10,6 +10,8 @@ elytra-recast: https://github.com/InLieuOfLuna/elytra-recast/
SpearHax: https://github.com/etianl/spearHax/ SpearHax: https://github.com/etianl/spearHax/
ProjectileTrajectoryPreview: https://github.com/maDU59/ProjectilesTrajectoryPreview ProjectileTrajectoryPreview: https://github.com/maDU59/ProjectilesTrajectoryPreview
DupersUnited public addon: https://discord.gg/dupes | https://dupers.wtf 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 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 :) 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
+1 -1
View File
@@ -10,6 +10,6 @@ discord_rpc_version=1.0.3
neutrino_version=1.1.0 neutrino_version=1.1.0
jdk_version=25 jdk_version=25
mod_version=5.4 mod_version=5.5
maven_group=me.retucio maven_group=me.retucio
archives_base_name=sputnik archives_base_name=sputnik
@@ -38,6 +38,7 @@ public class CommandManager {
addCommand(new EnderChestCommand()); addCommand(new EnderChestCommand());
addCommand(new FriendCommand()); addCommand(new FriendCommand());
addCommand(new GarbageCleanerCommand()); addCommand(new GarbageCleanerCommand());
addCommand(new PanoramaCommand());
addCommand(new PeekCommand()); addCommand(new PeekCommand());
addCommand(new PluginsCommand()); addCommand(new PluginsCommand());
addCommand(new PrefixCommand()); addCommand(new PrefixCommand());
@@ -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 <a href="https://github.com/0xTas/stardust/blob/main/src/main/java/dev/stardust/commands/Panorama.java">aquí</a>
*/
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<SharedSuggestionProvider> 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();
}
}
@@ -5,6 +5,7 @@ import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.misc.ScreenshotPlus; import me.retucio.sputnik.module.modules.misc.ScreenshotPlus;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.Screenshot; import net.minecraft.client.Screenshot;
import net.minecraft.network.chat.Component;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@@ -19,14 +20,14 @@ import java.util.function.Consumer;
@Mixin(Screenshot.class) @Mixin(Screenshot.class)
public abstract class ScreenshotMixin { 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) @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) { private static void sendActionsMessage(NativeImage image, File file, Consumer<Component> callback, CallbackInfo ci) {
ScreenshotPlus screenshotPlus = getScreenshotPlus(); ScreenshotPlus screenshotPlus = getScreenshotPlus();
if (screenshotPlus.isEnabled()) ci.cancel(); 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) @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 { private static void interceptScreenshotFile(NativeImage image, File file, Consumer<Component> callback, CallbackInfo ci) throws IOException {
ScreenshotPlus screenshotPlus = getScreenshotPlus(); ScreenshotPlus screenshotPlus = getScreenshotPlus();
// aunque el módulo esté apagado, pasar las referencias para poder usar los comandos para las capturas // aunque el módulo esté apagado, pasar las referencias para poder usar los comandos para las capturas
@@ -1,6 +1,7 @@
package me.retucio.sputnik.util; package me.retucio.sputnik.util;
import com.github.retucio.neutrino.EventListener; import com.github.retucio.neutrino.EventListener;
import me.retucio.sputnik.Sputnik;
import me.retucio.sputnik.event.TickEvent; import me.retucio.sputnik.event.TickEvent;
import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.client.Hud; 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.client.server.IntegratedServer;
import net.minecraft.core.Vec3i; import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import org.apache.commons.io.IOUtils;
import org.joml.Vector3d; import org.joml.Vector3d;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
import java.io.*;
import java.time.Instant; import java.time.Instant;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneOffset; 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) { public static Vec3 vec3dOf(Vector3f v) {
return new Vec3(v.x, v.y, v.z); return new Vec3(v.x, v.y, v.z);
} }
@@ -29,8 +29,13 @@
"sputnik.commands.friend.removed": "%s is not your friend anymore :(", "sputnik.commands.friend.removed": "%s is not your friend anymore :(",
"sputnik.commands.gc.collected": "garbage collected", "sputnik.commands.gc.collected": "garbage collected",
"sputnik.commands.gc.desc": "frees up ram using the garbage collector (gc)", "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.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.desc": "looks for the plugins the server has",
"sputnik.commands.plugins.erroranalyzingcommands": "error analyzing commands: %s", "sputnik.commands.plugins.erroranalyzingcommands": "error analyzing commands: %s",
"sputnik.commands.plugins.namespacesfound": "%s command namespace(s) found:", "sputnik.commands.plugins.namespacesfound": "%s command namespace(s) found:",
@@ -29,6 +29,11 @@
"sputnik.commands.friend.removed": "%s ya no es tu amigo :(", "sputnik.commands.friend.removed": "%s ya no es tu amigo :(",
"sputnik.commands.gc.collected": "basura recolectada", "sputnik.commands.gc.collected": "basura recolectada",
"sputnik.commands.gc.desc": "limpia la ram mediante el recolector de basura (gc)", "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.peek.desc": "cotillea el inventario de otro jugador",
"sputnik.commands.plugins.calmdown": "calmaaa", "sputnik.commands.plugins.calmdown": "calmaaa",
"sputnik.commands.plugins.desc": "busca los plugins que tenga el server", "sputnik.commands.plugins.desc": "busca los plugins que tenga el server",