diff --git a/docs/document-en_us.md b/docs/document-en_us.md index ece9c067..3d71598e 100644 --- a/docs/document-en_us.md +++ b/docs/document-en_us.md @@ -244,6 +244,26 @@ So the block state can be restored when you place blocks with the picked item - Default value: `LEFT_ALT` +### fireworkRocketThrottler + +Limits the maximum frequency of firework rocket usage, to avoid setting off rockets with auto-clicker + +- Category: Features +- Type: hotkey togglable boolean (Tweak) +- Default value: *no hotkey*, `false` + + +### fireworkRocketThrottlerCooldown + +The cooldown value of option fireworkRocketThrottler in seconds + +- Category: Features +- Type: double (Generic) +- Default value: `1.0` +- Minimum value: `0.0` +- Maximum value: `5.0` + + ### infoView The main switch of the info view feature diff --git a/docs/document-zh_cn.md b/docs/document-zh_cn.md index d5d27f20..5297433d 100644 --- a/docs/document-zh_cn.md +++ b/docs/document-zh_cn.md @@ -244,6 +244,26 @@ TweakerMore提供的新功能 - 默认值: `LEFT_ALT` +### 烟花火箭限速器 (fireworkRocketThrottler) + +限制烟花火箭的最大使用频率,防止连点器放烟花 + +- 分类: 功能 +- 类型: 带热键布尔值 (工具) +- 默认值: *无快捷键*, `false` + + +### 烟花火箭限速器-冷却时间 (fireworkRocketThrottlerCooldown) + +选项烟花火箭限速器的冷却时间,单位秒 + +- 分类: 功能 +- 类型: 实数 (通用) +- 默认值: `1.0` +- 最小值: `0.0` +- 最大值: `5.0` + + ### 信息展示 (infoView) 信息展示相关特性的总开关 diff --git a/src/main/java/me/fallenbreath/tweakermore/config/TweakerMoreConfigs.java b/src/main/java/me/fallenbreath/tweakermore/config/TweakerMoreConfigs.java index 5fa9d6f5..e83f3bac 100644 --- a/src/main/java/me/fallenbreath/tweakermore/config/TweakerMoreConfigs.java +++ b/src/main/java/me/fallenbreath/tweakermore/config/TweakerMoreConfigs.java @@ -174,6 +174,12 @@ public class TweakerMoreConfigs @Config(type = Config.Type.HOTKEY, category = Config.Category.FEATURES) public static final TweakerMoreConfigHotkeyWithSwitch CREATIVE_PICK_BLOCK_WITH_STATE = newConfigHotKeyWithSwitch("creativePickBlockWithState", false, "LEFT_ALT", KeybindSettings.MODIFIER_INGAME); + @Config(type = Config.Type.TWEAK, category = Config.Category.FEATURES) + public static final TweakerMoreConfigBooleanHotkeyed FIREWORK_ROCKET_THROTTLER = newConfigBooleanHotkeyed("fireworkRocketThrottler"); + + @Config(type = Config.Type.GENERIC, category = Config.Category.FEATURES) + public static final TweakerMoreConfigDouble FIREWORK_ROCKET_THROTTLER_COOLDOWN = newConfigDouble("fireworkRocketThrottlerCooldown", 1, 0, 5); + @Config(type = Config.Type.TWEAK, category = Config.Category.FEATURES) public static final TweakerMoreConfigBooleanHotkeyed INFO_VIEW = newConfigBooleanHotkeyed("infoView"); diff --git a/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java b/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java new file mode 100644 index 00000000..fd51e5f4 --- /dev/null +++ b/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java @@ -0,0 +1,142 @@ +/* + * This file is part of the TweakerMore project, licensed under the + * GNU Lesser General Public License v3.0 + * + * Copyright (C) 2024 Fallen_Breath and contributors + * + * TweakerMore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TweakerMore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with TweakerMore. If not, see . + */ + +package me.fallenbreath.tweakermore.mixins.tweaks.features.fireworkRocketThrottler; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import fi.dy.masa.malilib.util.InfoUtils; +import me.fallenbreath.tweakermore.config.TweakerMoreConfigs; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.client.world.ClientWorld; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.FireworkItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public abstract class ClientPlayerInteractionManagerMixin +{ + @Unique + private long lastFireworkRocketUsageMilli = 0; + + // ========================== activate cooldown ========================== + + @Inject( + method = "interactBlock", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/Packet;)V", + ordinal = 2 + ), + cancellable = true + ) + private void fireworkRocketThrottler_cancelIfCooldown_useOnBlock(ClientPlayerEntity player, ClientWorld world, Hand hand, BlockHitResult hitResult, CallbackInfoReturnable cir) + { + cancelIfCooldown(player, hand, cir); + } + + @Inject( + method = "interactItem", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;sendPacket(Lnet/minecraft/network/Packet;)V" + ), + cancellable = true + ) + private void fireworkRocketThrottler_cancelIfCooldown_useAtAir(PlayerEntity player, World world, Hand hand, CallbackInfoReturnable cir) + { + cancelIfCooldown(player, hand, cir); + } + + @Unique + private void cancelIfCooldown(PlayerEntity player, Hand hand, CallbackInfoReturnable cir) + { + if (TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER.getBooleanValue()) + { + ItemStack itemStack = player.getStackInHand(hand); + if (itemStack.getItem() instanceof FireworkItem) + { + long now = System.currentTimeMillis(); + double cooldown = TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER_COOLDOWN.getDoubleValue(); + double remaining = cooldown - (now - this.lastFireworkRocketUsageMilli) / 1000.0; + if (remaining > 0) + { + InfoUtils.printActionbarMessage("tweakermore.impl.fireworkRocketThrottler.throttled", String.format("%.1f", remaining)); + cir.setReturnValue(ActionResult.FAIL); + } + } + } + } + + // ========================== update cooldown ========================== + + @ModifyExpressionValue( + method = "interactBlock", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/ItemStack;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;" + ) + ) + private ActionResult fireworkRocketThrottler_updateCooldown_useOnBlock(ActionResult actionResult) + { + updateCooldownOnUse(actionResult); + return actionResult; + } + + @ModifyExpressionValue( + method = "interactItem", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/TypedActionResult;getResult()Lnet/minecraft/util/ActionResult;" + ) + ) + private ActionResult fireworkRocketThrottler_updateCooldown_useAtAir(ActionResult actionResult) + { + updateCooldownOnUse(actionResult); + return actionResult; + } + + @Unique + private void updateCooldownOnUse(ActionResult actionResult) + { + if (TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER.getBooleanValue()) + { + if ( + //#if MC >= 11500 + actionResult.isAccepted() + //#else + //$$ actionResult == ActionResult.SUCCESS + //#endif + ) + { + this.lastFireworkRocketUsageMilli = System.currentTimeMillis(); + } + } + } +} diff --git a/src/main/resources/assets/tweakermore/lang/en_us.yml b/src/main/resources/assets/tweakermore/lang/en_us.yml index ee258642..8682fbf9 100644 --- a/src/main/resources/assets/tweakermore/lang/en_us.yml +++ b/src/main/resources/assets/tweakermore/lang/en_us.yml @@ -86,6 +86,12 @@ tweakermore: When performing creative pick block action (middle click) with hotkey pressed, store the target block's block state into the nbt named "BlockStateTag" of the picked item. So the block state can be restored when you place blocks with the picked item + fireworkRocketThrottler: + .: fireworkRocketThrottler + comment: Limits the maximum frequency of firework rocket usage, to avoid setting off rockets with auto-clicker + fireworkRocketThrottlerCooldown: + .: fireworkRocketThrottlerCooldown + comment: The cooldown value of option @option#fireworkRocketThrottler@ in seconds infoView: .: infoView comment: |- @@ -1029,6 +1035,8 @@ tweakermore: empty_sign: '%1$s does not contain any text' creativePickBlockWithState: message: Block state of %1$s stored + fireworkRocketThrottler: + throttled: Firework rocket usage throttled (CD %ss) infoViewGrowthSpeed: copper: chance: Chance diff --git a/src/main/resources/assets/tweakermore/lang/zh_cn.yml b/src/main/resources/assets/tweakermore/lang/zh_cn.yml index 71977000..ffb24e8c 100644 --- a/src/main/resources/assets/tweakermore/lang/zh_cn.yml +++ b/src/main/resources/assets/tweakermore/lang/zh_cn.yml @@ -86,6 +86,12 @@ tweakermore: 当执行创造模式的提取方块(按下中键)时,如果热键处于按下状态, 则将目标方块的方块状态存储到提取的物品的nbt中,名为“BlockStateTag” 因此,在你用提取的物品放置方块时,你可以直接放下与之前被选择的方块状态相同的方块 + fireworkRocketThrottler: + .: 烟花火箭限速器 + comment: 限制烟花火箭的最大使用频率,防止连点器放烟花 + fireworkRocketThrottlerCooldown: + .: 烟花火箭限速器-冷却时间 + comment: 选项@option#fireworkRocketThrottler@的冷却时间,单位秒 infoView: .: 信息展示 comment: |- @@ -1029,6 +1035,8 @@ tweakermore: empty_sign: '%1$s不包含任何文本' creativePickBlockWithState: message: 已储存%1$s的方块状态 + fireworkRocketThrottler: + throttled: 触发烟花火箭使用限速 (冷却 %ss) infoViewGrowthSpeed: copper: chance: 锈蚀概率 diff --git a/src/main/resources/tweakermore.mixins.json b/src/main/resources/tweakermore.mixins.json index 37e0b62b..adf0518b 100644 --- a/src/main/resources/tweakermore.mixins.json +++ b/src/main/resources/tweakermore.mixins.json @@ -33,6 +33,7 @@ "tweaks.features.copyItemData.ContainerScreenAccessor", "tweaks.features.copySignTextToClipBoard.SignBlockEntityAccessor", "tweaks.features.creativePickBlockWithState.MinecraftClientMixin", + "tweaks.features.fireworkRocketThrottler.ClientPlayerInteractionManagerMixin", "tweaks.features.flawlessFrames.ScreenshotRendererMixin", "tweaks.features.flawlessFrames.VideoRendererMixin", "tweaks.features.infoView.beacon.BeaconBlockEntityAccessor", diff --git a/versions/1.19.2/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java b/versions/1.19.2/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java new file mode 100644 index 00000000..aa873ea2 --- /dev/null +++ b/versions/1.19.2/src/main/java/me/fallenbreath/tweakermore/mixins/tweaks/features/fireworkRocketThrottler/ClientPlayerInteractionManagerMixin.java @@ -0,0 +1,172 @@ +/* + * This file is part of the TweakerMore project, licensed under the + * GNU Lesser General Public License v3.0 + * + * Copyright (C) 2024 Fallen_Breath and contributors + * + * TweakerMore is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * TweakerMore is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with TweakerMore. If not, see . + */ + +package me.fallenbreath.tweakermore.mixins.tweaks.features.fireworkRocketThrottler; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; +import fi.dy.masa.malilib.util.InfoUtils; +import me.fallenbreath.tweakermore.config.TweakerMoreConfigs; +import net.minecraft.client.network.ClientPlayerEntity; +import net.minecraft.client.network.ClientPlayerInteractionManager; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.FireworkRocketItem; +import net.minecraft.item.ItemStack; +import net.minecraft.network.Packet; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import org.apache.commons.lang3.mutable.MutableObject; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ClientPlayerInteractionManager.class) +public abstract class ClientPlayerInteractionManagerMixin +{ + @Unique + private long lastFireworkRocketUsageMilli = 0; + + @Unique + private final ThreadLocal nullPacketSkipping = ThreadLocal.withInitial(() -> false); + + // ========================== activate cooldown ========================== + + @Inject( + method = "method_41933", // lambda method in interactBlock() + at = @At("HEAD"), + cancellable = true + ) + private void fireworkRocketThrottler_cancelIfCooldown_useOnBlock( + CallbackInfoReturnable> cir, + @Local(argsOnly = true) ClientPlayerEntity player, + @Local(argsOnly = true) Hand hand, + @Local(argsOnly = true) MutableObject actionResult + ) + { + if (checkCooldown(player, hand)) + { + this.nullPacketSkipping.set(true); + cir.setReturnValue(null); + actionResult.setValue(ActionResult.FAIL); + } + } + + @Inject( + method = "method_41929", // lambda method in interactItem + at = @At("HEAD"), + cancellable = true + ) + private void fireworkRocketThrottler_cancelIfCooldown_useAtAir( + CallbackInfoReturnable> cir, + @Local(argsOnly = true) PlayerEntity player, + @Local(argsOnly = true) Hand hand, + @Local(argsOnly = true) MutableObject actionResult + ) + { + if (checkCooldown(player, hand)) + { + this.nullPacketSkipping.set(true); + cir.setReturnValue(null); + actionResult.setValue(ActionResult.FAIL); + } + } + + @Unique + private boolean checkCooldown(PlayerEntity player, Hand hand) + { + if (TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER.getBooleanValue()) + { + ItemStack itemStack = player.getStackInHand(hand); + if (itemStack.getItem() instanceof FireworkRocketItem) + { + long now = System.currentTimeMillis(); + double cooldown = TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER_COOLDOWN.getDoubleValue(); + double remaining = cooldown - (now - this.lastFireworkRocketUsageMilli) / 1000.0; + if (remaining > 0) + { + InfoUtils.printActionbarMessage("tweakermore.impl.fireworkRocketThrottler.throttled", String.format("%.1f", remaining)); + return true; + } + } + } + return false; + } + + @Inject( + method = "sendSequencedPacket", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/network/SequencedPacketCreator;predict(I)Lnet/minecraft/network/Packet;", + shift = At.Shift.AFTER + ), + cancellable = true + ) + private void fireworkRocketThrottler_cancelSendSequencedPacketIfNull(CallbackInfo ci) + { + if (this.nullPacketSkipping.get()) + { + this.nullPacketSkipping.remove(); + ci.cancel(); + } + } + + // ========================== update cooldown ========================== + + @ModifyExpressionValue( + method = "interactBlockInternal", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/item/ItemStack;useOnBlock(Lnet/minecraft/item/ItemUsageContext;)Lnet/minecraft/util/ActionResult;" + ) + ) + private ActionResult fireworkRocketThrottler_updateCooldown_useOnBlock(ActionResult actionResult) + { + updateCooldownOnUse(actionResult); + return actionResult; + } + + @ModifyExpressionValue( + method = "method_41929", // lambda method in interactItem + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/TypedActionResult;getResult()Lnet/minecraft/util/ActionResult;" + ) + ) + private ActionResult fireworkRocketThrottler_updateCooldown_useAtAir(ActionResult actionResult) + { + updateCooldownOnUse(actionResult); + return actionResult; + } + + @Unique + private void updateCooldownOnUse(ActionResult actionResult) + { + if (TweakerMoreConfigs.FIREWORK_ROCKET_THROTTLER.getBooleanValue()) + { + if (actionResult.isAccepted()) + { + this.lastFireworkRocketUsageMilli = System.currentTimeMillis(); + } + } + } +}