This commit is contained in:
retucio committed 2026-07-19 23:14:33 +02:00
1 parent aeadfb28b1
commit 3913b31374
20 files changed
+415 -78

No files matched your search

+4 -20
View File
@@ -1,33 +1,17 @@
# v5.3 # v5.4
## novedades ## novedades
- actualizado a 26.2
### adiciones ### adiciones
- el mod ya está traducido y sigue el idioma del juego: de momento español e inglés - sistema de shaders
- nuevo elemento del HUD: `armadura`
#### módulos nuevos: #### 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 ### arreglos / mejoras
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
## problemas reconocidos ## problemas reconocidos
- lo de siempre. - 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
+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.3 mod_version=5.4
maven_group=me.retucio maven_group=me.retucio
archives_base_name=sputnik archives_base_name=sputnik
@@ -3,7 +3,7 @@ package me.retucio.sputnik.mixin.mixins.entity;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import me.retucio.sputnik.module.ModuleManager; 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.client.renderer.entity.EntityRenderer;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
@@ -17,21 +17,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public abstract class EntityRendererMixin { public abstract class EntityRendererMixin {
@Unique @Unique
private EntityESP esp; private EntityEsp esp;
@Inject(method = "<init>", at = @At("TAIL")) @Inject(method = "<init>", at = @At("TAIL"))
private void getModules(CallbackInfo ci) { 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")) @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) { 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")) @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) { 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; return original;
} }
} }
@@ -5,10 +5,9 @@ import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Local;
import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.world.NoMiningInterruptions; 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.gui.screens.Screen;
import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.player.LocalPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3; 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;")) @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) { 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; return original;
} }
@@ -6,10 +6,12 @@ import me.retucio.sputnik.event.render.GetFOVEvent;
import me.retucio.sputnik.module.ModuleManager; import me.retucio.sputnik.module.ModuleManager;
import me.retucio.sputnik.module.modules.camera.Freecam; import me.retucio.sputnik.module.modules.camera.Freecam;
import me.retucio.sputnik.module.modules.misc.UnfocusedCpu; 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.render.NoRender;
import me.retucio.sputnik.module.modules.camera.Zoom; 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.DeltaTracker;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer; import net.minecraft.client.renderer.GameRenderer;
@@ -38,15 +40,31 @@ public abstract class GameRendererMixin {
@Unique Zoom zoom; @Unique Zoom zoom;
@Unique Freecam freecam; @Unique Freecam freecam;
@Unique NoRender noRender; @Unique NoRender noRender;
@Unique Lsd lsd;
@Shadow @Final @Shadow @Final
private Minecraft minecraft; private Minecraft minecraft;
@Shadow @Final
private RenderTarget mainRenderTarget;
@Shadow @Final
private CrossFrameResourcePool resourcePool;
@Inject(method = "<init>", at = @At("TAIL")) @Inject(method = "<init>", at = @At("TAIL"))
private void getModules(Minecraft minecraft, ItemInHandRenderer itemInHandRenderer, ModelManager modelManager, CallbackInfo ci) { private void getModules(Minecraft minecraft, ItemInHandRenderer itemInHandRenderer, ModelManager modelManager, CallbackInfo ci) {
zoom = ModuleManager.INSTANCE.getModuleByClass(Zoom.class);; zoom = ModuleManager.INSTANCE.getModuleByClass(Zoom.class);;
freecam = ModuleManager.INSTANCE.getModuleByClass(Freecam.class); freecam = ModuleManager.INSTANCE.getModuleByClass(Freecam.class);
noRender = ModuleManager.INSTANCE.getModuleByClass(NoRender.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) @Inject(method = "render", at = @At("HEAD"), cancellable = true)
@@ -1,6 +1,7 @@
package me.retucio.sputnik.mixin.mixins.render; package me.retucio.sputnik.mixin.mixins.render;
import me.retucio.sputnik.util.render.Pipelines; import me.retucio.sputnik.util.render.Pipelines;
import me.retucio.sputnik.util.render.PostShaders;
import net.minecraft.client.renderer.ShaderManager; import net.minecraft.client.renderer.ShaderManager;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; 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")) @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) { private void onReload(CallbackInfo ci) {
Pipelines.init(); Pipelines.init();
PostShaders.reload();
} }
} }
@@ -2,7 +2,7 @@ package me.retucio.sputnik.module;
import com.github.retucio.neutrino.EventListener; import com.github.retucio.neutrino.EventListener;
import me.retucio.sputnik.Sputnik; 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.Friends;
import me.retucio.sputnik.module.modules.client.Hud; import me.retucio.sputnik.module.modules.client.Hud;
import me.retucio.sputnik.module.modules.camera.*; import me.retucio.sputnik.module.modules.camera.*;
@@ -79,7 +79,7 @@ public class ModuleManager {
} }
private void addClient() { private void addClient() {
add(new DiscordRPC()); add(new DiscordRpc());
add(new Friends()); add(new Friends());
add(new Hud()); add(new Hud());
add(new Notifications()); add(new Notifications());
@@ -111,10 +111,10 @@ public class ModuleManager {
add(new CreativeInventoryHotbarKeybinds()); add(new CreativeInventoryHotbarKeybinds());
add(new InventoryPlus()); add(new InventoryPlus());
add(new Offhand()); add(new Offhand());
add(new PortalGUI()); add(new PortalGui());
add(new Replenish()); add(new Replenish());
add(new ShulkerPeek()); add(new ShulkerPeek());
add(new UIMove()); add(new UiMove());
add(new XCarry()); add(new XCarry());
} }
@@ -159,17 +159,18 @@ public class ModuleManager {
} }
private void addRender() { private void addRender() {
add(new BlockESP()); add(new BlockEsp());
add(new BlockOutline()); add(new BlockOutline());
add(new Breadcrumbs()); add(new Breadcrumbs());
add(new BreakingProgress()); add(new BreakingProgress());
add(new Confetti()); add(new Confetti());
add(new CritsPlus()); add(new CritsPlus());
add(new DamageOverlay()); add(new DamageOverlay());
add(new EntityESP()); add(new EntityEsp());
add(new Fonts()); add(new Fonts());
add(new GlintPlus()); add(new GlintPlus());
add(new Hitboxes()); add(new Hitboxes());
add(new Lsd());
add(new Nametags()); add(new Nametags());
add(new NoRender()); add(new NoRender());
add(new Particles()); 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) // para obtener módulos más fácilmente (por nombre, clase, o la lista completa)
public List<Module> getModules() { public List<Module> getModules() {
return modules; 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 // 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 // nombre del módulo pero no al argumento, así que "cámara libre" no encontraba nada
public Module getModuleByName(String name) { public Module getModuleByName(String name) {
for (Module module : modules) for (Module module : modules) {
if (module.getName().equalsIgnoreCase(name)) if (module.getName().equalsIgnoreCase(name)) {
return module; return module;
}
}
return null; return null;
} }
// busca por nombre visible: lo que el usuario teclea en los comandos, sin tildes y con "_" // busca por nombre visible: lo que el usuario teclea en los comandos, sin tildes y con "_"
public Module getModuleByDisplayName(String name) { public Module getModuleByDisplayName(String name) {
String target = MiscUtil.removeAccentMarks(name).replace("_", " "); String target = MiscUtil.removeAccentMarks(name).replace("_", " ");
for (Module module : modules) for (Module module : modules) {
if (MiscUtil.removeAccentMarks(module.getDisplayName()).equalsIgnoreCase(target)) if (MiscUtil.removeAccentMarks(module.getDisplayName()).equalsIgnoreCase(target)) {
return module; return module;
}
}
return null; return null;
} }
public <T extends Module> T getModuleByClass(Class<T> clazz) { public <T extends Module> T getModuleByClass(Class<T> clazz) {
for (Module module : modules) for (Module module : modules) {
if (clazz.isInstance(module)) if (clazz.isInstance(module)) {
return clazz.cast(module); return clazz.cast(module);
}
}
return null; return null;
} }
} }
@@ -1,6 +1,5 @@
package me.retucio.sputnik.module.modules.client; package me.retucio.sputnik.module.modules.client;
import dev.firstdark.rpc.DiscordRpc;
import dev.firstdark.rpc.enums.ActivityType; import dev.firstdark.rpc.enums.ActivityType;
import dev.firstdark.rpc.enums.ErrorCode; import dev.firstdark.rpc.enums.ErrorCode;
import dev.firstdark.rpc.exceptions.UnsupportedOsType; import dev.firstdark.rpc.exceptions.UnsupportedOsType;
@@ -22,7 +21,7 @@ import net.minecraft.network.chat.Component;
* @author retucio * @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 sgImage = addSg(new SettingGroup("imágenes", false));
private final SettingGroup sgButton = addSg(new SettingGroup("botón", false)); private final SettingGroup sgButton = addSg(new SettingGroup("botón", false));
@@ -103,7 +102,7 @@ public class DiscordRPC extends Module {
// crear el rpc // crear el rpc
private boolean initialized; private boolean initialized;
DiscordRpc rpc = new DiscordRpc(); dev.firstdark.rpc.DiscordRpc rpc = new dev.firstdark.rpc.DiscordRpc();
RPCEventHandler handler = new RPCEventHandler() { RPCEventHandler handler = new RPCEventHandler() {
@Override @Override
public void ready(User user) { public void ready(User user) {
@@ -124,7 +123,7 @@ public class DiscordRPC extends Module {
}; };
public DiscordRPC() { public DiscordRpc() {
super("discordRPC", super("discordRPC",
"personaliza la actividad de tu perfil en dishkor", "personaliza la actividad de tu perfil en dishkor",
Category.CLIENT); Category.CLIENT);
@@ -8,9 +8,9 @@ import me.retucio.sputnik.module.Module;
* @see LocalPlayerMixin * @see LocalPlayerMixin
*/ */
public class PortalGUI extends Module { public class PortalGui extends Module {
public PortalGUI() { public PortalGui() {
super("interfaz en portales", super("interfaz en portales",
"te permite abrir interfaces dentro de portales, como el chat o el inventario", "te permite abrir interfaces dentro de portales, como el chat o el inventario",
Category.INVENTORY); Category.INVENTORY);
@@ -16,7 +16,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
public class UIMove extends Module { public class UiMove extends Module {
public ListSetting<MenuType<?>> screens = sgGeneral.add(new ListSetting<>("interfaces", "interfaces en las que te podrás mover", public ListSetting<MenuType<?>> screens = sgGeneral.add(new ListSetting<>("interfaces", "interfaces en las que te podrás mover",
Lists.screenList, Lists.allTrue(Lists.screenList), Lists.screenNames)); Lists.screenList, Lists.allTrue(Lists.screenList), Lists.screenNames));
@@ -28,7 +28,7 @@ public class UIMove extends Module {
private final MenuType<?> clickGuiHandlerType = MenuType.register( private final MenuType<?> clickGuiHandlerType = MenuType.register(
"sputnik_clickgui", ChestMenu::threeRows); // 9x3 porque no importa (creo) "sputnik_clickgui", ChestMenu::threeRows); // 9x3 porque no importa (creo)
public UIMove() { public UiMove() {
super("moverse en interfaz", super("moverse en interfaz",
"te permite seguir usando las teclas de movimiento aún estando en ciertas interfaces", "te permite seguir usando las teclas de movimiento aún estando en ciertas interfaces",
Category.INVENTORY); Category.INVENTORY);
@@ -24,7 +24,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap; 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 sgSearch = addSg(new SettingGroup("búsqueda", true));
private final SettingGroup sgOulines = addSg(new SettingGroup("contorno", true)); private final SettingGroup sgOulines = addSg(new SettingGroup("contorno", true));
@@ -103,7 +103,7 @@ public class BlockESP extends Module {
private boolean isSearching = false; private boolean isSearching = false;
public BlockESP() { public BlockEsp() {
super("resaltado de bloques", super("resaltado de bloques",
"te muestra la posición de cierto tipo de bloque renderizando una caja con su forma", "te muestra la posición de cierto tipo de bloque renderizando una caja con su forma",
Category.RENDER); Category.RENDER);
@@ -17,7 +17,7 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType; 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)); private final SettingGroup sgRender = addSg(new SettingGroup("cajas", true));
@@ -69,7 +69,7 @@ public class EntityESP extends Module {
false false
)); ));
public EntityESP() { public EntityEsp() {
super("resaltado de entidades", "resalta el contorno de las entidades seleccionadas a través de bloques", Category.RENDER); super("resaltado de entidades", "resalta el contorno de las entidades seleccionadas a través de bloques", Category.RENDER);
mode.onUpdate(v -> { mode.onUpdate(v -> {
glowColor.visibility(v == ESPMode.GLOW); glowColor.visibility(v == ESPMode.GLOW);
@@ -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);
}
}
@@ -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<Std140Builder> uniforms) {
if (!ensureReady()) return;
FrameGraphBuilder frame = new FrameGraphBuilder();
ResourceHandle<RenderTarget> mainHandle = frame.importExternal(name + "_main", target);
RenderTargetDescriptor swapDesc = new RenderTargetDescriptor(
target.width, target.height, true, NO_CLEAR, GpuFormat.RGBA8_UNORM);
ResourceHandle<RenderTarget> swapHandle = frame.createInternal(name + "_swap", swapDesc);
// pasada del efecto: main -> swap
FramePass effectPass = frame.addPass(name);
effectPass.reads(mainHandle);
ResourceHandle<RenderTarget> 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<RenderTarget> 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));
}
}
}
@@ -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<PostShader> SHADERS = new ArrayList<>();
static PostShader register(PostShader shader) {
SHADERS.add(shader);
return shader;
}
public static void reload() {
for (PostShader shader : SHADERS) {
shader.markDirty();
}
}
}
@@ -434,6 +434,8 @@
"sputnik.module.jugadorfalso.desc": "spawns a fake player entity for testing purposes", "sputnik.module.jugadorfalso.desc": "spawns a fake player entity for testing purposes",
"sputnik.module.jugadorfalso.name": "fake player", "sputnik.module.jugadorfalso.name": "fake player",
"sputnik.module.keypearl.nopearls": "you have no pearls", "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.macekill.oops": "oops: %s",
"sputnik.module.manos.desc": "changes the way hands are rendered", "sputnik.module.manos.desc": "changes the way hands are rendered",
"sputnik.module.manos.name": "hands", "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.desc": "what name to give the player",
"sputnik.setting.jugadorfalso.general.nombre.name": "name", "sputnik.setting.jugadorfalso.general.nombre.name": "name",
"sputnik.setting.key.reset": "keybind for module %s reset", "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.desc": "like the 1.8 ones, when hitting",
"sputnik.setting.manos.animaciones.animacionesviejas.name": "old animations", "sputnik.setting.manos.animaciones.animacionesviejas.name": "old animations",
"sputnik.setting.manos.animaciones.nocomer.desc": "doesn't render the eating animation", "sputnik.setting.manos.animaciones.nocomer.desc": "doesn't render the eating animation",
@@ -434,6 +434,8 @@
"sputnik.module.jugadorfalso.desc": "invoca una entidad de jugador falsa por motivos de testeo", "sputnik.module.jugadorfalso.desc": "invoca una entidad de jugador falsa por motivos de testeo",
"sputnik.module.jugadorfalso.name": "jugador falso", "sputnik.module.jugadorfalso.name": "jugador falso",
"sputnik.module.keypearl.nopearls": "no tienes perlas", "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.macekill.oops": "upsi: %s",
"sputnik.module.manos.desc": "modifica la manera en la que se renderizan las manos", "sputnik.module.manos.desc": "modifica la manera en la que se renderizan las manos",
"sputnik.module.manos.name": "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.desc": "qué nombre asignarle al jugador",
"sputnik.setting.jugadorfalso.general.nombre.name": "nombre", "sputnik.setting.jugadorfalso.general.nombre.name": "nombre",
"sputnik.setting.key.reset": "tecla del módulo %s restablecida", "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.desc": "como las de la 1.8, al pegar",
"sputnik.setting.manos.animaciones.animacionesviejas.name": "animaciones viejas", "sputnik.setting.manos.animaciones.animacionesviejas.name": "animaciones viejas",
"sputnik.setting.manos.animaciones.nocomer.desc": "no renderiza la animación de comer", "sputnik.setting.manos.animaciones.nocomer.desc": "no renderiza la animación de comer",
@@ -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;
}
@@ -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);
}
@@ -1,19 +1,23 @@
#version 330 core #version 330
#define M_PI 3.1415926535897932384626433832795 #define M_PI 3.1415926535897932384626433832795
layout(std140) uniform LsdUniforms { uniform sampler2D InSampler;
float strength;
float seed;
float time;
float rotations;
};
uniform sampler2D DiffuseSampler;
in vec2 texCoord; in vec2 texCoord;
out vec4 fragColor; 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 permute(vec4 x){ return mod(((x*34.0)+1.0)*x, 289.0); }
vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; } 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); } 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() { 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 perlin = cnoise(vec3(texCoord/oneTexel/100.0, time));
float ang = radians(perlin * 360.0) * rotations; float ang = radians(perlin * 360.0) * rotations;
float s = sin(ang); float s = sin(ang);
float c = cos(ang); float c = cos(ang);
vec2 offset = vec2(s, c) * perlin; 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); fragColor = mix(tex, vec4(hueShift(tex.rgb, seed * 2.0 * M_PI), 1.0), strength);
} }