From 3913b31374931d0a9fd9b2ce59069c1e18269fe9 Mon Sep 17 00:00:00 2001 From: retucio Date: Sun, 19 Jul 2026 23:14:33 +0200 Subject: [PATCH] v5.4 --- cambios.md | 24 +-- gradle.properties | 2 +- .../mixins/entity/EntityRendererMixin.java | 10 +- .../mixin/mixins/player/LocalPlayerMixin.java | 5 +- .../mixins/render/GameRendererMixin.java | 20 +- .../mixins/render/ShaderManagerMixin.java | 2 + .../retucio/sputnik/module/ModuleManager.java | 33 +-- .../{DiscordRPC.java => DiscordRpc.java} | 7 +- .../{PortalGUI.java => PortalGui.java} | 4 +- .../inventory/{UIMove.java => UiMove.java} | 4 +- .../render/{BlockESP.java => BlockEsp.java} | 4 +- .../render/{EntityESP.java => EntityEsp.java} | 4 +- .../sputnik/module/modules/render/Lsd.java | 85 ++++++++ .../sputnik/util/render/PostShader.java | 199 ++++++++++++++++++ .../sputnik/util/render/PostShaders.java | 21 ++ .../resources/assets/sputnik/lang/en_us.json | 10 + .../resources/assets/sputnik/lang/es_es.json | 10 + .../resources/assets/sputnik/shaders/lsd.vert | 11 - .../assets/sputnik/shaders/post/blit.fsh | 12 ++ .../shaders/{lsd.frag => post/lsd.fsh} | 28 +-- 20 files changed, 416 insertions(+), 79 deletions(-) rename src/main/java/me/retucio/sputnik/module/modules/client/{DiscordRPC.java => DiscordRpc.java} (98%) rename src/main/java/me/retucio/sputnik/module/modules/inventory/{PortalGUI.java => PortalGui.java} (86%) rename src/main/java/me/retucio/sputnik/module/modules/inventory/{UIMove.java => UiMove.java} (97%) rename src/main/java/me/retucio/sputnik/module/modules/render/{BlockESP.java => BlockEsp.java} (99%) rename src/main/java/me/retucio/sputnik/module/modules/render/{EntityESP.java => EntityEsp.java} (98%) create mode 100644 src/main/java/me/retucio/sputnik/module/modules/render/Lsd.java create mode 100644 src/main/java/me/retucio/sputnik/util/render/PostShader.java create mode 100644 src/main/java/me/retucio/sputnik/util/render/PostShaders.java delete mode 100644 src/main/resources/assets/sputnik/shaders/lsd.vert create mode 100644 src/main/resources/assets/sputnik/shaders/post/blit.fsh rename src/main/resources/assets/sputnik/shaders/{lsd.frag => post/lsd.fsh} (86%) diff --git a/cambios.md b/cambios.md index 4e3241c..1c8025e 100644 --- a/cambios.md +++ b/cambios.md @@ -1,33 +1,17 @@ -# v5.3 +# v5.4 ## novedades -- actualizado a 26.2 - ### adiciones -- el mod ya está traducido y sigue el idioma del juego: de momento español e inglés -- nuevo elemento del HUD: `armadura` +- sistema de shaders #### módulos nuevos: -### arreglos / mejoras +- lsd: droga dura -- los módulos con tilde en el nombre (`cámara libre`, `críticos`, `partículas`...) ya recuperan -la posición de su marco de ajustes al arrancar; eran 12 y ninguno la guardaba -- los marcos ya guardan su posición al moverlos, aunque no se hayan plegado nunca antes -- la barra de búsqueda ya no deja escondidos los módulos que no casaban al vaciarla o al desactivarla -- `teclas multimódulo` ya deja compartir tecla entre módulos, que es lo que dice hacer; -antes sólo servía para las teclas de vanilla y le quitaba la tecla al otro módulo igualmente -- `notificar` ya se respeta al desactivar un módulo, no sólo al activarlo -- `delay de paquetes` ya no se apaga solo al entrar a un servidor -- `selección de minado`: el `delay` de reintentos y el de selección ya no comparten el valor guardado -- `$bind` ya avisa de que una tecla está cogida (el aviso no saltaba nunca) y muestra bien el nombre de la tecla -- `$clickslot` ya acepta las sugerencias que él mismo da +### arreglos / mejoras ## problemas reconocidos - lo de siempre. -- los nombres de los módulos y de los ajustes se guardan en la config. en español, se ponga el idioma -que se ponga. es a propósito: son la clave con la que se guardan, así que traducirlos resetearía -los ajustes de todo el mundo al cambiar de idioma diff --git a/gradle.properties b/gradle.properties index 257c107..bab18dd 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.3 +mod_version=5.4 maven_group=me.retucio archives_base_name=sputnik \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java index cf7a50f..e6c924e 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/entity/EntityRendererMixin.java @@ -3,7 +3,7 @@ package me.retucio.sputnik.mixin.mixins.entity; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.sugar.Local; import me.retucio.sputnik.module.ModuleManager; -import me.retucio.sputnik.module.modules.render.EntityESP; +import me.retucio.sputnik.module.modules.render.EntityEsp; import net.minecraft.client.renderer.entity.EntityRenderer; import net.minecraft.world.entity.Entity; import org.spongepowered.asm.mixin.Mixin; @@ -17,21 +17,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public abstract class EntityRendererMixin { @Unique - private EntityESP esp; + private EntityEsp esp; @Inject(method = "", at = @At("TAIL")) private void getModules(CallbackInfo ci) { - esp = ModuleManager.INSTANCE.getModuleByClass(EntityESP.class); + esp = ModuleManager.INSTANCE.getModuleByClass(EntityEsp.class); } @ModifyExpressionValue(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;shouldEntityAppearGlowing(Lnet/minecraft/world/entity/Entity;)Z")) private boolean renderGlowESP(boolean original, @Local(argsOnly = true) Entity entity) { - return (esp.isEnabled() && esp.mode.is(EntityESP.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) || original; + return (esp.isEnabled() && esp.mode.is(EntityEsp.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) || original; } @ModifyExpressionValue(method = "extractRenderState", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/Entity;getTeamColor()I")) private int setGlowESPColor(int original, @Local(argsOnly = true) Entity entity) { - if (esp.isEnabled() && esp.mode.is(EntityESP.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) return esp.glowColor.getRGB(); + if (esp.isEnabled() && esp.mode.is(EntityEsp.ESPMode.GLOW) && esp.entities.isEnabled(entity.getType())) return esp.glowColor.getRGB(); return original; } } diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/player/LocalPlayerMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/player/LocalPlayerMixin.java index cf8b98b..ae08232 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/player/LocalPlayerMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/player/LocalPlayerMixin.java @@ -5,10 +5,9 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue; import com.llamalad7.mixinextras.sugar.Local; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.world.NoMiningInterruptions; -import me.retucio.sputnik.module.modules.inventory.PortalGUI; +import me.retucio.sputnik.module.modules.inventory.PortalGui; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.entity.Entity; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; @@ -29,7 +28,7 @@ public abstract class LocalPlayerMixin { @ModifyExpressionValue(method = "handlePortalTransitionEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/Gui;screen()Lnet/minecraft/client/gui/screens/Screen;")) private Screen allowScreensInPortals(Screen original) { - if (ModuleManager.INSTANCE.getModuleByClass(PortalGUI.class).isEnabled()) return null; + if (ModuleManager.INSTANCE.getModuleByClass(PortalGui.class).isEnabled()) return null; return original; } diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/render/GameRendererMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/render/GameRendererMixin.java index 57ac0c7..4ad9356 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/render/GameRendererMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/render/GameRendererMixin.java @@ -6,10 +6,12 @@ import me.retucio.sputnik.event.render.GetFOVEvent; import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.modules.camera.Freecam; import me.retucio.sputnik.module.modules.misc.UnfocusedCpu; +import me.retucio.sputnik.module.modules.render.Lsd; import me.retucio.sputnik.module.modules.render.NoRender; import me.retucio.sputnik.module.modules.camera.Zoom; - +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.resource.CrossFrameResourcePool; import net.minecraft.client.DeltaTracker; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.GameRenderer; @@ -38,15 +40,31 @@ public abstract class GameRendererMixin { @Unique Zoom zoom; @Unique Freecam freecam; @Unique NoRender noRender; + @Unique Lsd lsd; @Shadow @Final private Minecraft minecraft; + @Shadow @Final + private RenderTarget mainRenderTarget; + + @Shadow @Final + private CrossFrameResourcePool resourcePool; + @Inject(method = "", at = @At("TAIL")) private void getModules(Minecraft minecraft, ItemInHandRenderer itemInHandRenderer, ModelManager modelManager, CallbackInfo ci) { zoom = ModuleManager.INSTANCE.getModuleByClass(Zoom.class);; freecam = ModuleManager.INSTANCE.getModuleByClass(Freecam.class); noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.class); + lsd = ModuleManager.INSTANCE.getModuleByClass(Lsd.class); + } + + // se aplica justo donde vanilla mete sus efectos de post (nausea, etc.): el mundo ya está + // en mainRenderTarget y el HUD todavía no, así que sólo distorsiona la escena 3D + @Inject(method = "render", at = @At(value = "INVOKE", shift = At.Shift.AFTER, + target = "Lnet/minecraft/client/renderer/LevelRenderer;doEntityOutline()V")) + private void applyLsd(DeltaTracker deltaTracker, boolean advanceGameTime, CallbackInfo ci) { + if (lsd.isEnabled()) lsd.apply(mainRenderTarget, resourcePool); } @Inject(method = "render", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/me/retucio/sputnik/mixin/mixins/render/ShaderManagerMixin.java b/src/main/java/me/retucio/sputnik/mixin/mixins/render/ShaderManagerMixin.java index d2f2e92..f089f23 100644 --- a/src/main/java/me/retucio/sputnik/mixin/mixins/render/ShaderManagerMixin.java +++ b/src/main/java/me/retucio/sputnik/mixin/mixins/render/ShaderManagerMixin.java @@ -1,6 +1,7 @@ package me.retucio.sputnik.mixin.mixins.render; import me.retucio.sputnik.util.render.Pipelines; +import me.retucio.sputnik.util.render.PostShaders; import net.minecraft.client.renderer.ShaderManager; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -13,5 +14,6 @@ public abstract class ShaderManagerMixin { @Inject(method = "apply(Lnet/minecraft/client/renderer/ShaderManager$Configs;Lnet/minecraft/server/packs/resources/ResourceManager;Lnet/minecraft/util/profiling/ProfilerFiller;)V", at = @At("TAIL")) private void onReload(CallbackInfo ci) { Pipelines.init(); + PostShaders.reload(); } } \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/ModuleManager.java b/src/main/java/me/retucio/sputnik/module/ModuleManager.java index 6d27f3f..866e7a5 100644 --- a/src/main/java/me/retucio/sputnik/module/ModuleManager.java +++ b/src/main/java/me/retucio/sputnik/module/ModuleManager.java @@ -2,7 +2,7 @@ package me.retucio.sputnik.module; import com.github.retucio.neutrino.EventListener; import me.retucio.sputnik.Sputnik; -import me.retucio.sputnik.module.modules.client.DiscordRPC; +import me.retucio.sputnik.module.modules.client.DiscordRpc; import me.retucio.sputnik.module.modules.client.Friends; import me.retucio.sputnik.module.modules.client.Hud; import me.retucio.sputnik.module.modules.camera.*; @@ -79,7 +79,7 @@ public class ModuleManager { } private void addClient() { - add(new DiscordRPC()); + add(new DiscordRpc()); add(new Friends()); add(new Hud()); add(new Notifications()); @@ -111,10 +111,10 @@ public class ModuleManager { add(new CreativeInventoryHotbarKeybinds()); add(new InventoryPlus()); add(new Offhand()); - add(new PortalGUI()); + add(new PortalGui()); add(new Replenish()); add(new ShulkerPeek()); - add(new UIMove()); + add(new UiMove()); add(new XCarry()); } @@ -159,17 +159,18 @@ public class ModuleManager { } private void addRender() { - add(new BlockESP()); + add(new BlockEsp()); add(new BlockOutline()); add(new Breadcrumbs()); add(new BreakingProgress()); add(new Confetti()); add(new CritsPlus()); add(new DamageOverlay()); - add(new EntityESP()); + add(new EntityEsp()); add(new Fonts()); add(new GlintPlus()); add(new Hitboxes()); + add(new Lsd()); add(new Nametags()); add(new NoRender()); add(new Particles()); @@ -190,8 +191,6 @@ public class ModuleManager { } - - // para obtener módulos más fácilmente (por nombre, clase, o la lista completa) public List getModules() { return modules; @@ -208,25 +207,31 @@ public class ModuleManager { // busca por nombre interno, tal cual se guarda en la config. antes quitaba las tildes al // nombre del módulo pero no al argumento, así que "cámara libre" no encontraba nada public Module getModuleByName(String name) { - for (Module module : modules) - if (module.getName().equalsIgnoreCase(name)) + for (Module module : modules) { + if (module.getName().equalsIgnoreCase(name)) { return module; + } + } return null; } // busca por nombre visible: lo que el usuario teclea en los comandos, sin tildes y con "_" public Module getModuleByDisplayName(String name) { String target = MiscUtil.removeAccentMarks(name).replace("_", " "); - for (Module module : modules) - if (MiscUtil.removeAccentMarks(module.getDisplayName()).equalsIgnoreCase(target)) + for (Module module : modules) { + if (MiscUtil.removeAccentMarks(module.getDisplayName()).equalsIgnoreCase(target)) { return module; + } + } return null; } public T getModuleByClass(Class clazz) { - for (Module module : modules) - if (clazz.isInstance(module)) + for (Module module : modules) { + if (clazz.isInstance(module)) { return clazz.cast(module); + } + } return null; } } \ No newline at end of file diff --git a/src/main/java/me/retucio/sputnik/module/modules/client/DiscordRPC.java b/src/main/java/me/retucio/sputnik/module/modules/client/DiscordRpc.java similarity index 98% rename from src/main/java/me/retucio/sputnik/module/modules/client/DiscordRPC.java rename to src/main/java/me/retucio/sputnik/module/modules/client/DiscordRpc.java index ff7b417..bdd5972 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/client/DiscordRPC.java +++ b/src/main/java/me/retucio/sputnik/module/modules/client/DiscordRpc.java @@ -1,6 +1,5 @@ package me.retucio.sputnik.module.modules.client; -import dev.firstdark.rpc.DiscordRpc; import dev.firstdark.rpc.enums.ActivityType; import dev.firstdark.rpc.enums.ErrorCode; import dev.firstdark.rpc.exceptions.UnsupportedOsType; @@ -22,7 +21,7 @@ import net.minecraft.network.chat.Component; * @author retucio */ -public class DiscordRPC extends Module { +public class DiscordRpc extends Module { private final SettingGroup sgImage = addSg(new SettingGroup("imágenes", false)); private final SettingGroup sgButton = addSg(new SettingGroup("botón", false)); @@ -103,7 +102,7 @@ public class DiscordRPC extends Module { // crear el rpc private boolean initialized; - DiscordRpc rpc = new DiscordRpc(); + dev.firstdark.rpc.DiscordRpc rpc = new dev.firstdark.rpc.DiscordRpc(); RPCEventHandler handler = new RPCEventHandler() { @Override public void ready(User user) { @@ -124,7 +123,7 @@ public class DiscordRPC extends Module { }; - public DiscordRPC() { + public DiscordRpc() { super("discordRPC", "personaliza la actividad de tu perfil en dishkor", Category.CLIENT); diff --git a/src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGUI.java b/src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGui.java similarity index 86% rename from src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGUI.java rename to src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGui.java index f042666..96bdd30 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGUI.java +++ b/src/main/java/me/retucio/sputnik/module/modules/inventory/PortalGui.java @@ -8,9 +8,9 @@ import me.retucio.sputnik.module.Module; * @see LocalPlayerMixin */ -public class PortalGUI extends Module { +public class PortalGui extends Module { - public PortalGUI() { + public PortalGui() { super("interfaz en portales", "te permite abrir interfaces dentro de portales, como el chat o el inventario", Category.INVENTORY); diff --git a/src/main/java/me/retucio/sputnik/module/modules/inventory/UIMove.java b/src/main/java/me/retucio/sputnik/module/modules/inventory/UiMove.java similarity index 97% rename from src/main/java/me/retucio/sputnik/module/modules/inventory/UIMove.java rename to src/main/java/me/retucio/sputnik/module/modules/inventory/UiMove.java index 17d10cd..9bde268 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/inventory/UIMove.java +++ b/src/main/java/me/retucio/sputnik/module/modules/inventory/UiMove.java @@ -16,7 +16,7 @@ import java.util.Arrays; import java.util.List; -public class UIMove extends Module { +public class UiMove extends Module { public ListSetting> screens = sgGeneral.add(new ListSetting<>("interfaces", "interfaces en las que te podrás mover", Lists.screenList, Lists.allTrue(Lists.screenList), Lists.screenNames)); @@ -28,7 +28,7 @@ public class UIMove extends Module { private final MenuType clickGuiHandlerType = MenuType.register( "sputnik_clickgui", ChestMenu::threeRows); // 9x3 porque no importa (creo) - public UIMove() { + public UiMove() { super("moverse en interfaz", "te permite seguir usando las teclas de movimiento aún estando en ciertas interfaces", Category.INVENTORY); diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/BlockESP.java b/src/main/java/me/retucio/sputnik/module/modules/render/BlockEsp.java similarity index 99% rename from src/main/java/me/retucio/sputnik/module/modules/render/BlockESP.java rename to src/main/java/me/retucio/sputnik/module/modules/render/BlockEsp.java index 3a5c58c..4a9abf9 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/BlockESP.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/BlockEsp.java @@ -24,7 +24,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; -public class BlockESP extends Module { +public class BlockEsp extends Module { private final SettingGroup sgSearch = addSg(new SettingGroup("búsqueda", true)); private final SettingGroup sgOulines = addSg(new SettingGroup("contorno", true)); @@ -103,7 +103,7 @@ public class BlockESP extends Module { private boolean isSearching = false; - public BlockESP() { + public BlockEsp() { super("resaltado de bloques", "te muestra la posición de cierto tipo de bloque renderizando una caja con su forma", Category.RENDER); diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/EntityESP.java b/src/main/java/me/retucio/sputnik/module/modules/render/EntityEsp.java similarity index 98% rename from src/main/java/me/retucio/sputnik/module/modules/render/EntityESP.java rename to src/main/java/me/retucio/sputnik/module/modules/render/EntityEsp.java index c47d78a..cb2066c 100644 --- a/src/main/java/me/retucio/sputnik/module/modules/render/EntityESP.java +++ b/src/main/java/me/retucio/sputnik/module/modules/render/EntityEsp.java @@ -17,7 +17,7 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntityType; -public class EntityESP extends Module { +public class EntityEsp extends Module { private final SettingGroup sgRender = addSg(new SettingGroup("cajas", true)); @@ -69,7 +69,7 @@ public class EntityESP extends Module { false )); - public EntityESP() { + public EntityEsp() { super("resaltado de entidades", "resalta el contorno de las entidades seleccionadas a través de bloques", Category.RENDER); mode.onUpdate(v -> { glowColor.visibility(v == ESPMode.GLOW); diff --git a/src/main/java/me/retucio/sputnik/module/modules/render/Lsd.java b/src/main/java/me/retucio/sputnik/module/modules/render/Lsd.java new file mode 100644 index 0000000..7ccdba3 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/module/modules/render/Lsd.java @@ -0,0 +1,85 @@ +package me.retucio.sputnik.module.modules.render; + +import com.mojang.blaze3d.buffers.Std140SizeCalculator; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.resource.GraphicsResourceAllocator; + +import me.retucio.sputnik.Sputnik; +import me.retucio.sputnik.module.Category; +import me.retucio.sputnik.module.Module; +import me.retucio.sputnik.module.setting.settings.NumberSetting; +import me.retucio.sputnik.util.render.PostShader; + +import net.minecraft.resources.Identifier; + + +/** + * efecto de pantalla completa que distorsiona y tiñe el mundo. el shader vive en + * {@code assets/sputnik/shaders/post/lsd.fsh} y lo aplica un {@link PostShader}. + * + * @see me.retucio.sputnik.mixin.mixins.render.GameRendererMixin + */ + +public class Lsd extends Module { + + // strength, seed, rotations, time (vec4 en std140). el tiempo va en el uniform y no se lee de + // Globals.GameTime porque ese es una fracción del día (se reinicia cada 24000 ticks) y animaría + // el patrón lentísimo con un salto cada 20 minutos + private static final PostShader SHADER = PostShader.builder( + "lsd", Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "post/lsd")) + .uniforms("LsdConfig", new Std140SizeCalculator() + .putFloat().putFloat().putFloat().putFloat().get()) + .build(); + + private final NumberSetting strength = sgGeneral.add(new NumberSetting( + "intensidad", + "cuánto se distorsiona y se tiñe la imagen", + 0.5, 0, 1, 0.01 + )); + + private final NumberSetting rotations = sgGeneral.add(new NumberSetting( + "rotaciones", + "cuántas vueltas da el desplazamiento; más = más remolinos", + 1, 0, 10, 0.1 + )); + + private final NumberSetting seed = sgGeneral.add(new NumberSetting( + "tono", + "desplazamiento de tono que se aplica sobre la imagen", + 0.3, 0, 1, 0.01 + )); + + private final NumberSetting speed = sgGeneral.add(new NumberSetting( + "velocidad", + "a qué ritmo se mueve el patrón (0 lo congela)", + 1, 0, 5, 0.1 + )); + + private long startNanos = -1; + + public Lsd() { + super("lsd", "pero qué cojones macho", Category.RENDER); + } + + @Override + public void onEnable() { + super.onEnable(); + startNanos = System.nanoTime(); + } + + /** lo llama el mixin de {@code GameRenderer} tras renderizar el mundo. */ + public void apply(RenderTarget target, GraphicsResourceAllocator pool) { + SHADER.render(target, pool, b -> b + .putFloat(strength.getFloatValue()) + .putFloat(seed.getFloatValue()) + .putFloat(rotations.getFloatValue()) + .putFloat(getTime())); + } + + private float getTime() { + if (startNanos < 0) startNanos = System.nanoTime(); + // en segundos, acotado para no perder precisión de float en sesiones largas + double elapsed = (System.nanoTime() - startNanos) / 1_000_000_000.0; + return (float) ((elapsed * speed.getValue()) % 100_000.0); + } +} diff --git a/src/main/java/me/retucio/sputnik/util/render/PostShader.java b/src/main/java/me/retucio/sputnik/util/render/PostShader.java new file mode 100644 index 0000000..371a0f8 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/util/render/PostShader.java @@ -0,0 +1,199 @@ +package me.retucio.sputnik.util.render; + +import com.mojang.blaze3d.GpuFormat; +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import com.mojang.blaze3d.buffers.Std140Builder; +import com.mojang.blaze3d.framegraph.FrameGraphBuilder; +import com.mojang.blaze3d.framegraph.FramePass; +import com.mojang.blaze3d.pipeline.BindGroupLayout; +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.resource.GraphicsResourceAllocator; +import com.mojang.blaze3d.resource.RenderTargetDescriptor; +import com.mojang.blaze3d.resource.ResourceHandle; +import com.mojang.blaze3d.shaders.UniformType; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.RenderPass; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.FilterMode; +import com.mojang.blaze3d.textures.GpuSampler; + +import me.retucio.sputnik.Sputnik; + +import net.minecraft.client.renderer.MappableRingBuffer; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.resources.Identifier; + +import org.jetbrains.annotations.Nullable; +import org.joml.Vector4f; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.function.Consumer; + + +/** + * un shader de post-procesado de pantalla completa reutilizable: aplica un fragment shader sobre + * el mundo ya renderizado, distorsionándolo o tiñéndolo. + * + * @see PostShaders + */ + +public class PostShader { + + private static final Identifier SCREENQUAD = Identifier.withDefaultNamespace("core/screenquad"); + private static final Vector4f NO_CLEAR = new Vector4f(0.0F, 0.0F, 0.0F, 0.0F); + private static final String INPUT_SAMPLER = "InSampler"; + + // copia el objetivo intermedio de vuelta al principal; lo comparten todos los efectos + private static final RenderPipeline BLIT_PIPELINE = RenderPipeline.builder(RenderPipelines.POST_PROCESSING_SNIPPET) + .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/post/blit")) + .withVertexShader(SCREENQUAD) + .withFragmentShader(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "post/blit")) + .withBindGroupLayout(BindGroupLayout.builder().withSampler(INPUT_SAMPLER).build()) + .build(); + + private final String name; + private final RenderPipeline pipeline; + private final FilterMode filter; + + private final @Nullable String uniformBlock; + private final int uniformSize; + private @Nullable MappableRingBuffer uniformUbo; + + // 0 sin intentar, 1 ok, -1 falló al compilar (shader roto: no reintentar hasta recarga) + private int state = 0; + + private PostShader(String name, RenderPipeline pipeline, FilterMode filter, + @Nullable String uniformBlock, int uniformSize) { + this.name = name; + this.pipeline = pipeline; + this.filter = filter; + this.uniformBlock = uniformBlock; + this.uniformSize = uniformSize; + } + + public static Builder builder(String name, Identifier fragmentShader) { + return new Builder(name, fragmentShader); + } + + /** lo llama {@link PostShaders#reload} al recargar recursos, cuando la caché de pipelines se vacía. */ + void markDirty() { + state = 0; + } + + private boolean ensureReady() { + if (state != 0) return state == 1; + + boolean ok = RenderSystem.getDevice().precompilePipeline(pipeline).isValid() + && RenderSystem.getDevice().precompilePipeline(BLIT_PIPELINE).isValid(); + if (ok && uniformBlock != null && uniformUbo == null) { + // 130 = USAGE_UNIFORM (128) | USAGE_MAP_WRITE (2): se reescribe cada frame + uniformUbo = new MappableRingBuffer(() -> "sputnik post " + name, 130, uniformSize); + } + state = ok ? 1 : -1; + if (!ok) Sputnik.LOGGER.error("no se pudo compilar el shader de post '{}'", name); + return ok; + } + + /** + * aplica el efecto sobre el objetivo dado in situ. + * + * @param pool el pool de recursos del {@code GameRenderer}, para los objetivos intermedios + * @param uniforms rellena el bloque de uniforms del frame (en el orden del std140); {@code null} + * si el shader no declara bloque + */ + public void render(RenderTarget target, GraphicsResourceAllocator pool, @Nullable Consumer uniforms) { + if (!ensureReady()) return; + + FrameGraphBuilder frame = new FrameGraphBuilder(); + ResourceHandle mainHandle = frame.importExternal(name + "_main", target); + RenderTargetDescriptor swapDesc = new RenderTargetDescriptor( + target.width, target.height, true, NO_CLEAR, GpuFormat.RGBA8_UNORM); + ResourceHandle swapHandle = frame.createInternal(name + "_swap", swapDesc); + + // pasada del efecto: main -> swap + FramePass effectPass = frame.addPass(name); + effectPass.reads(mainHandle); + ResourceHandle swapWrite = effectPass.readsAndWrites(swapHandle); + effectPass.executes(() -> { + if (uniformUbo != null && uniforms != null) { + try (GpuBufferSlice.MappedView view = uniformUbo.currentBuffer().map(false, true)) { + uniforms.accept(Std140Builder.intoBuffer(view.data())); + } + } + draw(pipeline, mainHandle.get(), swapWrite.get(), + uniformUbo != null ? uniformUbo.currentBuffer() : null); + if (uniformUbo != null) uniformUbo.rotate(); + }); + + // pasada de blit: swap -> main + FramePass blitPass = frame.addPass(name + "_blit"); + blitPass.reads(swapWrite); + ResourceHandle mainWrite = blitPass.readsAndWrites(mainHandle); + blitPass.executes(() -> draw(BLIT_PIPELINE, swapWrite.get(), mainWrite.get(), null)); + + frame.execute(pool); + } + + private void draw(RenderPipeline pipeline, RenderTarget in, RenderTarget out, @Nullable GpuBuffer uniform) { + CommandEncoder encoder = RenderSystem.getDevice().createCommandEncoder(); + GpuSampler sampler = RenderSystem.getSamplerCache().getClampToEdge(filter); + + try (RenderPass pass = encoder.createRenderPass( + () -> "sputnik post " + name, + out.getColorTextureView(), + Optional.empty(), + out.useDepth ? out.getDepthTextureView() : null, + OptionalDouble.empty())) { + pass.setPipeline(pipeline); + RenderSystem.bindDefaultUniforms(pass); + if (uniform != null && uniformBlock != null) pass.setUniform(uniformBlock, uniform); + pass.bindTexture(INPUT_SAMPLER, in.getColorTextureView(), sampler); + pass.draw(3, 1, 0, 0); + } + } + + + public static class Builder { + + private final String name; + private final Identifier fragmentShader; + private FilterMode filter = FilterMode.NEAREST; + private @Nullable String uniformBlock; + private int uniformSize; + + private Builder(String name, Identifier fragmentShader) { + this.name = name; + this.fragmentShader = fragmentShader; + } + + /** declara el bloque de uniforms std140 del shader; el tamaño se saca con {@code Std140SizeCalculator}. */ + public Builder uniforms(String block, int std140Size) { + this.uniformBlock = block; + this.uniformSize = std140Size; + return this; + } + + /** filtrado del sampler de entrada; {@link FilterMode#NEAREST} por defecto. */ + public Builder filter(FilterMode filter) { + this.filter = filter; + return this; + } + + public PostShader build() { + BindGroupLayout.Builder layout = BindGroupLayout.builder().withSampler(INPUT_SAMPLER); + if (uniformBlock != null) layout.withUniform(uniformBlock, UniformType.UNIFORM_BUFFER); + + RenderPipeline pipeline = RenderPipeline.builder(RenderPipelines.POST_PROCESSING_SNIPPET) + .withLocation(Identifier.fromNamespaceAndPath(Sputnik.MOD_ID, "pipeline/post/" + name)) + .withVertexShader(SCREENQUAD) + .withFragmentShader(fragmentShader) + .withBindGroupLayout(layout.build()) + .build(); + + return PostShaders.register(new PostShader(name, pipeline, filter, uniformBlock, uniformSize)); + } + } +} diff --git a/src/main/java/me/retucio/sputnik/util/render/PostShaders.java b/src/main/java/me/retucio/sputnik/util/render/PostShaders.java new file mode 100644 index 0000000..c6403c1 --- /dev/null +++ b/src/main/java/me/retucio/sputnik/util/render/PostShaders.java @@ -0,0 +1,21 @@ +package me.retucio.sputnik.util.render; + +import java.util.ArrayList; +import java.util.List; + + +public class PostShaders { + + private static final List SHADERS = new ArrayList<>(); + + static PostShader register(PostShader shader) { + SHADERS.add(shader); + return shader; + } + + public static void reload() { + for (PostShader shader : SHADERS) { + shader.markDirty(); + } + } +} diff --git a/src/main/resources/assets/sputnik/lang/en_us.json b/src/main/resources/assets/sputnik/lang/en_us.json index 909dfba..faa4f0f 100644 --- a/src/main/resources/assets/sputnik/lang/en_us.json +++ b/src/main/resources/assets/sputnik/lang/en_us.json @@ -434,6 +434,8 @@ "sputnik.module.jugadorfalso.desc": "spawns a fake player entity for testing purposes", "sputnik.module.jugadorfalso.name": "fake player", "sputnik.module.keypearl.nopearls": "you have no pearls", + "sputnik.module.lsd.desc": "what the actual FUCK", + "sputnik.module.lsd.name": "lsd", "sputnik.module.macekill.oops": "oops: %s", "sputnik.module.manos.desc": "changes the way hands are rendered", "sputnik.module.manos.name": "hands", @@ -963,6 +965,14 @@ "sputnik.setting.jugadorfalso.general.nombre.desc": "what name to give the player", "sputnik.setting.jugadorfalso.general.nombre.name": "name", "sputnik.setting.key.reset": "keybind for module %s reset", + "sputnik.setting.lsd.general.intensidad.desc": "how much the image is distorted and tinted", + "sputnik.setting.lsd.general.intensidad.name": "strength", + "sputnik.setting.lsd.general.rotaciones.desc": "how much the offset swirls; higher = more whirls", + "sputnik.setting.lsd.general.rotaciones.name": "rotations", + "sputnik.setting.lsd.general.tono.desc": "hue shift applied over the image", + "sputnik.setting.lsd.general.tono.name": "hue", + "sputnik.setting.lsd.general.velocidad.desc": "how fast the pattern moves (0 freezes it)", + "sputnik.setting.lsd.general.velocidad.name": "speed", "sputnik.setting.manos.animaciones.animacionesviejas.desc": "like the 1.8 ones, when hitting", "sputnik.setting.manos.animaciones.animacionesviejas.name": "old animations", "sputnik.setting.manos.animaciones.nocomer.desc": "doesn't render the eating animation", diff --git a/src/main/resources/assets/sputnik/lang/es_es.json b/src/main/resources/assets/sputnik/lang/es_es.json index 54acb50..3799f9b 100644 --- a/src/main/resources/assets/sputnik/lang/es_es.json +++ b/src/main/resources/assets/sputnik/lang/es_es.json @@ -434,6 +434,8 @@ "sputnik.module.jugadorfalso.desc": "invoca una entidad de jugador falsa por motivos de testeo", "sputnik.module.jugadorfalso.name": "jugador falso", "sputnik.module.keypearl.nopearls": "no tienes perlas", + "sputnik.module.lsd.desc": "distorsiona y tiñe la pantalla", + "sputnik.module.lsd.name": "lsd", "sputnik.module.macekill.oops": "upsi: %s", "sputnik.module.manos.desc": "modifica la manera en la que se renderizan las manos", "sputnik.module.manos.name": "manos", @@ -963,6 +965,14 @@ "sputnik.setting.jugadorfalso.general.nombre.desc": "qué nombre asignarle al jugador", "sputnik.setting.jugadorfalso.general.nombre.name": "nombre", "sputnik.setting.key.reset": "tecla del módulo %s restablecida", + "sputnik.setting.lsd.general.intensidad.desc": "cuánto se distorsiona y se tiñe la imagen", + "sputnik.setting.lsd.general.intensidad.name": "intensidad", + "sputnik.setting.lsd.general.rotaciones.desc": "cuántas vueltas da el desplazamiento; más = más remolinos", + "sputnik.setting.lsd.general.rotaciones.name": "rotaciones", + "sputnik.setting.lsd.general.tono.desc": "desplazamiento de tono que se aplica sobre la imagen", + "sputnik.setting.lsd.general.tono.name": "tono", + "sputnik.setting.lsd.general.velocidad.desc": "a qué ritmo se mueve el patrón (0 lo congela)", + "sputnik.setting.lsd.general.velocidad.name": "velocidad", "sputnik.setting.manos.animaciones.animacionesviejas.desc": "como las de la 1.8, al pegar", "sputnik.setting.manos.animaciones.animacionesviejas.name": "animaciones viejas", "sputnik.setting.manos.animaciones.nocomer.desc": "no renderiza la animación de comer", diff --git a/src/main/resources/assets/sputnik/shaders/lsd.vert b/src/main/resources/assets/sputnik/shaders/lsd.vert deleted file mode 100644 index 87fcc03..0000000 --- a/src/main/resources/assets/sputnik/shaders/lsd.vert +++ /dev/null @@ -1,11 +0,0 @@ -#version 330 core - -layout(location = 0) in vec3 Position; -layout(location = 1) in vec2 TexCoord; - -out vec2 texCoord; - -void main() { - gl_Position = vec4(Position, 1.0); - texCoord = TexCoord; -} \ No newline at end of file diff --git a/src/main/resources/assets/sputnik/shaders/post/blit.fsh b/src/main/resources/assets/sputnik/shaders/post/blit.fsh new file mode 100644 index 0000000..8a0b2ad --- /dev/null +++ b/src/main/resources/assets/sputnik/shaders/post/blit.fsh @@ -0,0 +1,12 @@ +#version 330 + +// copia el resultado de la pasada lsd (un objetivo intermedio) de vuelta al objetivo +// principal: una pasada de post no puede leer y escribir la misma textura a la vez +uniform sampler2D InSampler; + +in vec2 texCoord; +out vec4 fragColor; + +void main() { + fragColor = texture(InSampler, texCoord); +} diff --git a/src/main/resources/assets/sputnik/shaders/lsd.frag b/src/main/resources/assets/sputnik/shaders/post/lsd.fsh similarity index 86% rename from src/main/resources/assets/sputnik/shaders/lsd.frag rename to src/main/resources/assets/sputnik/shaders/post/lsd.fsh index 9ab4847..7db2f66 100644 --- a/src/main/resources/assets/sputnik/shaders/lsd.frag +++ b/src/main/resources/assets/sputnik/shaders/post/lsd.fsh @@ -1,19 +1,23 @@ -#version 330 core +#version 330 #define M_PI 3.1415926535897932384626433832795 -layout(std140) uniform LsdUniforms { - float strength; - float seed; - float time; - float rotations; -}; - -uniform sampler2D DiffuseSampler; +uniform sampler2D InSampler; in vec2 texCoord; out vec4 fragColor; +// 26.2 pasó los samplers con nombre a bind groups; los uniforms van en un bloque std140 +// que rellena LsdPostProcessor cada frame. el tiempo se lleva aquí en vez de leerlo de +// Globals.GameTime porque ese es una fracción del día (se reinicia cada 24000 ticks) y +// animaría la distorsión lentísimo con un salto cada 20 minutos +layout(std140) uniform LsdConfig { + float strength; + float seed; + float rotations; + float time; +}; + vec4 permute(vec4 x){ return mod(((x*34.0)+1.0)*x, 289.0); } vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; } vec3 fade(vec3 t) { return t*t*t*(t*(t*6.0-15.0)+10.0); } @@ -114,13 +118,13 @@ vec3 hueShift(vec3 color, float hueAdjust){ } void main() { - vec2 oneTexel = vec2(1.0) / vec2(textureSize(DiffuseSampler, 0)); + vec2 oneTexel = vec2(1.0) / vec2(textureSize(InSampler, 0)); float perlin = cnoise(vec3(texCoord/oneTexel/100.0, time)); float ang = radians(perlin * 360.0) * rotations; float s = sin(ang); float c = cos(ang); vec2 offset = vec2(s, c) * perlin; - vec4 tex = texture(DiffuseSampler, texCoord + (offset * strength * 30.0) * oneTexel); + vec4 tex = texture(InSampler, texCoord + (offset * strength * 30.0) * oneTexel); fragColor = mix(tex, vec4(hueShift(tex.rgb, seed * 2.0 * M_PI), 1.0), strength); -} \ No newline at end of file +}