diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index 8b8c0d89..d18ebb81 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -175,6 +175,7 @@ public static void registerCommands(CommandDispatcher // PlayerInfoCommand.register(dispatcher); PluginsCommand.register(dispatcher); PosCommand.register(dispatcher); + PostEffectCommand.register(dispatcher); PredictBrushablesCommand.register(dispatcher); RelogCommand.register(dispatcher); RenderCommand.register(dispatcher); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java new file mode 100644 index 00000000..14e6ee8f --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/PostEffectCommand.java @@ -0,0 +1,32 @@ +package net.earthcomputer.clientcommands.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.Nullable; + +import static net.earthcomputer.clientcommands.command.arguments.PostEffectArgument.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class PostEffectCommand { + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("cposteffect") + .then(argument("posteffect", postEffect()) + .executes(ctx -> applyPostEffect(ctx.getSource(), getPostEffect(ctx, "posteffect")))) + .then(literal("reset") + .executes(ctx -> applyPostEffect(ctx.getSource(), null)))); + } + + private static int applyPostEffect(FabricClientCommandSource source, @Nullable ResourceLocation postEffect) { + if (postEffect == null) { + source.getClient().gameRenderer.clearPostEffect(); + source.sendFeedback(Component.translatable("commands.cposteffect.reset.success")); + return Command.SINGLE_SUCCESS; + } + source.getClient().gameRenderer.setPostEffect(postEffect); + source.sendFeedback(Component.translatable("commands.cposteffect.apply.success", postEffect)); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java new file mode 100644 index 00000000..7df07aba --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/PostEffectArgument.java @@ -0,0 +1,69 @@ +package net.earthcomputer.clientcommands.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelTargetBundle; +import net.minecraft.client.renderer.PostChainConfig; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; + +public class PostEffectArgument implements ArgumentType { + + private static final Collection EXAMPLES = Arrays.asList("invert", "minecraft:spider", "clientcommands:crt"); + + private static final DynamicCommandExceptionType UNKNOWN_POST_EFFECT_EXCEPTION = new DynamicCommandExceptionType(postEffect -> Component.translatable("commands.cposteffect.unknownPostEffect", postEffect)); + + public static PostEffectArgument postEffect() { + return new PostEffectArgument(); + } + + public static ResourceLocation getPostEffect(final CommandContext context, final String name) { + return context.getArgument(name, ResourceLocation.class); + } + + @Override + public ResourceLocation parse(StringReader reader) throws CommandSyntaxException { + int start = reader.getCursor(); + ResourceLocation postEffectId = ResourceLocation.read(reader); + + boolean valid = getValidPostChains().anyMatch(id -> id.equals(postEffectId)); + if (!valid) { + reader.setCursor(start); + throw UNKNOWN_POST_EFFECT_EXCEPTION.createWithContext(reader, postEffectId); + } + return postEffectId; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource(getValidPostChains(), builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } + + private Stream getValidPostChains() { + return Minecraft.getInstance().getShaderManager().compilationCache.configs.postChains().entrySet().stream() + .filter(entry -> entry.getValue().passes().stream() + .flatMap(PostChainConfig.Pass::referencedTargets) + .filter(location -> !entry.getValue().internalTargets().containsKey(location)) + .allMatch(LevelTargetBundle.MAIN_TARGETS::contains)) + .map(Map.Entry::getKey); + } +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index be22ab06..943ba578 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -234,6 +234,10 @@ "commands.cpos.level.the_end": "the End", "commands.cpos.level.the_nether": "the Nether", + "commands.cposteffect.apply.success": "Successfully applied post effect %s", + "commands.cposteffect.reset.success": "Successfully reset post effect", + "commands.cposteffect.unknownPostEffect": "Unknown post effect %s", + "commands.cpredictbrushables.foundBrushableBlock": "Found %s at %s with item %s %s", "commands.cpredictbrushables.notFound": "No brushable blocks found", "commands.cpredictbrushables.starting": "Searching for brushable blocks", diff --git a/src/main/resources/assets/clientcommands/post_effect/crt.json b/src/main/resources/assets/clientcommands/post_effect/crt.json new file mode 100644 index 00000000..0cbb0358 --- /dev/null +++ b/src/main/resources/assets/clientcommands/post_effect/crt.json @@ -0,0 +1,44 @@ +{ + "targets": { + "swap": {} + }, + "passes": [ + { + "vertex_shader": "minecraft:core/screenquad", + "fragment_shader": "clientcommands:post/crt", + "inputs": [ + { + "sampler_name": "In", + "target": "minecraft:main" + } + ], + "output": "swap", + "uniforms": {} + }, + { + "vertex_shader": "minecraft:core/screenquad", + "fragment_shader": "minecraft:post/bits", + "inputs": [ + { + "sampler_name": "In", + "target": "swap" + } + ], + "output": "minecraft:main", + "uniforms": { + "BitsConfig": [ + { + "name": "Resolution", + "type": "float", + "value": 16.0 + }, + { + "name": "MosaicSize", + "type": "float", + "value": 4.0 + } + ] + } + } + ] +} diff --git a/src/main/resources/assets/clientcommands/shaders/post/crt.fsh b/src/main/resources/assets/clientcommands/shaders/post/crt.fsh new file mode 100644 index 00000000..2906b8e3 --- /dev/null +++ b/src/main/resources/assets/clientcommands/shaders/post/crt.fsh @@ -0,0 +1,32 @@ +#version 330 + +uniform sampler2D InSampler; + +in vec2 texCoord; + +layout(std140) uniform SamplerInfo { + vec2 OutSize; + vec2 InSize; +}; + +out vec4 fragColor; + +void main() { + vec2 oneTexel = 1.0 / InSize; + + float aberr = 0.002; + vec3 col; + col.r = texture(InSampler, texCoord + vec2(aberr, 0.0)).r; + col.g = texture(InSampler, texCoord).g; + col.b = texture(InSampler, texCoord - vec2(aberr, 0.0)).b; + + // Add scanlines + float scan = sin(texCoord.y * 800.0) * 0.1; // adjust frequency + col *= 1.0 - scan * 0.3; + + // Vignette + float vignette = smoothstep(1.2, 0.6, length(texCoord - 0.5)); + col *= vignette; + + fragColor = vec4(col, 1.0); +} diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index 9f304b5f..cca15860 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -50,6 +50,12 @@ accessible method net/minecraft/client/Screenshot getFile (Ljava/io/File;)Ljava/ # cpermissionlevel accessible method net/minecraft/client/player/LocalPlayer getPermissionLevel ()I +# cposteffect +accessible field net/minecraft/client/renderer/GameRenderer BLUR_POST_CHAIN_ID Lnet/minecraft/resources/ResourceLocation; +accessible method net/minecraft/client/renderer/GameRenderer setPostEffect (Lnet/minecraft/resources/ResourceLocation;)V +accessible field net/minecraft/client/renderer/ShaderManager compilationCache Lnet/minecraft/client/renderer/ShaderManager$CompilationCache; +accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache configs Lnet/minecraft/client/renderer/ShaderManager$Configs; + # Game Options accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object;