diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 628281a..527bcf5 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -410,16 +410,21 @@ public class ControllerBindings { registerModdedKeybinds(); - // key events are executed in Minecraft#execute, aka runTick.runAllTasks() - // which is called before ticking (the event below). so we can safely imitate clicks - // before key mappings are handled in tick() - ClientTickEvents.START_CLIENT_TICK.register(this::onTick); + // key events are executed in Minecraft#execute, which run at runTick.runAllTasks() + // which this event runs directly after. A normal tick could run multiple + // times per frame, so you could get double clicks if lagging. + InputHandledEvent.EVENT.register(this::imitateVanillaClick); ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> KeyMapping.releaseAll()); } public ControllerBinding register(ControllerBinding binding) { registry.put(binding.id(), binding); + + if (binding.override() != null) { + ((KeyMappingOverrideHolder) binding.override().keyMapping()).controlify$addOverride(binding); + } + return binding; } @@ -481,10 +486,6 @@ public class ControllerBindings { return clean; } - public void onTick(Minecraft minecraft) { - imitateVanillaClick(); - } - private void registerModdedKeybinds() { for (KeyMapping keyMapping : KeyBindingRegistryImplAccessor.getCustomKeys()) { if (EXCLUDED_VANILLA_BINDS.contains(keyMapping)) @@ -538,8 +539,6 @@ public class ControllerBindings { // must set field directly to avoid ToggleKeyMapping breaking things accessor.setIsDown(!accessor.getIsDown()); } - } else if (!override.keyMapping().isDown()) { - KeyMapping.set(vanillaKeyCode, binding.held()); } if (binding.justPressed()) KeyMapping.click(vanillaKeyCode); diff --git a/src/main/java/dev/isxander/controlify/bindings/InputHandledEvent.java b/src/main/java/dev/isxander/controlify/bindings/InputHandledEvent.java new file mode 100644 index 0000000..570c947 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/InputHandledEvent.java @@ -0,0 +1,14 @@ +package dev.isxander.controlify.bindings; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +public interface InputHandledEvent { + void onInputHandled(); + + Event EVENT = EventFactory.createArrayBacked(InputHandledEvent.class, (listeners) -> () -> { + for (InputHandledEvent listener : listeners) { + listener.onInputHandled(); + } + }); +} diff --git a/src/main/java/dev/isxander/controlify/bindings/KeyMappingOverrideHolder.java b/src/main/java/dev/isxander/controlify/bindings/KeyMappingOverrideHolder.java new file mode 100644 index 0000000..a35b910 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/KeyMappingOverrideHolder.java @@ -0,0 +1,7 @@ +package dev.isxander.controlify.bindings; + +import dev.isxander.controlify.api.bind.ControllerBinding; + +public interface KeyMappingOverrideHolder { + void controlify$addOverride(ControllerBinding binding); +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/bind/KeyMappingMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/bind/KeyMappingMixin.java new file mode 100644 index 0000000..8d8a20d --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/bind/KeyMappingMixin.java @@ -0,0 +1,27 @@ +package dev.isxander.controlify.mixins.feature.bind; + +import com.llamalad7.mixinextras.injector.ModifyReturnValue; +import dev.isxander.controlify.api.bind.ControllerBinding; +import dev.isxander.controlify.bindings.KeyMappingOverrideHolder; +import net.minecraft.client.KeyMapping; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.ArrayList; +import java.util.List; + +@Mixin(KeyMapping.class) +public class KeyMappingMixin implements KeyMappingOverrideHolder { + @Unique private final List overrides = new ArrayList<>(); + + @ModifyReturnValue(method = "isDown", at = @At("RETURN")) + private boolean injectOverrideState(boolean keyMappingState) { + return keyMappingState || overrides.stream().anyMatch(override -> override.override() != null && override.override().toggleable().getAsBoolean() && override.held()); + } + + @Override + public void controlify$addOverride(ControllerBinding binding) { + this.overrides.add(binding); + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/feature/bind/MinecraftMixin.java b/src/main/java/dev/isxander/controlify/mixins/feature/bind/MinecraftMixin.java new file mode 100644 index 0000000..0dc7e66 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/feature/bind/MinecraftMixin.java @@ -0,0 +1,19 @@ +package dev.isxander.controlify.mixins.feature.bind; + +import dev.isxander.controlify.bindings.InputHandledEvent; +import net.minecraft.client.Minecraft; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MinecraftMixin { + // KeyboardHandler and MouseHandler run input events through Minecraft#execute, + // which is polled by the injection point below. Cannot be done in normal tick event + // as that could run up to 10 times per frame depending on framerate. + @Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;runAllTasks()V", shift = At.Shift.AFTER)) + private void onTasksExecuted(boolean tick, CallbackInfo ci) { + InputHandledEvent.EVENT.invoker().onInputHandled(); + } +} diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index 737cf4a..f44f414 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -36,6 +36,8 @@ "feature.accessibility.LocalPlayerMixin", "feature.autoswitch.ToastComponentAccessor", "feature.bind.KeyMappingAccessor", + "feature.bind.KeyMappingMixin", + "feature.bind.MinecraftMixin", "feature.bind.ToggleKeyMappingAccessor", "feature.chatkbheight.ChatComponentMixin", "feature.chatkbheight.ChatScreenMixin",