diff --git a/src/main/java/chylex/bettercontrols/input/ToggleTracker.java b/src/main/java/chylex/bettercontrols/input/ToggleTracker.java new file mode 100644 index 0000000..3ef492a --- /dev/null +++ b/src/main/java/chylex/bettercontrols/input/ToggleTracker.java @@ -0,0 +1,83 @@ +package chylex.bettercontrols.input; +import net.minecraft.client.options.KeyBinding; + +public class ToggleTracker{ + protected final KeyBinding bindingToggle; + protected final KeyBinding bindingReset; + + protected boolean isToggled; + + private boolean waitForRelease; + private boolean hasToggledWhileHoldingReset; + private boolean skipNextToggle; + + public ToggleTracker(final KeyBinding bindingToggle, final KeyBinding bindingReset){ + this.bindingToggle = bindingToggle; + this.bindingReset = bindingReset; + } + + /* + * Assume holding CTRL is used to sprint (reset key) and either G or CTRL + G is used to toggle sprint on. + * The toggle modifier actually does not matter, having it just prevents trigger if the key is pressed alone. + * + * Pressing the toggle key alone switches the toggled state, then the key has to be released. + * Holding the reset key while toggled will reset the toggle, but continue the sprint until released. + * + * Pressing the toggle key while holding reset key switches the toggled state, but allows both keys to be + * released without interrupting the toggle (hasToggledWhileHoldingReset). + * + * Note that holding the reset key and pressing toggle key twice will switch the toggle twice, as expected. + * + * Holding the reset key while toggled, then pressing the toggle key again, will do nothing (skipNextToggle). + * This allows resetting the toggle both by pressing the reset key alone, or with the full toggle key combo. + * However, if the toggle key is then pressed again, it will function as usual. + * + * All of the logic combined allows for complex scenarios such as: + * + * +CTRL, +G, -CTRL --> toggled on + * +CTRL, +G, -CTRL, +CTRL --> toggled off (quick reset) + * +CTRL, +G, -CTRL, +CTRL, +G --> toggled off (full combo) + * +CTRL, +G, -CTRL, +CTRL, +G, +G --> toggled on + */ + + public boolean tick(){ + final boolean isHoldingReset = isResetKeyPressed(); + + if (bindingToggle.isPressed()){ + if (!waitForRelease){ + if (skipNextToggle){ + skipNextToggle = false; + } + else{ + isToggled = !isToggled; + } + + waitForRelease = true; + hasToggledWhileHoldingReset = isHoldingReset; + } + } + else{ + waitForRelease = false; + } + + if (isToggled){ + if (hasToggledWhileHoldingReset && !isHoldingReset){ + hasToggledWhileHoldingReset = false; + } + else if (!hasToggledWhileHoldingReset && isHoldingReset){ + isToggled = false; + skipNextToggle = true; + } + } + + if (skipNextToggle && !isHoldingReset){ + skipNextToggle = false; + } + + return isToggled; + } + + protected boolean isResetKeyPressed(){ + return bindingReset.isPressed(); + } +} diff --git a/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java b/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java new file mode 100644 index 0000000..da3d3c8 --- /dev/null +++ b/src/main/java/chylex/bettercontrols/input/ToggleTrackerForStickyKey.java @@ -0,0 +1,35 @@ +package chylex.bettercontrols.input; +import chylex.bettercontrols.mixin.AccessKeyBindingFields; +import it.unimi.dsi.fastutil.booleans.BooleanConsumer; +import net.minecraft.client.options.KeyBinding; +import java.util.HashSet; +import java.util.Set; + +public final class ToggleTrackerForStickyKey extends ToggleTracker{ + private static final Set<KeyBinding> enabledOverrides = new HashSet<>(); + + public static boolean isOverrideEnabled(final KeyBinding binding){ + return enabledOverrides.contains(binding); + } + + private final BooleanConsumer setToggleState; + + public ToggleTrackerForStickyKey(final KeyBinding bindingToggle, final KeyBinding bindingStickyReset, final BooleanConsumer setToggleState){ + super(bindingToggle, bindingStickyReset); + this.setToggleState = setToggleState; + this.setToggleState.accept(false); + enabledOverrides.add(bindingStickyReset); + } + + @Override + public boolean tick(){ + final boolean isToggled = super.tick(); + setToggleState.accept(isToggled); + return isToggled; + } + + @Override + protected boolean isResetKeyPressed(){ + return ((AccessKeyBindingFields)bindingReset).isPressedField(); + } +} diff --git a/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java b/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java index c1b4499..a8ca93e 100644 --- a/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java +++ b/src/main/java/chylex/bettercontrols/mixin/AccessKeyBindingFields.java @@ -10,4 +10,10 @@ public interface AccessKeyBindingFields{ static Map<String, Integer> getCategoryOrderMap(){ throw new AssertionError(); } + + @Accessor("pressed") + boolean isPressedField(); + + @Accessor("pressed") + void setPressedField(boolean value); } diff --git a/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java b/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java new file mode 100644 index 0000000..331d8ef --- /dev/null +++ b/src/main/java/chylex/bettercontrols/mixin/HookStickyKeyBindingState.java @@ -0,0 +1,35 @@ +package chylex.bettercontrols.mixin; +import chylex.bettercontrols.input.ToggleTrackerForStickyKey; +import net.minecraft.client.options.KeyBinding; +import net.minecraft.client.options.StickyKeyBinding; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.function.BooleanSupplier; + +@Mixin(StickyKeyBinding.class) +public abstract class HookStickyKeyBindingState extends KeyBinding{ + @Shadow + @Final + private BooleanSupplier toggleGetter; + + public HookStickyKeyBindingState(final String translationKey, final int code, final String category){ + super(translationKey, code, category); + } + + @Inject(method = "setPressed(Z)V", at = @At("HEAD"), cancellable = true) + public void setPressed(final boolean pressed, final CallbackInfo info){ + if (ToggleTrackerForStickyKey.isOverrideEnabled(this)){ + ((AccessKeyBindingFields)this).setPressedField(pressed); + info.cancel(); + } + } + + @Override + public boolean isPressed(){ + return super.isPressed() || (ToggleTrackerForStickyKey.isOverrideEnabled(this) && toggleGetter.getAsBoolean()); + } +} diff --git a/src/main/resources/mixins.json b/src/main/resources/mixins.json index 70c7709..77d4e93 100644 --- a/src/main/resources/mixins.json +++ b/src/main/resources/mixins.json @@ -7,7 +7,8 @@ "AccessKeyBindingFields", "AccessScreenButtons", "HookLoadGameOptions", - "HookOpenScreen" + "HookOpenScreen", + "HookStickyKeyBindingState" ], "injectors": { "defaultRequire": 1