diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..86c4ba55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,420 @@ +# Created by https://www.toptal.com/developers/gitignore/api/visualstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/visualstudio + +enc_temp_folder/ + +# Game data / assets +Minecraft.Client/Schematics/ +Minecraft.Client/Windows64/GameHDD/ + +# Intermediate build files (per-project) +Minecraft.Client/x64/ +Minecraft.Client/Debug/ +Minecraft.Client/Release/ +Minecraft.World/x64/ +Minecraft.World/x64_Debug/ +Minecraft.World/Debug/ +Minecraft.World/Release/ diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index 5d7c03b5..52cec351 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -746,17 +746,32 @@ void ClientConnection::handleAddPlayer(shared_ptr packet) // Some remote players could actually be local players that are already added for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx) { - // need to use the XUID here - PlayerUID playerXUIDOnline = INVALID_XUID, playerXUIDOffline = INVALID_XUID; - ProfileManager.GetXUID(idx,&playerXUIDOnline,true); - ProfileManager.GetXUID(idx,&playerXUIDOffline,false); - if( (playerXUIDOnline != INVALID_XUID && ProfileManager.AreXUIDSEqual(playerXUIDOnline,packet->xuid) ) || + // need to use the XUID here + PlayerUID playerXUIDOnline = INVALID_XUID, playerXUIDOffline = INVALID_XUID; + ProfileManager.GetXUID(idx,&playerXUIDOnline,true); + ProfileManager.GetXUID(idx,&playerXUIDOffline,false); + if( (playerXUIDOnline != INVALID_XUID && ProfileManager.AreXUIDSEqual(playerXUIDOnline,packet->xuid) ) || (playerXUIDOffline != INVALID_XUID && ProfileManager.AreXUIDSEqual(playerXUIDOffline,packet->xuid) ) ) { app.DebugPrintf("AddPlayerPacket received with XUID of local player\n"); return; } } +#ifdef _WINDOWS64 + // On Windows64 all XUIDs are INVALID_XUID so the XUID check above never fires. + // packet->m_playerIndex is the server-assigned sequential index (set via LoginPacket), + // NOT the controller slot — so we must scan all local player slots and match by + // their stored server index rather than using it directly as an array subscript. + for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if(minecraft->localplayers[idx] != NULL && + minecraft->localplayers[idx]->getPlayerIndex() == packet->m_playerIndex) + { + app.DebugPrintf("AddPlayerPacket received for local player (controller %d, server index %d), skipping RemotePlayer creation\n", idx, packet->m_playerIndex); + return; + } + } +#endif double x = packet->x / 32.0; double y = packet->y / 32.0; diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index 06463a69..efecfb44 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -6567,7 +6567,7 @@ wstring CMinecraftApp::GetActionReplacement(int iPad, unsigned char ucAction) int size = 30; #elif defined _WIN64 int size = 45; - if(ui.getScreenWidth() < 1920) size = 30; + if(ui.getScreenHeight() < 1080) size = 30; #else int size = 45; #endif @@ -6696,7 +6696,7 @@ wstring CMinecraftApp::GetVKReplacement(unsigned int uiVKey) int size = 30; #elif defined _WIN64 int size = 45; - if(ui.getScreenWidth() < 1920) size = 30; + if(ui.getScreenHeight() < 1080) size = 30; #else int size = 45; #endif @@ -6727,7 +6727,7 @@ wstring CMinecraftApp::GetIconReplacement(unsigned int uiIcon) int size = 22; #elif defined _WIN64 int size = 33; - if(ui.getScreenWidth() < 1920) size = 22; + if(ui.getScreenHeight() < 1080) size = 22; #else int size = 33; #endif @@ -9481,7 +9481,11 @@ bool CMinecraftApp::IsLocalMultiplayerAvailable() if( InputManager.IsPadConnected(i) || ProfileManager.IsSignedIn(i) ) ++connectedControllers; } +#ifdef _WINDOWS64 + bool available = connectedControllers > 1; +#else bool available = RenderManager.IsHiDef() && connectedControllers > 1; +#endif #ifdef __ORBIS__ // Check for remote play diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index 8a7ffe74..f1ac6e0a 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -182,7 +182,7 @@ UIController::UIController() #if defined _WINDOWS64 || defined _DURANGO || defined __ORBIS__ m_fScreenWidth = 1920.0f; m_fScreenHeight = 1080.0f; - m_bScreenWidthSetup = true; + m_bScreenWidthSetup = false; #else m_fScreenWidth = 1280.0f; m_fScreenHeight = 720.0f; @@ -500,36 +500,36 @@ void UIController::loadSkins() #elif defined __PSVITA__ platformSkinPath = L"skinVita.swf"; #elif defined _WINDOWS64 - if(m_fScreenHeight==1080.0f) + if(m_fScreenHeight>=1080.0f) { platformSkinPath = L"skinHDWin.swf"; } else { - platformSkinPath = L"skinWin.swf"; + platformSkinPath = L"skinWin.swf"; } #elif defined _DURANGO - if(m_fScreenHeight==1080.0f) + if(m_fScreenHeight>=1080.0f) { - platformSkinPath = L"skinHDDurango.swf"; + platformSkinPath = L"skinHDDurango.swf"; } else { - platformSkinPath = L"skinDurango.swf"; + platformSkinPath = L"skinDurango.swf"; } #elif defined __ORBIS__ - if(m_fScreenHeight==1080.0f) + if(m_fScreenHeight>=1080.0f) { - platformSkinPath = L"skinHDOrbis.swf"; + platformSkinPath = L"skinHDOrbis.swf"; } else { - platformSkinPath = L"skinOrbis.swf"; + platformSkinPath = L"skinOrbis.swf"; } #endif // Every platform has one of these, so nothing shared - if(m_fScreenHeight==1080.0f) + if(m_fScreenHeight>=1080.0f) { m_iggyLibraries[eLibrary_Platform] = loadSkin(platformSkinPath, L"platformskinHD.swf"); } @@ -994,8 +994,31 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) pressed = InputManager.ButtonPressed(iPad,key); // Toggle released = InputManager.ButtonReleased(iPad,key); // Toggle - //if(pressed) app.DebugPrintf("Pressed %d\n",key); - //if(released) app.DebugPrintf("Released %d\n",key); +#ifdef _WINDOWS64 + // Keyboard menu input for player 0 + if (iPad == 0) + { + bool kbDown = false, kbPressed = false, kbReleased = false; + switch(key) + { + case ACTION_MENU_UP: kbDown = KMInput.IsKeyDown(VK_UP); kbPressed = KMInput.IsKeyPressed(VK_UP); kbReleased = KMInput.IsKeyReleased(VK_UP); break; + case ACTION_MENU_DOWN: kbDown = KMInput.IsKeyDown(VK_DOWN); kbPressed = KMInput.IsKeyPressed(VK_DOWN); kbReleased = KMInput.IsKeyReleased(VK_DOWN); break; + case ACTION_MENU_LEFT: kbDown = KMInput.IsKeyDown(VK_LEFT); kbPressed = KMInput.IsKeyPressed(VK_LEFT); kbReleased = KMInput.IsKeyReleased(VK_LEFT); break; + case ACTION_MENU_RIGHT: kbDown = KMInput.IsKeyDown(VK_RIGHT); kbPressed = KMInput.IsKeyPressed(VK_RIGHT); kbReleased = KMInput.IsKeyReleased(VK_RIGHT); break; + case ACTION_MENU_OK: kbDown = KMInput.IsKeyDown(VK_RETURN); kbPressed = KMInput.IsKeyPressed(VK_RETURN); kbReleased = KMInput.IsKeyReleased(VK_RETURN); break; + case ACTION_MENU_A: kbDown = KMInput.IsKeyDown(VK_RETURN); kbPressed = KMInput.IsKeyPressed(VK_RETURN); kbReleased = KMInput.IsKeyReleased(VK_RETURN); break; + case ACTION_MENU_CANCEL: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break; + case ACTION_MENU_B: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break; + case ACTION_MENU_PAUSEMENU: kbDown = KMInput.IsKeyDown(VK_ESCAPE); kbPressed = KMInput.IsKeyPressed(VK_ESCAPE); kbReleased = KMInput.IsKeyReleased(VK_ESCAPE); break; + } + pressed = pressed || kbPressed; + released = released || kbReleased; + down = down || kbDown; + } +#endif + + if(pressed) app.DebugPrintf("Pressed %d\n",key); + if(released) app.DebugPrintf("Released %d\n",key); // Repeat handling if(pressed) { diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp index f0b7e54c..0ee92eef 100644 --- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.cpp @@ -5,6 +5,9 @@ #include "..\..\..\Minecraft.World\net.minecraft.world.inventory.h" #include "..\..\..\Minecraft.World\net.minecraft.world.item.h" #include "..\..\MultiplayerLocalPlayer.h" +#ifdef _WINDOWS64 +#include "..\..\Windows64\KeyboardMouseInput.h" +#endif UIScene_AbstractContainerMenu::UIScene_AbstractContainerMenu(int iPad, UILayer *parentLayer) : UIScene(iPad, parentLayer) { @@ -25,6 +28,9 @@ UIScene_AbstractContainerMenu::UIScene_AbstractContainerMenu(int iPad, UILayer * ui.OverrideSFX(m_iPad,ACTION_MENU_DOWN,true); m_bIgnoreInput=false; +#ifdef _WINDOWS64 + m_bMouseDragSlider=false; +#endif } UIScene_AbstractContainerMenu::~UIScene_AbstractContainerMenu() @@ -109,8 +115,8 @@ void UIScene_AbstractContainerMenu::PlatformInitialize(int iPad, int startIndex) #ifdef __ORBIS__ // we need to map the touchpad rectangle to the UI rectangle. While it works great for the creative menu, it is much too sensitive for the smaller menus. //X coordinate of the touch point (0 to 1919) - //Y coordinate of the touch point (0 to 941: DUALSHOCK®4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation®4 development tool, - //0 to 753: JDX-1000x series controllers for the PlayStation®4 development tool,) + //Y coordinate of the touch point (0 to 941: DUALSHOCK�4 wireless controllers and the CUH-ZCT1J/CAP-ZCT1J/CAP-ZCT1U controllers for the PlayStation�4 development tool, + //0 to 753: JDX-1000x series controllers for the PlayStation�4 development tool,) m_fTouchPadMulX=fPanelWidth/1919.0f; m_fTouchPadMulY=fPanelHeight/941.0f; m_fTouchPadDeadZoneX=15.0f*m_fTouchPadMulX; @@ -176,13 +182,118 @@ void UIScene_AbstractContainerMenu::tick() { UIScene::tick(); +#ifdef _WINDOWS64 + bool mouseActive = (m_iPad == 0 && !KMInput.IsCaptured()); + float rawMouseMovieX = 0, rawMouseMovieY = 0; + // Map Windows mouse position to the virtual pointer in movie coordinates + if (mouseActive) + { + RECT clientRect; + GetClientRect(KMInput.GetHWnd(), &clientRect); + int clientWidth = clientRect.right; + int clientHeight = clientRect.bottom; + if (clientWidth > 0 && clientHeight > 0) + { + int mouseX = KMInput.GetMouseX(); + int mouseY = KMInput.GetMouseY(); + + // Convert mouse position to movie coordinates using the movie/client ratio + float mx = (float)mouseX * ((float)m_movieWidth / (float)clientWidth); + float my = (float)mouseY * ((float)m_movieHeight / (float)clientHeight); + + m_pointerPos.x = mx; + m_pointerPos.y = my; + rawMouseMovieX = mx; + rawMouseMovieY = my; + } + } +#endif + onMouseTick(); +#ifdef _WINDOWS64 + // Dispatch mouse clicks AFTER onMouseTick() has updated m_eCurrSection from the new pointer position + if (mouseActive) + { + if (KMInput.ConsumeMousePress(0)) + { + if (m_eCurrSection == eSectionInventoryCreativeSlider) + { + // Scrollbar click: use raw mouse position (onMouseTick may have snapped m_pointerPos) + m_bMouseDragSlider = true; + m_pointerPos.x = rawMouseMovieX; + m_pointerPos.y = rawMouseMovieY; + handleOtherClicked(m_iPad, eSectionInventoryCreativeSlider, 0, false); + } + else + { + handleKeyDown(m_iPad, ACTION_MENU_A, false); + } + } + else if (m_bMouseDragSlider && KMInput.IsMouseDown(0)) + { + // Continue scrollbar drag: update scroll position from current mouse Y + m_pointerPos.x = rawMouseMovieX; + m_pointerPos.y = rawMouseMovieY; + handleOtherClicked(m_iPad, eSectionInventoryCreativeSlider, 0, false); + } + + if (!KMInput.IsMouseDown(0)) + m_bMouseDragSlider = false; + + if (KMInput.ConsumeMousePress(1)) + { + handleKeyDown(m_iPad, ACTION_MENU_X, false); + } + if (KMInput.ConsumeMousePress(2)) + { + handleKeyDown(m_iPad, ACTION_MENU_Y, false); + } + + // Mouse scroll wheel for page scrolling + int scrollDelta = KMInput.ConsumeScrollDelta(); + if (scrollDelta > 0) + { + handleKeyDown(m_iPad, ACTION_MENU_OTHER_STICK_UP, false); + } + else if (scrollDelta < 0) + { + handleKeyDown(m_iPad, ACTION_MENU_OTHER_STICK_DOWN, false); + } + + // ESC to close — must be last since it may destroy this scene + if (KMInput.ConsumeKeyPress(VK_ESCAPE)) + { + handleKeyDown(m_iPad, ACTION_MENU_B, false); + return; + } + } +#endif + IggyEvent mouseEvent; S32 width, height; m_parentLayer->getRenderDimensions(width, height); + +#ifdef _WINDOWS64 + S32 x, y; + if (mouseActive) + { + // Send raw mouse position directly as Iggy event to avoid coordinate round-trip errors + // Scale mouse client coords to the Iggy display space (which was set to getRenderDimensions()) + RECT clientRect; + GetClientRect(KMInput.GetHWnd(), &clientRect); + x = (S32)((float)KMInput.GetMouseX() * ((float)width / (float)clientRect.right)); + y = (S32)((float)KMInput.GetMouseY() * ((float)height / (float)clientRect.bottom)); + } + else + { + x = (S32)(m_pointerPos.x * ((float)width / m_movieWidth)); + y = (S32)(m_pointerPos.y * ((float)height / m_movieHeight)); + } +#else S32 x = m_pointerPos.x*((float)width/m_movieWidth); S32 y = m_pointerPos.y*((float)height/m_movieHeight); +#endif IggyMakeEventMouseMove( &mouseEvent, x, y); // 4J Stu - This seems to be broken on Durango, so do it ourself diff --git a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h index 5f313a0e..8afa44f7 100644 --- a/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h +++ b/Minecraft.Client/Common/UI/UIScene_AbstractContainerMenu.h @@ -10,6 +10,9 @@ class UIScene_AbstractContainerMenu : public UIScene, public virtual IUIScene_Ab private: ESceneSection m_focusSection; bool m_bIgnoreInput; +#ifdef _WINDOWS64 + bool m_bMouseDragSlider; +#endif protected: UIControl m_controlMainPanel; diff --git a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp index 1a81bb77..f7cca84b 100644 --- a/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_CreateWorldMenu.cpp @@ -957,13 +957,18 @@ void UIScene_CreateWorldMenu::checkStateAndStartGame() #endif else - { + { +#ifdef _WINDOWS64 + // On Windows64, Xbox Live is unavailable. Skip QuadrantSignin and start directly. + CreateGame(this, 0); +#else //ProfileManager.RequestSignInUI(false, false, false, true, false,&CScene_MultiGameCreate::StartGame_SignInReturned, this,ProfileManager.GetPrimaryPad()); SignInInfo info; info.Func = &UIScene_CreateWorldMenu::StartGame_SignInReturned; info.lpParam = this; info.requireOnline = m_MoreOptionsParams.bOnlineGame; ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_QuadrantSignin,&info); +#endif } } else @@ -1342,12 +1347,18 @@ int UIScene_CreateWorldMenu::ConfirmCreateReturned(void *pParam,int iPad,C4JStor if(isClientSide && app.IsLocalMultiplayerAvailable()) { +#ifdef _WINDOWS64 + // On Windows64, Xbox Live is unavailable. Skip QuadrantSignin and start directly. + CreateGame(pClass, 0); + return 0; +#else //ProfileManager.RequestSignInUI(false, false, false, true, false,&UIScene_CreateWorldMenu::StartGame_SignInReturned, pClass,ProfileManager.GetPrimaryPad()); SignInInfo info; info.Func = &UIScene_CreateWorldMenu::StartGame_SignInReturned; info.lpParam = pClass; info.requireOnline = pClass->m_MoreOptionsParams.bOnlineGame; ui.NavigateToScene(ProfileManager.GetPrimaryPad(),eUIScene_QuadrantSignin,&info); +#endif } else { diff --git a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp index b9e6c5cc..7c69e5b8 100644 --- a/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp +++ b/Minecraft.Client/Common/UI/UIScene_LoadMenu.cpp @@ -1444,7 +1444,14 @@ int UIScene_LoadMenu::LoadDataComplete(void *pParam) #endif else { +#ifdef _WINDOWS64 + // On Windows64, IsSignedInLive() returns true as a stub but Xbox Live is + // not available. Skip QuadrantSignin and proceed directly with local play. + DWORD dwLocalUsersMask = CGameNetworkManager::GetLocalPlayerMask(ProfileManager.GetPrimaryPad()); + StartGameFromSave(pClass, dwLocalUsersMask); +#else pClass->m_bRequestQuadrantSignin = true; +#endif } } } diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 334f0123..f6ad76c1 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -197,7 +197,15 @@ bool IQNetPlayer::IsHost() { return this == &IQNet::m_player[0]; } bool IQNetPlayer::IsGuest() { return false; } bool IQNetPlayer::IsLocal() { return true; } PlayerUID IQNetPlayer::GetXuid() { return INVALID_XUID; } -LPCWSTR IQNetPlayer::GetGamertag() { static const wchar_t *test = L"stub"; return test; } +LPCWSTR IQNetPlayer::GetGamertag() +{ + static wchar_t tags[4][16]; + int idx = GetUserIndex(); + if(idx < 0 || idx >= 4) idx = 0; + mbstowcs(tags[idx], ProfileManager.GetGamertag(idx), 15); + tags[idx][15] = L'\0'; + return tags[idx]; +} int IQNetPlayer::GetSessionIndex() { return 0; } bool IQNetPlayer::IsTalking() { return false; } bool IQNetPlayer::IsMutedByLocalUser(DWORD dwUserIndex) { return false; } @@ -487,8 +495,22 @@ char fakeGamerTag[32] = "PlayerName"; void SetFakeGamertag(char *name){ strcpy_s(fakeGamerTag, name); } char* C_4JProfile::GetGamertag(int iPad){ return fakeGamerTag; } #else -char* C_4JProfile::GetGamertag(int iPad){ return "PlayerName"; } -wstring C_4JProfile::GetDisplayName(int iPad){ return L"PlayerName"; } +char* C_4JProfile::GetGamertag(int iPad) +{ + static char tags[4][16] = { "Player 1", "Player 2", "Player 3", "Player 4" }; + if(iPad >= 0 && iPad < 4) return tags[iPad]; + return tags[0]; +} +wstring C_4JProfile::GetDisplayName(int iPad) +{ + switch(iPad) + { + case 1: return L"Player 2"; + case 2: return L"Player 3"; + case 3: return L"Player 4"; + default: return L"Player 1"; + } +} #endif bool C_4JProfile::IsFullVersion() { return s_bProfileIsFullVersion; } void C_4JProfile::SetSignInChangeCallback(void ( *Func)(LPVOID, bool, unsigned int),LPVOID lpParam) {} diff --git a/Minecraft.Client/Input.cpp b/Minecraft.Client/Input.cpp index 080f2e81..40e9f7de 100644 --- a/Minecraft.Client/Input.cpp +++ b/Minecraft.Client/Input.cpp @@ -18,6 +18,7 @@ Input::Input() lReset = false; rReset = false; + m_gamepadSneaking = false; } void Input::tick(LocalPlayer *player) @@ -34,12 +35,30 @@ void Input::tick(LocalPlayer *player) xa = -InputManager.GetJoypadStick_LX(iPad); else xa = 0.0f; - + if( pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_FORWARD) || pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_BACKWARD) ) ya = InputManager.GetJoypadStick_LY(iPad); else ya = 0.0f; +#ifdef _WINDOWS64 + // WASD movement (combine with gamepad) + if (iPad == 0) + { + float kbX = 0.0f, kbY = 0.0f; + if (KMInput.IsKeyDown('W')) kbY += 1.0f; + if (KMInput.IsKeyDown('S')) kbY -= 1.0f; + if (KMInput.IsKeyDown('A')) kbX += 1.0f; // inverted like gamepad + if (KMInput.IsKeyDown('D')) kbX -= 1.0f; + // Normalize diagonal + if (kbX != 0.0f && kbY != 0.0f) { kbX *= 0.707f; kbY *= 0.707f; } + if (pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LEFT) || pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_RIGHT)) + xa = max(min(xa + kbX, 1.0f), -1.0f); + if (pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_FORWARD) || pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_BACKWARD)) + ya = max(min(ya + kbY, 1.0f), -1.0f); + } +#endif + #ifndef _CONTENT_PACKAGE if (app.GetFreezePlayers()) { @@ -47,7 +66,7 @@ void Input::tick(LocalPlayer *player) player->abilities.flying = true; } #endif - + if (!lReset) { if (xa*xa+ya*ya==0.0f) @@ -62,9 +81,16 @@ void Input::tick(LocalPlayer *player) { if((player->ullButtonsPressed&(1LL<localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_SNEAK_TOGGLE)) { - sneaking=!sneaking; + m_gamepadSneaking=!m_gamepadSneaking; } } + sneaking = m_gamepadSneaking; + +#ifdef _WINDOWS64 + // Keyboard hold-to-sneak (overrides gamepad toggle) + if (iPad == 0 && KMInput.IsKeyDown(VK_SHIFT) && !player->abilities.flying) + sneaking = true; +#endif if(sneaking) { @@ -80,7 +106,7 @@ void Input::tick(LocalPlayer *player) tx = InputManager.GetJoypadStick_RX(iPad)*(((float)app.GetGameSettings(iPad,eGameSetting_Sensitivity_InGame))/100.0f); // apply sensitivity to look if( pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_UP) || pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_LOOK_DOWN) ) ty = InputManager.GetJoypadStick_RY(iPad)*(((float)app.GetGameSettings(iPad,eGameSetting_Sensitivity_InGame))/100.0f); // apply sensitivity to look - + #ifndef _CONTENT_PACKAGE if (app.GetFreezePlayers()) tx = ty = 0.0f; #endif @@ -100,16 +126,43 @@ void Input::tick(LocalPlayer *player) tx = ty = 0.0f; } player->interpolateTurn(tx * abs(tx) * turnSpeed, ty * abs(ty) * turnSpeed); - + +#ifdef _WINDOWS64 + // Mouse look is now handled per-frame in Minecraft::applyFrameMouseLook() + // to eliminate the 20Hz tick delay. Only flush any remaining delta here + // as a safety measure. + if (iPad == 0 && KMInput.IsCaptured()) + { + float rawDx, rawDy; + KMInput.ConsumeMouseDelta(rawDx, rawDy); + // Delta should normally be 0 since applyFrameMouseLook() already consumed it + if (rawDx != 0.0f || rawDy != 0.0f) + { + float mouseSensitivity = 0.5f; + float mdx = rawDx * mouseSensitivity; + float mdy = -rawDy * mouseSensitivity; + if (app.GetGameSettings(iPad, eGameSetting_ControlInvertLook)) + mdy = -mdy; + player->interpolateTurn(mdx, mdy); + } + } +#endif + //jumping = controller.isButtonPressed(0); - + unsigned int jump = InputManager.GetValue(iPad, MINECRAFT_ACTION_JUMP); if( jump > 0 && pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_JUMP) ) jumping = true; else jumping = false; +#ifdef _WINDOWS64 + // Keyboard jump (Space) + if (iPad == 0 && KMInput.IsKeyDown(VK_SPACE) && pMinecraft->localgameModes[iPad]->isInputAllowed(MINECRAFT_ACTION_JUMP)) + jumping = true; +#endif + #ifndef _CONTENT_PACKAGE if (app.GetFreezePlayers()) jumping = false; #endif diff --git a/Minecraft.Client/Input.h b/Minecraft.Client/Input.h index 0a44d765..ef1dcd0c 100644 --- a/Minecraft.Client/Input.h +++ b/Minecraft.Client/Input.h @@ -16,7 +16,8 @@ public: virtual void tick(LocalPlayer *player); private: - + bool lReset; bool rReset; + bool m_gamepadSneaking; }; \ No newline at end of file diff --git a/Minecraft.Client/LocalPlayer.cpp b/Minecraft.Client/LocalPlayer.cpp index 77ce5121..407c55e2 100644 --- a/Minecraft.Client/LocalPlayer.cpp +++ b/Minecraft.Client/LocalPlayer.cpp @@ -279,6 +279,15 @@ void LocalPlayer::aiStep() // world with low food, then reload it in creative. if(abilities.mayfly || isAllowedToFly() ) enoughFoodToSprint = true; +#ifdef _WINDOWS64 + // Keyboard sprint: Ctrl held while moving forward + if (GetXboxPad() == 0 && KMInput.IsKeyDown(VK_CONTROL) && input->ya > 0.0f && + enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness) && onGround) + { + if (!isSprinting()) setSprinting(true); + } +#endif + // 4J - altered this slightly to make sure that the joypad returns to below returnTreshold in between registering two movements up to runThreshold if (onGround && !isSprinting() && enoughFoodToSprint && !isUsingItem() && !hasEffect(MobEffect::blindness)) { diff --git a/Minecraft.Client/Minecraft.Client.vcxproj b/Minecraft.Client/Minecraft.Client.vcxproj index b37fd00d..49d1ad68 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj +++ b/Minecraft.Client/Minecraft.Client.vcxproj @@ -1464,6 +1464,11 @@ if not exist "$(TargetDir)\savedata" mkdir "$(TargetDir)\savedata" NotSet false + + Copying sound assets to output directory + xcopy /q /y /i /s /e "$(ProjectDir)Durango\Sound" "$(OutDir)Durango\Sound" +xcopy /q /y /i /s /e "$(ProjectDir)music" "$(OutDir)music" + $(ProjectDir)xbox\xex-dev.xml @@ -1593,6 +1598,11 @@ xcopy /q /y /i /s /e $(ProjectDir)DurangoMedia\CU $(LayoutDir)Image\Loose\CUNotSet false + + Copying sound assets to output directory + xcopy /q /y /i /s /e "$(ProjectDir)Durango\Sound" "$(OutDir)Durango\Sound" +xcopy /q /y /i /s /e "$(ProjectDir)music" "$(OutDir)music" + $(ProjectDir)xbox\xex-dev.xml @@ -19136,6 +19146,51 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUtrue true + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + false + false + false + false + false + false + false + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true true @@ -34141,6 +34196,51 @@ xcopy /q /y /i /s /e $(ProjectDir)Durango\CU $(LayoutDir)Image\Loose\CUtrue true + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + false + false + false + false + false + false + false + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true true diff --git a/Minecraft.Client/Minecraft.Client.vcxproj.filters b/Minecraft.Client/Minecraft.Client.vcxproj.filters index c1b43c00..9e78bdb1 100644 --- a/Minecraft.Client/Minecraft.Client.vcxproj.filters +++ b/Minecraft.Client/Minecraft.Client.vcxproj.filters @@ -2344,6 +2344,9 @@ Windows64 + + Windows64\Source Files + Windows64 @@ -4911,6 +4914,9 @@ Windows64\Source Files + + Windows64\Source Files + Windows64 diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index 488be368..4fc32457 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -1169,6 +1169,53 @@ void Minecraft::createPrimaryLocalPlayer(int iPad) } } +#ifdef _WINDOWS64 +void Minecraft::applyFrameMouseLook() +{ + // Per-frame mouse look: consume mouse deltas every frame instead of waiting + // for the 20Hz game tick. Apply the same delta to both xRot/yRot AND xRotO/yRotO + // so the render interpolation instantly reflects the change without waiting for a tick. + if (level == NULL) return; + + for (int i = 0; i < XUSER_MAX_COUNT; i++) + { + if (localplayers[i] == NULL) continue; + int iPad = localplayers[i]->GetXboxPad(); + if (iPad != 0) continue; // Mouse only applies to pad 0 + + if (!KMInput.IsCaptured()) continue; + if (localgameModes[iPad] == NULL) continue; + + float rawDx, rawDy; + KMInput.ConsumeMouseDelta(rawDx, rawDy); + if (rawDx == 0.0f && rawDy == 0.0f) continue; + + float mouseSensitivity = 0.5f; + float mdx = rawDx * mouseSensitivity; + float mdy = -rawDy * mouseSensitivity; + if (app.GetGameSettings(iPad, eGameSetting_ControlInvertLook)) + mdy = -mdy; + + // Apply 0.15f scaling (same as Entity::interpolateTurn / Entity::turn) + float dyaw = mdx * 0.15f; + float dpitch = -mdy * 0.15f; + + // Apply to both current and old rotation so render interpolation + // reflects the change immediately (no 50ms tick delay) + localplayers[i]->yRot += dyaw; + localplayers[i]->yRotO += dyaw; + localplayers[i]->xRot += dpitch; + localplayers[i]->xRotO += dpitch; + + // Clamp pitch + if (localplayers[i]->xRot < -90.0f) localplayers[i]->xRot = -90.0f; + if (localplayers[i]->xRot > 90.0f) localplayers[i]->xRot = 90.0f; + if (localplayers[i]->xRotO < -90.0f) localplayers[i]->xRotO = -90.0f; + if (localplayers[i]->xRotO > 90.0f) localplayers[i]->xRotO = 90.0f; + } +} +#endif + void Minecraft::run_middle() { static __int64 lastTime = 0; @@ -1398,6 +1445,21 @@ void Minecraft::run_middle() if(InputManager.ButtonPressed(i, MINECRAFT_ACTION_RENDER_THIRD_PERSON)) localplayers[i]->ullButtonsPressed|=1LL<ullButtonsPressed|=1LL<ullButtonsPressed |= 1LL<ullButtonsPressed |= 1LL<ullButtonsPressed |= 1LL<ullButtonsPressed |= 1LL<ullButtonsPressed |= 1LL<abilities.flying && KMInput.IsKeyDown(VK_SHIFT)) + localplayers[i]->ullButtonsPressed |= 1LL< 0) + s_startPressLatch[i]--; + s_prevXButtons[i] = xCurButtons; + } + bool startJustPressed = s_startPressLatch[i] > 0; + bool tryJoin = !pause && !ui.IsIgnorePlayerJoinMenuDisplayed(ProfileManager.GetPrimaryPad()) && g_NetworkManager.SessionHasSpace() && xCurButtons != 0; +#else bool tryJoin = !pause && !ui.IsIgnorePlayerJoinMenuDisplayed(ProfileManager.GetPrimaryPad()) && g_NetworkManager.SessionHasSpace() && RenderManager.IsHiDef() && InputManager.ButtonPressed(i); +#endif #ifdef __ORBIS__ // Check for remote play tryJoin = tryJoin && InputManager.IsLocalMultiplayerAvailable(); @@ -1476,6 +1562,8 @@ void Minecraft::run_middle() // did we just get input from a player who doesn't exist? They'll be wanting to join the game then #ifdef __ORBIS__ if(InputManager.ButtonPressed(i, ACTION_MENU_A)) +#elif defined _WINDOWS64 + if(startJustPressed) #else if(InputManager.ButtonPressed(i, MINECRAFT_ACTION_PAUSEMENU)) #endif @@ -1483,7 +1571,11 @@ void Minecraft::run_middle() // Let them join // are they signed in? +#ifdef _WINDOWS64 + if(ProfileManager.IsSignedIn(i) || (g_NetworkManager.IsLocalGame() && InputManager.IsPadConnected(i))) +#else if(ProfileManager.IsSignedIn(i)) +#endif { // if this is a local game, then the player just needs to be signed in if( g_NetworkManager.IsLocalGame() || (ProfileManager.IsSignedInLive(i) && ProfileManager.AllowedToPlayMultiplayer(i) ) ) @@ -3337,6 +3429,30 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) { wheel = -1; } + +#ifdef _WINDOWS64 + // Mouse scroll wheel for hotbar + if (iPad == 0) + { + int kbWheel = KMInput.ConsumeScrollDelta(); + if (kbWheel > 0 && gameMode->isInputAllowed(MINECRAFT_ACTION_LEFT_SCROLL)) wheel += 1; + else if (kbWheel < 0 && gameMode->isInputAllowed(MINECRAFT_ACTION_RIGHT_SCROLL)) wheel -= 1; + + // 1-9 keys for direct hotbar selection + if (gameMode->isInputAllowed(MINECRAFT_ACTION_LEFT_SCROLL)) + { + for (int k = '1'; k <= '9'; k++) + { + if (KMInput.ConsumeKeyPress(k)) + { + player->inventory->selected = k - '1'; + app.SetOpacityTimer(iPad); + break; + } + } + } + } +#endif if (wheel != 0) { player->inventory->swapPaint(wheel); @@ -3368,6 +3484,13 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) player->handleMouseClick(0); player->lastClickTick[0] = ticks; } +#ifdef _WINDOWS64 + else if (iPad == 0 && KMInput.IsCaptured() && KMInput.ConsumeMousePress(0)) + { + player->handleMouseClick(0); + player->lastClickTick[0] = ticks; + } +#endif if (InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION) && ticks - player->lastClickTick[0] >= timer->ticksPerSecond / 4) { @@ -3375,8 +3498,19 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) player->handleMouseClick(0); player->lastClickTick[0] = ticks; } +#ifdef _WINDOWS64 + else if (iPad == 0 && KMInput.IsCaptured() && KMInput.IsMouseDown(0) && ticks - player->lastClickTick[0] >= timer->ticksPerSecond / 4) + { + player->handleMouseClick(0); + player->lastClickTick[0] = ticks; + } +#endif - if(InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION) ) + if(InputManager.ButtonDown(iPad, MINECRAFT_ACTION_ACTION) +#ifdef _WINDOWS64 + || (iPad == 0 && KMInput.IsCaptured() && KMInput.IsMouseDown(0)) +#endif + ) { player->handleMouseDown(0, true ); } @@ -3397,14 +3531,23 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) */ if( player->isUsingItem() ) { - if(!InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE)) gameMode->releaseUsingItem(player); + if(!InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) +#ifdef _WINDOWS64 + && !(iPad == 0 && KMInput.IsCaptured() && KMInput.IsMouseDown(1)) +#endif + ) gameMode->releaseUsingItem(player); } else if( gameMode->isInputAllowed(MINECRAFT_ACTION_USE) ) { +#ifdef _WINDOWS64 + bool useButtonDown = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) || (iPad == 0 && KMInput.IsCaptured() && KMInput.IsMouseDown(1)); +#else + bool useButtonDown = InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE); +#endif if( player->abilities.instabuild ) { // 4J - attempt to handle click in special creative mode fashion if possible (used for placing blocks at regular intervals) - bool didClick = player->creativeModeHandleMouseClick(1, InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) ); + bool didClick = player->creativeModeHandleMouseClick(1, useButtonDown ); // If this handler has put us in lastClick_oldRepeat mode then it is because we aren't placing blocks - behave largely as the code used to if( player->lastClickState == LocalPlayer::lastClick_oldRepeat ) { @@ -3416,7 +3559,7 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) else { // Otherwise just the original game code for handling autorepeat - if (InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) && ticks - player->lastClickTick[1] >= timer->ticksPerSecond / 4) + if (useButtonDown && ticks - player->lastClickTick[1] >= timer->ticksPerSecond / 4) { player->handleMouseClick(1); player->lastClickTick[1] = ticks; @@ -3432,7 +3575,7 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) bool firstClick = ( player->lastClickTick[1] == 0 ); bool autoRepeat = ticks - player->lastClickTick[1] >= timer->ticksPerSecond / 4; if ( player->isRiding() || player->isSprinting() || player->isSleeping() ) autoRepeat = false; - if (InputManager.ButtonDown(iPad, MINECRAFT_ACTION_USE) ) + if (useButtonDown ) { // If the player has just exited a bed, then delay the time before a repeat key is allowed without releasing if(player->isSleeping() ) player->lastClickTick[1] = ticks + (timer->ticksPerSecond * 2); diff --git a/Minecraft.Client/Minecraft.h b/Minecraft.Client/Minecraft.h index 2fe1297a..cc1e5d18 100644 --- a/Minecraft.Client/Minecraft.h +++ b/Minecraft.Client/Minecraft.h @@ -217,6 +217,9 @@ public: static Minecraft *GetInstance(); void run_middle(); void run_end(); +#ifdef _WINDOWS64 + void applyFrameMouseLook(); // Per-frame mouse look to reduce input latency +#endif void emergencySave(); diff --git a/Minecraft.Client/Options.cpp b/Minecraft.Client/Options.cpp index 9952fc8c..c18c136b 100644 --- a/Minecraft.Client/Options.cpp +++ b/Minecraft.Client/Options.cpp @@ -116,7 +116,7 @@ void Options::init() bobView = true; anaglyph3d = false; advancedOpengl = false; - framerateLimit = 2; + framerateLimit = 0; fancyGraphics = true; ambientOcclusion = true; renderClouds = true; @@ -522,4 +522,4 @@ void Options::save() bool Options::isCloudsOn() { return viewDistance < 2 && renderClouds; -} \ No newline at end of file +} diff --git a/Minecraft.Client/Screen.cpp b/Minecraft.Client/Screen.cpp index 0019b54d..6a402b04 100644 --- a/Minecraft.Client/Screen.cpp +++ b/Minecraft.Client/Screen.cpp @@ -5,6 +5,9 @@ #include "Tesselator.h" #include "Textures.h" #include "..\Minecraft.World\SoundTypes.h" +#ifdef _WINDOWS64 +#include "Windows64\KeyboardMouseInput.h" +#endif @@ -103,20 +106,71 @@ void Screen::init() void Screen::updateEvents() { +#ifdef _WINDOWS64 + // Poll mouse button state and dispatch click/release events + for (int btn = 0; btn < 3; btn++) + { + if (KMInput.ConsumeMousePress(btn)) + { + int xm = Mouse::getX() * width / minecraft->width; + int ym = height - Mouse::getY() * height / minecraft->height - 1; + mouseClicked(xm, ym, btn); + } + if (KMInput.ConsumeMouseRelease(btn)) + { + int xm = Mouse::getX() * width / minecraft->width; + int ym = height - Mouse::getY() * height / minecraft->height - 1; + mouseReleased(xm, ym, btn); + } + } + + // Poll keyboard events + for (int vk = 0; vk < 256; vk++) + { + if (KMInput.ConsumeKeyPress(vk)) + { + // Map Windows virtual key to the Keyboard constants used by Screen::keyPressed + int mappedKey = -1; + wchar_t ch = 0; + if (vk == VK_ESCAPE) mappedKey = Keyboard::KEY_ESCAPE; + else if (vk == VK_RETURN) mappedKey = Keyboard::KEY_RETURN; + else if (vk == VK_BACK) mappedKey = Keyboard::KEY_BACK; + else if (vk == VK_UP) mappedKey = Keyboard::KEY_UP; + else if (vk == VK_DOWN) mappedKey = Keyboard::KEY_DOWN; + else if (vk == VK_LEFT) mappedKey = Keyboard::KEY_LEFT; + else if (vk == VK_RIGHT) mappedKey = Keyboard::KEY_RIGHT; + else if (vk == VK_LSHIFT || vk == VK_RSHIFT) mappedKey = Keyboard::KEY_LSHIFT; + else if (vk == VK_TAB) mappedKey = Keyboard::KEY_TAB; + else if (vk >= 'A' && vk <= 'Z') + { + ch = (wchar_t)(vk - 'A' + L'a'); + if (KMInput.IsKeyDown(VK_SHIFT)) ch = (wchar_t)vk; + } + else if (vk >= '0' && vk <= '9') ch = (wchar_t)vk; + else if (vk == VK_SPACE) ch = L' '; + + if (mappedKey != -1) keyPressed(ch, mappedKey); + else if (ch != 0) keyPressed(ch, -1); + } + } +#else /* 4J - TODO - while (Mouse.next()) { - mouseEvent(); - } + while (Mouse.next()) { + mouseEvent(); + } - while (Keyboard.next()) { - keyboardEvent(); - } + while (Keyboard.next()) { + keyboardEvent(); + } */ - +#endif } void Screen::mouseEvent() { +#ifdef _WINDOWS64 + // Mouse event dispatching is handled directly in updateEvents() for Windows +#else /* 4J - TODO if (Mouse.getEventButtonState()) { int xm = Mouse.getEventX() * width / minecraft.width; @@ -128,6 +182,7 @@ void Screen::mouseEvent() mouseReleased(xm, ym, Mouse.getEventButton()); } */ +#endif } void Screen::keyboardEvent() diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Input.lib index 5fecd971..724a1228 100644 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input.lib and b/Minecraft.Client/Windows64/4JLibs/libs/4J_Input.lib differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC.lib index e46e7f93..38c56659 100644 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC.lib and b/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC.lib differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage.lib index e1d70b87..d8157a20 100644 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage.lib and b/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage.lib differ diff --git a/Minecraft.Client/Windows64/GameHDD/20140401093851/saveData.ms b/Minecraft.Client/Windows64/GameHDD/20140401093851/saveData.ms index c227422d..939d5139 100644 Binary files a/Minecraft.Client/Windows64/GameHDD/20140401093851/saveData.ms and b/Minecraft.Client/Windows64/GameHDD/20140401093851/saveData.ms differ diff --git a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl index 1ab1f13f..e827cd82 100644 --- a/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl +++ b/Minecraft.Client/Windows64/Iggy/gdraw/gdraw_d3d1x_shared.inl @@ -502,7 +502,7 @@ static GDrawTexture * RADLINK gdraw_MakeTextureEnd(GDraw_MakeTexture_ProcessingI } // actually create texture - D3D1X_(TEXTURE2D_DESC) desc = { w, h, nmips, 1, (DXGI_FORMAT) p->i3, { 1, 0 }, + D3D1X_(TEXTURE2D_DESC) desc = { static_cast(w), static_cast(h), static_cast(nmips), 1, static_cast(p->i3), { 1, 0 }, (p->i2 & GDRAW_MAKETEXTURE_FLAGS_updatable) ? D3D1X_(USAGE_DEFAULT) : D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_SHADER_RESOURCE), 0, 0 }; @@ -541,7 +541,7 @@ static rrbool RADLINK gdraw_UpdateTextureBegin(GDrawTexture *t, void *unique_id, static void RADLINK gdraw_UpdateTextureRect(GDrawTexture *t, void * /*unique_id*/, S32 x, S32 y, S32 stride, S32 w, S32 h, U8 *samples, gdraw_texture_format /*format*/) { GDrawHandle *s = (GDrawHandle *) t; - D3D1X_(BOX) box = { x, y, 0, x+w, y+h, 1 }; + D3D1X_(BOX) box = { static_cast(x), static_cast(y), 0U, static_cast(x + w), static_cast(y + h), 1U }; gdraw->d3d_context->UpdateSubresource(s->handle.tex.d3d, 0, &box, samples, stride, 0); } @@ -586,8 +586,8 @@ static void RADLINK gdraw_SetAntialiasTexture(S32 width, U8 *rgba) safe_release(gdraw->aa_tex_view); safe_release(gdraw->aa_tex); - D3D1X_(TEXTURE2D_DESC) desc = { width, 1, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, { 1, 0 }, D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_SHADER_RESOURCE), 0, 0 }; - D3D1X_(SUBRESOURCE_DATA) data = { rgba, width*4, 0 }; + D3D1X_(TEXTURE2D_DESC) desc = { static_cast(width), 1U, 1U, 1U, DXGI_FORMAT_R8G8B8A8_UNORM, { 1, 0 }, D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_SHADER_RESOURCE), 0U, 0U }; + D3D1X_(SUBRESOURCE_DATA) data = { rgba, static_cast(width) * 4U, 0U }; hr = gdraw->d3d_device->CreateTexture2D(&desc, &data, &gdraw->aa_tex); if (FAILED(hr)) { @@ -646,10 +646,10 @@ static GDrawVertexBuffer * RADLINK gdraw_MakeVertexBufferEnd(GDraw_MakeVertexBuf GDrawHandle *vb = (GDrawHandle *) p->p0; HRESULT hr; - D3D1X_(BUFFER_DESC) vbdesc = { p->vertex_data_length, D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_VERTEX_BUFFER), 0, 0 }; + D3D1X_(BUFFER_DESC) vbdesc = { static_cast(p->vertex_data_length), D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_VERTEX_BUFFER), 0U, 0U }; D3D1X_(SUBRESOURCE_DATA) vbdata = { p->vertex_data, 0, 0 }; - D3D1X_(BUFFER_DESC) ibdesc = { p->index_data_length, D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_INDEX_BUFFER), 0, 0 }; + D3D1X_(BUFFER_DESC) ibdesc = { static_cast(p->index_data_length), D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_INDEX_BUFFER), 0U, 0U }; D3D1X_(SUBRESOURCE_DATA) ibdata = { p->index_data, 0, 0 }; hr = gdraw->d3d_device->CreateBuffer(&vbdesc, &vbdata, &vb->handle.vbuf.verts); @@ -722,8 +722,8 @@ static GDrawHandle *get_color_rendertarget(GDrawStats *stats) return t; } - D3D1X_(TEXTURE2D_DESC) desc = { gdraw->frametex_width, gdraw->frametex_height, 1, 1, DXGI_FORMAT_R8G8B8A8_UNORM, { 1, 0 }, - D3D1X_(USAGE_DEFAULT), D3D1X_(BIND_SHADER_RESOURCE) | D3D1X_(BIND_RENDER_TARGET), 0, 0 }; + D3D1X_(TEXTURE2D_DESC) desc = { static_cast(gdraw->frametex_width), static_cast(gdraw->frametex_height), 1U, 1U, DXGI_FORMAT_R8G8B8A8_UNORM, { 1, 0 }, + D3D1X_(USAGE_DEFAULT), D3D1X_(BIND_SHADER_RESOURCE) | D3D1X_(BIND_RENDER_TARGET), 0U, 0U }; t->handle.tex.d3d = NULL; t->handle.tex.d3d_view = NULL; @@ -765,8 +765,8 @@ static ID3D1X(DepthStencilView) *get_rendertarget_depthbuffer(GDrawStats *stats) char *failed_call; assert(!gdraw->rt_depth_buffer); - D3D1X_(TEXTURE2D_DESC) desc = { gdraw->frametex_width, gdraw->frametex_height, 1, 1, DXGI_FORMAT_D24_UNORM_S8_UINT, { 1, 0 }, - D3D1X_(USAGE_DEFAULT), D3D1X_(BIND_DEPTH_STENCIL), 0, 0 }; + D3D1X_(TEXTURE2D_DESC) desc = { static_cast(gdraw->frametex_width), static_cast(gdraw->frametex_height), 1U, 1U, DXGI_FORMAT_D24_UNORM_S8_UINT, { 1, 0 }, + D3D1X_(USAGE_DEFAULT), D3D1X_(BIND_DEPTH_STENCIL), 0U, 0U }; HRESULT hr = gdraw->d3d_device->CreateTexture2D(&desc, NULL, &gdraw->rt_depth_buffer); failed_call = "CreateTexture2D"; @@ -2399,8 +2399,8 @@ GDrawTexture * RADLINK gdraw_D3D1X_(MakeTextureFromResource)(U8 *resource_file, mipmaps = texture->mipmaps; blk = 1; - D3D1X_(TEXTURE2D_DESC) desc = { width, height, mipmaps, 1, DXGI_FORMAT_UNKNOWN, { 1, 0 }, - D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_SHADER_RESOURCE), 0, 0 }; + D3D1X_(TEXTURE2D_DESC) desc = { static_cast(width), static_cast(height), static_cast(mipmaps), 1U, DXGI_FORMAT_UNKNOWN, { 1, 0 }, + D3D1X_(USAGE_IMMUTABLE), D3D1X_(BIND_SHADER_RESOURCE), 0U, 0U }; switch (texture->format) { case IFT_FORMAT_rgba_8888 : size= 4; d3dfmt = DXGI_FORMAT_R8G8B8A8_UNORM; break; diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.cpp b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp new file mode 100644 index 00000000..fc763394 --- /dev/null +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.cpp @@ -0,0 +1,260 @@ +#include "stdafx.h" + +#ifdef _WINDOWS64 + +#include "KeyboardMouseInput.h" + +KeyboardMouseInput KMInput; + +KeyboardMouseInput::KeyboardMouseInput() + : m_mouseDeltaXAccum(0.0f) + , m_mouseDeltaYAccum(0.0f) + , m_scrollDeltaAccum(0) + , m_captured(false) + , m_hWnd(NULL) + , m_initialized(false) + , m_mouseX(0) + , m_mouseY(0) +{ + memset(m_keyState, 0, sizeof(m_keyState)); + memset(m_keyStatePrev, 0, sizeof(m_keyStatePrev)); + memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + memset(m_mouseButtonsPrev, 0, sizeof(m_mouseButtonsPrev)); + memset(m_keyPressedAccum, 0, sizeof(m_keyPressedAccum)); + memset(m_mousePressedAccum, 0, sizeof(m_mousePressedAccum)); + memset(m_mouseReleasedAccum, 0, sizeof(m_mouseReleasedAccum)); +} + +KeyboardMouseInput::~KeyboardMouseInput() +{ + if (m_captured) + { + SetCapture(false); + } +} + +void KeyboardMouseInput::Init(HWND hWnd) +{ + m_hWnd = hWnd; + m_initialized = true; + + // Register for raw mouse input + RAWINPUTDEVICE rid; + rid.usUsagePage = HID_USAGE_PAGE_GENERIC; + rid.usUsage = HID_USAGE_GENERIC_MOUSE; + rid.dwFlags = 0; + rid.hwndTarget = hWnd; + RegisterRawInputDevices(&rid, 1, sizeof(rid)); +} + +void KeyboardMouseInput::Tick() +{ + // Keep cursor pinned to center while captured + if (m_captured) + CenterCursor(); +} + +void KeyboardMouseInput::EndFrame() +{ + // Advance previous state for next frame's edge detection. + // Must be called AFTER all per-frame consumers have read IsKeyPressed/Released etc. + memcpy(m_keyStatePrev, m_keyState, sizeof(m_keyState)); + memcpy(m_mouseButtonsPrev, m_mouseButtons, sizeof(m_mouseButtons)); +} + +void KeyboardMouseInput::OnKeyDown(WPARAM vk) +{ + if (vk < 256) + { + if (!m_keyState[vk]) m_keyPressedAccum[vk] = true; + m_keyState[vk] = true; + } +} + +void KeyboardMouseInput::OnKeyUp(WPARAM vk) +{ + if (vk < 256) + { + m_keyState[vk] = false; + } +} + +void KeyboardMouseInput::OnRawMouseInput(LPARAM lParam) +{ + if (!m_captured) return; + + UINT dwSize = 0; + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)); + + BYTE* lpb = (BYTE*)alloca(dwSize); + if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) + return; + + RAWINPUT* raw = (RAWINPUT*)lpb; + if (raw->header.dwType == RIM_TYPEMOUSE) + { + if (raw->data.mouse.usFlags == MOUSE_MOVE_RELATIVE) + { + m_mouseDeltaXAccum += (float)raw->data.mouse.lLastX; + m_mouseDeltaYAccum += (float)raw->data.mouse.lLastY; + } + } +} + +void KeyboardMouseInput::OnMouseButton(int button, bool down) +{ + if (button >= 0 && button < 3) + { + if (down && !m_mouseButtons[button]) m_mousePressedAccum[button] = true; + if (!down && m_mouseButtons[button]) m_mouseReleasedAccum[button] = true; + m_mouseButtons[button] = down; + } +} + +void KeyboardMouseInput::OnMouseWheel(int delta) +{ + m_scrollDeltaAccum += delta; +} + +void KeyboardMouseInput::OnMouseMove(int x, int y) +{ + m_mouseX = x; + m_mouseY = y; +} + +int KeyboardMouseInput::GetMouseX() const { return m_mouseX; } +int KeyboardMouseInput::GetMouseY() const { return m_mouseY; } +HWND KeyboardMouseInput::GetHWnd() const { return m_hWnd; } + +void KeyboardMouseInput::ClearAllState() +{ + memset(m_keyState, 0, sizeof(m_keyState)); + memset(m_mouseButtons, 0, sizeof(m_mouseButtons)); + memset(m_keyPressedAccum, 0, sizeof(m_keyPressedAccum)); + memset(m_mousePressedAccum, 0, sizeof(m_mousePressedAccum)); + memset(m_mouseReleasedAccum, 0, sizeof(m_mouseReleasedAccum)); + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; + m_scrollDeltaAccum = 0; +} + +// Per-frame key queries +bool KeyboardMouseInput::IsKeyDown(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return m_keyState[vk]; +} + +bool KeyboardMouseInput::IsKeyPressed(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return m_keyState[vk] && !m_keyStatePrev[vk]; +} + +bool KeyboardMouseInput::IsKeyReleased(int vk) const +{ + if (vk < 0 || vk >= 256) return false; + return !m_keyState[vk] && m_keyStatePrev[vk]; +} + +// Per-frame mouse button queries +bool KeyboardMouseInput::IsMouseDown(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return m_mouseButtons[btn]; +} + +bool KeyboardMouseInput::IsMousePressed(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return m_mouseButtons[btn] && !m_mouseButtonsPrev[btn]; +} + +bool KeyboardMouseInput::IsMouseReleased(int btn) const +{ + if (btn < 0 || btn >= 3) return false; + return !m_mouseButtons[btn] && m_mouseButtonsPrev[btn]; +} + +// Game-tick consume methods +bool KeyboardMouseInput::ConsumeKeyPress(int vk) +{ + if (vk < 0 || vk >= 256) return false; + bool pressed = m_keyPressedAccum[vk]; + m_keyPressedAccum[vk] = false; + return pressed; +} + +bool KeyboardMouseInput::ConsumeMousePress(int btn) +{ + if (btn < 0 || btn >= 3) return false; + bool pressed = m_mousePressedAccum[btn]; + m_mousePressedAccum[btn] = false; + return pressed; +} + +bool KeyboardMouseInput::ConsumeMouseRelease(int btn) +{ + if (btn < 0 || btn >= 3) return false; + bool released = m_mouseReleasedAccum[btn]; + m_mouseReleasedAccum[btn] = false; + return released; +} + +void KeyboardMouseInput::ConsumeMouseDelta(float &dx, float &dy) +{ + dx = m_mouseDeltaXAccum; + dy = m_mouseDeltaYAccum; + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; +} + +int KeyboardMouseInput::ConsumeScrollDelta() +{ + int delta = m_scrollDeltaAccum; + m_scrollDeltaAccum = 0; + return delta; +} + +// Mouse capture +void KeyboardMouseInput::SetCapture(bool capture) +{ + if (capture == m_captured) return; + m_captured = capture; + + if (capture) + { + ShowCursor(FALSE); + RECT rect; + GetClientRect(m_hWnd, &rect); + POINT topLeft = { rect.left, rect.top }; + POINT bottomRight = { rect.right, rect.bottom }; + ClientToScreen(m_hWnd, &topLeft); + ClientToScreen(m_hWnd, &bottomRight); + RECT screenRect = { topLeft.x, topLeft.y, bottomRight.x, bottomRight.y }; + ClipCursor(&screenRect); + CenterCursor(); + + // Flush accumulated deltas so the snap-to-center doesn't cause a jump + m_mouseDeltaXAccum = 0.0f; + m_mouseDeltaYAccum = 0.0f; + } + else + { + ShowCursor(TRUE); + ClipCursor(NULL); + } +} + +bool KeyboardMouseInput::IsCaptured() const { return m_captured; } + +void KeyboardMouseInput::CenterCursor() +{ + RECT rect; + GetClientRect(m_hWnd, &rect); + POINT center = { (rect.left + rect.right) / 2, (rect.top + rect.bottom) / 2 }; + ClientToScreen(m_hWnd, ¢er); + SetCursorPos(center.x, center.y); +} + +#endif // _WINDOWS64 diff --git a/Minecraft.Client/Windows64/KeyboardMouseInput.h b/Minecraft.Client/Windows64/KeyboardMouseInput.h new file mode 100644 index 00000000..a09843f9 --- /dev/null +++ b/Minecraft.Client/Windows64/KeyboardMouseInput.h @@ -0,0 +1,91 @@ +#pragma once + +#ifdef _WINDOWS64 + +#include + +// HID usage page and usage for raw input registration +#ifndef HID_USAGE_PAGE_GENERIC +#define HID_USAGE_PAGE_GENERIC ((USHORT)0x01) +#endif +#ifndef HID_USAGE_GENERIC_MOUSE +#define HID_USAGE_GENERIC_MOUSE ((USHORT)0x02) +#endif + +class KeyboardMouseInput +{ +public: + KeyboardMouseInput(); + ~KeyboardMouseInput(); + + void Init(HWND hWnd); + void Tick(); + void EndFrame(); + + // Called from WndProc + void OnKeyDown(WPARAM vk); + void OnKeyUp(WPARAM vk); + void OnRawMouseInput(LPARAM lParam); + void OnMouseButton(int button, bool down); + void OnMouseWheel(int delta); + void ClearAllState(); + + // Per-frame edge detection (for UI / per-frame logic like Alt toggle) + bool IsKeyDown(int vk) const; + bool IsKeyPressed(int vk) const; + bool IsKeyReleased(int vk) const; + bool IsMouseDown(int btn) const; + bool IsMousePressed(int btn) const; + bool IsMouseReleased(int btn) const; + + // Game-tick consume methods: accumulate across frames, clear on read. + // Use these from code that runs at game tick rate (20Hz). + bool ConsumeKeyPress(int vk); + bool ConsumeMousePress(int btn); + bool ConsumeMouseRelease(int btn); + void ConsumeMouseDelta(float &dx, float &dy); + int ConsumeScrollDelta(); + + // Absolute cursor position (client-area coordinates, for GUI when not captured) + void OnMouseMove(int x, int y); + int GetMouseX() const; + int GetMouseY() const; + HWND GetHWnd() const; + + // Mouse capture for FPS look + void SetCapture(bool capture); + bool IsCaptured() const; + +private: + void CenterCursor(); + + // Per-frame double-buffered state (for IsKeyPressed/Released per-frame edge detection) + bool m_keyState[256]; + bool m_keyStatePrev[256]; + bool m_mouseButtons[3]; + bool m_mouseButtonsPrev[3]; + + // Sticky press accumulators (persist until consumed by game tick) + bool m_keyPressedAccum[256]; + bool m_mousePressedAccum[3]; + bool m_mouseReleasedAccum[3]; + + // Mouse delta accumulators (persist until consumed by game tick) + float m_mouseDeltaXAccum; + float m_mouseDeltaYAccum; + + // Scroll accumulator (persists until consumed by game tick) + int m_scrollDeltaAccum; + + bool m_captured; + HWND m_hWnd; + bool m_initialized; + + // Absolute cursor position in client coordinates + int m_mouseX; + int m_mouseY; +}; + +extern KeyboardMouseInput KMInput; + +#endif // _WINDOWS64 diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index d2251e4e..931e7f17 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -39,6 +39,10 @@ #include "Xbox/resource.h" +#ifdef _MSC_VER +#pragma comment(lib, "legacy_stdio_definitions.lib") +#endif + HINSTANCE hMyInst; LRESULT CALLBACK DlgProc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam); char chGlobalText[256]; @@ -79,6 +83,10 @@ BOOL g_bWidescreen = TRUE; int g_iScreenWidth = 1920; int g_iScreenHeight = 1080; +// Fullscreen toggle state +static bool g_isFullscreen = false; +static WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) }; + void DefineActions(void) { // The app needs to define the actions required, and the possible mappings for these @@ -177,11 +185,11 @@ void DefineActions(void) InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_CRAFTING, _360_JOY_BUTTON_X); InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_RENDER_THIRD_PERSON, _360_JOY_BUTTON_RTHUMB); InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_GAME_INFO, _360_JOY_BUTTON_BACK); - + InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_DPAD_LEFT, _360_JOY_BUTTON_DPAD_LEFT); InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_DPAD_RIGHT, _360_JOY_BUTTON_DPAD_RIGHT); InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_DPAD_UP, _360_JOY_BUTTON_DPAD_UP); - InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_DPAD_DOWN, _360_JOY_BUTTON_DPAD_DOWN); + InputManager.SetGameJoypadMaps(MAP_STYLE_1,MINECRAFT_ACTION_DPAD_DOWN, _360_JOY_BUTTON_DPAD_DOWN); InputManager.SetGameJoypadMaps(MAP_STYLE_2,ACTION_MENU_A, _360_JOY_BUTTON_A); InputManager.SetGameJoypadMaps(MAP_STYLE_2,ACTION_MENU_B, _360_JOY_BUTTON_B); @@ -226,11 +234,11 @@ void DefineActions(void) InputManager.SetGameJoypadMaps(MAP_STYLE_2,ACTION_MENU_OTHER_STICK_DOWN, _360_JOY_BUTTON_RSTICK_DOWN); InputManager.SetGameJoypadMaps(MAP_STYLE_2,ACTION_MENU_OTHER_STICK_LEFT, _360_JOY_BUTTON_RSTICK_LEFT); InputManager.SetGameJoypadMaps(MAP_STYLE_2,ACTION_MENU_OTHER_STICK_RIGHT, _360_JOY_BUTTON_RSTICK_RIGHT); - + InputManager.SetGameJoypadMaps(MAP_STYLE_2,MINECRAFT_ACTION_DPAD_LEFT, _360_JOY_BUTTON_DPAD_LEFT); InputManager.SetGameJoypadMaps(MAP_STYLE_2,MINECRAFT_ACTION_DPAD_RIGHT, _360_JOY_BUTTON_DPAD_RIGHT); InputManager.SetGameJoypadMaps(MAP_STYLE_2,MINECRAFT_ACTION_DPAD_UP, _360_JOY_BUTTON_DPAD_UP); - InputManager.SetGameJoypadMaps(MAP_STYLE_2,MINECRAFT_ACTION_DPAD_DOWN, _360_JOY_BUTTON_DPAD_DOWN); + InputManager.SetGameJoypadMaps(MAP_STYLE_2,MINECRAFT_ACTION_DPAD_DOWN, _360_JOY_BUTTON_DPAD_DOWN); } #if 0 @@ -257,7 +265,7 @@ HRESULT InitD3D( IDirect3DDevice9 **ppDevice, pd3dPP->EnableAutoDepthStencil = TRUE; pd3dPP->AutoDepthStencilFormat = D3DFMT_D24S8; pd3dPP->SwapEffect = D3DSWAPEFFECT_DISCARD; - pd3dPP->PresentationInterval = D3DPRESENT_INTERVAL_ONE; + pd3dPP->PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //pd3dPP->Flags = D3DPRESENTFLAG_NO_LETTERBOX; //ERR[D3D]: Can't set D3DPRESENTFLAG_NO_LETTERBOX when wide-screen is enabled // in the launcher/dashboard. @@ -339,6 +347,75 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_DESTROY: PostQuitMessage(0); break; + + // Keyboard/Mouse input handling + case WM_KEYDOWN: + if (!(lParam & 0x40000000)) // ignore auto-repeat + KMInput.OnKeyDown(wParam); + break; + case WM_KEYUP: + KMInput.OnKeyUp(wParam); + break; + case WM_SYSKEYDOWN: + if (wParam == VK_MENU) // Alt key + { + if (!(lParam & 0x40000000)) + KMInput.OnKeyDown(wParam); + return 0; // prevent default Alt behavior + } + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_SYSKEYUP: + if (wParam == VK_MENU) + { + KMInput.OnKeyUp(wParam); + return 0; + } + return DefWindowProc(hWnd, message, wParam, lParam); + case WM_INPUT: + KMInput.OnRawMouseInput(lParam); + break; + case WM_LBUTTONDOWN: + KMInput.OnMouseButton(0, true); + break; + case WM_LBUTTONUP: + KMInput.OnMouseButton(0, false); + break; + case WM_RBUTTONDOWN: + KMInput.OnMouseButton(1, true); + break; + case WM_RBUTTONUP: + KMInput.OnMouseButton(1, false); + break; + case WM_MBUTTONDOWN: + KMInput.OnMouseButton(2, true); + break; + case WM_MBUTTONUP: + KMInput.OnMouseButton(2, false); + break; + case WM_MOUSEWHEEL: + KMInput.OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; + case WM_MOUSEMOVE: + KMInput.OnMouseMove(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; + case WM_ACTIVATE: + if (LOWORD(wParam) == WA_INACTIVE) + KMInput.SetCapture(false); + break; + case WM_KILLFOCUS: + KMInput.SetCapture(false); + KMInput.ClearAllState(); + break; + + case WM_SETCURSOR: + // Hide the OS cursor when an Iggy/Flash menu is displayed (it has its own Flash cursor) + if (LOWORD(lParam) == HTCLIENT && !KMInput.IsCaptured() && ui.GetMenuDisplayed(0)) + { + SetCursor(NULL); + return TRUE; + } + return DefWindowProc(hWnd, message, wParam, lParam); + default: return DefWindowProc(hWnd, message, wParam, lParam); } @@ -529,6 +606,7 @@ app.DebugPrintf("width: %d, height: %d\n", width, height); // Create a depth stencil buffer D3D11_TEXTURE2D_DESC descDepth; + ZeroMemory(&descDepth, sizeof(descDepth)); descDepth.Width = width; descDepth.Height = height; @@ -544,6 +622,7 @@ app.DebugPrintf("width: %d, height: %d\n", width, height); hr = g_pd3dDevice->CreateTexture2D(&descDepth, NULL, &g_pDepthStencilBuffer); D3D11_DEPTH_STENCIL_VIEW_DESC descDSView; + ZeroMemory(&descDSView, sizeof(descDSView)); descDSView.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; descDSView.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; descDSView.Texture2D.MipSlice = 0; @@ -584,6 +663,36 @@ void Render() g_pSwapChain->Present( 0, 0 ); } +//-------------------------------------------------------------------------------------- +// Toggle borderless fullscreen +//-------------------------------------------------------------------------------------- +void ToggleFullscreen() +{ + DWORD dwStyle = GetWindowLong(g_hWnd, GWL_STYLE); + if (!g_isFullscreen) + { + MONITORINFO mi = { sizeof(mi) }; + if (GetWindowPlacement(g_hWnd, &g_wpPrev) && + GetMonitorInfo(MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY), &mi)) + { + SetWindowLong(g_hWnd, GWL_STYLE, dwStyle & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(g_hWnd, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, + mi.rcMonitor.right - mi.rcMonitor.left, + mi.rcMonitor.bottom - mi.rcMonitor.top, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(g_hWnd, GWL_STYLE, dwStyle | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(g_hWnd, &g_wpPrev); + SetWindowPos(g_hWnd, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + g_isFullscreen = !g_isFullscreen; +} + //-------------------------------------------------------------------------------------- // Clean up the objects we've created //-------------------------------------------------------------------------------------- @@ -607,6 +716,17 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); + // Declare DPI awareness so GetSystemMetrics returns physical pixels + SetProcessDPIAware(); + g_iScreenWidth = GetSystemMetrics(SM_CXSCREEN); + g_iScreenHeight = GetSystemMetrics(SM_CYSCREEN); + + { + char buf[128]; + sprintf(buf, "Screen resolution: %dx%d\n", g_iScreenWidth, g_iScreenHeight); + OutputDebugStringA(buf); + } + if(lpCmdLine) { if(lpCmdLine[0] == '1') @@ -704,7 +824,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, app.loadMediaArchive(); RenderManager.Initialise(g_pd3dDevice, g_pSwapChain); - + app.loadStringTable(); ui.init(g_pd3dDevice,g_pImmediateContext,g_pRenderTargetView,g_pDepthStencilView,g_iScreenWidth,g_iScreenHeight); @@ -715,6 +835,9 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Set the number of possible joypad layouts that the user can switch between, and the number of actions InputManager.Initialise(1,3,MINECRAFT_ACTION_MAX, ACTION_MAX_MENU); + // Initialize keyboard/mouse input + KMInput.Init(g_hWnd); + // Set the default joypad action mappings for Minecraft DefineActions(); InputManager.SetJoypadMapVal(0,0); @@ -940,6 +1063,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, app.UpdateTime(); PIXBeginNamedEvent(0,"Input manager tick"); InputManager.Tick(); + KMInput.Tick(); PIXEndNamedEvent(); PIXBeginNamedEvent(0,"Profile manager tick"); // ProfileManager.Tick(); @@ -971,6 +1095,7 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Render game graphics. if(app.GetGameStarted()) { + pMinecraft->applyFrameMouseLook(); // Per-frame mouse look (before ticks + render) pMinecraft->run_middle(); app.SetAppPaused( g_NetworkManager.IsLocalGame() && g_NetworkManager.GetPlayerCount() == 1 && ui.IsPauseMenuDisplayed(ProfileManager.GetPrimaryPad()) ); } @@ -1067,8 +1192,35 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, RenderManager.Present(); ui.CheckMenuDisplayed(); + + // Update mouse capture: capture when in-game and no menu is open + { + static bool altToggleSuppressCapture = false; + bool shouldCapture = app.GetGameStarted() && !ui.GetMenuDisplayed(0) && pMinecraft->screen == NULL; + // Left Alt key toggles capture on/off for debugging + if (KMInput.IsKeyPressed(VK_MENU)) + { + if (KMInput.IsCaptured()) { KMInput.SetCapture(false); altToggleSuppressCapture = true; } + else if (shouldCapture) { KMInput.SetCapture(true); altToggleSuppressCapture = false; } + } + else if (!shouldCapture) + { + if (KMInput.IsCaptured()) KMInput.SetCapture(false); + altToggleSuppressCapture = false; + } + else if (shouldCapture && !KMInput.IsCaptured() && GetFocus() == g_hWnd && !altToggleSuppressCapture) + { + KMInput.SetCapture(true); + } + } + + // F11 toggles fullscreen + if (KMInput.IsKeyPressed(VK_F11)) + { + ToggleFullscreen(); + } + #if 0 - PIXBeginNamedEvent(0,"Profile load check"); // has the game defined profile data been changed (by a profile load) if(app.uiGameDefinedDataChangedBitmask!=0) { @@ -1158,6 +1310,8 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, // Fix for #7318 - Title crashes after short soak in the leaderboards menu // A memory leak was caused because the icon renderer kept creating new Vec3's because the pool wasn't reset Vec3::resetPool(); + + KMInput.EndFrame(); } // Free resources, unregister custom classes, and exit. diff --git a/Minecraft.Client/Xbox/MinecraftWindows.rc b/Minecraft.Client/Xbox/MinecraftWindows.rc index 69ce5334..e4fd4960 100644 Binary files a/Minecraft.Client/Xbox/MinecraftWindows.rc and b/Minecraft.Client/Xbox/MinecraftWindows.rc differ diff --git a/Minecraft.Client/Xbox/Resource.h b/Minecraft.Client/Xbox/Resource.h index 55435726..c5c2988f 100644 Binary files a/Minecraft.Client/Xbox/Resource.h and b/Minecraft.Client/Xbox/Resource.h differ diff --git a/Minecraft.Client/stdafx.h b/Minecraft.Client/stdafx.h index 1a115c94..25d93572 100644 --- a/Minecraft.Client/stdafx.h +++ b/Minecraft.Client/stdafx.h @@ -66,6 +66,7 @@ typedef unsigned __int64 __uint64; #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers // Windows Header Files: #include +#include #include #include // TODO: reference additional headers your program requires here @@ -174,10 +175,12 @@ typedef XUID GameSessionUID; #include "Durango\4JLibs\inc\4J_Render.h" #include "Durango\4JLibs\inc\4J_Storage.h" #elif defined _WINDOWS64 - #include "Windows64\4JLibs\inc\4J_Input.h" + #include + #include "Windows64\4JLibs\inc\4J_Input.h" #include "Windows64\4JLibs\inc\4J_Profile.h" #include "Windows64\4JLibs\inc\4J_Render.h" #include "Windows64\4JLibs\inc\4J_Storage.h" + #include "Windows64\KeyboardMouseInput.h" #elif defined __PSVITA__ #include "PSVita\4JLibs\inc\4J_Input.h" #include "PSVita\4JLibs\inc\4J_Profile.h" diff --git a/Minecraft.Client/stubs.cpp b/Minecraft.Client/stubs.cpp index 0c7e680a..16215ba2 100644 --- a/Minecraft.Client/stubs.cpp +++ b/Minecraft.Client/stubs.cpp @@ -1,5 +1,46 @@ #include "stdafx.h" +#ifdef _WINDOWS64 +#include "Windows64\KeyboardMouseInput.h" + +int Mouse::getX() +{ + return KMInput.GetMouseX(); +} + +int Mouse::getY() +{ + // Return Y in bottom-up coordinates (OpenGL convention, matching original Java LWJGL Mouse) + RECT rect; + GetClientRect(KMInput.GetHWnd(), &rect); + return (rect.bottom - 1) - KMInput.GetMouseY(); +} + +bool Mouse::isButtonDown(int button) +{ + return KMInput.IsMouseDown(button); +} + +bool Keyboard::isKeyDown(int key) +{ + // Map Keyboard constants to Windows virtual key codes + if (key == Keyboard::KEY_LSHIFT) return KMInput.IsKeyDown(VK_LSHIFT); + if (key == Keyboard::KEY_RSHIFT) return KMInput.IsKeyDown(VK_RSHIFT); + if (key == Keyboard::KEY_ESCAPE) return KMInput.IsKeyDown(VK_ESCAPE); + if (key == Keyboard::KEY_RETURN) return KMInput.IsKeyDown(VK_RETURN); + if (key == Keyboard::KEY_BACK) return KMInput.IsKeyDown(VK_BACK); + if (key == Keyboard::KEY_SPACE) return KMInput.IsKeyDown(VK_SPACE); + if (key == Keyboard::KEY_TAB) return KMInput.IsKeyDown(VK_TAB); + if (key == Keyboard::KEY_UP) return KMInput.IsKeyDown(VK_UP); + if (key == Keyboard::KEY_DOWN) return KMInput.IsKeyDown(VK_DOWN); + if (key == Keyboard::KEY_LEFT) return KMInput.IsKeyDown(VK_LEFT); + if (key == Keyboard::KEY_RIGHT) return KMInput.IsKeyDown(VK_RIGHT); + if (key >= Keyboard::KEY_A && key <= Keyboard::KEY_Z) + return KMInput.IsKeyDown('A' + (key - Keyboard::KEY_A)); + return false; +} +#endif + void glReadPixels(int,int, int, int, int, int, ByteBuffer *) { } diff --git a/Minecraft.Client/stubs.h b/Minecraft.Client/stubs.h index f9196e20..cc788867 100644 --- a/Minecraft.Client/stubs.h +++ b/Minecraft.Client/stubs.h @@ -186,7 +186,11 @@ class Keyboard public: static void create() {} static void destroy() {} +#ifdef _WINDOWS64 + static bool isKeyDown(int key); +#else static bool isKeyDown(int) {return false;} +#endif static wstring getKeyName(int) { return L"KEYNAME"; } static void enableRepeatEvents(bool) {} static const int KEY_A = 0; @@ -224,6 +228,8 @@ public: static const int KEY_UP = 32; static const int KEY_DOWN = 33; static const int KEY_TAB = 34; + static const int KEY_LEFT = 35; + static const int KEY_RIGHT = 36; }; class Mouse @@ -231,9 +237,15 @@ class Mouse public: static void create() {} static void destroy() {} +#ifdef _WINDOWS64 + static int getX(); + static int getY(); + static bool isButtonDown(int button); +#else static int getX() { return 0; } static int getY() { return 0; } static bool isButtonDown(int) { return false; } +#endif }; class Display diff --git a/README.md b/README.md index 626e16b7..279f7051 100644 Binary files a/README.md and b/README.md differ diff --git a/img.png b/img.png new file mode 100644 index 00000000..4f958dc4 Binary files /dev/null and b/img.png differ