mirror of
https://github.com/chylex/Better-Controls.git
synced 2025-04-09 15:15:51 +02:00
Add sticky keybind hooks to support better toggling
This commit is contained in:
parent
bee7dee256
commit
482daeba3f
src/main
java/chylex/bettercontrols
input
mixin
resources
83
src/main/java/chylex/bettercontrols/input/ToggleTracker.java
Normal file
83
src/main/java/chylex/bettercontrols/input/ToggleTracker.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
"AccessKeyBindingFields",
|
||||
"AccessScreenButtons",
|
||||
"HookLoadGameOptions",
|
||||
"HookOpenScreen"
|
||||
"HookOpenScreen",
|
||||
"HookStickyKeyBindingState"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
Loading…
Reference in New Issue
Block a user