diff --git a/4J.Input/4J_Input.cpp b/4J.Input/4J_Input.cpp index b378b5c2e..6dc9de349 100644 --- a/4J.Input/4J_Input.cpp +++ b/4J.Input/4J_Input.cpp @@ -1,34 +1,527 @@ +// 4J_Input.cpp - GLFW keyboard + mouse input for the Linux port +// Replaces the SDL2 oldimpl with GLFW equivalents. +// Uses glfwGetCurrentContext() to get the window the render manager created, +// avoiding a coupling dependency on 4J_Render.h. + #include "4J_Input.h" +#include "../Minecraft.Client/Build/Common/App_enums.h" +#include +#include +#include +#include C_4JInput InputManager; -void C_4JInput::Initialise(int iInputStateC, unsigned char ucMapC, unsigned char ucActionC, unsigned char ucMenuActionC) {} -void C_4JInput::Tick(void) {} -void C_4JInput::SetDeadzoneAndMovementRange(unsigned int uiDeadzone, unsigned int uiMovementRangeMax) {} -void C_4JInput::SetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction, unsigned int uiActionVal) {} -unsigned int C_4JInput::GetGameJoypadMaps(unsigned char ucMap, unsigned char ucAction) { return 0; } -void C_4JInput::SetJoypadMapVal(int iPad, unsigned char ucMap) {} -unsigned char C_4JInput::GetJoypadMapVal(int iPad) { return 0; } -void C_4JInput::SetJoypadSensitivity(int iPad, float fSensitivity) {} -unsigned int C_4JInput::GetValue(int iPad, unsigned char ucAction, bool bRepeat) { return 0; } -bool C_4JInput::ButtonPressed(int iPad, unsigned char ucAction) { return false; } -bool C_4JInput::ButtonReleased(int iPad, unsigned char ucAction) { return false; } -bool C_4JInput::ButtonDown(int iPad, unsigned char ucAction) { return false; } -void C_4JInput::SetJoypadStickAxisMap(int iPad, unsigned int uiFrom, unsigned int uiTo) {} -void C_4JInput::SetJoypadStickTriggerMap(int iPad, unsigned int uiFrom, unsigned int uiTo) {} -void C_4JInput::SetKeyRepeatRate(float fRepeatDelaySecs, float fRepeatRateSecs) {} -void C_4JInput::SetDebugSequence(const char *chSequenceA, int(*Func)(LPVOID), LPVOID lpParam) {} -FLOAT C_4JInput::GetIdleSeconds(int iPad) { return 0.0f; } -bool C_4JInput::IsPadConnected(int iPad) { return iPad == 0; } -float C_4JInput::GetJoypadStick_LX(int iPad, bool bCheckMenuDisplay) { return 0.0f; } -float C_4JInput::GetJoypadStick_LY(int iPad, bool bCheckMenuDisplay) { return 0.0f; } -float C_4JInput::GetJoypadStick_RX(int iPad, bool bCheckMenuDisplay) { return 0.0f; } -float C_4JInput::GetJoypadStick_RY(int iPad, bool bCheckMenuDisplay) { return 0.0f; } -unsigned char C_4JInput::GetJoypadLTrigger(int iPad, bool bCheckMenuDisplay) { return 0; } -unsigned char C_4JInput::GetJoypadRTrigger(int iPad, bool bCheckMenuDisplay) { return 0; } -void C_4JInput::SetMenuDisplayed(int iPad, bool bVal) {} -EKeyboardResult C_4JInput::RequestKeyboard(LPCWSTR Title, LPCWSTR Text, DWORD dwPad, UINT uiMaxChars, int(*Func)(LPVOID, const bool), LPVOID lpParam, C_4JInput::EKeyboardMode eMode) { return EKeyboard_Cancelled; } +// --------------------------------------------------------------------------- +// State - all static to avoid adding new fields to C_4JInput +// --------------------------------------------------------------------------- + +static const int KEY_COUNT = GLFW_KEY_LAST + 1; // 349 on GLFW 3 + +static bool s_keysCurrent[KEY_COUNT] = {}; +static bool s_keysPrev[KEY_COUNT] = {}; + +static bool s_mouseLeftCurrent = false, s_mouseLeftPrev = false; +static bool s_mouseRightCurrent = false, s_mouseRightPrev = false; + +// Accumulated cursor delta from the GLFW cursor-pos callback. +// Snapshotted into s_frameRelX/Y at Tick() start, then reset to 0. +static double s_lastCursorX = 0.0, s_lastCursorY = 0.0; +static bool s_cursorInitialized = false; +static float s_mouseAccumX = 0.0f, s_mouseAccumY = 0.0f; // callback accumulator +static float s_frameRelX = 0.0f, s_frameRelY = 0.0f; // per-frame snapshot + +// Scroll wheel +static float s_scrollAccum = 0.0f; // callback accumulator +static float s_scrollFrame = 0.0f; // current frame snapshot +static float s_scrollPrevFrame = 0.0f; + +// Mouse lock / menu state +static bool s_mouseLocked = false; +static bool s_menuDisplayed[4] = {}; +static bool s_prevMenuDisplayed = true; // start as "in menu" so auto-lock triggers after first frame + +// Sensitivity: scales raw pixel delta before sqrt-compression +// Smaller value = less mouse movement per pixel +static const float MOUSE_SCALE = 0.012f; + +// --------------------------------------------------------------------------- +// GLFW window (obtained lazily via glfwGetCurrentContext on the render thread) +// --------------------------------------------------------------------------- +static GLFWwindow *s_inputWindow = nullptr; + +static GLFWwindow *getWindow() { + if (!s_inputWindow) { + s_inputWindow = glfwGetCurrentContext(); + } + return s_inputWindow; +} + +// --------------------------------------------------------------------------- +// GLFW callbacks +// --------------------------------------------------------------------------- + +static void onCursorPos(GLFWwindow * /*w*/, double x, double y) { + if (s_cursorInitialized) { + s_mouseAccumX += (float)(x - s_lastCursorX); + s_mouseAccumY += (float)(y - s_lastCursorY); + } else { + s_cursorInitialized = true; + } + s_lastCursorX = x; + s_lastCursorY = y; +} + +static void onScroll(GLFWwindow * /*w*/, double /*xoffset*/, double yoffset) { + s_scrollAccum += (float)yoffset; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static inline bool KDown(int key) { + return (key >= 0 && key < KEY_COUNT) ? s_keysCurrent[key] : false; +} +static inline bool KPressed(int key) { + return (key >= 0 && key < KEY_COUNT) ? (s_keysCurrent[key] && !s_keysPrev[key]) : false; +} +static inline bool KReleased(int key) { + return (key >= 0 && key < KEY_COUNT) ? (!s_keysCurrent[key] && s_keysPrev[key]) : false; +} + +static inline bool MouseLDown() { return s_mouseLeftCurrent; } +static inline bool MouseLPressed() { return s_mouseLeftCurrent && !s_mouseLeftPrev; } +static inline bool MouseLReleased() { return !s_mouseLeftCurrent && s_mouseLeftPrev; } +static inline bool MouseRDown() { return s_mouseRightCurrent; } +static inline bool MouseRPressed() { return s_mouseRightCurrent && !s_mouseRightPrev; } +static inline bool MouseRReleased() { return !s_mouseRightCurrent && s_mouseRightPrev; } + +static inline bool WheelUp() { return s_scrollFrame > 0.1f; } +static inline bool WheelDown() { return s_scrollFrame < -0.1f; } +static inline bool WheelUpEdge() { return s_scrollFrame > 0.1f && s_scrollPrevFrame <= 0.1f; } +static inline bool WheelDownEdge() { return s_scrollFrame < -0.1f && s_scrollPrevFrame >= -0.1f; } + +// Keys to snapshot each Tick (avoid iterating all 349 entries) +static const int s_watchedKeys[] = { + GLFW_KEY_W, GLFW_KEY_A, GLFW_KEY_S, GLFW_KEY_D, + GLFW_KEY_SPACE, GLFW_KEY_LEFT_SHIFT, GLFW_KEY_RIGHT_SHIFT, + GLFW_KEY_E, GLFW_KEY_Q, GLFW_KEY_F, + GLFW_KEY_ESCAPE, GLFW_KEY_ENTER, + GLFW_KEY_F3, GLFW_KEY_F5, + GLFW_KEY_UP, GLFW_KEY_DOWN, GLFW_KEY_LEFT, GLFW_KEY_RIGHT, + GLFW_KEY_PAGE_UP, GLFW_KEY_PAGE_DOWN, + GLFW_KEY_TAB, + GLFW_KEY_LEFT_CONTROL, GLFW_KEY_RIGHT_CONTROL, + GLFW_KEY_1, GLFW_KEY_2, GLFW_KEY_3, GLFW_KEY_4, GLFW_KEY_5, + GLFW_KEY_6, GLFW_KEY_7, GLFW_KEY_8, GLFW_KEY_9, +}; +static const int s_watchedKeyCount = (int)(sizeof(s_watchedKeys) / sizeof(s_watchedKeys[0])); + +// --------------------------------------------------------------------------- +// C_4JInput::Initialise +// --------------------------------------------------------------------------- +void C_4JInput::Initialise(int /*iInputStateC*/, unsigned char /*ucMapC*/, + unsigned char /*ucActionC*/, unsigned char /*ucMenuActionC*/) { + memset(s_keysCurrent, 0, sizeof(s_keysCurrent)); + memset(s_keysPrev, 0, sizeof(s_keysPrev)); + memset(s_menuDisplayed, 0, sizeof(s_menuDisplayed)); + + s_mouseLeftCurrent = s_mouseLeftPrev = false; + s_mouseRightCurrent = s_mouseRightPrev = false; + s_mouseAccumX = s_mouseAccumY = 0.0f; + s_frameRelX = s_frameRelY = 0.0f; + s_scrollAccum = s_scrollFrame = s_scrollPrevFrame = 0.0f; + s_mouseLocked = false; + s_prevMenuDisplayed = true; // triggers auto-lock once game leaves first menu + s_cursorInitialized = false; + + GLFWwindow *w = getWindow(); + if (w) { + glfwSetCursorPosCallback(w, onCursorPos); + glfwSetScrollCallback(w, onScroll); + if (glfwRawMouseMotionSupported()) { + glfwSetInputMode(w, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + } + } + + printf("[4J_Input] GLFW input initialised\n"); + printf(" WASD=move Mouse=look LMB=attack RMB=use\n"); + printf(" Space=jump LShift=sneak E=inventory Q=drop Esc=pause\n"); + printf(" F5=3rd-person F3=debug Scroll=hotbar\n"); + fflush(stdout); +} + +// --------------------------------------------------------------------------- +// C_4JInput::Tick (called once per frame, BEFORE Present / glfwPollEvents) +// --------------------------------------------------------------------------- +void C_4JInput::Tick(void) { + GLFWwindow *w = getWindow(); + if (!w) return; + + // 1. Save previous frame + memcpy(s_keysPrev, s_keysCurrent, sizeof(s_keysCurrent)); + s_mouseLeftPrev = s_mouseLeftCurrent; + s_mouseRightPrev = s_mouseRightCurrent; + s_scrollPrevFrame = s_scrollFrame; + + // 2. Snapshot current keyboard state for watched keys + for (int i = 0; i < s_watchedKeyCount; i++) { + int k = s_watchedKeys[i]; + s_keysCurrent[k] = (glfwGetKey(w, k) == GLFW_PRESS); + } + + // 3. Snapshot and reset scroll accumulator + s_scrollFrame = s_scrollAccum; + s_scrollAccum = 0.0f; + + // 4. Mouse-lock management based on menu display state + bool menuNow = s_menuDisplayed[0]; + + if (menuNow && s_mouseLocked) { + // Re-entered a menu → release mouse cursor + s_mouseLocked = false; + glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + // Discard stale delta so the view doesn't jerk on re-lock + s_mouseAccumX = s_mouseAccumY = 0.0f; + s_cursorInitialized = false; + } + if (!menuNow && s_prevMenuDisplayed && !s_mouseLocked) { + // Left the menu → lock mouse for look control + s_mouseLocked = true; + glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + s_mouseAccumX = s_mouseAccumY = 0.0f; + s_cursorInitialized = false; + } + s_prevMenuDisplayed = menuNow; + + // 5. Snapshot and reset mouse delta from callback + s_frameRelX = s_mouseAccumX; + s_frameRelY = s_mouseAccumY; + s_mouseAccumX = s_mouseAccumY = 0.0f; + + // 6. Mouse buttons (only meaningful when locked in-game) + if (s_mouseLocked) { + s_mouseLeftCurrent = (glfwGetMouseButton(w, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS); + s_mouseRightCurrent = (glfwGetMouseButton(w, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS); + } else { + // Not locked. Allow a left-click to re-lock (if not in a menu) + bool lclick = (glfwGetMouseButton(w, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS); + if (!menuNow && lclick) { + s_mouseLocked = true; + glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + s_mouseAccumX = s_mouseAccumY = 0.0f; + s_cursorInitialized = false; + } + s_mouseLeftCurrent = false; + s_mouseRightCurrent = false; + s_frameRelX = s_frameRelY = 0.0f; + } +} + +// --------------------------------------------------------------------------- +// ButtonDown – is action held this frame? +// --------------------------------------------------------------------------- +bool C_4JInput::ButtonDown(int /*iPad*/, unsigned char ucAction) { + switch (ucAction) { + // ---- Menu navigation ---- + case ACTION_MENU_UP: return KDown(GLFW_KEY_UP) || KDown(GLFW_KEY_W); + case ACTION_MENU_DOWN: return KDown(GLFW_KEY_DOWN) || KDown(GLFW_KEY_S); + case ACTION_MENU_LEFT: return KDown(GLFW_KEY_LEFT) || KDown(GLFW_KEY_A); + case ACTION_MENU_RIGHT: return KDown(GLFW_KEY_RIGHT) || KDown(GLFW_KEY_D); + + case ACTION_MENU_A: + case ACTION_MENU_OK: + return KDown(GLFW_KEY_ENTER) || KDown(GLFW_KEY_SPACE); + + case ACTION_MENU_B: + case ACTION_MENU_CANCEL: + return KDown(GLFW_KEY_ESCAPE); + + case ACTION_MENU_X: return KDown(GLFW_KEY_F); + case ACTION_MENU_Y: return KDown(GLFW_KEY_E); + + case ACTION_MENU_PAGEUP: return KDown(GLFW_KEY_PAGE_UP) || KDown(GLFW_KEY_LEFT_SHIFT); + case ACTION_MENU_PAGEDOWN: return KDown(GLFW_KEY_PAGE_DOWN) || KDown(GLFW_KEY_RIGHT_SHIFT); + case ACTION_MENU_RIGHT_SCROLL: return KDown(GLFW_KEY_RIGHT_SHIFT) || WheelUp(); + case ACTION_MENU_LEFT_SCROLL: return KDown(GLFW_KEY_LEFT_SHIFT) || WheelDown(); + + case ACTION_MENU_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_UP: return KDown(GLFW_KEY_UP); + case ACTION_MENU_OTHER_STICK_DOWN: return KDown(GLFW_KEY_DOWN); + case ACTION_MENU_OTHER_STICK_LEFT: return KDown(GLFW_KEY_LEFT); + case ACTION_MENU_OTHER_STICK_RIGHT: return KDown(GLFW_KEY_RIGHT); + + case ACTION_MENU_PAUSEMENU: return KDown(GLFW_KEY_ESCAPE); + + // ---- Minecraft in-game ---- + case MINECRAFT_ACTION_JUMP: return KDown(GLFW_KEY_SPACE); + case MINECRAFT_ACTION_FORWARD: return KDown(GLFW_KEY_W); + case MINECRAFT_ACTION_BACKWARD: return KDown(GLFW_KEY_S); + case MINECRAFT_ACTION_LEFT: return KDown(GLFW_KEY_A); + case MINECRAFT_ACTION_RIGHT: return KDown(GLFW_KEY_D); + + // Look axes are handled by analog stick RX/RY (mouse) + case MINECRAFT_ACTION_LOOK_LEFT: return false; + case MINECRAFT_ACTION_LOOK_RIGHT: return false; + case MINECRAFT_ACTION_LOOK_UP: return false; + case MINECRAFT_ACTION_LOOK_DOWN: return false; + + case MINECRAFT_ACTION_USE: return MouseRDown(); + case MINECRAFT_ACTION_ACTION: return MouseLDown(); + case MINECRAFT_ACTION_LEFT_SCROLL: return WheelDown(); + case MINECRAFT_ACTION_RIGHT_SCROLL: return WheelUp(); + + case MINECRAFT_ACTION_INVENTORY: return KDown(GLFW_KEY_E); + case MINECRAFT_ACTION_PAUSEMENU: return KDown(GLFW_KEY_ESCAPE); + case MINECRAFT_ACTION_DROP: return KDown(GLFW_KEY_Q); + case MINECRAFT_ACTION_SNEAK_TOGGLE: return KDown(GLFW_KEY_LEFT_SHIFT); + case MINECRAFT_ACTION_CRAFTING: return KDown(GLFW_KEY_F); + case MINECRAFT_ACTION_RENDER_THIRD_PERSON: return KDown(GLFW_KEY_F5); + case MINECRAFT_ACTION_GAME_INFO: return KDown(GLFW_KEY_F3); + + case MINECRAFT_ACTION_DPAD_LEFT: return KDown(GLFW_KEY_LEFT); + case MINECRAFT_ACTION_DPAD_RIGHT: return KDown(GLFW_KEY_RIGHT); + case MINECRAFT_ACTION_DPAD_UP: return KDown(GLFW_KEY_UP); + case MINECRAFT_ACTION_DPAD_DOWN: return KDown(GLFW_KEY_DOWN); + + default: return false; + } +} + +// --------------------------------------------------------------------------- +// ButtonPressed – rising edge (press event this frame) +// --------------------------------------------------------------------------- +bool C_4JInput::ButtonPressed(int /*iPad*/, unsigned char ucAction) { + switch (ucAction) { + case ACTION_MENU_UP: return KPressed(GLFW_KEY_UP) || KPressed(GLFW_KEY_W); + case ACTION_MENU_DOWN: return KPressed(GLFW_KEY_DOWN) || KPressed(GLFW_KEY_S); + case ACTION_MENU_LEFT: return KPressed(GLFW_KEY_LEFT) || KPressed(GLFW_KEY_A); + case ACTION_MENU_RIGHT: return KPressed(GLFW_KEY_RIGHT) || KPressed(GLFW_KEY_D); + + case ACTION_MENU_A: + case ACTION_MENU_OK: + return KPressed(GLFW_KEY_ENTER) || KPressed(GLFW_KEY_SPACE); + + case ACTION_MENU_B: + case ACTION_MENU_CANCEL: + return KPressed(GLFW_KEY_ESCAPE); + + case ACTION_MENU_X: return KPressed(GLFW_KEY_F); + case ACTION_MENU_Y: return KPressed(GLFW_KEY_E); + + case ACTION_MENU_PAGEUP: return KPressed(GLFW_KEY_PAGE_UP) || KPressed(GLFW_KEY_LEFT_SHIFT); + case ACTION_MENU_PAGEDOWN: return KPressed(GLFW_KEY_PAGE_DOWN) || KPressed(GLFW_KEY_RIGHT_SHIFT); + case ACTION_MENU_RIGHT_SCROLL: return KPressed(GLFW_KEY_RIGHT_SHIFT) || WheelUpEdge(); + case ACTION_MENU_LEFT_SCROLL: return KPressed(GLFW_KEY_LEFT_SHIFT) || WheelDownEdge(); + + case ACTION_MENU_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_UP: return KPressed(GLFW_KEY_UP); + case ACTION_MENU_OTHER_STICK_DOWN: return KPressed(GLFW_KEY_DOWN); + case ACTION_MENU_OTHER_STICK_LEFT: return KPressed(GLFW_KEY_LEFT); + case ACTION_MENU_OTHER_STICK_RIGHT: return KPressed(GLFW_KEY_RIGHT); + case ACTION_MENU_PAUSEMENU: return KPressed(GLFW_KEY_ESCAPE); + + case MINECRAFT_ACTION_JUMP: return KPressed(GLFW_KEY_SPACE); + case MINECRAFT_ACTION_FORWARD: return KPressed(GLFW_KEY_W); + case MINECRAFT_ACTION_BACKWARD: return KPressed(GLFW_KEY_S); + case MINECRAFT_ACTION_LEFT: return KPressed(GLFW_KEY_A); + case MINECRAFT_ACTION_RIGHT: return KPressed(GLFW_KEY_D); + + case MINECRAFT_ACTION_LOOK_LEFT: + case MINECRAFT_ACTION_LOOK_RIGHT: + case MINECRAFT_ACTION_LOOK_UP: + case MINECRAFT_ACTION_LOOK_DOWN: + return false; + + case MINECRAFT_ACTION_USE: return MouseRPressed(); + case MINECRAFT_ACTION_ACTION: return MouseLPressed(); + case MINECRAFT_ACTION_LEFT_SCROLL: return WheelDownEdge(); + case MINECRAFT_ACTION_RIGHT_SCROLL: return WheelUpEdge(); + + case MINECRAFT_ACTION_INVENTORY: return KPressed(GLFW_KEY_E); + case MINECRAFT_ACTION_PAUSEMENU: return KPressed(GLFW_KEY_ESCAPE); + case MINECRAFT_ACTION_DROP: return KPressed(GLFW_KEY_Q); + case MINECRAFT_ACTION_SNEAK_TOGGLE: return KPressed(GLFW_KEY_LEFT_SHIFT); + case MINECRAFT_ACTION_CRAFTING: return KPressed(GLFW_KEY_F); + case MINECRAFT_ACTION_RENDER_THIRD_PERSON: return KPressed(GLFW_KEY_F5); + case MINECRAFT_ACTION_GAME_INFO: return KPressed(GLFW_KEY_F3); + + case MINECRAFT_ACTION_DPAD_LEFT: return KPressed(GLFW_KEY_LEFT); + case MINECRAFT_ACTION_DPAD_RIGHT: return KPressed(GLFW_KEY_RIGHT); + case MINECRAFT_ACTION_DPAD_UP: return KPressed(GLFW_KEY_UP); + case MINECRAFT_ACTION_DPAD_DOWN: return KPressed(GLFW_KEY_DOWN); + + default: return false; + } +} + +// --------------------------------------------------------------------------- +// ButtonReleased – falling edge (released this frame) +// --------------------------------------------------------------------------- +bool C_4JInput::ButtonReleased(int /*iPad*/, unsigned char ucAction) { + switch (ucAction) { + case ACTION_MENU_UP: return KReleased(GLFW_KEY_UP) || KReleased(GLFW_KEY_W); + case ACTION_MENU_DOWN: return KReleased(GLFW_KEY_DOWN) || KReleased(GLFW_KEY_S); + case ACTION_MENU_LEFT: return KReleased(GLFW_KEY_LEFT) || KReleased(GLFW_KEY_A); + case ACTION_MENU_RIGHT: return KReleased(GLFW_KEY_RIGHT) || KReleased(GLFW_KEY_D); + + case ACTION_MENU_A: + case ACTION_MENU_OK: + return KReleased(GLFW_KEY_ENTER) || KReleased(GLFW_KEY_SPACE); + + case ACTION_MENU_B: + case ACTION_MENU_CANCEL: + return KReleased(GLFW_KEY_ESCAPE); + + case ACTION_MENU_X: return KReleased(GLFW_KEY_F); + case ACTION_MENU_Y: return KReleased(GLFW_KEY_E); + + case ACTION_MENU_PAGEUP: return KReleased(GLFW_KEY_PAGE_UP) || KReleased(GLFW_KEY_LEFT_SHIFT); + case ACTION_MENU_PAGEDOWN: return KReleased(GLFW_KEY_PAGE_DOWN) || KReleased(GLFW_KEY_RIGHT_SHIFT); + case ACTION_MENU_RIGHT_SCROLL: return KReleased(GLFW_KEY_RIGHT_SHIFT); + case ACTION_MENU_LEFT_SCROLL: return KReleased(GLFW_KEY_LEFT_SHIFT); + + case ACTION_MENU_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_PRESS: return false; + case ACTION_MENU_OTHER_STICK_UP: return KReleased(GLFW_KEY_UP); + case ACTION_MENU_OTHER_STICK_DOWN: return KReleased(GLFW_KEY_DOWN); + case ACTION_MENU_OTHER_STICK_LEFT: return KReleased(GLFW_KEY_LEFT); + case ACTION_MENU_OTHER_STICK_RIGHT: return KReleased(GLFW_KEY_RIGHT); + case ACTION_MENU_PAUSEMENU: return KReleased(GLFW_KEY_ESCAPE); + + case MINECRAFT_ACTION_JUMP: return KReleased(GLFW_KEY_SPACE); + case MINECRAFT_ACTION_FORWARD: return KReleased(GLFW_KEY_W); + case MINECRAFT_ACTION_BACKWARD: return KReleased(GLFW_KEY_S); + case MINECRAFT_ACTION_LEFT: return KReleased(GLFW_KEY_A); + case MINECRAFT_ACTION_RIGHT: return KReleased(GLFW_KEY_D); + + case MINECRAFT_ACTION_LOOK_LEFT: + case MINECRAFT_ACTION_LOOK_RIGHT: + case MINECRAFT_ACTION_LOOK_UP: + case MINECRAFT_ACTION_LOOK_DOWN: + return false; + + case MINECRAFT_ACTION_USE: return MouseRReleased(); + case MINECRAFT_ACTION_ACTION: return MouseLReleased(); + case MINECRAFT_ACTION_LEFT_SCROLL: return false; // scroll wheel has no "release" + case MINECRAFT_ACTION_RIGHT_SCROLL: return false; + + case MINECRAFT_ACTION_INVENTORY: return KReleased(GLFW_KEY_E); + case MINECRAFT_ACTION_PAUSEMENU: return KReleased(GLFW_KEY_ESCAPE); + case MINECRAFT_ACTION_DROP: return KReleased(GLFW_KEY_Q); + case MINECRAFT_ACTION_SNEAK_TOGGLE: return KReleased(GLFW_KEY_LEFT_SHIFT); + case MINECRAFT_ACTION_CRAFTING: return KReleased(GLFW_KEY_F); + case MINECRAFT_ACTION_RENDER_THIRD_PERSON: return KReleased(GLFW_KEY_F5); + case MINECRAFT_ACTION_GAME_INFO: return KReleased(GLFW_KEY_F3); + + case MINECRAFT_ACTION_DPAD_LEFT: return KReleased(GLFW_KEY_LEFT); + case MINECRAFT_ACTION_DPAD_RIGHT: return KReleased(GLFW_KEY_RIGHT); + case MINECRAFT_ACTION_DPAD_UP: return KReleased(GLFW_KEY_UP); + case MINECRAFT_ACTION_DPAD_DOWN: return KReleased(GLFW_KEY_DOWN); + + default: return false; + } +} + +// --------------------------------------------------------------------------- +// GetValue – returns 1 if action held, 0 otherwise +// --------------------------------------------------------------------------- +unsigned int C_4JInput::GetValue(int iPad, unsigned char ucAction, bool /*bRepeat*/) { + return ButtonDown(iPad, ucAction) ? 1u : 0u; +} + +// --------------------------------------------------------------------------- +// Analog sticks +// +// Left stick = WASD keyboard (±1.0) +// Right stick = mouse delta with sqrt-compression to linearise the quadratic +// look-speed formula used by the console game code: +// turnSpeed = rx * |rx| * 50 * sensitivity +// Passing sqrt(|raw|)*sign(raw) makes turn speed proportional +// to raw pixel delta, giving a natural mouse feel. +// --------------------------------------------------------------------------- + +float C_4JInput::GetJoypadStick_LX(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + // Return 0 while cursor is still in menu mode so Input::tick()'s + // lReset guard sees a zero-stick frame during the menu->game transition. + // Once s_mouseLocked becomes true (cursor captured for gameplay), return + // the real WASD values. + if (!s_mouseLocked) return 0.0f; + float v = 0.0f; + if (KDown(GLFW_KEY_A)) v -= 1.0f; + if (KDown(GLFW_KEY_D)) v += 1.0f; + return v; +} + +float C_4JInput::GetJoypadStick_LY(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + if (!s_mouseLocked) return 0.0f; + float v = 0.0f; + if (KDown(GLFW_KEY_W)) v -= 1.0f; // W = forward = negative Y on consoles + if (KDown(GLFW_KEY_S)) v += 1.0f; + return v; +} + +float C_4JInput::GetJoypadStick_RX(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + if (!s_mouseLocked) return 0.0f; + float raw = s_frameRelX * MOUSE_SCALE; + float absRaw = fabsf(raw); + if (absRaw > 1.0f) absRaw = 1.0f; + if (absRaw < 0.0001f) return 0.0f; + return (raw >= 0.0f ? 1.0f : -1.0f) * sqrtf(absRaw); +} + +float C_4JInput::GetJoypadStick_RY(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + if (!s_mouseLocked) return 0.0f; + float raw = s_frameRelY * MOUSE_SCALE; + float absRaw = fabsf(raw); + if (absRaw > 1.0f) absRaw = 1.0f; + if (absRaw < 0.0001f) return 0.0f; + return (raw >= 0.0f ? 1.0f : -1.0f) * sqrtf(absRaw); +} + +// Left trigger = right mouse button (use/place) +// Right trigger = left mouse button (attack/destroy) +unsigned char C_4JInput::GetJoypadLTrigger(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + return s_mouseRightCurrent ? 255 : 0; +} +unsigned char C_4JInput::GetJoypadRTrigger(int /*iPad*/, bool /*bCheckMenuDisplay*/) { + return s_mouseLeftCurrent ? 255 : 0; +} + +// --------------------------------------------------------------------------- +// Joypad map / sensitivity stubs (not meaningful for keyboard+mouse) +// --------------------------------------------------------------------------- +void C_4JInput::SetDeadzoneAndMovementRange(unsigned int /*uiDeadzone*/, unsigned int /*uiMovementRangeMax*/) {} +void C_4JInput::SetGameJoypadMaps(unsigned char /*ucMap*/, unsigned char /*ucAction*/, unsigned int /*uiActionVal*/) {} +unsigned int C_4JInput::GetGameJoypadMaps(unsigned char /*ucMap*/, unsigned char /*ucAction*/) { return 0; } +void C_4JInput::SetJoypadMapVal(int /*iPad*/, unsigned char /*ucMap*/) {} +unsigned char C_4JInput::GetJoypadMapVal(int /*iPad*/) { return 0; } +void C_4JInput::SetJoypadSensitivity(int /*iPad*/, float /*fSensitivity*/) {} +void C_4JInput::SetJoypadStickAxisMap(int /*iPad*/, unsigned int /*uiFrom*/, unsigned int /*uiTo*/) {} +void C_4JInput::SetJoypadStickTriggerMap(int /*iPad*/, unsigned int /*uiFrom*/, unsigned int /*uiTo*/) {} +void C_4JInput::SetKeyRepeatRate(float /*fRepeatDelaySecs*/, float /*fRepeatRateSecs*/) {} +void C_4JInput::SetDebugSequence(const char * /*chSequenceA*/, int(*/*Func*/)(LPVOID), LPVOID /*lpParam*/) {} +FLOAT C_4JInput::GetIdleSeconds(int /*iPad*/) { return 0.0f; } +bool C_4JInput::IsPadConnected(int iPad) { return iPad == 0; } // slot 0 = keyboard+mouse + +void C_4JInput::SetMenuDisplayed(int iPad, bool bVal) { + if (iPad >= 0 && iPad < 4) s_menuDisplayed[iPad] = bVal; +} + +// --------------------------------------------------------------------------- +// Keyboard (text entry) / string verification stubs +// --------------------------------------------------------------------------- +EKeyboardResult C_4JInput::RequestKeyboard(LPCWSTR /*Title*/, LPCWSTR /*Text*/, DWORD /*dwPad*/, + UINT /*uiMaxChars*/, + int(*/*Func*/)(LPVOID, const bool), LPVOID /*lpParam*/, + C_4JInput::EKeyboardMode /*eMode*/) { + return EKeyboard_Cancelled; +} void C_4JInput::GetText(uint16_t *UTF16String) { if (UTF16String) UTF16String[0] = 0; } -bool C_4JInput::VerifyStrings(WCHAR **pwStringA, int iStringC, int(*Func)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID lpParam) { return true; } -void C_4JInput::CancelQueuedVerifyStrings(int(*Func)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID lpParam) {} +bool C_4JInput::VerifyStrings(WCHAR ** /*pwStringA*/, int /*iStringC*/, + int(*/*Func*/)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID /*lpParam*/) { return true; } +void C_4JInput::CancelQueuedVerifyStrings(int(*/*Func*/)(LPVOID, STRING_VERIFY_RESPONSE *), LPVOID /*lpParam*/) {} void C_4JInput::CancelAllVerifyInProgress(void) {} diff --git a/4J.Render/4J_Render.cpp b/4J.Render/4J_Render.cpp index 317a3641a..02dfa7df2 100644 --- a/4J.Render/4J_Render.cpp +++ b/4J.Render/4J_Render.cpp @@ -300,7 +300,13 @@ void C4JRender::DrawVertices(ePrimitiveType PrimitiveType, int count, int8_t nz = (int8_t)((normalInt >> 16) & 0xFF); ::glNormal3f(nx / 127.0f, ny / 127.0f, nz / 127.0f); - ::glColor4ub(cr, cg, cb, ca); + // Only override current GL color when the vertex actually carries one. + // colorInt == 0 is the Tesselator sentinel for "no colour set" + // (alpha=0 with all channels zero). Skipping glColor4ub here lets + // sky/fog colour set before glCallList() pass through unchanged. + if (colorInt != 0) { + ::glColor4ub(cr, cg, cb, ca); + } ::glTexCoord2f(fdata[3], fdata[4]); ::glVertex3f(fdata[0], fdata[1], fdata[2]); } diff --git a/Minecraft.Client/Build/Common/GameRules/GameRuleManager.cpp b/Minecraft.Client/Build/Common/GameRules/GameRuleManager.cpp index fc3cd15d6..51364e002 100644 --- a/Minecraft.Client/Build/Common/GameRules/GameRuleManager.cpp +++ b/Minecraft.Client/Build/Common/GameRules/GameRuleManager.cpp @@ -667,11 +667,42 @@ void GameRuleManager::loadDefaultGameRules() if ( app.m_dlcManager.readDLCDataFile(dwFilesProcessed,fpTutorial,pack,true) ) { app.m_dlcManager.addPack(pack); - m_levelGenerators.getLevelGenerators()->at(0)->setWorldName(app.GetString(IDS_PLAY_TUTORIAL)); - m_levelGenerators.getLevelGenerators()->at(0)->setDefaultSaveName(app.GetString(IDS_TUTORIALSAVENAME)); + if (!m_levelGenerators.getLevelGenerators()->empty()) + { + m_levelGenerators.getLevelGenerators()->at(0)->setWorldName(app.GetString(IDS_PLAY_TUTORIAL)); + m_levelGenerators.getLevelGenerators()->at(0)->setDefaultSaveName(app.GetString(IDS_TUTORIALSAVENAME)); + } + else + { + app.DebugPrintf("loadDefaultGameRules: Tutorial.pck parsed OK but no level generators were added (missing GameRules/LevelGenerationOptions tag?)\n"); + } + } + else + { + app.DebugPrintf("loadDefaultGameRules: readDLCDataFile failed for Tutorial.pck (version too old, IO error, or DLC_TYPE_GameRules not found)\n"); + delete pack; } - else delete pack; } + else + { + app.DebugPrintf("loadDefaultGameRules: Tutorial.pck not found in archive\n"); + } + + // Linux/PC port fallback: if Tutorial.pck parsing didn't populate level generators + // (e.g. DLC version mismatch, missing GameRules tag), create a minimal placeholder so + // the game doesn't crash with vector::at(0) in LoadTrial(). + if (m_levelGenerators.getLevelGenerators()->empty()) + { + app.DebugPrintf("loadDefaultGameRules: creating minimal fallback LevelGenerationOptions\n"); + LevelGenerationOptions *lgo = new LevelGenerationOptions(); + lgo->setGrSource(new JustGrSource()); + lgo->setSrc(LevelGenerationOptions::eSrc_tutorial); + lgo->setWorldName(app.GetString(IDS_PLAY_TUTORIAL)); + lgo->setDefaultSaveName(app.GetString(IDS_TUTORIALSAVENAME)); + lgo->setLoadedData(); + addLevelGenerationOptions(lgo); + } + /*StringTable *strings = new StringTable(baStrings.data, baStrings.length); LevelGenerationOptions *lgo = new LevelGenerationOptions(); lgo->setGrSource( new JustGrSource() ); diff --git a/Minecraft.Client/Build/Common/UI/UIScene_MainMenu.cpp b/Minecraft.Client/Build/Common/UI/UIScene_MainMenu.cpp index 624dddfdb..dfd485a4b 100644 --- a/Minecraft.Client/Build/Common/UI/UIScene_MainMenu.cpp +++ b/Minecraft.Client/Build/Common/UI/UIScene_MainMenu.cpp @@ -2009,6 +2009,12 @@ void UIScene_MainMenu::LoadTrial(void) param->settings = app.GetGameHostOption( eGameHostOption_Tutorial ) | app.GetGameHostOption(eGameHostOption_DisableSaving); vector *generators = app.getLevelGenerators(); + if (generators->empty()) + { + app.DebugPrintf("LoadTrial: no level generators available, cannot start tutorial\n"); + delete param; + return; + } param->levelGen = generators->at(0); LoadingInputParams *loadingParams = new LoadingInputParams(); diff --git a/Minecraft.Client/Build/Common/XUI/XUI_MainMenu.cpp b/Minecraft.Client/Build/Common/XUI/XUI_MainMenu.cpp index 59f01a98e..278dec9fa 100644 --- a/Minecraft.Client/Build/Common/XUI/XUI_MainMenu.cpp +++ b/Minecraft.Client/Build/Common/XUI/XUI_MainMenu.cpp @@ -911,6 +911,12 @@ void CScene_Main::LoadTrial(void) param->settings = app.GetGameHostOption( eGameHostOption_Tutorial ); vector *generators = app.getLevelGenerators(); + if (generators->empty()) + { + app.DebugPrintf("XUI LoadTrial: no level generators available, cannot start tutorial\n"); + delete param; + return; + } param->levelGen = generators->at(0); LoadingInputParams *loadingParams = new LoadingInputParams(); diff --git a/Minecraft.Client/Textures/Textures.cpp b/Minecraft.Client/Textures/Textures.cpp index 49a13330c..c08b0de03 100644 --- a/Minecraft.Client/Textures/Textures.cpp +++ b/Minecraft.Client/Textures/Textures.cpp @@ -497,7 +497,9 @@ void Textures::loadTexture(BufferedImage *img, int id, bool blur, bool clamp) if (MIPMAP) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + // Linux/PC port: force GL_NEAREST to avoid mip-level distance blurring and keep + // Minecraft textures pixel-crisp at all distances. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); /* * glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0);