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

No files matched your search

+4 -20
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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 = "<init>", 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;
}
}
@@ -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;
}
@@ -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 = "<init>", 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)
@@ -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();
}
}
@@ -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<Module> 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 extends Module> T getModuleByClass(Class<T> clazz) {
for (Module module : modules)
if (clazz.isInstance(module))
for (Module module : modules) {
if (clazz.isInstance(module)) {
return clazz.cast(module);
}
}
return null;
}
}
@@ -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);
@@ -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);
@@ -16,7 +16,7 @@ import java.util.Arrays;
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",
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);
@@ -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);
@@ -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);
@@ -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.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",
@@ -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",
@@ -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
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);
}
}