diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index d46580da..a7febe05 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -151,6 +151,7 @@ public static void registerCommands(CommandDispatcher FindItemCommand.register(dispatcher, context); FishCommand.register(dispatcher, context); FovCommand.register(dispatcher); + FramerateCommand.register(dispatcher); GammaCommand.register(dispatcher); GetDataCommand.register(dispatcher); GhostBlockCommand.register(dispatcher, context); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/FramerateCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/FramerateCommand.java new file mode 100644 index 00000000..895ee7f7 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/FramerateCommand.java @@ -0,0 +1,64 @@ +package net.earthcomputer.clientcommands.command; + +import com.mojang.brigadier.CommandDispatcher; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; + +import static com.mojang.brigadier.arguments.IntegerArgumentType.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class FramerateCommand { + private static final int[] COMMON_REFRESH_RATES = new int[] { + 30, 45, 60, 75, 90, 100, 120, 144, 165, 180, 240, 300, 360, 420, 480, 540, 600 + }; + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("cfps") + .executes(ctx -> getMaxFps(ctx.getSource())) + .then(literal("unlimited") + .executes(ctx -> maxFps(ctx.getSource(), Integer.MAX_VALUE))) + .then(argument("maxfps", integer()) + .suggests((context, builder) -> { + int maxFps = getDisplayMaxFramerate(); + for (int refreshRate : COMMON_REFRESH_RATES) { + if (refreshRate > maxFps) { + break; + } + builder.suggest(refreshRate); + } + return builder.buildFuture(); + }) + .executes(ctx -> maxFps(ctx.getSource(), getInteger(ctx, "maxfps"))) + ) + ); + + } + + private static int getMaxFps(FabricClientCommandSource source) { + int framerateLimit = source.getClient().getFramerateLimitTracker().getFramerateLimit(); + if (framerateLimit < Integer.MAX_VALUE) { + source.sendFeedback(Component.translatable("commands.cfps.getMaxFps", framerateLimit)); + } else { + source.sendFeedback(Component.translatable("commands.cfps.getMaxFps.unlimited")); + } + return framerateLimit; + } + + private static int maxFps(FabricClientCommandSource source, int maxFps) { + source.getClient().getFramerateLimitTracker().setFramerateLimit(maxFps); + if (maxFps == Integer.MAX_VALUE) { + source.sendFeedback(Component.translatable("commands.cfps.setMaxFps.unlimited")); + } else { + source.sendFeedback(Component.translatable("commands.cfps.setMaxFps", maxFps)); + } + return maxFps; + } + + private static int getDisplayMaxFramerate() { + return Minecraft.getInstance().virtualScreen.screenManager.monitors.values().stream() + .mapToInt(monitor -> monitor.getCurrentMode().getRefreshRate()) + .max().orElseThrow(); + } + +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/MinecraftMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/MinecraftMixin.java new file mode 100644 index 00000000..97575c52 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/MinecraftMixin.java @@ -0,0 +1,17 @@ +package net.earthcomputer.clientcommands.mixin.commands.fps; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import net.minecraft.client.Minecraft; +import net.minecraft.client.Options; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + + @ModifyExpressionValue(method = "runTick", at = @At(value = "CONSTANT", args = "intValue=" + Options.UNLIMITED_FRAMERATE_CUTOFF)) + private int fixMaxFps(int fps) { + return Integer.MAX_VALUE; + } + +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/OptionsMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/OptionsMixin.java new file mode 100644 index 00000000..186b9cdd --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/fps/OptionsMixin.java @@ -0,0 +1,20 @@ +package net.earthcomputer.clientcommands.mixin.commands.fps; + +import net.minecraft.client.Options; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(Options.class) +public class OptionsMixin { + + @ModifyArg(method = "lambda$new$8", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/FramerateLimitTracker;setFramerateLimit(I)V")) + private static int fixMaxFpsSet(int maxFps) { + if (maxFps == Options.UNLIMITED_FRAMERATE_CUTOFF) { + return Integer.MAX_VALUE; + } + return maxFps; + } + + +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 529bff32..67ffbdb5 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -129,6 +129,11 @@ "commands.cfov.success": "Set FOV to %s", + "commands.cfps.getMaxFps": "Max FPS is %s", + "commands.cfps.getMaxFps.unlimited": "Max FPS is unlimited", + "commands.cfps.setMaxFps": "Set max FPS to %s", + "commands.cfps.setMaxFps.unlimited": "Set max FPS to unlimited", + "commands.cfunction.limitReached": "Command limit (%s) reached", "commands.cfunction.success": "Ran %s commands from function %s", diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index 061089a8..10e4df7c 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -17,6 +17,11 @@ accessible method net/minecraft/world/level/block/ShulkerBoxBlock canOpen (Lnet/ # cfish accessible method net/minecraft/world/entity/projectile/FishingHook canHitEntity (Lnet/minecraft/world/entity/Entity;)Z +# cfps +accessible field net/minecraft/client/Minecraft virtualScreen Lnet/minecraft/client/renderer/VirtualScreen; +accessible field net/minecraft/client/renderer/VirtualScreen screenManager Lcom/mojang/blaze3d/platform/ScreenManager; +accessible field com/mojang/blaze3d/platform/ScreenManager monitors Lit/unimi/dsi/fastutil/longs/Long2ObjectMap; + # cgive accessible method net/minecraft/world/entity/player/Inventory addResource (ILnet/minecraft/world/item/ItemStack;)I accessible method net/minecraft/world/entity/player/Inventory hasRemainingSpaceForItem (Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index f5e92d95..def97043 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -68,6 +68,8 @@ "commands.alias.ClientSuggestionProviderMixin", "commands.enchant.MultiPlayerGameModeMixin", "commands.findblock.ClientLevelMixin", + "commands.fps.MinecraftMixin", + "commands.fps.OptionsMixin", "commands.generic.CommandSuggestionsMixin", "commands.glow.LivingEntityRenderStateMixin", "commands.reply.ClientPacketListenerMixin",