From 251c7b109f7e3e6187a17c8f243aaef23a02c7f0 Mon Sep 17 00:00:00 2001 From: Kam Date: Tue, 24 Feb 2026 15:15:06 -0500 Subject: [PATCH] Better Key Press Handling CNPC+ --- .../kamkeel/npcs/util/AttributeItemUtil.java | 7 +- .../npcs/client/ClientEventHandler.java | 42 ++-- .../java/noppes/npcs/client/ClientProxy.java | 1 + .../noppes/npcs/client/ClientTickHandler.java | 99 +-------- .../noppes/npcs/client/KeyPressHandler.java | 203 ++++++++++++++++++ .../hud/ability/AbilityHotbarComponent.java | 18 +- 6 files changed, 231 insertions(+), 139 deletions(-) create mode 100644 src/main/java/noppes/npcs/client/KeyPressHandler.java diff --git a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java index 74061d29c..d16df9347 100644 --- a/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java +++ b/src/main/java/kamkeel/npcs/util/AttributeItemUtil.java @@ -14,11 +14,12 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.StatCollector; +import net.minecraft.client.settings.GameSettings; import noppes.npcs.client.ClientProxy; +import noppes.npcs.client.KeyPressHandler; import noppes.npcs.client.gui.player.GuiAuctionInterface; import noppes.npcs.controllers.MagicController; import noppes.npcs.controllers.data.Magic; -import org.lwjgl.input.Keyboard; import java.util.ArrayList; import java.util.Collections; @@ -236,7 +237,7 @@ public class AttributeItemUtil { // Get the RPGCore compound and then the Attributes compound. NBTTagCompound rpgCore = compound.hasKey(TAG_RPGCORE) ? compound.getCompoundTag(TAG_RPGCORE) : new NBTTagCompound(); NBTTagCompound attrTag = rpgCore.hasKey(TAG_ATTRIBUTES) ? rpgCore.getCompoundTag(TAG_ATTRIBUTES) : new NBTTagCompound(); - if (Keyboard.isKeyDown(ClientProxy.NPCButton.getKeyCode()) || override) { + if (KeyPressHandler.isKeyBindDown(ClientProxy.NPCButton) || override) { List newTooltips = new ArrayList<>(); if (!tooltip.isEmpty() && !override) { newTooltips.add(tooltip.get(0)); @@ -332,7 +333,7 @@ public class AttributeItemUtil { tooltip = newTooltips; } } else { - String keyName = Keyboard.getKeyName(ClientProxy.NPCButton.getKeyCode()); + String keyName = GameSettings.getKeyDisplayString(ClientProxy.NPCButton.getKeyCode()); tooltip.add(EnumChatFormatting.YELLOW + "" + EnumChatFormatting.ITALIC + StatCollector.translateToLocal("rpgcore:tooltip").replace("%key%", keyName)); } diff --git a/src/main/java/noppes/npcs/client/ClientEventHandler.java b/src/main/java/noppes/npcs/client/ClientEventHandler.java index 1e8d7a56d..bffda878d 100644 --- a/src/main/java/noppes/npcs/client/ClientEventHandler.java +++ b/src/main/java/noppes/npcs/client/ClientEventHandler.java @@ -27,6 +27,7 @@ import noppes.npcs.client.gui.hud.EnumHudComponent; import noppes.npcs.client.gui.hud.QuestTrackingComponent; import noppes.npcs.client.gui.hud.ability.AbilityHotbarComponent; import noppes.npcs.client.gui.player.AuctionTooltipHandler; +import noppes.npcs.client.KeyPressHandler; import noppes.npcs.client.renderer.MarkRenderer; import noppes.npcs.client.renderer.RenderCNPCPlayer; import noppes.npcs.constants.EnumAnimationPart; @@ -85,6 +86,14 @@ public class ClientEventHandler { @SubscribeEvent public void onMouse(MouseEvent event) { + // Always track mouse button state for KeyPressHandler FIRST. + // Canceled MouseEvents skip FML's MouseInputEvent entirely + // (Minecraft.java: if postMouseEvent() continue), so this is the + // only reliable place to capture mouse button press/release. + if (event.button >= 0) { + KeyPressHandler.trackMouseButton(event.button, event.buttonstate); + } + Minecraft mc = Minecraft.getMinecraft(); if (mc.currentScreen != null) return; @@ -100,30 +109,17 @@ public class ClientEventHandler { } // Ability Hotbar: Hold HUD key + scroll wheel to cycle - if (event.dwheel != 0 && ClientProxy.AbilityHudKey != null) { - int hudKeyCode = ClientProxy.AbilityHudKey.getKeyCode(); - boolean hudKeyHeld = false; - if (hudKeyCode > 0) { - hudKeyHeld = hudKeyCode < org.lwjgl.input.Keyboard.getKeyCount() - && org.lwjgl.input.Keyboard.isKeyDown(hudKeyCode); - } else if (hudKeyCode < 0) { - int mouseButton = hudKeyCode + 100; - hudKeyHeld = mouseButton >= 0 - && mouseButton < org.lwjgl.input.Mouse.getButtonCount() - && org.lwjgl.input.Mouse.isButtonDown(mouseButton); - } - if (hudKeyHeld) { - AbilityHotbarComponent comp = (AbilityHotbarComponent) ClientHudManager.getInstance() - .getHudComponents().get(EnumHudComponent.AbilityHotbar); - if (comp != null && comp.hasAnyAbilities()) { - if (event.dwheel > 0) { - comp.onCyclePrev(); - } else { - comp.onCycleNext(); - } - event.setCanceled(true); - return; + if (event.dwheel != 0 && KeyPressHandler.isHudKeyHeld()) { + AbilityHotbarComponent comp = (AbilityHotbarComponent) ClientHudManager.getInstance() + .getHudComponents().get(EnumHudComponent.AbilityHotbar); + if (comp != null && comp.hasAnyAbilities()) { + if (event.dwheel > 0) { + comp.onCyclePrev(); + } else { + comp.onCycleNext(); } + event.setCanceled(true); + return; } } diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index 44ccd806a..170d706cf 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -294,6 +294,7 @@ public class ClientProxy extends CommonProxy { RenderingRegistry.registerEntityRenderingHandler(EntityNPCGolem.class, new RenderNPCHumanMale( new ModelNPCGolem(0), new ModelNPCGolem(1F), new ModelNPCGolem(0.5F))); FMLCommonHandler.instance().bus().register(new ClientTickHandler()); + FMLCommonHandler.instance().bus().register(new KeyPressHandler()); ClientRegistry.bindTileEntitySpecialRenderer(TileBlockAnvil.class, new BlockCarpentryBenchRenderer()); ClientRegistry.bindTileEntitySpecialRenderer(TileMailbox.class, new BlockMailboxRenderer()); diff --git a/src/main/java/noppes/npcs/client/ClientTickHandler.java b/src/main/java/noppes/npcs/client/ClientTickHandler.java index 3636f48fa..c0b501c4d 100644 --- a/src/main/java/noppes/npcs/client/ClientTickHandler.java +++ b/src/main/java/noppes/npcs/client/ClientTickHandler.java @@ -2,7 +2,6 @@ package noppes.npcs.client; import cpw.mods.fml.common.eventhandler.EventPriority; import cpw.mods.fml.common.eventhandler.SubscribeEvent; -import cpw.mods.fml.common.gameevent.InputEvent; import cpw.mods.fml.common.gameevent.TickEvent; import cpw.mods.fml.common.gameevent.TickEvent.Phase; import cpw.mods.fml.relauncher.Side; @@ -12,33 +11,26 @@ import kamkeel.npcs.controllers.data.telegraph.TelegraphManager; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.data.RequestProperSpawnData; import kamkeel.npcs.network.packets.player.CheckPlayerValue; -import kamkeel.npcs.network.packets.player.InputDevicePacket; import kamkeel.npcs.network.packets.player.ScreenSizePacket; -import kamkeel.npcs.network.packets.player.SpecialKeyStatePacket; import net.minecraft.client.Minecraft; import net.minecraft.client.settings.KeyBinding; import net.minecraft.entity.Entity; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.ContainerPlayer; import net.minecraft.world.World; -import noppes.npcs.CustomNpcs; import noppes.npcs.client.controllers.MusicController; import noppes.npcs.client.controllers.ScriptSoundController; import noppes.npcs.client.gui.hud.ClientHudManager; import noppes.npcs.client.gui.hud.CompassHudComponent; import noppes.npcs.client.gui.hud.EnumHudComponent; import noppes.npcs.client.gui.hud.HudComponent; -import noppes.npcs.client.gui.player.inventory.GuiCNPCInventory; import noppes.npcs.client.renderer.RenderNPCInterface; import noppes.npcs.constants.EnumRoleType; import noppes.npcs.constants.MarkType; +import noppes.npcs.CustomNpcs; import noppes.npcs.controllers.data.MarkData; -import noppes.npcs.controllers.data.PlayerData; import noppes.npcs.entity.EntityNPCInterface; import noppes.npcs.roles.RoleMount; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import tconstruct.client.tabs.InventoryTabCustomNpc; import java.util.ArrayList; @@ -49,10 +41,6 @@ public class ClientTickHandler { private int prevWidth = 0; private int prevHeight = 0; private boolean otherContainer = false; - private int buttonPressed = -1; - private long buttonTime = 0L; - private final int[] ignoreKeys = new int[]{157, 29, 54, 42, 184, 56, 220, 219}; - private boolean lastSpecialKeyDown = false; private boolean wasMovementSuppressed = false; @SubscribeEvent(priority = EventPriority.LOWEST) @@ -72,18 +60,6 @@ public class ClientTickHandler { if (event.phase == Phase.START) { EntityPlayer player = mc.thePlayer; if (player != null) { - // Only process special key when no GUI screen is open - boolean specialKeyDown = mc.currentScreen == null - && ClientProxy.SpecialKey != null && Keyboard.isKeyDown(ClientProxy.SpecialKey.getKeyCode()); - if (specialKeyDown != lastSpecialKeyDown) { - PlayerData data = CustomNpcs.proxy.getPlayerData(player); - if (data != null) { - data.setSpecialKeyDown(specialKeyDown); - } - SpecialKeyStatePacket.send(specialKeyDown); - lastSpecialKeyDown = specialKeyDown; - } - boolean suppressMovement = mc.currentScreen == null && ClientAbilityState.shouldSuppressMovementInput(); if (!suppressMovement && wasMovementSuppressed) { syncMovementKeyStates(mc); @@ -257,56 +233,6 @@ public class ClientTickHandler { } } - @SubscribeEvent - public void onMouse(InputEvent.MouseInputEvent event) { - if (Mouse.getEventButton() == -1 && Mouse.getEventDWheel() == 0) - return; - - InputDevicePacket.sendMouse(Mouse.getEventButton(), Mouse.getEventDWheel(), Mouse.isButtonDown(Mouse.getEventButton())); - } - - @SubscribeEvent - public void onKey(InputEvent.KeyInputEvent event) { - Minecraft mc = Minecraft.getMinecraft(); - - if (ClientProxy.NPCButton.isPressed()) { - if (mc.currentScreen == null) { - InventoryTabCustomNpc.tabHelper(); - } else if (mc.currentScreen instanceof GuiCNPCInventory) - mc.setIngameFocus(); - } - - if (!Keyboard.isRepeatEvent()) { - int key = Keyboard.getEventKey(); - boolean keyDown = Keyboard.isKeyDown(key); - - // Block movement keys during ability lock (only when no GUI is open and world exists). - // Non-movement keys (ESC, chat, inventory, etc.) always pass through. - if (mc.theWorld != null && mc.currentScreen == null && keyDown - && ClientAbilityState.shouldSuppressMovementInput() && isMovementKey(key)) { - // Unpress the keybind so vanilla doesn't process it - KeyBinding.setKeyBindState(key, false); - return; - } - - InputDevicePacket.sendKeyboard(key, keyDown); - } - } - - /** - * Check if a key code corresponds to a movement keybind (WASD, jump, sneak, sprint). - */ - private boolean isMovementKey(int keyCode) { - Minecraft mc = Minecraft.getMinecraft(); - return keyCode == mc.gameSettings.keyBindForward.getKeyCode() - || keyCode == mc.gameSettings.keyBindBack.getKeyCode() - || keyCode == mc.gameSettings.keyBindLeft.getKeyCode() - || keyCode == mc.gameSettings.keyBindRight.getKeyCode() - || keyCode == mc.gameSettings.keyBindJump.getKeyCode() - || keyCode == mc.gameSettings.keyBindSneak.getKeyCode() - || keyCode == mc.gameSettings.keyBindSprint.getKeyCode(); - } - private void syncMovementKeyStates(Minecraft mc) { syncKeyBindingState(mc.gameSettings.keyBindForward); syncKeyBindingState(mc.gameSettings.keyBindBack); @@ -318,28 +244,7 @@ public class ClientTickHandler { } private void syncKeyBindingState(KeyBinding keyBinding) { - int keyCode = keyBinding.getKeyCode(); - boolean pressed; - if (keyCode < 0) { - pressed = Mouse.isButtonDown(keyCode + 100); - } else { - pressed = Keyboard.isKeyDown(keyCode); - } - KeyBinding.setKeyBindState(keyCode, pressed); - } - - private boolean isIgnoredKey(int key) { - int[] var2 = this.ignoreKeys; - int var3 = var2.length; - - for (int var4 = 0; var4 < var3; ++var4) { - int i = var2[var4]; - if (i == key) { - return true; - } - } - - return false; + KeyBinding.setKeyBindState(keyBinding.getKeyCode(), KeyPressHandler.isKeyBindDown(keyBinding)); } private final int SCAN_RANGE = 128; diff --git a/src/main/java/noppes/npcs/client/KeyPressHandler.java b/src/main/java/noppes/npcs/client/KeyPressHandler.java new file mode 100644 index 000000000..3b74b9d51 --- /dev/null +++ b/src/main/java/noppes/npcs/client/KeyPressHandler.java @@ -0,0 +1,203 @@ +package noppes.npcs.client; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.InputEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import kamkeel.npcs.network.packets.player.InputDevicePacket; +import kamkeel.npcs.network.packets.player.SpecialKeyStatePacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.settings.KeyBinding; +import noppes.npcs.CustomNpcs; +import noppes.npcs.client.gui.player.inventory.GuiCNPCInventory; +import noppes.npcs.controllers.data.PlayerData; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import tconstruct.client.tabs.InventoryTabCustomNpc; + +/** + * Centralized input handler for CustomNPC+ keybindings. + * Handles both keyboard and mouse input events so keybinds work + * regardless of whether they're bound to a key or mouse button. + *

+ * All keybind state queries (pressed, held, released) should go through + * this class to avoid crashes with mouse-bound keys. + *

+ * Mouse button states are tracked from MouseEvent (Forge bus) via + * {@link #trackMouseButton}, because canceled MouseEvents cause Minecraft + * to skip FML's MouseInputEvent entirely. This is the only reliable source. + */ +public class KeyPressHandler { + + // ── Mouse Button Tracking ──────────────────────────────────────────── + // Tracked from MouseEvent (Forge bus, via ClientEventHandler.onMouse) + // which fires BEFORE cancellation can skip MouseInputEvent. + private static final boolean[] mouseButtonsDown = new boolean[16]; + + // ── Tracked Held States ────────────────────────────────────────────── + private static boolean specialKeyHeld = false; + private static boolean hudKeyHeld = false; + + // ── Event Handlers ────────────────────────────────────────────────── + + @SubscribeEvent + public void onKeyPress(InputEvent.KeyInputEvent event) { + Minecraft mc = Minecraft.getMinecraft(); + + updateHeldStates(mc); + handleNPCButton(mc); + + if (!Keyboard.isRepeatEvent()) { + int key = Keyboard.getEventKey(); + boolean keyDown = Keyboard.isKeyDown(key); + + // Block movement keys during ability lock (only when no GUI is open). + if (mc.theWorld != null && mc.currentScreen == null && keyDown + && ClientAbilityState.shouldSuppressMovementInput() && isMovementKey(key)) { + KeyBinding.setKeyBindState(key, false); + return; + } + + InputDevicePacket.sendKeyboard(key, keyDown); + } + } + + @SubscribeEvent + public void onMousePress(InputEvent.MouseInputEvent event) { + Minecraft mc = Minecraft.getMinecraft(); + + // Button state already tracked by trackMouseButton() from MouseEvent. + // Just update held states and handle keybind actions. + updateHeldStates(mc); + handleNPCButton(mc); + + int button = Mouse.getEventButton(); + if (button == -1 && Mouse.getEventDWheel() == 0) + return; + + InputDevicePacket.sendMouse(button, Mouse.getEventDWheel(), + button >= 0 && Mouse.getEventButtonState()); + } + + /** + * Per-tick safety net: sync tracked mouse button states with hardware + * in case an event was missed (e.g. window focus loss). + */ + @SubscribeEvent + public void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.START) return; + + // Correct any stuck mouse buttons from missed release events + for (int i = 0; i < mouseButtonsDown.length; i++) { + if (mouseButtonsDown[i] && i < Mouse.getButtonCount() && !Mouse.isButtonDown(i)) { + mouseButtonsDown[i] = false; + } + } + + updateHeldStates(Minecraft.getMinecraft()); + } + + // ── Mouse Button Tracking (called from Forge MouseEvent) ───────────── + + /** + * Called from {@code ClientEventHandler.onMouse(MouseEvent)} at the very + * start, BEFORE any cancellation logic. This is the primary source of + * mouse button state because canceled MouseEvents cause Minecraft to + * skip {@code FMLCommonHandler.fireMouseInput()} entirely, so + * MouseInputEvent never fires for those events. + */ + public static void trackMouseButton(int button, boolean down) { + if (button >= 0 && button < mouseButtonsDown.length) { + mouseButtonsDown[button] = down; + } + updateHeldStates(Minecraft.getMinecraft()); + } + + // ── State Updates ──────────────────────────────────────────────────── + + /** + * Refresh all tracked held states. + * Called on every input event and every tick. + */ + private static void updateHeldStates(Minecraft mc) { + boolean noScreen = mc.currentScreen == null; + + // ── SpecialKey (edge-detected, sends packet on change) ── + boolean specialDown = noScreen && isKeyBindDown(ClientProxy.SpecialKey); + if (specialDown != specialKeyHeld) { + specialKeyHeld = specialDown; + if (mc.thePlayer != null) { + PlayerData data = CustomNpcs.proxy.getPlayerData(mc.thePlayer); + if (data != null) { + data.setSpecialKeyDown(specialDown); + } + SpecialKeyStatePacket.send(specialDown); + } + } + + // ── AbilityHudKey ── + hudKeyHeld = noScreen && isKeyBindDown(ClientProxy.AbilityHudKey); + } + + // ── Keybind Actions ───────────────────────────────────────────────── + + private static void handleNPCButton(Minecraft mc) { + if (ClientProxy.NPCButton == null) return; + if (ClientProxy.NPCButton.isPressed()) { + if (mc.currentScreen == null) { + InventoryTabCustomNpc.tabHelper(); + } else if (mc.currentScreen instanceof GuiCNPCInventory) { + mc.setIngameFocus(); + } + } + } + + // ── Public State Queries ───────────────────────────────────────────── + + /** + * Whether the SpecialKey is currently held down. + */ + public static boolean isSpecialKeyHeld() { + return specialKeyHeld; + } + + /** + * Whether the AbilityHudKey is currently held down. + */ + public static boolean isHudKeyHeld() { + return hudKeyHeld; + } + + /** + * Check if a KeyBinding is currently held down. + * Works for both keyboard keys (positive codes) and mouse buttons (negative codes). + *

+ * Mouse buttons use event-tracked state (from MouseEvent via trackMouseButton) + * so the result is accurate even when called during keyboard event processing. + */ + public static boolean isKeyBindDown(KeyBinding keyBinding) { + if (keyBinding == null) return false; + int keyCode = keyBinding.getKeyCode(); + if (keyCode == 0) return false; + + if (keyCode < 0) { + int mouseButton = keyCode + 100; + return mouseButton >= 0 && mouseButton < mouseButtonsDown.length + && mouseButtonsDown[mouseButton]; + } + + return keyCode < Keyboard.getKeyCount() && Keyboard.isKeyDown(keyCode); + } + + // ── Internal Helpers ──────────────────────────────────────────────── + + private static boolean isMovementKey(int keyCode) { + Minecraft mc = Minecraft.getMinecraft(); + return keyCode == mc.gameSettings.keyBindForward.getKeyCode() + || keyCode == mc.gameSettings.keyBindBack.getKeyCode() + || keyCode == mc.gameSettings.keyBindLeft.getKeyCode() + || keyCode == mc.gameSettings.keyBindRight.getKeyCode() + || keyCode == mc.gameSettings.keyBindJump.getKeyCode() + || keyCode == mc.gameSettings.keyBindSneak.getKeyCode() + || keyCode == mc.gameSettings.keyBindSprint.getKeyCode(); + } +} diff --git a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java index fda6547c5..d08ee1bed 100644 --- a/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java +++ b/src/main/java/noppes/npcs/client/gui/hud/ability/AbilityHotbarComponent.java @@ -10,6 +10,7 @@ import kamkeel.npcs.network.packets.player.ability.AbilityHotbarSelectPacket; import net.minecraft.client.Minecraft; import noppes.npcs.client.ClientProxy; import noppes.npcs.client.ClientAbilityState; +import noppes.npcs.client.KeyPressHandler; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.ScaledResolution; @@ -602,22 +603,7 @@ public class AbilityHotbarComponent extends HudComponent { } private boolean isHudKeyHeld() { - if (ClientProxy.AbilityHudKey == null) return false; - int keyCode = ClientProxy.AbilityHudKey.getKeyCode(); - if (keyCode == 0 || keyCode == -1) return false; - - // Mouse bindings are encoded as negative key codes (offset by 100). - if (keyCode < -1) { - int mouseButton = keyCode + 100; - return mouseButton >= 0 - && mouseButton < org.lwjgl.input.Mouse.getButtonCount() - && org.lwjgl.input.Mouse.isButtonDown(mouseButton); - } - - // Guard against invalid/out-of-range keyboard codes to avoid Buffer.checkIndex crashes. - return keyCode > 0 - && keyCode < org.lwjgl.input.Keyboard.getKeyCount() - && org.lwjgl.input.Keyboard.isKeyDown(keyCode); + return KeyPressHandler.isHudKeyHeld(); } public boolean hasAnyAbilities() {