diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index 85531e472..c0d1c9d3d 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -267,8 +267,13 @@ int CPlatformNetworkManagerStub::GetLocalPlayerMask(int playerIndex) bool CPlatformNetworkManagerStub::AddLocalPlayerByUserIndex( int userIndex ) { - NotifyPlayerJoined(m_pIQNet->GetLocalPlayerByUserIndex(userIndex)); - return ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) == S_OK ); + if ( m_pIQNet->AddLocalPlayerByUserIndex(userIndex) != S_OK ) + return false; + // Player is now registered in IQNet — get a pointer and notify the network layer. + // Use the static array directly: GetLocalPlayerByUserIndex checks customData which + // isn't set until addNetworkPlayer runs inside NotifyPlayerJoined. + NotifyPlayerJoined(&IQNet::m_player[userIndex]); + return true; } bool CPlatformNetworkManagerStub::RemoveLocalPlayerByUserIndex( int userIndex ) diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index f059d8211..9418d9509 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -804,13 +804,16 @@ void UIController::tickInput() eUILayer_Fullscreen, eUILayer_Scene, }; - for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l) + // Only check the fullscreen group and the primary (KBM) player's group. + // Other splitscreen players use controllers — mouse must not affect them. + const int mouseGroups[] = { (int)eUIGroup_Fullscreen, ProfileManager.GetPrimaryPad() + 1 }; + for (int l = 0; l < _countof(mouseLayers) && !pScene; ++l) + { + for (int g = 0; g < _countof(mouseGroups) && !pScene; ++g) { - for (int grp = 0; grp < eUIGroup_COUNT && !pScene; ++grp) - { - pScene = m_groups[grp]->GetTopScene(mouseLayers[l]); - } + pScene = m_groups[mouseGroups[g]]->GetTopScene(mouseLayers[l]); } + } if (pScene && pScene->getMovie()) { int rawMouseX = g_KBMInput.GetMouseX(); @@ -823,7 +826,12 @@ void UIController::tickInput() m_lastHoverMouseX = rawMouseX; m_lastHoverMouseY = rawMouseY; - // Convert mouse to scene/movie coordinates + // Convert mouse window-pixel coords to Flash/SWF authoring coords. + // In split-screen the scene is rendered at a tile-origin offset + // and at a smaller display size, so we must: + // 1. Map window pixels → UIController screen space + // 2. Subtract the viewport tile origin + // 3. Scale from display dimensions to SWF authoring dimensions F32 sceneMouseX = (F32)rawMouseX; F32 sceneMouseY = (F32)rawMouseY; { @@ -835,8 +843,43 @@ void UIController::tickInput() int winH = rc.bottom - rc.top; if (winW > 0 && winH > 0) { - sceneMouseX = sceneMouseX * ((F32)pScene->getRenderWidth() / (F32)winW); - sceneMouseY = sceneMouseY * ((F32)pScene->getRenderHeight() / (F32)winH); + // Step 1: window pixels → screen space + F32 screenX = sceneMouseX * (getScreenWidth() / (F32)winW); + F32 screenY = sceneMouseY * (getScreenHeight() / (F32)winH); + + // Step 2 & 3: account for split-screen viewport + C4JRender::eViewportType vp = pScene->GetParentLayer()->getViewport(); + S32 displayW = 0, displayH = 0; + getRenderDimensions(vp, displayW, displayH); + + S32 originX = 0, originY = 0; + switch (vp) + { + case C4JRender::VIEWPORT_TYPE_SPLIT_TOP: + originX = (S32)(getScreenWidth() / 4); break; + case C4JRender::VIEWPORT_TYPE_SPLIT_BOTTOM: + originX = (S32)(getScreenWidth() / 4); + originY = (S32)(getScreenHeight() / 2); break; + case C4JRender::VIEWPORT_TYPE_SPLIT_LEFT: + originY = (S32)(getScreenHeight() / 4); break; + case C4JRender::VIEWPORT_TYPE_SPLIT_RIGHT: + originX = (S32)(getScreenWidth() / 2); + originY = (S32)(getScreenHeight() / 4); break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_TOP_RIGHT: + originX = (S32)(getScreenWidth() / 2); break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_LEFT: + originY = (S32)(getScreenHeight() / 2); break; + case C4JRender::VIEWPORT_TYPE_QUADRANT_BOTTOM_RIGHT: + originX = (S32)(getScreenWidth() / 2); + originY = (S32)(getScreenHeight() / 2); break; + default: break; // FULLSCREEN and QUADRANT_TOP_LEFT: origin (0,0) + } + + if (displayW > 0 && displayH > 0) + { + sceneMouseX = (screenX - originX) * ((F32)pScene->getRenderWidth() / (F32)displayW); + sceneMouseY = (screenY - originY) * ((F32)pScene->getRenderHeight() / (F32)displayH); + } } } } diff --git a/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp b/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp index b28564c3a..be3ee118d 100644 --- a/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp +++ b/Minecraft.Client/Common/UI/UIScene_Keyboard.cpp @@ -38,6 +38,7 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye } m_win64TextBuffer = defaultText; + m_iCursorPos = (int)m_win64TextBuffer.length(); m_EnterTextLabel.init(titleText); m_KeyboardTextInput.init(defaultText, -1); @@ -111,6 +112,9 @@ UIScene_Keyboard::UIScene_Keyboard(int iPad, void *initData, UILayer *parentLaye if (IggyValuePathMakeNameRef(&keyPath, root, s_keyNames[i])) IggyValueSetBooleanRS(&keyPath, nameVisible, NULL, false); } + + m_KeyboardTextInput.setCaretVisible(true); + m_KeyboardTextInput.setCaretIndex(m_iCursorPos); } #endif @@ -165,9 +169,13 @@ void UIScene_Keyboard::tick() // Sync our buffer from Flash so we pick up changes made via controller/on-screen buttons. // Without this, switching between controller and keyboard would use stale text. - const wchar_t* flashText = m_KeyboardTextInput.getLabel(); - if (flashText) - m_win64TextBuffer = flashText; + // In PC mode we own the buffer — skip sync to preserve cursor position. + if (!m_bPCMode) + { + const wchar_t* flashText = m_KeyboardTextInput.getLabel(); + if (flashText) + m_win64TextBuffer = flashText; + } // Accumulate physical keyboard chars into our own buffer, then push to Flash via setLabel. // This bypasses Iggy's focus system (char events only route to the focused element). @@ -178,7 +186,16 @@ void UIScene_Keyboard::tick() { if (ch == 0x08) // backspace { - if (!m_win64TextBuffer.empty()) + if (m_bPCMode) + { + if (m_iCursorPos > 0) + { + m_win64TextBuffer.erase(m_iCursorPos - 1, 1); + m_iCursorPos--; + changed = true; + } + } + else if (!m_win64TextBuffer.empty()) { m_win64TextBuffer.pop_back(); changed = true; @@ -194,13 +211,45 @@ void UIScene_Keyboard::tick() } else if ((int)m_win64TextBuffer.length() < m_win64MaxChars) { - m_win64TextBuffer += ch; + if (m_bPCMode) + { + m_win64TextBuffer.insert(m_iCursorPos, 1, ch); + m_iCursorPos++; + } + else + { + m_win64TextBuffer += ch; + } + changed = true; + } + } + + if (m_bPCMode) + { + // Arrow keys, Home, End, Delete for cursor movement + if (g_KBMInput.IsKeyPressed(VK_LEFT) && m_iCursorPos > 0) + m_iCursorPos--; + if (g_KBMInput.IsKeyPressed(VK_RIGHT) && m_iCursorPos < (int)m_win64TextBuffer.length()) + m_iCursorPos++; + if (g_KBMInput.IsKeyPressed(VK_HOME)) + m_iCursorPos = 0; + if (g_KBMInput.IsKeyPressed(VK_END)) + m_iCursorPos = (int)m_win64TextBuffer.length(); + if (g_KBMInput.IsKeyPressed(VK_DELETE) && m_iCursorPos < (int)m_win64TextBuffer.length()) + { + m_win64TextBuffer.erase(m_iCursorPos, 1); changed = true; } } if (changed) m_KeyboardTextInput.setLabel(m_win64TextBuffer.c_str(), true /*instant*/); + + if (m_bPCMode) + { + m_KeyboardTextInput.setCaretVisible(true); + m_KeyboardTextInput.setCaretIndex(m_iCursorPos); + } } #endif @@ -269,11 +318,17 @@ void UIScene_Keyboard::handleInput(int iPad, int key, bool repeat, bool pressed, switch(key) { case ACTION_MENU_OK: + sendInputToMovie(key, repeat, pressed, released); + handled = true; + break; case ACTION_MENU_LEFT: case ACTION_MENU_RIGHT: case ACTION_MENU_UP: case ACTION_MENU_DOWN: - sendInputToMovie(key, repeat, pressed, released); +#ifdef _WINDOWS64 + if (!m_bPCMode) +#endif + sendInputToMovie(key, repeat, pressed, released); handled = true; break; } diff --git a/Minecraft.Client/Common/UI/UIScene_Keyboard.h b/Minecraft.Client/Common/UI/UIScene_Keyboard.h index 054322f20..146934c1f 100644 --- a/Minecraft.Client/Common/UI/UIScene_Keyboard.h +++ b/Minecraft.Client/Common/UI/UIScene_Keyboard.h @@ -13,6 +13,7 @@ private: wstring m_win64TextBuffer; int m_win64MaxChars; bool m_bPCMode; // Hides on-screen keyboard buttons; physical keyboard only + int m_iCursorPos; #endif protected: diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 29865e400..d37238a6b 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -233,7 +233,20 @@ void Win64_SetupRemoteQNetPlayer(IQNetPlayer * player, BYTE smallId, bool isHost static bool Win64_IsActivePlayer(IQNetPlayer* p, DWORD index); -HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) { return S_OK; } +HRESULT IQNet::AddLocalPlayerByUserIndex(DWORD dwUserIndex) { + if (dwUserIndex >= MINECRAFT_NET_MAX_PLAYERS) return E_FAIL; + m_player[dwUserIndex].m_isRemote = false; + m_player[dwUserIndex].m_isHostPlayer = false; + // Give the joining player a distinct gamertag + extern wchar_t g_Win64UsernameW[17]; + if (dwUserIndex == 0) + wcscpy_s(m_player[0].m_gamertag, 32, g_Win64UsernameW); + else + swprintf_s(m_player[dwUserIndex].m_gamertag, 32, L"%s(%d)", g_Win64UsernameW, dwUserIndex + 1); + if (dwUserIndex >= s_playerCount) + s_playerCount = dwUserIndex + 1; + return S_OK; +} IQNetPlayer* IQNet::GetHostPlayer() { return &m_player[0]; } IQNetPlayer* IQNet::GetLocalPlayerByUserIndex(DWORD dwUserIndex) { @@ -559,7 +572,7 @@ void C_4JProfile::SetTrialTextStringTable(CXuiStringTable * pStringTable, int void C_4JProfile::SetTrialAwardText(eAwardType AwardType, int iTitle, int iText) {} int C_4JProfile::GetLockedProfile() { return 0; } void C_4JProfile::SetLockedProfile(int iProf) {} -bool C_4JProfile::IsSignedIn(int iQuadrant) { return (iQuadrant == 0); } +bool C_4JProfile::IsSignedIn(int iQuadrant) { return InputManager.IsPadConnected(iQuadrant); } bool C_4JProfile::IsSignedInLive(int iProf) { return true; } bool C_4JProfile::IsGuest(int iQuadrant) { return false; } UINT C_4JProfile::RequestSignInUI(bool bFromInvite, bool bLocalGame, bool bNoGuestsAllowed, bool bMultiplayerSignIn, bool bAddUser, int(*Func)(LPVOID, const bool, const int iPad), LPVOID lpParam, int iQuadrant) { return 0; } @@ -570,13 +583,8 @@ bool C_4JProfile::QuerySigninStatus(void) { return true; } void C_4JProfile::GetXUID(int iPad, PlayerUID * pXuid, bool bOnlineXuid) { #ifdef _WINDOWS64 - if (iPad != 0) - { - *pXuid = INVALID_XUID; - return; - } if (IQNet::s_isHosting) - *pXuid = 0xe000d45248242f2e; + *pXuid = 0xe000d45248242f2e + iPad; else *pXuid = 0xe000d45248242f2e + WinsockNetLayer::GetLocalSmallId(); #else diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index b87745f03..38bfcdbf4 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -2325,16 +2325,21 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) } #ifdef _WINDOWS64 - if ((screen != NULL || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed()) + // Mouse grab/release only for the primary (KBM) player — splitscreen + // players use controllers and must never fight over the cursor state. + if (iPad == ProfileManager.GetPrimaryPad()) { - g_KBMInput.SetMouseGrabbed(false); + if ((screen != NULL || ui.GetMenuDisplayed(iPad)) && g_KBMInput.IsMouseGrabbed()) + { + g_KBMInput.SetMouseGrabbed(false); + } } #endif if (screen == NULL && !ui.GetMenuDisplayed(iPad) ) { #ifdef _WINDOWS64 - if (!g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused()) + if (iPad == ProfileManager.GetPrimaryPad() && !g_KBMInput.IsMouseGrabbed() && g_KBMInput.IsWindowFocused()) { g_KBMInput.SetMouseGrabbed(true); } diff --git a/Minecraft.Client/PS3/Media/splashes.txt b/Minecraft.Client/PS3/Media/splashes.txt index 7f7835c6c..df4a4be19 100644 --- a/Minecraft.Client/PS3/Media/splashes.txt +++ b/Minecraft.Client/PS3/Media/splashes.txt @@ -43,7 +43,7 @@ Dungeon! Exclusive! The bee's knees! Down with O.P.P.! -Closed source! +Closed source xD! Classy! Wow! Not on steam! diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 2eef5f737..ae26b6d6b 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -86,6 +86,13 @@ DWORD dwProfileSettingsA[NUM_PROFILE_VALUES]= BOOL g_bWidescreen = TRUE; +// DO NOT auto-detect from the monitor (GetSystemMetrics etc.) — the game resolution +// must stay 1920x1080. The precompiled 4J_Render lib, all Flash SWF assets, and the +// split-screen viewport math (RenderManager.StateSetViewport, UIController::getRenderDimensions, +// setupRenderPosition) all assume a 16:9 framebuffer. Using the native monitor resolution +// breaks split-screen on ultrawide/non-16:9 displays: the UI renders offset and the +// bottom portion of each viewport is left empty. +// ApplyScreenMode() can still override these for debug/test resolutions via launch args. int g_iScreenWidth = 1920; int g_iScreenHeight = 1080; @@ -1177,10 +1184,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, if (pSlash) { *(pSlash + 1) = '\0'; SetCurrentDirectoryA(szExeDir); } } - // Declare DPI awareness so GetSystemMetrics returns physical pixels + // Declare DPI awareness so pixel coordinates are consistent SetProcessDPIAware(); - g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN); - g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN); // Load username from username.txt char exePath[MAX_PATH] = {};