diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d98d228..7ce049e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.24) -project(MinecraftConsoles LANGUAGES C CXX RC ASM_MASM) +project(LCE-Revelations LANGUAGES C CXX RC ASM_MASM) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/Minecraft.Client/CMakeLists.txt b/Minecraft.Client/CMakeLists.txt index ff75cea1..ebf9e9e9 100644 --- a/Minecraft.Client/CMakeLists.txt +++ b/Minecraft.Client/CMakeLists.txt @@ -128,8 +128,6 @@ set(ASSET_FOLDER_PAIRS "${CMAKE_CURRENT_SOURCE_DIR}/music" "music" "${CMAKE_CURRENT_SOURCE_DIR}/Common/Media" "Common/Media" "${CMAKE_CURRENT_SOURCE_DIR}/Common/res" "Common/res" - "${CMAKE_CURRENT_SOURCE_DIR}/Common/Trial" "Common/Trial" - "${CMAKE_CURRENT_SOURCE_DIR}/Common/Tutorial" "Common/Tutorial" "${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}Media" "${PLATFORM_NAME}Media" ) setup_asset_folder_copy(Minecraft.Client "${ASSET_FOLDER_PAIRS}") diff --git a/Minecraft.Client/ChatScreen.cpp b/Minecraft.Client/ChatScreen.cpp index feb01d5d..32f824ea 100644 --- a/Minecraft.Client/ChatScreen.cpp +++ b/Minecraft.Client/ChatScreen.cpp @@ -12,6 +12,7 @@ const wstring ChatScreen::allowedChars = SharedConstants::acceptableLetters; vector ChatScreen::s_chatHistory; int ChatScreen::s_historyIndex = -1; wstring ChatScreen::s_historyDraft; +int ChatScreen::s_chatIndex = 0; bool ChatScreen::isAllowedChatChar(wchar_t c) { @@ -28,6 +29,8 @@ ChatScreen::ChatScreen() frame = 0; cursorIndex = 0; s_historyIndex = -1; + + ChatScreen::s_chatIndex = 0; } void ChatScreen::init() @@ -89,6 +92,20 @@ void ChatScreen::handleHistoryDown() applyHistoryMessage(); } +int ChatScreen::getChatIndex() +{ + return ChatScreen::s_chatIndex; +} + +void ChatScreen::correctChatIndex(int newChatIndex) { + ChatScreen::s_chatIndex = newChatIndex; +} + +void ChatScreen::setWheelValue(int wheel) { + ChatScreen::s_chatIndex += wheel; + if (ChatScreen::s_chatIndex < 0) ChatScreen::s_chatIndex = 0; +} + void ChatScreen::keyPressed(wchar_t ch, int eventKey) { if (eventKey == Keyboard::KEY_ESCAPE) @@ -140,7 +157,7 @@ void ChatScreen::keyPressed(wchar_t ch, int eventKey) cursorIndex--; return; } - if (isAllowedChatChar(ch) && static_cast(message.length()) < SharedConstants::maxChatLength) + if (isAllowedChatChar(ch) && static_cast(message.length()) < SharedConstants::maxVisibleLength) { message.insert(cursorIndex, 1, ch); cursorIndex++; diff --git a/Minecraft.Client/ChatScreen.h b/Minecraft.Client/ChatScreen.h index c4e37a93..70d65e8c 100644 --- a/Minecraft.Client/ChatScreen.h +++ b/Minecraft.Client/ChatScreen.h @@ -16,6 +16,7 @@ private: static std::vector s_chatHistory; static int s_historyIndex; static wstring s_historyDraft; + static int s_chatIndex; static const wstring allowedChars; static bool isAllowedChatChar(wchar_t c); @@ -28,6 +29,9 @@ public: virtual void handleHistoryUp(); virtual void handleHistoryDown(); + static int getChatIndex(); + static void correctChatIndex(int newChatIndex); + static void setWheelValue(int wheel); protected: void keyPressed(wchar_t ch, int eventKey); public: diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index b89567c0..e996c6fb 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -66,6 +66,7 @@ #include "../Minecraft.World/DurangoStats.h" #include "../Minecraft.World/GenericStats.h" #endif +#include namespace { @@ -1607,17 +1608,35 @@ void ClientConnection::handleChat(shared_ptr packet) bool replaceEntitySource = false; bool replaceItem = false; + static std::wregex IDS_Pattern(LR"(\{\*IDS_(\d+)\*\})"); //maybe theres a better way to do translateable IDS + + int stringArgsSize = packet->m_stringArgs.size(); + wstring playerDisplayName = L""; wstring sourceDisplayName = L""; // On platforms other than Xbox One this just sets display name to gamertag - if (packet->m_stringArgs.size() >= 1) playerDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[0]); - if (packet->m_stringArgs.size() >= 2) sourceDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[1]); + if (stringArgsSize >= 1) playerDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[0]); + if (stringArgsSize >= 2) sourceDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[1]); switch(packet->m_messageType) { case ChatPacket::e_ChatCustom: - message = (packet->m_stringArgs.size() >= 1) ? packet->m_stringArgs[0] : L""; + case ChatPacket::e_ChatActionBar: + if (stringArgsSize >= 1) { + message = packet->m_stringArgs[0]; + + std::wsmatch match; + while (std::regex_search(message, match, IDS_Pattern)) { + message = replaceAll(message, match[0], app.GetString(std::stoi(match[1].str()))); + } + + message = app.EscapeHTMLString(message); //do this to enforce escaped string + message = app.FormatChatMessage(message); //this needs to be last cause it converts colors to html colors that would have been escaped + } else { + message = L"empty message"; + } + displayOnGui = (packet->m_messageType == ChatPacket::e_ChatCustom); break; case ChatPacket::e_ChatBedOccupied: message = app.GetString(IDS_TILE_BED_OCCUPIED); @@ -1967,7 +1986,7 @@ void ClientConnection::handleChat(shared_ptr packet) if(replacePlayer) { - message = replaceAll(message,L"{*PLAYER*}",playerDisplayName); + message = replaceAll(message,L"{*PLAYER*}", playerDisplayName); } if(replaceEntitySource) @@ -2002,7 +2021,9 @@ void ClientConnection::handleChat(shared_ptr packet) // flag that a message is a death message bool bIsDeathMessage = (packet->m_messageType>=ChatPacket::e_ChatDeathInFire) && (packet->m_messageType<=ChatPacket::e_ChatDeathIndirectMagicItem); - if( displayOnGui ) minecraft->gui->addMessage(message,m_userIndex, bIsDeathMessage); + if( displayOnGui ) minecraft->gui->addMessage(message, m_userIndex, bIsDeathMessage); + + if (!displayOnGui && !message.empty()) minecraft->gui->setActionBarMessage(message); } void ClientConnection::handleAnimate(shared_ptr packet) diff --git a/Minecraft.Client/Common/Audio/SoundNames.cpp b/Minecraft.Client/Common/Audio/SoundNames.cpp index e0564f27..3ca33c32 100644 --- a/Minecraft.Client/Common/Audio/SoundNames.cpp +++ b/Minecraft.Client/Common/Audio/SoundNames.cpp @@ -270,6 +270,8 @@ const WCHAR *ConsoleSoundEngine::wchSoundNames[eSoundType_MAX]= L"item.armor.equip_generic4", L"item.armor.equip_generic5", L"item.armor.equip_generic6" + + L"damage.critical", //eSoundType_DAMAGE_CRITICAL, }; diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index a4c340b9..3fd86720 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -6786,6 +6786,87 @@ wstring CMinecraftApp::FormatHTMLString(int iPad, const wstring &desc, int shado return text; } +//found list of html escapes at https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html +wstring CMinecraftApp::EscapeHTMLString(const wstring& desc) +{ + static std::unordered_map replacementMap = { + {L'&', L"&"}, + {L'<', L"<"}, + {L'>', L">"}, + {L'\"', L"""}, + {L'\'', L"'"}, + }; + + wstring finalString = L""; + for (int i = 0; i < desc.size(); i++) { + wchar_t _char = desc[i]; + auto it = replacementMap.find(_char); + + if (it != replacementMap.end()) finalString += it->second; + else finalString += _char; + } + + return finalString; +} + +wstring CMinecraftApp::FormatChatMessage(const wstring& desc, bool applyColor) +{ + static std::wstring_view colorFormatString = L""; + + wstring results = desc; + wchar_t replacements[64]; + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_0), 0xFFFFFFFF); + results = replaceAll(results, L"§0", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_1), 0xFFFFFFFF); + results = replaceAll(results, L"§1", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_2), 0xFFFFFFFF); + results = replaceAll(results, L"§2", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_3), 0xFFFFFFFF); + results = replaceAll(results, L"§3", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_4), 0xFFFFFFFF); + results = replaceAll(results, L"§4", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_5), 0xFFFFFFFF); + results = replaceAll(results, L"§5", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_6), 0xFFFFFFFF); + results = replaceAll(results, L"§6", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_7), 0xFFFFFFFF); + results = replaceAll(results, L"§7", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_8), 0xFFFFFFFF); + results = replaceAll(results, L"§8", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_9), 0xFFFFFFFF); + results = replaceAll(results, L"§9", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_a), 0xFFFFFFFF); + results = replaceAll(results, L"§a", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_b), 0xFFFFFFFF); + results = replaceAll(results, L"§b", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_c), 0xFFFFFFFF); + results = replaceAll(results, L"§c", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_d), 0xFFFFFFFF); + results = replaceAll(results, L"§d", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_e), 0xFFFFFFFF); + results = replaceAll(results, L"§e", replacements); + + swprintf(replacements, 64, (applyColor ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_f), 0xFFFFFFFF); + results = replaceAll(results, L"§f", replacements); + + return results; +} + wstring CMinecraftApp::GetActionReplacement(int iPad, unsigned char ucAction) { unsigned int input = InputManager.GetGameJoypadMaps(InputManager.GetJoypadMapVal(iPad) ,ucAction); diff --git a/Minecraft.Client/Common/Consoles_App.h b/Minecraft.Client/Common/Consoles_App.h index 4d7b13d2..97b61ac6 100644 --- a/Minecraft.Client/Common/Consoles_App.h +++ b/Minecraft.Client/Common/Consoles_App.h @@ -567,7 +567,9 @@ public: int GetHTMLColour(eMinecraftColour colour); int GetHTMLColor(eMinecraftColour colour) { return GetHTMLColour(colour); } int GetHTMLFontSize(EHTMLFontSize size); - wstring FormatHTMLString(int iPad, const wstring &desc, int shadowColour = 0xFFFFFFFF); + wstring FormatHTMLString(int iPad, const wstring& desc, int shadowColour = 0xFFFFFFFF); + wstring EscapeHTMLString(const wstring &desc); + wstring FormatChatMessage(const wstring& desc, bool applyColor = true); wstring GetActionReplacement(int iPad, unsigned char ucAction); wstring GetVKReplacement(unsigned int uiVKey); wstring GetIconReplacement(unsigned int uiIcon); diff --git a/Minecraft.Client/Common/Network/GameNetworkManager.cpp b/Minecraft.Client/Common/Network/GameNetworkManager.cpp index ca13e019..0e83c8b2 100644 --- a/Minecraft.Client/Common/Network/GameNetworkManager.cpp +++ b/Minecraft.Client/Common/Network/GameNetworkManager.cpp @@ -204,6 +204,12 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame ProfileManager.SetDeferredSignoutEnabled(true); #endif + // Clear any stale cancel flag latched by the previous join's progress + // UI teardown, otherwise the next join's first tick insta-closes. + EnterCriticalSection(&bCancelRequestedCS); + g_NetworkManager.m_bCancelRequested = false; + LeaveCriticalSection(&bCancelRequestedCS); + int64_t seed = 0; bool dedicatedNoLocalHostPlayer = false; if (lpParameter != nullptr) diff --git a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp index e777019d..6cb9a328 100644 --- a/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp +++ b/Minecraft.Client/Common/Network/PlatformNetworkManagerStub.cpp @@ -134,7 +134,14 @@ void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer) if (socket != nullptr) { if (m_pIQNet->IsHost()) + { g_NetworkManager.CloseConnection(networkPlayer); + + // Propagate the TCP drop to the game Socket so any orphaned + // PendingConnection at this smallId cleans up before its login + // timer fires and leaks a DisconnectPacket to the reused slot. + socket->close(true); + } } if (m_pIQNet->IsHost()) diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp index f50aab50..245d9b2e 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.cpp @@ -33,6 +33,7 @@ IUIScene_AbstractContainerMenu::IUIScene_AbstractContainerMenu() m_pointerPos.y = 0.0f; m_bPointerDrivenByMouse = false; + m_iLastMouseTickTimeNs = -1; } IUIScene_AbstractContainerMenu::~IUIScene_AbstractContainerMenu() @@ -311,6 +312,21 @@ void IUIScene_AbstractContainerMenu::handleEnchantButton(int slot, int iPad) { void IUIScene_AbstractContainerMenu::onMouseTick() { + // Frame-rate independent cursor input, normalized to a 60Hz reference frame. + const int64_t kRefFrameNs = 1000000000LL / 60; + const int64_t kMinDeltaNs = 1000000LL; + const int64_t kMaxDeltaNs = 100000000LL; + int64_t iNowNs = System::nanoTime(); + float fFrameScale = 1.0f; + if ( m_iLastMouseTickTimeNs > 0 ) + { + int64_t iDeltaNs = iNowNs - m_iLastMouseTickTimeNs; + if ( iDeltaNs < kMinDeltaNs ) iDeltaNs = kMinDeltaNs; + if ( iDeltaNs > kMaxDeltaNs ) iDeltaNs = kMaxDeltaNs; + fFrameScale = static_cast(iDeltaNs) / static_cast(kRefFrameNs); + } + m_iLastMouseTickTimeNs = iNowNs; + Minecraft *pMinecraft = Minecraft::GetInstance(); if( pMinecraft->localgameModes[getPad()] != nullptr) { @@ -467,10 +483,10 @@ void IUIScene_AbstractContainerMenu::onMouseTick() // The SD/splitscreen scenes are approximately 0.6 times the size of the fullscreen on if(!RenderManager.IsHiDef() || app.GetLocalPlayerCount() > 1) fInputScale *= 0.6f; - fInputX *= fInputScale; - fInputY *= fInputScale; + fInputX *= fInputScale * fFrameScale; + fInputY *= fInputScale * fFrameScale; -#ifdef USE_POINTER_ACCEL +#ifdef USE_POINTER_ACCEL m_fPointerAccelX += fInputX / 50.0f; m_fPointerAccelY += fInputY / 50.0f; @@ -1317,36 +1333,8 @@ void IUIScene_AbstractContainerMenu::onMouseTick() vPointerPos.x -= m_fPointerImageOffsetX; vPointerPos.y -= m_fPointerImageOffsetY; - // Update pointer position. - // 4J-PB - do not allow sub pixel positions or we get broken lines in box edges - - // problem here when sensitivity is low - we'll be moving a sub pixel size, so it'll clamp, and we'll never move. In that case, move 1 pixel - if(fInputDirX!=0.0f) - { - if(fInputDirX==1.0f) - { - vPointerPos.x+=0.999999f; - } - else - { - vPointerPos.x-=0.999999f; - } - } - - if(fInputDirY!=0.0f) - { - if(fInputDirY==1.0f) - { - vPointerPos.y+=0.999999f; - } - else - { - vPointerPos.y-=0.999999f; - } - } - - vPointerPos.x = static_cast(floor(vPointerPos.x + 0.5f)); - vPointerPos.y = static_cast(floor(vPointerPos.y + 0.5f)); + // Keep sub-pixel float state so deltas <1px accumulate across frames; the renderer + // truncates to integer pixels when emitting the Iggy mouse event. m_pointerPos = vPointerPos; adjustPointerForSafeZone(); diff --git a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h index 8fb5d5c5..c466dc00 100644 --- a/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h +++ b/Minecraft.Client/Common/UI/IUIScene_AbstractContainerMenu.h @@ -146,6 +146,8 @@ protected: int m_iConsectiveInputTicks; + int64_t m_iLastMouseTickTimeNs; + // Used for detecting quick "taps" in a direction, should jump cursor to next slot. enum ETapState { diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index 1eb09ef0..d1d0cf4d 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "UIController.h" +#include #include "UI.h" #include "UIScene.h" #include "UIControl_Slider.h" @@ -1459,6 +1460,9 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key) } #endif + if (key == 4) ChatScreen::setWheelValue(1); + if (key == 5) ChatScreen::setWheelValue(-1); + if(pressed) app.DebugPrintf("Pressed %d\n",key); if(released) app.DebugPrintf("Released %d\n",key); // Repeat handling diff --git a/Minecraft.Client/Common/UI/UIScene_Credits.cpp b/Minecraft.Client/Common/UI/UIScene_Credits.cpp index a475acc3..f41ca757 100644 --- a/Minecraft.Client/Common/UI/UIScene_Credits.cpp +++ b/Minecraft.Client/Common/UI/UIScene_Credits.cpp @@ -490,18 +490,21 @@ SCreditTextItemDef UIScene_Credits::gs_aCreditDefs[MAX_CREDIT_STRINGS] = #endif {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, - {L"MinecraftConsoles", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eExtraLargeText}, + {L"LCE-Revelations", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eExtraLargeText}, + {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"Project Maintainers", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eLargeText}, - {L"smartcmd", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"codeHusky", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"mattsumi", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"itsRevela", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"Former Maintainers", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eLargeText}, + {L"smartcmd", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"Patoke", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"rtm516", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, - {L"mattsumi", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, - {L"dxf", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, - {L"la", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, - {L"Thank you to our 100+ contributors on GitHub!", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eLargeText}, - {L"github.com/smartcmd/MinecraftConsoles", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"Thank you to our 120+ contributors on GitHub!", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eLargeText}, + {L"github.com/MCLCE/MinecraftConsoles", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, + {L"(formerly smartcmd/MinecraftConsoles)", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eSmallText}, {L"Additional Thanks", NO_TRANSLATED_STRING, NO_TRANSLATED_STRING, eLargeText}, diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.cpp b/Minecraft.Client/Common/UI/UIScene_HUD.cpp index c9b99b52..38b824f4 100644 --- a/Minecraft.Client/Common/UI/UIScene_HUD.cpp +++ b/Minecraft.Client/Common/UI/UIScene_HUD.cpp @@ -9,6 +9,7 @@ #include "../../EnderDragonRenderer.h" #include "../../../Minecraft.World/net.minecraft.world.inventory.h" #include "../../../Minecraft.World/StringHelpers.h" +#include UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer) { @@ -23,8 +24,10 @@ UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UISce for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i) { m_labelChatText[i].init(L""); + IggyValueSetBooleanRS(m_labelChatText[i].getIggyValuePath(), 0, "m_bUseHtmlText", true); } m_labelJukebox.init(L""); + IggyValueSetBooleanRS(m_labelJukebox.getIggyValuePath(), 0, "m_bUseHtmlText", true); addTimer(0, 100); } @@ -787,16 +790,31 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor void UIScene_HUD::handleTimerComplete(int id) { Minecraft *pMinecraft = Minecraft::GetInstance(); + bool isChatOpen = (dynamic_cast(pMinecraft->getScreen()) != nullptr); bool anyVisible = false; if(pMinecraft->localplayers[m_iPad]!= nullptr) { Gui *pGui = pMinecraft->gui; - //DWORD messagesToDisplay = min( CHAT_LINES_COUNT, pGui->getMessagesCount(m_iPad) ); - for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i ) + DWORD totalMessages = pGui->getMessagesCount(m_iPad); + DWORD messagesToDisplay = min( CHAT_LINES_COUNT, totalMessages); + DWORD maxScroll = max(0, totalMessages - messagesToDisplay); + + bool canScroll = messagesToDisplay < totalMessages; + int startIndex = (canScroll && isChatOpen ? ChatScreen::getChatIndex() : 0); + + if (startIndex > maxScroll) { + ChatScreen::correctChatIndex(maxScroll); + startIndex = maxScroll; + } + + app.DebugPrintf("handleTimerComplete: %d | %d | %d\n", maxScroll, startIndex, totalMessages); + + for( unsigned int i = 0; i < messagesToDisplay; ++i ) { - float opacity = pGui->getOpacity(m_iPad, i); - if( opacity > 0 ) + unsigned int msgIndex = startIndex + i; + float opacity = pGui->getOpacity(m_iPad, msgIndex); + if( opacity > 0 || isChatOpen) { #if 0 // def _WINDOWS64 // Use Iggy chat until Gui::render has visual parity // Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats. @@ -804,9 +822,10 @@ void UIScene_HUD::handleTimerComplete(int id) m_labelChatText[i].setOpacity(0); m_labelChatText[i].setLabel(L""); #else - m_controlLabelBackground[i].setOpacity(opacity); - m_labelChatText[i].setOpacity(opacity); - m_labelChatText[i].setLabel( pGui->getMessagesCount(m_iPad) ? pGui->getMessage(m_iPad,i) : L"" ); + + m_controlLabelBackground[i].setOpacity((isChatOpen ? 1 : opacity)); + m_labelChatText[i].setOpacity((isChatOpen ? 1 : opacity)); + m_labelChatText[i].setLabel(pGui->getMessage(m_iPad, msgIndex)); #endif anyVisible = true; } diff --git a/Minecraft.Client/Common/UI/UIScene_HUD.h b/Minecraft.Client/Common/UI/UIScene_HUD.h index 4739ec8f..87dc34b0 100644 --- a/Minecraft.Client/Common/UI/UIScene_HUD.h +++ b/Minecraft.Client/Common/UI/UIScene_HUD.h @@ -11,7 +11,7 @@ private: bool m_bSplitscreen; protected: - UIControl_Label m_labelChatText[CHAT_LINES_COUNT]; + UIControl_HTMLLabel m_labelChatText[CHAT_LINES_COUNT]; UIControl_Label m_labelJukebox; UIControl m_controlLabelBackground[CHAT_LINES_COUNT]; UIControl_Label m_labelDisplayName; diff --git a/Minecraft.Client/EntityTracker.cpp b/Minecraft.Client/EntityTracker.cpp index 4dbcf2c3..469e3966 100644 --- a/Minecraft.Client/EntityTracker.cpp +++ b/Minecraft.Client/EntityTracker.cpp @@ -86,7 +86,7 @@ void EntityTracker::addEntity(shared_ptr e, int range, int updateInterva { assert(false); // Entity already tracked } - if( e->entityId >= 2048 ) + if( e->entityId >= 16384 ) { __debugbreak(); } diff --git a/Minecraft.Client/Font.cpp b/Minecraft.Client/Font.cpp index 6e0bc7e4..735a0c2e 100644 --- a/Minecraft.Client/Font.cpp +++ b/Minecraft.Client/Font.cpp @@ -404,6 +404,8 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor) t->begin(); t->color(currentColor & 0x00ffffff, (currentColor >> 24) & 255); + bool prev = t->setMipmapEnable(false); // Disable mipmapping for fonts, and save previous enabled value to be restored later - Botch + for (int i = 0; i < static_cast(cleanStr.length()); ++i) { // Map character @@ -481,6 +483,8 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor) } } + t->setMipmapEnable(prev); //Reinstates previously used enabled value - Botch + t->end(); } diff --git a/Minecraft.Client/Gui.cpp b/Minecraft.Client/Gui.cpp index b2e91764..88d06e42 100644 --- a/Minecraft.Client/Gui.cpp +++ b/Minecraft.Client/Gui.cpp @@ -1209,7 +1209,20 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) // Disable the depth test so the text shows on top of the paperdoll glDisable(GL_DEPTH_TEST); +#ifdef _WINDOWS64 + float scaleWidth = (g_rScreenWidth / 1920.0f); + float scaleHeight = (g_rScreenHeight / 1080.0f); + float scale = min(scaleWidth, scaleHeight); //stop stretching + + if (scale < 0.5f) scale = 0.5f; // force minimum scale + if (scale > 1.2f) // resolutions over 1296 pixels tall + { + scale = scale - 0.33f; // tame overscaling on 1440p + } + + glScalef(scale, scale, 1); +#endif // Loop through the lines and draw them all on screen int yPos = debugTop; for (const auto &line : lines) @@ -1218,6 +1231,9 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse) yPos += 10; } +#ifdef _WINDOWS64 + glScalef(1, 1, 1); +#endif // Restore the depth test glEnable(GL_DEPTH_TEST); @@ -1579,6 +1595,13 @@ float Gui::getOpacity(int iPad, DWORD index) return opacityPercentage; } +//just like java functionality it overwrites the jukebox label +void Gui::setActionBarMessage(wstring message) +{ + overlayMessageString = message; + overlayMessageTime = 20 * 4; //idk how long it should last, need to check java usage +} + float Gui::getJukeboxOpacity(int iPad) { float t = overlayMessageTime - lastTickA; @@ -1594,7 +1617,7 @@ void Gui::setNowPlaying(const wstring& string) // overlayMessageString = L"Now playing: " + string; overlayMessageString = app.GetString(IDS_NOWPLAYING) + string; overlayMessageTime = 20 * 3; - animateOverlayMessageColor = true; + animateOverlayMessageColor = true; //appears to be unused, @DrPerkyLegit plans to add in later pr } void Gui::displayClientMessage(int messageId, int iPad) diff --git a/Minecraft.Client/Gui.h b/Minecraft.Client/Gui.h index 64b8dfbe..527b0237 100644 --- a/Minecraft.Client/Gui.h +++ b/Minecraft.Client/Gui.h @@ -17,6 +17,7 @@ private: static const int m_iMaxMessageWidth = 280; static ItemRenderer *itemRenderer; vector guiMessages[XUSER_MAX_COUNT]; + int chatIndex = 0; Random *random; Minecraft *minecraft; @@ -63,6 +64,8 @@ public: wstring getMessage(int iPad, DWORD index) { return guiMessages[iPad].at(index).string; } float getOpacity(int iPad, DWORD index); + void setActionBarMessage(wstring message); //uses jukebox label + wstring getJukeboxMessage(int iPad) { return overlayMessageString; } float getJukeboxOpacity(int iPad); diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index b32ffd53..7ef8bda1 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -1544,8 +1544,12 @@ void Minecraft::run_middle() // Utility keys always work regardless of KBM active state if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_PAUSE) && !ui.GetMenuDisplayed(i)) { - localplayers[i]->ullButtonsPressed|=1LL<(getScreen()) != nullptr) { + setScreen(nullptr); + } else { + localplayers[i]->ullButtonsPressed|=1LL< packet) } #else wstring formatted = L"<" + player->name + L"> " + message; - server->getPlayers()->broadcastAll(shared_ptr(new ChatPacket(formatted))); + server->getPlayers()->broadcastAll(shared_ptr(new ChatPacket(app.FormatChatMessage(formatted, false)))); #endif chatSpamTickCount += SharedConstants::TICKS_PER_SECOND; if (chatSpamTickCount > SharedConstants::TICKS_PER_SECOND * 10) diff --git a/Minecraft.Client/Screen.cpp b/Minecraft.Client/Screen.cpp index ec6be9dc..071b42cc 100644 --- a/Minecraft.Client/Screen.cpp +++ b/Minecraft.Client/Screen.cpp @@ -161,7 +161,7 @@ void Screen::updateEvents() static bool s_arrowFirstRepeat[2] = { false, false }; const DWORD ARROW_REPEAT_DELAY_MS = 250; const DWORD ARROW_REPEAT_INTERVAL_MS = 50; - DWORD now = GetTickCount(); + DWORD now = GetTickCount64(); // Poll keyboard events (special keys that may not come through WM_CHAR, e.g. Escape, arrows) for (int vk = 0; vk < 256; vk++) diff --git a/Minecraft.Client/ServerPlayer.cpp b/Minecraft.Client/ServerPlayer.cpp index ead24838..00cc04b5 100644 --- a/Minecraft.Client/ServerPlayer.cpp +++ b/Minecraft.Client/ServerPlayer.cpp @@ -295,12 +295,12 @@ void ServerPlayer::flagEntitiesToBeRemoved(unsigned int *flags, bool *removedFou { *removedFound = true; // before this left 192 bytes uninitialized!!!!! - memset(flags, 0, (2048 / 32) * sizeof(unsigned int)); + memset(flags, 0, (16384 / 32) * sizeof(unsigned int)); } for(int index : entitiesToRemove) { - if( index < 2048 ) + if( index < 16384 ) { unsigned int i = index / 32; unsigned int j = index % 32; diff --git a/Minecraft.Client/Settings.cpp b/Minecraft.Client/Settings.cpp index c9cfcb94..e959f84d 100644 --- a/Minecraft.Client/Settings.cpp +++ b/Minecraft.Client/Settings.cpp @@ -78,7 +78,7 @@ void Settings::saveProperties() if (!stream.is_open()) return; - stream << "# MinecraftConsoles dedicated server properties\r\n"; + stream << "# LCE-Revelations dedicated server properties\r\n"; for (unordered_map::const_iterator it = properties.begin(); it != properties.end(); ++it) { string key = string(wstringtochararray(it->first)); diff --git a/Minecraft.Client/SheepRenderer.cpp b/Minecraft.Client/SheepRenderer.cpp index 33d96fc1..1289d057 100644 --- a/Minecraft.Client/SheepRenderer.cpp +++ b/Minecraft.Client/SheepRenderer.cpp @@ -13,11 +13,10 @@ SheepRenderer::SheepRenderer(Model *model, Model *armor, float shadow) : MobRend int SheepRenderer::prepareArmor(shared_ptr _sheep, int layer, float a) { - // 4J - dynamic cast required because we aren't using templates/generics in our version shared_ptr sheep = dynamic_pointer_cast(_sheep); if (layer == 0 && !sheep->isSheared() && - !sheep->isInvisibleTo(Minecraft::GetInstance()->player)) // 4J-JEV: Todo, merge with java fix (for invisible sheep armour) in '1.7.5'. + !sheep->isInvisibleTo(Minecraft::GetInstance()->player)) { MemSect(31); bindTexture(&SHEEP_FUR_LOCATION); @@ -25,9 +24,8 @@ int SheepRenderer::prepareArmor(shared_ptr _sheep, int layer, floa if (sheep->hasCustomName() && sheep->getCustomName().compare(L"jeb_") == 0) { - // easter egg... int colorDuration = 25; - int value = (sheep->tickCount / colorDuration); + int value = (sheep->tickCount / colorDuration) + sheep->entityId; int c1 = value % Sheep::COLOR_LENGTH; int c2 = (value + 1) % Sheep::COLOR_LENGTH; float subStep = ((sheep->tickCount % colorDuration) + a) / static_cast(colorDuration); @@ -64,4 +62,4 @@ void SheepRenderer::render(shared_ptr mob, double x, double y, double z, ResourceLocation *SheepRenderer::getTextureLocation(shared_ptr mob) { return &SHEEP_LOCATION; -} +} \ No newline at end of file diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_d.lib.bak b/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_d.lib.bak deleted file mode 100644 index 6b3303b4..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_d.lib.bak and /dev/null differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_r.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_r.lib deleted file mode 100644 index cae46c31..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Input_r.lib and /dev/null differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Profile_r.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Profile_r.lib deleted file mode 100644 index 4b9b716a..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Profile_r.lib and /dev/null differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC_d.lib.bak b/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC_d.lib.bak deleted file mode 100644 index aa2118d3..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Render_PC_d.lib.bak and /dev/null differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_d.lib.bak b/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_d.lib.bak deleted file mode 100644 index 17f457ea..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_d.lib.bak and /dev/null differ diff --git a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_r.lib b/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_r.lib deleted file mode 100644 index 1d7e6b72..00000000 Binary files a/Minecraft.Client/Windows64/4JLibs/libs/4J_Storage_r.lib and /dev/null differ diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index b51d712a..c1b113be 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -3,6 +3,8 @@ #include "stdafx.h" +#include // IDXGISwapChain3 for SetColorSpace1 + #include #include #include @@ -58,6 +60,13 @@ extern Renderer InternalRenderManager; #include "Xbox/Resource.h" +// request use of dedicated GPU from AMD and Nvidia drivers +extern "C" +{ + __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; + __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +} + #ifdef _MSC_VER #pragma comment(lib, "legacy_stdio_definitions.lib") #endif @@ -471,7 +480,6 @@ ID3D11Device* g_pd3dDevice = nullptr; ID3D11DeviceContext* g_pImmediateContext = nullptr; IDXGISwapChain* g_pSwapChain = nullptr; bool g_bVSync = false; -static bool g_bTearingSupported = false; static bool g_bPendingExclusiveFullscreen = false; static bool g_bPendingExclusiveFullscreenValue = false; @@ -546,50 +554,15 @@ static bool TakeScreenshot(wstring& outFilename) return success; } -// COM proxy for IDXGISwapChain — delegates all calls to the real swap chain, -// but overrides Present() to set SyncInterval=1 when VSync is enabled. -// Avoids vtable patching, which conflicts with the D3D11 debug layer. -static class SwapChainVSyncProxy : public IDXGISwapChain -{ -public: - void SetTarget(IDXGISwapChain* pReal) { m_pReal = pReal; } - - // IUnknown - HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override { return m_pReal->QueryInterface(riid, ppvObject); } - ULONG STDMETHODCALLTYPE AddRef() override { return m_pReal->AddRef(); } - ULONG STDMETHODCALLTYPE Release() override { return m_pReal->Release(); } - - // IDXGIObject - HRESULT STDMETHODCALLTYPE SetPrivateData(REFGUID Name, UINT DataSize, const void* pData) override { return m_pReal->SetPrivateData(Name, DataSize, pData); } - HRESULT STDMETHODCALLTYPE SetPrivateDataInterface(REFGUID Name, const IUnknown* pUnknown) override { return m_pReal->SetPrivateDataInterface(Name, pUnknown); } - HRESULT STDMETHODCALLTYPE GetPrivateData(REFGUID Name, UINT* pDataSize, void* pData) override { return m_pReal->GetPrivateData(Name, pDataSize, pData); } - HRESULT STDMETHODCALLTYPE GetParent(REFIID riid, void** ppParent) override { return m_pReal->GetParent(riid, ppParent); } - - // IDXGIDeviceSubObject - HRESULT STDMETHODCALLTYPE GetDevice(REFIID riid, void** ppDevice) override { return m_pReal->GetDevice(riid, ppDevice); } - - // IDXGISwapChain - // NOTE: The 4J RenderManager library hardcodes SyncInterval=1 and does NOT - // dispatch Present through this proxy's vtable. VSync control is handled - // directly in the main loop (see the Present-the-frame block) instead. - HRESULT STDMETHODCALLTYPE Present(UINT SyncInterval, UINT Flags) override { return m_pReal->Present(SyncInterval, Flags); } - HRESULT STDMETHODCALLTYPE GetBuffer(UINT Buffer, REFIID riid, void** ppSurface) override { return m_pReal->GetBuffer(Buffer, riid, ppSurface); } - HRESULT STDMETHODCALLTYPE SetFullscreenState(BOOL Fullscreen, IDXGIOutput* pTarget) override { return m_pReal->SetFullscreenState(Fullscreen, pTarget); } - HRESULT STDMETHODCALLTYPE GetFullscreenState(BOOL* pFullscreen, IDXGIOutput** ppTarget) override { return m_pReal->GetFullscreenState(pFullscreen, ppTarget); } - HRESULT STDMETHODCALLTYPE GetDesc(DXGI_SWAP_CHAIN_DESC* pDesc) override { return m_pReal->GetDesc(pDesc); } - HRESULT STDMETHODCALLTYPE ResizeBuffers(UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) override { return m_pReal->ResizeBuffers(BufferCount, Width, Height, NewFormat, SwapChainFlags); } - HRESULT STDMETHODCALLTYPE ResizeTarget(const DXGI_MODE_DESC* pNewTargetParameters) override { return m_pReal->ResizeTarget(pNewTargetParameters); } - HRESULT STDMETHODCALLTYPE GetContainingOutput(IDXGIOutput** ppOutput) override { return m_pReal->GetContainingOutput(ppOutput); } - HRESULT STDMETHODCALLTYPE GetFrameStatistics(DXGI_FRAME_STATISTICS* pStats) override { return m_pReal->GetFrameStatistics(pStats); } - HRESULT STDMETHODCALLTYPE GetLastPresentCount(UINT* pLastPresentCount) override { return m_pReal->GetLastPresentCount(pLastPresentCount); } - -private: - IDXGISwapChain* m_pReal = nullptr; -} g_swapChainProxy; - ID3D11RenderTargetView* g_pRenderTargetView = nullptr; ID3D11DepthStencilView* g_pDepthStencilView = nullptr; ID3D11Texture2D* g_pDepthStencilBuffer = nullptr; +static const float kClearColorWhite[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +static const float kClearColorBlack[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; + +// True when the swap chain is in DXGI exclusive fullscreen. Lets ResizeD3D +// skip its destroy-and-recreate path, which would break exclusive ownership. +static bool g_bDxgiExclusiveFullscreen = false; // // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) @@ -937,33 +910,30 @@ HRESULT InitDevice() }; UINT numFeatureLevels = ARRAYSIZE( featureLevels ); - // Check tearing support before device/swap chain creation - { - IDXGIFactory5* factory5 = nullptr; - if (SUCCEEDED(CreateDXGIFactory1(__uuidof(IDXGIFactory5), (void**)&factory5))) - { - BOOL allowTearing = FALSE; - if (SUCCEEDED(factory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)))) - g_bTearingSupported = (allowTearing == TRUE); - factory5->Release(); - } - } - + // Use the legacy bitblt DISCARD swap model (SwapEffect left as default 0). + // DXGI_SWAP_EFFECT_FLIP_DISCARD gives lower latency and tearing support, but + // takes exclusive ownership of the HWND — which makes window resize via + // CreateSwapChain fail with E_ACCESSDENIED and ResizeBuffers fail with + // DXGI_ERROR_INVALID_CALL (the closed-source 4J Renderer holds hidden + // backbuffer refs we can't release). Bitblt DISCARD has no HWND lock, so + // the "destroy old, create new" resize path in ResizeD3D() works cleanly. + // VSync toggle still works via the SyncInterval parameter on Present(). + // RefreshRate=0/0 so DXGI matches the current display mode. Hardcoding a + // rate would force a mode switch on SetFullscreenState, which can produce + // "input signal out of range" errors on high-refresh monitors. DXGI_SWAP_CHAIN_DESC sd; ZeroMemory( &sd, sizeof( sd ) ); - sd.BufferCount = 2; + sd.BufferCount = 1; sd.BufferDesc.Width = width; sd.BufferDesc.Height = height; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 0; sd.BufferDesc.RefreshRate.Denominator = 0; - sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; sd.OutputWindow = g_hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; - sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; for( UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++ ) { @@ -1024,8 +994,7 @@ HRESULT InitDevice() vp.TopLeftY = 0; g_pImmediateContext->RSSetViewports( 1, &vp ); - g_swapChainProxy.SetTarget(g_pSwapChain); - RenderManager.Initialise(g_pd3dDevice, &g_swapChainProxy); + RenderManager.Initialise(g_pd3dDevice, g_pSwapChain); PostProcesser::GetInstance().Init(); @@ -1041,7 +1010,7 @@ void Render() const float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor ); - g_pSwapChain->Present(0, g_bTearingSupported ? DXGI_PRESENT_ALLOW_TEARING : 0); + g_pSwapChain->Present(0, 0); } //-------------------------------------------------------------------------------------- @@ -1052,6 +1021,9 @@ static bool ResizeD3D(int newW, int newH) if (newW <= 0 || newH <= 0) return false; if (!g_pSwapChain) return false; if (!g_bResizeReady) return false; + // In exclusive fullscreen the swap chain must not be recreated. + if (g_bDxgiExclusiveFullscreen) + return false; int bbW = newW; int bbH = newH; @@ -1081,11 +1053,11 @@ static bool ResizeD3D(int newW, int newH) // Verify offsets by checking device and swap chain pointers ID3D11Device** ppRM_Device = (ID3D11Device**)(pRM + 0x10); - if (*ppRM_Device != g_pd3dDevice || *ppRM_SC != (IDXGISwapChain*)&g_swapChainProxy) + if (*ppRM_Device != g_pd3dDevice || *ppRM_SC != g_pSwapChain) { app.DebugPrintf("[RESIZE] ERROR: RenderManager offset verification failed! " "device=%p (expected %p) swapchain=%p (expected %p)\n", - *ppRM_Device, g_pd3dDevice, *ppRM_SC, (IDXGISwapChain*)&g_swapChainProxy); + *ppRM_Device, g_pd3dDevice, *ppRM_SC, g_pSwapChain); return false; } @@ -1116,61 +1088,63 @@ static bool ResizeD3D(int newW, int newH) gdraw_D3D11_PreReset(); + // Get IDXGIFactory from the existing device BEFORE destroying the old swap + // chain. If anything fails before we have a new swap chain, we abort + // without destroying the old one — leaving the Renderer in a valid + // (old-size) state. IDXGISwapChain* pOldSwapChain = g_pSwapChain; bool success = false; HRESULT hr; - // Create a brand-new swap chain instead of ResizeBuffers. - // ResizeBuffers requires ALL backbuffer refs released, but the closed-source - // Renderer holds hidden refs we can't track — causing DXGI_ERROR_INVALID_CALL - // and leaving the Renderer with NULL views (black screen). - // Creating a new swap chain orphans the old backbuffer (tiny leak) but avoids - // the need to release every hidden reference. - { - IDXGIDevice* dxgiDevice = NULL; - IDXGIAdapter* dxgiAdapter = NULL; - IDXGIFactory* dxgiFactory = NULL; - hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice); - if (FAILED(hr)) goto postReset; - hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter); - dxgiDevice->Release(); - if (FAILED(hr)) goto postReset; - hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory); - dxgiAdapter->Release(); - if (FAILED(hr)) goto postReset; + IDXGIDevice* dxgiDevice = NULL; + IDXGIAdapter* dxgiAdapter = NULL; + IDXGIFactory* dxgiFactory = NULL; + hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice); + if (FAILED(hr)) goto postReset; + hr = dxgiDevice->GetParent(__uuidof(IDXGIAdapter), (void**)&dxgiAdapter); + if (FAILED(hr)) { dxgiDevice->Release(); goto postReset; } + hr = dxgiAdapter->GetParent(__uuidof(IDXGIFactory), (void**)&dxgiFactory); + dxgiAdapter->Release(); + dxgiDevice->Release(); + if (FAILED(hr)) goto postReset; + // Create a brand-new swap chain at the target size and swap it in. + // Must use the SAME swap-chain config as InitDevice (legacy bitblt + // DISCARD model), otherwise DXGI may return E_ACCESSDENIED. The + // Renderer's old RTV/SRV/DSV are intentionally NOT released here — they + // become orphaned with the old swap chain (tiny leak, but avoids + // fighting unknown refs inside the closed-source Renderer library). + { DXGI_SWAP_CHAIN_DESC sd = {}; - sd.BufferCount = 2; + sd.BufferCount = 1; sd.BufferDesc.Width = bbW; sd.BufferDesc.Height = bbH; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + // RefreshRate=0/0 matches InitDevice; see comment there. sd.BufferDesc.RefreshRate.Numerator = 0; sd.BufferDesc.RefreshRate.Denominator = 0; - sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT; sd.OutputWindow = g_hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; - sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - sd.Flags = g_bTearingSupported ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; IDXGISwapChain* pNewSwapChain = NULL; hr = dxgiFactory->CreateSwapChain(g_pd3dDevice, &sd, &pNewSwapChain); dxgiFactory->Release(); - if (FAILED(hr)) + if (FAILED(hr) || pNewSwapChain == NULL) { - app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X\n", (unsigned)hr); + app.DebugPrintf("[RESIZE] CreateSwapChain FAILED hr=0x%08X — keeping old swap chain\n", (unsigned)hr); goto postReset; } - // Destroy old, install new + // New swap chain created successfully — NOW destroy the old one. pOldSwapChain->Release(); g_pSwapChain = pNewSwapChain; - g_swapChainProxy.SetTarget(g_pSwapChain); } - // Patch Renderer's swap chain pointer (use proxy so VSync override stays active) - *ppRM_SC = &g_swapChainProxy; + // Patch Renderer's swap chain pointer to the new raw swap chain. + *ppRM_SC = g_pSwapChain; // Create render target views from new backbuffer { @@ -1364,18 +1338,99 @@ void SetExclusiveFullscreen(bool enabled) g_bPendingExclusiveFullscreenValue = enabled; } -// Uses borderless fullscreen (ToggleFullscreen) rather than DXGI SetFullscreenState. -// With DXGI_SWAP_EFFECT_FLIP_DISCARD + DXGI_PRESENT_ALLOW_TEARING, borderless -// fullscreen gets the same direct-flip path as exclusive fullscreen on Windows 10+ — -// identical latency and uncapped FPS. True DXGI exclusive fullscreen is blocked by -// the 4J Renderer holding hidden backbuffer references that prevent ResizeBuffers. +// Enter or leave true DXGI exclusive fullscreen. With our bitblt swap chain, +// Present(SyncInterval=0) in exclusive mode produces real screen tearing via +// direct scanout (DWM is out of the pipeline). Flip mode with ALLOW_TEARING +// would also work but is blocked by the 4J Renderer's deferred context refs +// on the backbuffer, which DXGI's ResizeBuffers cannot release. static void ApplyExclusiveFullscreen(bool enabled) { - // Toggle into/out of borderless fullscreen if state doesn't match - if (enabled && !g_isFullscreen) - ToggleFullscreen(); - else if (!enabled && g_isFullscreen) - ToggleFullscreen(); + if (!g_pSwapChain) + return; + + LONG styleBefore = GetWindowLong(g_hWnd, GWL_STYLE); + + if (enabled) + { + // Grow the window to cover the monitor first. This fires WM_SIZE which + // runs ResizeD3D and recreates the backbuffer at monitor-native size. + // Otherwise a small windowed backbuffer would enter exclusive fullscreen + // at that smaller size and DXGI would scale it to fill the monitor, + // producing a filtered / washed-out look. + HMONITOR hMon = MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY); + MONITORINFO mi = {}; + mi.cbSize = sizeof(mi); + if (GetMonitorInfo(hMon, &mi)) + { + int monW = mi.rcMonitor.right - mi.rcMonitor.left; + int monH = mi.rcMonitor.bottom - mi.rcMonitor.top; + SetWindowLong(g_hWnd, GWL_STYLE, (styleBefore & ~WS_OVERLAPPEDWINDOW) | WS_VISIBLE); + SetWindowPos(g_hWnd, HWND_TOP, + mi.rcMonitor.left, mi.rcMonitor.top, monW, monH, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + + // ResizeTarget pins the display mode to the backbuffer size with + // no scaling. Microsoft's pattern is ResizeTarget then + // SetFullscreenState then ResizeTarget again (see below). + DXGI_MODE_DESC targetMode = {}; + targetMode.Width = monW; + targetMode.Height = monH; + targetMode.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + targetMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + targetMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + g_pSwapChain->ResizeTarget(&targetMode); + } + } + + HRESULT hr = g_pSwapChain->SetFullscreenState(enabled ? TRUE : FALSE, nullptr); + if (FAILED(hr)) + return; + + g_bDxgiExclusiveFullscreen = enabled; + + if (enabled) + { + // Explicitly declare sRGB. Default for R8G8B8A8_UNORM but some drivers + // behave differently if the color space is never set. + IDXGISwapChain3* pSwapChain3 = nullptr; + if (SUCCEEDED(g_pSwapChain->QueryInterface(__uuidof(IDXGISwapChain3), (void**)&pSwapChain3)) && pSwapChain3) + { + UINT colorSpaceSupport = 0; + pSwapChain3->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, &colorSpaceSupport); + if (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT) + pSwapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709); + pSwapChain3->Release(); + } + + // Second ResizeTarget per Microsoft's recommendation to make the mode stick. + HMONITOR hMon2 = MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY); + MONITORINFO mi2 = {}; + mi2.cbSize = sizeof(mi2); + if (GetMonitorInfo(hMon2, &mi2)) + { + DXGI_MODE_DESC targetMode2 = {}; + targetMode2.Width = mi2.rcMonitor.right - mi2.rcMonitor.left; + targetMode2.Height = mi2.rcMonitor.bottom - mi2.rcMonitor.top; + targetMode2.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + targetMode2.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + targetMode2.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + g_pSwapChain->ResizeTarget(&targetMode2); + } + g_isFullscreen = true; + } + else + { + // Force a real decorated windowed state on exit. DXGI would otherwise + // restore whatever state the window had before SetFullscreenState, + // which may still be borderless. + SetWindowLong(g_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE); + const int w = 1280, h = 720; + const int sw = GetSystemMetrics(SM_CXSCREEN); + const int sh = GetSystemMetrics(SM_CYSCREEN); + SetWindowPos(g_hWnd, HWND_TOP, (sw - w) / 2, (sh - h) / 2, w, h, + SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + g_isFullscreen = false; + } } //-------------------------------------------------------------------------------------- @@ -1610,10 +1665,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, return 0; } - // Restore fullscreen state from previous session - if (LoadFullscreenOption() && !g_isFullscreen || launchOptions.fullscreen) + // Restore fullscreen state from previous session. Route through the + // deferred exclusive fullscreen path so the main loop applies it on the + // first tick (safer than transitioning during init). + if ((LoadFullscreenOption() && !g_isFullscreen) || launchOptions.fullscreen) { - ToggleFullscreen(); + g_bPendingExclusiveFullscreen = true; + g_bPendingExclusiveFullscreenValue = true; } #if 0 @@ -1722,7 +1780,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, continue; } + const float* clearColor = app.GetGameStarted() ? kClearColorBlack : kClearColorWhite; + RenderManager.SetClearColour(clearColor); RenderManager.StartFrame(); + if (!app.GetGameStarted()) + { + RenderManager.SetClearColour(kClearColorWhite); // set intro scene background to white + RenderManager.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } #if 0 if(pMinecraft->soundEngine->isStreamingWavebankReady() && !pMinecraft->soundEngine->isPlayingStreamingGameMusic() && @@ -1904,13 +1969,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, RenderManager.Set_matrixDirty(); #endif - // Present the frame. - // RenderManager.Present() hardcodes SyncInterval=1 internally. - // When VSync is off, bypass it and call the swap chain directly. - if (!g_bVSync && g_bTearingSupported && g_pSwapChain) + // Present the frame. RenderManager.Present() hardcodes SyncInterval=1, + // so when VSync is off we bypass it for uncapped frames. In DXGI + // exclusive fullscreen this produces real tearing via direct scanout. + if (!g_bVSync && g_pSwapChain) { - HRESULT hrPresent = g_pSwapChain->Present(0, DXGI_PRESENT_ALLOW_TEARING); - // If tearing Present fails (e.g. during fullscreen transition), + HRESULT hrPresent = g_pSwapChain->Present(0, 0); + // If the direct Present fails (e.g. during fullscreen transition), // fall back to the library's VSync'd Present for this frame. if (FAILED(hrPresent)) RenderManager.Present(); @@ -2008,11 +2073,11 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, } #endif - // toggle fullscreen + // toggle fullscreen (DXGI exclusive via ApplyExclusiveFullscreen) if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_FULLSCREEN)) { - ToggleFullscreen(); - app.SetGameSettings(ProfileManager.GetPrimaryPad(), eGameSetting_ExclusiveFullscreen, g_isFullscreen ? 1 : 0); + ApplyExclusiveFullscreen(!g_bDxgiExclusiveFullscreen); + app.SetGameSettings(ProfileManager.GetPrimaryPad(), eGameSetting_ExclusiveFullscreen, g_bDxgiExclusiveFullscreen ? 1 : 0); } // Apply deferred exclusive fullscreen toggle diff --git a/Minecraft.Client/Windows64Media/Sound/Minecraft/damage/critical.mp3 b/Minecraft.Client/Windows64Media/Sound/Minecraft/damage/critical.mp3 new file mode 100644 index 00000000..b3687e43 Binary files /dev/null and b/Minecraft.Client/Windows64Media/Sound/Minecraft/damage/critical.mp3 differ diff --git a/Minecraft.Client/cmake/sources/Windows.cmake b/Minecraft.Client/cmake/sources/Windows.cmake index a28ed56a..981acede 100644 --- a/Minecraft.Client/cmake/sources/Windows.cmake +++ b/Minecraft.Client/cmake/sources/Windows.cmake @@ -1,12 +1,5 @@ set(BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/") -set(_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO - "${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/minecraft.xsb" - "${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/resident.xwb" - "${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/streamed.xwb" -) -source_group("Common/res/audio" FILES ${_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO}) - set(_MINECRAFT_CLIENT_WINDOWS_COMMON_AUDIO "${CMAKE_CURRENT_SOURCE_DIR}/Common/Audio/SoundEngine.cpp" ) @@ -479,7 +472,6 @@ set(_MINECRAFT_CLIENT_WINDOWS_NET_MINECRAFT_STATS source_group("net/minecraft/stats" FILES ${_MINECRAFT_CLIENT_WINDOWS_NET_MINECRAFT_STATS}) set(MINECRAFT_CLIENT_WINDOWS - ${_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO} ${_MINECRAFT_CLIENT_WINDOWS_COMMON_AUDIO} ${_MINECRAFT_CLIENT_WINDOWS_COMMON_NETWORK} ${_MINECRAFT_CLIENT_WINDOWS_COMMON_UI} diff --git a/Minecraft.Server/Windows64/ServerMain.cpp b/Minecraft.Server/Windows64/ServerMain.cpp index 04c82eae..a51f9d05 100644 --- a/Minecraft.Server/Windows64/ServerMain.cpp +++ b/Minecraft.Server/Windows64/ServerMain.cpp @@ -735,7 +735,7 @@ int main(int argc, char **argv) break; } - if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle) + if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { LogWorldIO("autosave completed"); autosaveRequested = false; @@ -749,7 +749,7 @@ int main(int argc, char **argv) DWORD now = GetTickCount(); if ((LONG)(now - nextAutosaveTick) >= 0) { - if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle) + if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { LogWorldIO("requesting autosave"); app.SetXuiServerAction(kServerActionPad, eXuiServerAction_AutoSaveGame); @@ -768,16 +768,18 @@ int main(int argc, char **argv) LogInfof("shutdown", "Dedicated server stopped"); MinecraftServer *server = MinecraftServer::getInstance(); - if (server != NULL) - { - server->setSaveOnExit(true); - } - if (server != NULL) + if (server != NULL && !ConsoleSaveFileOriginal::hasPendingBackgroundSave()) { + server->setSaveOnExit(true); LogWorldIO("requesting save before shutdown"); LogWorldIO("using saveOnExit for shutdown"); } + if (ConsoleSaveFileOriginal::hasPendingBackgroundSave()) + { + LogWorldIO("Waiting for autosave to complete..."); + } + MinecraftServer::HaltServer(); if (g_NetworkManager.ServerStoppedValid()) diff --git a/Minecraft.Server/cmake/sources/Common.cmake b/Minecraft.Server/cmake/sources/Common.cmake index ec41d3c6..9215df80 100644 --- a/Minecraft.Server/cmake/sources/Common.cmake +++ b/Minecraft.Server/cmake/sources/Common.cmake @@ -515,6 +515,7 @@ set(_MINECRAFT_SERVER_COMMON_ROOT "${_MS_SRC}/../Minecraft.Client/iob_shim.asm" "${_MS_SRC}/../Minecraft.Client/stdafx.cpp" "${_MS_SRC}/../Minecraft.Client/stubs.cpp" + "${_MS_SRC}/../Minecraft.World/Entity.cpp" "${_MS_SRC}/../Minecraft.World/AbstractContainerMenu.cpp" "${_MS_SRC}/../Minecraft.World/CompoundContainer.h" "${_MS_SRC}/../Minecraft.World/ItemEntity.cpp" diff --git a/Minecraft.World/ChatPacket.h b/Minecraft.World/ChatPacket.h index ea9b3806..7ffebc5e 100644 --- a/Minecraft.World/ChatPacket.h +++ b/Minecraft.World/ChatPacket.h @@ -98,6 +98,7 @@ public: e_ChatCommandTeleportMe, e_ChatCommandTeleportToMe, + e_ChatActionBar, }; public: diff --git a/Minecraft.World/DamageSource.cpp b/Minecraft.World/DamageSource.cpp index bc3adc80..f567385d 100644 --- a/Minecraft.World/DamageSource.cpp +++ b/Minecraft.World/DamageSource.cpp @@ -124,6 +124,7 @@ DamageSource::DamageSource(ChatPacket::EChatPacketMessage msgId, ChatPacket::ECh _isProjectile = false; _isMagic = false; _isExplosion = false; + _isCritical = false; //this->msgId = msgId; m_msgId = msgId; @@ -153,7 +154,15 @@ DamageSource *DamageSource::bypassInvul() _bypassInvul = true; return this; } - +bool DamageSource::isCritical() +{ + return _isCritical; +} +DamageSource *DamageSource::setIsCritical() +{ + _isCritical = true; + return this; +} DamageSource *DamageSource::setIsFire() { isFireSource = true; diff --git a/Minecraft.World/DamageSource.h b/Minecraft.World/DamageSource.h index 1173db4d..6d0d5326 100644 --- a/Minecraft.World/DamageSource.h +++ b/Minecraft.World/DamageSource.h @@ -47,8 +47,11 @@ private: bool _scalesWithDifficulty; bool _isMagic; bool _isExplosion; + bool _isCritical; public: + bool isCritical(); + DamageSource *setIsCritical(); bool isProjectile(); DamageSource *setProjectile(); bool isExplosion(); diff --git a/Minecraft.World/Entity.cpp b/Minecraft.World/Entity.cpp index 5042a9b2..e502a7d5 100644 --- a/Minecraft.World/Entity.cpp +++ b/Minecraft.World/Entity.cpp @@ -27,13 +27,17 @@ const wstring Entity::RIDING_TAG = L"Riding"; -int Entity::entityCounter = 2048; // 4J - changed initialiser to 2048, as we are using range 0 - 2047 as special unique smaller ids for things that need network tracked +//int Entity::entityCounter = 2048; // 4J - changed initialiser to 2048, as we are using range 0 - 2047 as special unique smaller ids for things that need network tracked +int Entity::entityCounter = 16384; //now using full range of 0 - 16383, limit is 32k but we shouldnt need that yet DWORD Entity::tlsIdx = TlsAlloc(); // 4J - added getSmallId & freeSmallId methods -unsigned int Entity::entityIdUsedFlags[2048/32] = {0}; -unsigned int Entity::entityIdWanderFlags[2048/32] = {0}; -unsigned int Entity::entityIdRemovingFlags[2048/32] = {0}; +//unsigned int Entity::entityIdUsedFlags[2048/32] = {0}; +//unsigned int Entity::entityIdWanderFlags[2048/32] = {0}; +//unsigned int Entity::entityIdRemovingFlags[2048/32] = {0}; +unsigned int Entity::entityIdUsedFlags[16384/32] = {0}; +unsigned int Entity::entityIdWanderFlags[16384/32] = {0}; +unsigned int Entity::entityIdRemovingFlags[16384/32] = {0}; int Entity::extraWanderIds[EXTRA_WANDER_MAX] = {0}; int Entity::extraWanderTicks = 0; int Entity::extraWanderCount = 0; @@ -65,7 +69,7 @@ int Entity::getSmallId() } } - for( int i = 0; i < (2048 / 32 ); i++ ) + for( int i = 0; i < (16384 / 32 ); i++ ) { unsigned int uiFlags = *puiUsedFlags; if( uiFlags != 0xffffffff ) @@ -102,7 +106,7 @@ int Entity::getSmallId() if (entityCounter == 0x7ffffff) { - entityCounter = 2048; + entityCounter = 16384; } return fallbackId; #else @@ -116,7 +120,7 @@ void Entity::countFlagsForPIX() { int freecount = 0; unsigned int *puiUsedFlags = entityIdUsedFlags; - for( int i = 0; i < (2048 / 32 ); i++ ) + for( int i = 0; i < (16384 / 32 ); i++ ) { unsigned int uiFlags = *puiUsedFlags; if( uiFlags != 0xffffffff ) @@ -134,7 +138,7 @@ void Entity::countFlagsForPIX() puiUsedFlags++; } PIXAddNamedCounter(freecount,"Small Ids free"); - PIXAddNamedCounter(2048 - freecount,"Small Ids used"); + PIXAddNamedCounter(16384 - freecount,"Small Ids used"); } void Entity::resetSmallId() @@ -149,7 +153,7 @@ void Entity::resetSmallId() void Entity::freeSmallId(int index) { if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread - if( index >= 2048 ) return; // Don't do anything if this isn't a short id + if( index >= 16384 ) return; // Don't do anything if this isn't a short id unsigned int i = index / 32; unsigned int j = index % 32; @@ -172,7 +176,7 @@ void Entity::useSmallIds() void Entity::considerForExtraWandering(bool enable) { if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread - if( entityId >= 2048 ) return; // Don't do anything if this isn't a short id + if( entityId >= 16384 ) return; // Don't do anything if this isn't a short id unsigned int i = entityId / 32; unsigned int j = entityId % 32; @@ -192,7 +196,7 @@ void Entity::considerForExtraWandering(bool enable) bool Entity::isExtraWanderingEnabled() { if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return false; // Don't do anything with small ids if this isn't the server thread - if( entityId >= 2048 ) return false; // Don't do anything if this isn't a short id + if( entityId >= 16384 ) return false; // Don't do anything if this isn't a short id for( int i = 0; i < extraWanderCount; i++ ) { @@ -224,12 +228,12 @@ void Entity::tickExtraWandering() int entityId = 0; if( extraWanderCount ) { - entityId = ( extraWanderIds[ extraWanderCount - 1 ] + 1 ) % 2048; + entityId = ( extraWanderIds[ extraWanderCount - 1 ] + 1 ) % 16384; } extraWanderCount = 0; - for( int k = 0; ( k < 2048 ) && ( extraWanderCount < EXTRA_WANDER_MAX); k++ ) + for( int k = 0; ( k < 16384 ) && ( extraWanderCount < EXTRA_WANDER_MAX); k++ ) { unsigned int i = entityId / 32; unsigned int j = entityId % 32; @@ -241,7 +245,7 @@ void Entity::tickExtraWandering() // printf("%d, ", entityId); } - entityId = ( entityId + 1 ) % 2048; + entityId = ( entityId + 1 ) % 16384; } // printf("\n"); } @@ -261,7 +265,7 @@ void Entity::_init(bool useSmallId, Level *level) else { entityId = Entity::entityCounter++; - if(entityCounter == 0x7ffffff ) entityCounter = 2048; + if(entityCounter == 0x7ffffff ) entityCounter = 16384; } viewScale = 1.0; diff --git a/Minecraft.World/Entity.h b/Minecraft.World/Entity.h index a738c2ba..9fb0f548 100644 --- a/Minecraft.World/Entity.h +++ b/Minecraft.World/Entity.h @@ -382,9 +382,12 @@ private: int getSmallId(); void freeSmallId(int index); - static unsigned int entityIdUsedFlags[2048/32]; - static unsigned int entityIdWanderFlags[2048/32]; - static unsigned int entityIdRemovingFlags[2048/32]; + //static unsigned int entityIdUsedFlags[2048/32]; + //static unsigned int entityIdWanderFlags[2048/32]; + //static unsigned int entityIdRemovingFlags[2048/32]; + static unsigned int entityIdUsedFlags[16384/32]; + static unsigned int entityIdWanderFlags[16384/32]; + static unsigned int entityIdRemovingFlags[16384/32]; static int extraWanderIds[EXTRA_WANDER_MAX]; static int extraWanderCount; static int extraWanderTicks; diff --git a/Minecraft.World/EntityEvent.h b/Minecraft.World/EntityEvent.h index 4d2064ab..20e363c6 100644 --- a/Minecraft.World/EntityEvent.h +++ b/Minecraft.World/EntityEvent.h @@ -5,6 +5,9 @@ class EntityEvent public: static const BYTE JUMP = 1; static const BYTE HURT = 2; + //New + static const BYTE HURT_CRITICAL = 19; + static const BYTE DEATH_CRITICAL = 20; static const BYTE DEATH = 3; static const BYTE START_ATTACKING = 4; static const BYTE STOP_ATTACKING = 5; diff --git a/Minecraft.World/LivingEntity.cpp b/Minecraft.World/LivingEntity.cpp index ac6a1b7e..bc00f2cc 100644 --- a/Minecraft.World/LivingEntity.cpp +++ b/Minecraft.World/LivingEntity.cpp @@ -881,7 +881,12 @@ bool LivingEntity::hurt(DamageSource *source, float dmg) if (sound) { - level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT); + if (source->isCritical()) { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT_CRITICAL); + } + else { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT); + } if (source != DamageSource::drown) markHurt(); if (sourceEntity != nullptr) { @@ -904,12 +909,19 @@ bool LivingEntity::hurt(DamageSource *source, float dmg) MemSect(31); if (getHealth() <= 0) { - if (sound) playSound(getDeathSound(), getSoundVolume(), getVoicePitch()); + if (sound) { + //New: both death AND hurt sounds should play critical sound as well. + if (source->isCritical()) playSound(getCriticalSound(), getSoundVolume(), getVoicePitch()); + playSound(getDeathSound(), getSoundVolume(), getVoicePitch()); + }; die(source); } else { - if (sound) playSound(getHurtSound(), getSoundVolume(), getVoicePitch()); + if (sound) { + if (source->isCritical()) playSound(getCriticalSound(), getSoundVolume(), getVoicePitch()); + playSound(getHurtSound(), getSoundVolume(), getVoicePitch()); + } } MemSect(0); @@ -988,7 +1000,11 @@ void LivingEntity::die(DamageSource *source) } } - level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH); + if (source->isCritical()) { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH_CRITICAL); + } else { + level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH); + } } void LivingEntity::dropEquipment(bool byPlayer, int playerBonusLevel) @@ -1021,7 +1037,10 @@ int LivingEntity::getHurtSound() { return eSoundType_DAMAGE_HURT; } - +int LivingEntity::getCriticalSound() +{ + return eSoundType_DAMAGE_CRITICAL; +} int LivingEntity::getDeathSound() { return eSoundType_DAMAGE_HURT; @@ -1265,7 +1284,8 @@ void LivingEntity::swing() void LivingEntity::handleEntityEvent(byte id) { - if (id == EntityEvent::HURT) + //These gotta be in parentheses + if ((id == EntityEvent::HURT) || (id == EntityEvent::HURT_CRITICAL)) { walkAnimSpeed = 1.5f; @@ -1275,19 +1295,30 @@ void LivingEntity::handleEntityEvent(byte id) MemSect(31); // 4J-PB -added because villagers have no sounds - int iHurtSound=getHurtSound(); + int iHurtSound = getHurtSound(); + int iCritSound = getCriticalSound(); if(iHurtSound!=-1) { playSound(iHurtSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); } + if(iCritSound!=-1 && (id == EntityEvent::HURT_CRITICAL)) + { + playSound(iCritSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); + } MemSect(0); hurt(DamageSource::genericSource, 0); } - else if (id == EntityEvent::DEATH) + else if ((id == EntityEvent::DEATH) || (id == EntityEvent::DEATH_CRITICAL)) { MemSect(31); // 4J-PB -added because villagers have no sounds int iDeathSound=getDeathSound(); + int iCritSound = getCriticalSound(); + + if (iCritSound != -1 && (id == EntityEvent::DEATH_CRITICAL)) + { + playSound(iCritSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); + } if(iDeathSound!=-1) { playSound(iDeathSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f); diff --git a/Minecraft.World/LivingEntity.h b/Minecraft.World/LivingEntity.h index ee8a742d..e1275409 100644 --- a/Minecraft.World/LivingEntity.h +++ b/Minecraft.World/LivingEntity.h @@ -190,6 +190,7 @@ public: virtual void knockback(shared_ptr source, float dmg, double xd, double zd); protected: + virtual int getCriticalSound(); virtual int getHurtSound(); virtual int getDeathSound(); diff --git a/Minecraft.World/MoveEntityPacket.cpp b/Minecraft.World/MoveEntityPacket.cpp index cae28e91..aef9e621 100644 --- a/Minecraft.World/MoveEntityPacket.cpp +++ b/Minecraft.World/MoveEntityPacket.cpp @@ -35,7 +35,7 @@ void MoveEntityPacket::read(DataInputStream *dis) //throws IOException void MoveEntityPacket::write(DataOutputStream *dos) //throws IOException { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); diff --git a/Minecraft.World/MoveEntityPacketSmall.cpp b/Minecraft.World/MoveEntityPacketSmall.cpp index ec67f37f..7d91a15d 100644 --- a/Minecraft.World/MoveEntityPacketSmall.cpp +++ b/Minecraft.World/MoveEntityPacketSmall.cpp @@ -19,7 +19,7 @@ MoveEntityPacketSmall::MoveEntityPacketSmall() MoveEntityPacketSmall::MoveEntityPacketSmall(int id) { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); @@ -42,7 +42,7 @@ void MoveEntityPacketSmall::read(DataInputStream *dis) //throws IOException void MoveEntityPacketSmall::write(DataOutputStream *dos) //throws IOException { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); @@ -99,7 +99,7 @@ void MoveEntityPacketSmall::PosRot::read(DataInputStream *dis) //throws IOExcept void MoveEntityPacketSmall::PosRot::write(DataOutputStream *dos) //throws IOException { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); @@ -138,7 +138,7 @@ void MoveEntityPacketSmall::Pos::read(DataInputStream *dis) //throws IOException void MoveEntityPacketSmall::Pos::write(DataOutputStream *dos) //throws IOException { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); @@ -176,7 +176,7 @@ void MoveEntityPacketSmall::Rot::read(DataInputStream *dis) //throws IOException void MoveEntityPacketSmall::Rot::write(DataOutputStream *dos) //throws IOException { - if( (id < 0 ) || (id >= 2048 ) ) + if( (id < 0 ) || (id >= 16384 ) ) { // We shouln't be tracking an entity that doesn't have a short type of id __debugbreak(); diff --git a/Minecraft.World/Player.cpp b/Minecraft.World/Player.cpp index 07a89e67..85cac0c5 100644 --- a/Minecraft.World/Player.cpp +++ b/Minecraft.World/Player.cpp @@ -1635,6 +1635,10 @@ void Player::attack(shared_ptr entity) } DamageSource *damageSource = DamageSource::playerAttack(dynamic_pointer_cast(shared_from_this())); + + if (bCrit) { + damageSource->setIsCritical(); + } bool wasHurt = entity->hurt(damageSource, dmg); delete damageSource; if (wasHurt) diff --git a/Minecraft.World/SharedConstants.h b/Minecraft.World/SharedConstants.h index a8924e47..c4db88ae 100644 --- a/Minecraft.World/SharedConstants.h +++ b/Minecraft.World/SharedConstants.h @@ -20,7 +20,8 @@ class SharedConstants static wstring readAcceptableChars(); public: - static const int maxChatLength = 100; + static const int maxChatLength = 255; + static const int maxVisibleLength = 100; //to be changed static wstring acceptableLetters; static const int ILLEGAL_FILE_CHARACTERS_LENGTH = 15; diff --git a/Minecraft.World/Socket.cpp b/Minecraft.World/Socket.cpp index f07e3b2b..74e14889 100644 --- a/Minecraft.World/Socket.cpp +++ b/Minecraft.World/Socket.cpp @@ -493,6 +493,10 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int } else { + // Don't write on a closing socket: an orphan whose smallId has been + // recycled would otherwise leak a packet onto the new client. + if( m_socket->isClosing() ) return; + XRNM_SEND_BUFFER buffer; buffer.pbyData = &b[offset]; buffer.dwDataSize = length; diff --git a/Minecraft.World/SoundTypes.h b/Minecraft.World/SoundTypes.h index 388cd43a..eb3283cb 100644 --- a/Minecraft.World/SoundTypes.h +++ b/Minecraft.World/SoundTypes.h @@ -260,6 +260,8 @@ enum eSOUND_TYPE eSoundType_ITEM_ARMOR_equipGeneric5, eSoundType_ITEM_ARMOR_equipGeneric6, + eSoundType_DAMAGE_CRITICAL, + eSoundType_MAX }; diff --git a/Update-NightlyRelease.ps1 b/Update-NightlyRelease.ps1 index 00e45879..4da653a5 100644 --- a/Update-NightlyRelease.ps1 +++ b/Update-NightlyRelease.ps1 @@ -16,7 +16,7 @@ $ErrorActionPreference = "Stop" # --- Configuration --- $RepoOwner = "itsRevela" -$RepoName = "MinecraftConsoles" +$RepoName = "LCE-Revelations" $ReleaseTag = "Nightly" $ReleaseDir = Join-Path $PSScriptRoot "build\Minecraft.Client\Release" $ZipName = "LCREWindows64.zip" diff --git a/cmake/CopyAssets.cmake b/cmake/CopyAssets.cmake index be37c048..d8fc98a0 100644 --- a/cmake/CopyAssets.cmake +++ b/cmake/CopyAssets.cmake @@ -7,16 +7,19 @@ function(setup_asset_folder_copy TARGET_NAME ASSET_FOLDER_PAIRS) "*.xml" "*.lang" "*.bat" "*.cmd" "*.msscmp" "*.binka" # Old audio formats - #"*.swf" # These are built into the .arc + "*.swf" "*.resx" #"*.loc" "*.wav" # Unsupported audio format - "*.xui" + "*.xui" "*.xgs" + "*.xwb" "*.xsb" + "*.xap" "*.xzp" ) # Global folder exclusions applied to every folder copy -# set(ASSET_EXCLUDE_FOLDERS -# "Graphics" -# ) + set(ASSET_EXCLUDE_FOLDERS + #"Graphics" + "Gamerules" + ) # Exclude platform-specific media folders set(PLATFORM_MEDIA_FOLDERS diff --git a/docker-compose.dedicated-server.ghcr.yml b/docker-compose.dedicated-server.ghcr.yml index e03d6bf9..58749e3f 100644 --- a/docker-compose.dedicated-server.ghcr.yml +++ b/docker-compose.dedicated-server.ghcr.yml @@ -1,6 +1,6 @@ services: lce-revelations-dedicated-server: - image: ghcr.io/lce-hub/lce-revelations-dedicated-server:nightly + image: ghcr.io/itsrevela/lce-revelations-dedicated-server:nightly container_name: lce-revelations-dedicated-server restart: unless-stopped tty: true diff --git a/docs/FOURKIT_PORT_RECON.md b/docs/FOURKIT_PORT_RECON.md deleted file mode 100644 index ed84d918..00000000 --- a/docs/FOURKIT_PORT_RECON.md +++ /dev/null @@ -1,180 +0,0 @@ -# FourKit Port Reconnaissance - -Phase 1 deliverable for porting the FourKit plugin system into LCE-Revelations. This document is the source of truth for what needs to change, where, and how risky each change is. Phases 2-3 of the port consume this document directly. - -## Source repos referenced - -| Role | Path | -|---|---| -| Vanilla baseline | `C:\Users\revela\Documents\Minecraft\itsRevela` | -| Donor (has FourKit) | `C:\Users\revela\Documents\Minecraft\FourKit\MinecraftConsoles` | -| Target (this repo) | `C:\Users\revela\Documents\Minecraft\LCE-Hub\LCE-Revelations` | - -Vanilla and donor were forked from upstream at similar points; the donor adds FourKit on top. The target is a more recent independent fork that has its own divergence (Security subsystem, OpManager, revoke-token command, additional ServerProperties fields). - -## Executive summary - -- **27 files** in the donor are FourKit-bearing across `Minecraft.Server/`, `Minecraft.World/`, and `Minecraft.Client/`. Of those, **23 are clean patches** (target identical to vanilla in those files), and **only 4 require manual merge work** (all in `Minecraft.Server/`). -- **7 pure-add native files** drop in unmodified. -- **1 entire managed project** (`Minecraft.Server.FourKit/`) drops in unmodified. -- **No blockers identified.** Every Fire* hook site exists in target at vanilla content; the conflicts are concentrated in build files and a small set of `Minecraft.Server/` source files where target diverged. -- **Native callback ABI is intact.** Spot-checks of player/world/inventory subsystem APIs that FourKitNatives.cpp depends on all match between donor and target. - -## A. File diff matrix - -### A.1 Pure adds (drop in verbatim) - -| File | Notes | -|---|---| -| `Minecraft.Server/FourKitBridge.cpp` | Event firing + callback registry | -| `Minecraft.Server/FourKitBridge.h` | Public Fire* and Handle* surface | -| `Minecraft.Server/FourKitNatives.cpp` | C# → C++ callback implementations (~80 functions) | -| `Minecraft.Server/FourKitNatives.h` | Native function declarations | -| `Minecraft.Server/FourKitRuntime.cpp` | hostfxr load + .NET 10 bootstrap | -| `Minecraft.Server/FourKitRuntime.h` | Runtime init API | -| `Minecraft.Server/FourKitMappers.cpp` | Type/enum mapping helpers | -| `Minecraft.Server.FourKit/` (entire project) | Managed plugin host, Bukkit-style API, ~54 events | - -### A.2 Modified files in `Minecraft.Server/` (the merge work) - -| File | Donor delta | Target diverges from vanilla? | Merge class | -|---|---|---|---| -| `CMakeLists.txt` | +24 lines (FourKit dep + post-build copy) | No | CLEAN_PATCH | -| `cmake/sources/Common.cmake` | +7 FourKit sources, donor also strips OpManager/Security (donor lacks those) | No | **SELECTIVE_MERGE**: add donor's FourKit sources only; preserve target's OpManager/Security entries | -| `Windows64/ServerMain.cpp` | FourKit init/shutdown + 1 inline FireWorldSave | No | CLEAN_PATCH | -| `Console/ServerCliEngine.cpp` | HandleConsoleCommand hook + GetPluginCommandHelp integration | No | CLEAN_PATCH | -| `Console/commands/help/CliCommandHelp.cpp` | Plugin command help integration | Unknown: check before merge | LIKELY_CLEAN | -| `Console/commands/whitelist/CliCommandWhitelist.cpp` | Unknown delta | Unknown | LIKELY_CLEAN | -| `Access/Access.cpp` | Donor delta unrelated to FourKit hooks per grep | No | CLEAN_PATCH (verify) | -| `Access/Access.h` | Same | No | CLEAN_PATCH (verify) | -| `ServerLogManager.cpp` | Logger plumbing for FourKit log routing | No | CLEAN_PATCH | -| `ServerLogManager.h` | Same | No | CLEAN_PATCH | -| `ServerLogger.cpp` | Same | No | CLEAN_PATCH | -| `ServerProperties.cpp` | Donor REMOVES fields target relies on | **Yes** | **CONFLICT: KEEP TARGET** | -| `ServerProperties.h` | Same | **Yes** | **CONFLICT: KEEP TARGET** | - -### A.3 Modified files in `Minecraft.World/` and `Minecraft.Client/` - -The donor changed 53 files in `Minecraft.World/` and 95 files in `Minecraft.Client/` overall, but most of those are upstream churn unrelated to FourKit. The FourKit-specific subset is identified by `grep "FourKit"` and yields **23 files**, all of which are identical between vanilla and target: i.e., every single one is CLEAN_PATCH. - -#### `Minecraft.World/` FourKit hook files (17, all CLEAN_PATCH) - -`AbstractContainerMenu.cpp`, `CactusTile.cpp`, `CocoaTile.cpp`, `CropTile.cpp`, `EggTile.cpp`, `FireTile.cpp`, `GrassTile.cpp`, `ItemEntity.cpp`, `LiquidTileDynamic.cpp`, `LivingEntity.cpp`, `Mushroom.cpp`, `NetherWartTile.cpp`, `PistonBaseTile.cpp`, `ReedTile.cpp`, `Sapling.cpp`, `StemTile.cpp`, `ThrownEnderpearl.cpp` - -#### `Minecraft.Client/` FourKit hook files (6, all CLEAN_PATCH) - -`PendingConnection.cpp`, `PlayerConnection.cpp`, `ServerLevel.cpp`, `ServerPlayer.cpp`, `ServerPlayerGameMode.cpp`, `TeleportCommand.cpp` - -> **Note on naming:** Despite the `Minecraft.Client/` folder name, several of these files (`PlayerConnection`, `ServerPlayer`, `ServerLevel`, etc.) are shared engine code that the dedicated server also compiles. They are valid hook sites for a server-side plugin system. - -#### Build files in `Minecraft.World/` and `Minecraft.Client/` - -The donor also modifies `Minecraft.World/cmake/sources/Common.cmake`, `Minecraft.Client/cmake/sources/Common.cmake`, `Minecraft.Client/cmake/sources/Windows.cmake`, and `Minecraft.Client/CMakeLists.txt`, but none of these contain FourKit references: they are upstream churn and should NOT be touched as part of the FourKit port. - -## B. Fire* hook site inventory - -Donor exposes Fire*/Handle* via `FourKitBridge::` (see `Minecraft.Server/FourKitBridge.h`). Hook calls appear in 26 source files across the three subprojects. - -### B.1 Hook sites by subsystem - -| Subsystem | Files (in donor) | -|---|---| -| Server lifecycle | `Minecraft.Server/Windows64/ServerMain.cpp` (init, shutdown, FireWorldSave) | -| Console / commands | `Minecraft.Server/Console/ServerCliEngine.cpp` (HandleConsoleCommand, GetPluginCommandHelp), `Minecraft.Server/Console/commands/help/CliCommandHelp.cpp` | -| Player connection / login | `Minecraft.Client/PlayerConnection.cpp`, `Minecraft.Client/PendingConnection.cpp` | -| Player gameplay | `Minecraft.Client/ServerPlayer.cpp`, `Minecraft.Client/ServerPlayerGameMode.cpp`, `Minecraft.Client/TeleportCommand.cpp` | -| World / level | `Minecraft.Client/ServerLevel.cpp` | -| Living entities & damage | `Minecraft.World/LivingEntity.cpp`, `Minecraft.World/ItemEntity.cpp`, `Minecraft.World/ThrownEnderpearl.cpp` | -| Block / tile (growth, burn, spread, ignite, piston) | `Minecraft.World/CactusTile.cpp`, `CocoaTile.cpp`, `CropTile.cpp`, `EggTile.cpp`, `FireTile.cpp`, `GrassTile.cpp`, `LiquidTileDynamic.cpp`, `Mushroom.cpp`, `NetherWartTile.cpp`, `PistonBaseTile.cpp`, `ReedTile.cpp`, `Sapling.cpp`, `StemTile.cpp` | -| Inventory / containers | `Minecraft.World/AbstractContainerMenu.cpp` | - -### B.2 Hook insertion-site safety - -Per A.3, **every file in B.1 outside of `Minecraft.Server/` is byte-identical between vanilla and target**. That means donor's hook insertions can be applied as a straight patch without adapting around target-side refactors. - -In `Minecraft.Server/`, the hook-bearing files (`ServerMain.cpp`, `ServerCliEngine.cpp`, `CliCommandHelp.cpp`) are also identical or near-identical to vanilla per A.2. Net result: **the locked Phase 3 hook adaptation policy ("adapt to LCE-Revelations refactors") will not need to fire** for any hook site in the current state of the target. If the user pulls upstream changes between now and Phase 3, this assumption must be re-validated. - -### B.3 Three concrete hook sites verified end-to-end (others follow same pattern) - -1. **`Minecraft.Server/Windows64/ServerMain.cpp:681`**: `FourKitBridge::FireWorldSave()` inside the autosave trigger block. Target file is byte-identical to vanilla → clean insertion. -2. **`Minecraft.Server/Console/ServerCliEngine.cpp:165`**: `if (FourKitBridge::HandleConsoleCommand(normalizedLine)) return true;` early return for plugin command preprocessing. Target file byte-identical → clean insertion. -3. **`Minecraft.Server/Console/ServerCliEngine.cpp:215-246`**: `FourKitBridge::GetPluginCommandHelp(...)` integration into the help printer. Same file, clean insertion. - -## C. Native callback surface (FourKitNatives.cpp ↔ engine APIs) - -`FourKitNatives.cpp` implements ~80 functions that wrap engine APIs and expose them to managed code. Spot-check of the highest-risk subsystems: - -| Native function | Engine API called | Target has it? | Signature drift? | -|---|---|---|---| -| `NativeSetPlayerHealth(int, float)` | `ServerPlayer::setHealth(float)` | Yes | None | -| `NativeTeleportPlayer(int, double, double, double)` | `PlayerConnection::teleport(...)` | Yes | None | -| `NativeSetTile(int, int, int, int, int, int)` | `ServerLevel::setBlock(...)` | Yes | None | -| `NativeGetPlayerAddress(int, char*, int, int*)` | `PlayerConnection` accessors | Yes | None | -| `NativeGetPlayerLatency(int)` | `PlayerConnection::latency` | Yes (field) | None | -| `NativeBanPlayer(int, char*, int)` | `Access::AddPlayerBan(...)` | Yes | None | -| `NativeSetItemMeta(int, int, char*, int)` | `ItemInstance` serialization | Yes | None | -| `NativeGetContainerContents(int, int*, int)` | `AbstractContainerMenu` traversal | Yes | None | - -**Verdict:** No drift detected in critical subsystems. Phase 2's native bridge build is expected to compile against target's headers without shimming. Any drift discovered at compile time will be a Phase 2.3 finding, not a known risk. - -## D. Critical hot files - -| File | Vanilla LOC | Donor LOC | Target LOC | Status | -|---|---|---|---|---| -| `Minecraft.Server/Windows64/ServerMain.cpp` | 1,257 | 1,257 | 1,257 | Identical across all three. Donor's FourKit additions sit on the vanilla baseline; target has not touched it. | -| `Minecraft.Server/CMakeLists.txt` | 86 | 110 (+24) | 86 | Target is vanilla. Donor adds FourKit deps and a post-build copy step. Clean apply. | -| `Minecraft.Server/cmake/sources/Common.cmake` | 628 | 640 (+12 net; donor also strips ~16 lines of OpManager/Security) | 628 | Target is vanilla. **Selective merge:** apply donor's `FourKit*.cpp` additions, do NOT apply donor's removals. | -| `Minecraft.Server/ServerProperties.cpp` | 965 | 930 (-35) | 965 | Donor is older / leaner; target carries hardcore + security fields donor never had. **Keep target version unchanged**, do not merge donor's deletions. | -| `Minecraft.Server/ServerProperties.h` | n/a | n/a | n/a | Same logic: keep target. | -| `Minecraft.Server/Console/commands/` | 20 subdirs | 18 subdirs (no `revoketoken`) | 20 subdirs (has `revoketoken`) | Donor predates `revoketoken`; target has it. Keep target's command set. | - -## E. Top-level structure of the target - -| Item | Status | -|---|---| -| `samples/` at repo root | NOT FOUND: Phase 7 will create it | -| `docs/` at repo root | NOT FOUND: created by this document | -| `docker/` | EXISTS: contains `dedicated-server/{Dockerfile,entrypoint.sh}`. Phase 5 must inspect to decide whether the image builds the server from source or consumes the release zip | -| `Minecraft.Server.FourKit/` | NOT FOUND (as expected) | - -## Merge strategy summary (input to Phase 2 and Phase 3) - -1. **Phase 2 source drop:** Copy the 7 PURE_ADD files into `Minecraft.Server/` verbatim. Copy the entire `Minecraft.Server.FourKit/` project to repo root verbatim. -2. **Phase 2 build files:** - - `Minecraft.Server/CMakeLists.txt`: apply donor's diff verbatim (target is vanilla). - - `Minecraft.Server/cmake/sources/Common.cmake`: apply ONLY the donor's additions of `FourKit*.cpp`. Do not delete OpManager or Security entries. -3. **Phase 3 hooks (the entire surface, all CLEAN_PATCH):** - - 3 sites in `Minecraft.Server/` (`ServerMain.cpp`, `ServerCliEngine.cpp`, `CliCommandHelp.cpp`) - - 17 sites in `Minecraft.World/` (block/entity/container) - - 6 sites in `Minecraft.Client/` (player/level/connection) - - 1 lifecycle hook pair in `ServerMain.cpp` for `FourKitRuntime::Initialize` / shutdown - - Logger plumbing in `ServerLogManager.{cpp,h}` and `ServerLogger.cpp` -4. **Phase 3 conflicts to leave alone:** - - `ServerProperties.{cpp,h}`: keep target version, do not merge donor. - - `Console/commands/revoketoken/`: keep target version, donor lacks it. - - `Access/OpManager.{cpp,h}`: keep target version, donor lacks it. - - `Security/`: keep target version, donor lacks it. -5. **Spot-checks needed before merge:** - - `Console/commands/help/CliCommandHelp.cpp`: verify donor's delta is purely the plugin help integration and not entangled with anything target changed. - - `Console/commands/whitelist/CliCommandWhitelist.cpp`: confirm scope of donor's edit. - - `Access/Access.{cpp,h}`: confirm donor's delta is not security-related. - -## Risks updated post-recon - -| Risk | Original severity | Post-recon severity | Reason | -|---|---|---|---| -| LCE-Revelations divergence at hook sites | HIGH | **LOW** | All 26 hook-bearing source files are byte-identical between vanilla and target. | -| CMake structural divergence | HIGH | **LOW** | Target's `CMakeLists.txt` and `Common.cmake` are byte-identical to vanilla. | -| ServerMain.cpp init-order conflicts | MEDIUM | **LOW** | File is identical to vanilla. | -| ServerProperties conflict | NEW | **MEDIUM** | Donor strips fields target relies on. Mitigation: do not merge donor's version. | -| Native ABI drift in subsystem APIs | MEDIUM | **LOW** | Spot-checks all clean. Compile-time will catch any miss. | -| Donor predates target features (revoketoken, OpManager, Security) | NEW | **LOW** | Easy to preserve target's additions; donor doesn't depend on their absence. | -| Self-contained .NET 10 publish size | MEDIUM | **MEDIUM** | Unchanged. ~70-100 MB release zip growth. | -| Hot-reload absent | LOW | **LOW** | Inherited limitation, documented. | - -## Open follow-ups for Phase 2 - -1. Confirm `Console/commands/help/CliCommandHelp.cpp` and `Console/commands/whitelist/CliCommandWhitelist.cpp` deltas are FourKit-related (currently marked LIKELY_CLEAN, not verified). -2. Confirm `Access/Access.{cpp,h}` delta scope. -3. Decide whether the donor's changes to `ServerLogManager` and `ServerLogger` are strictly additive (capture for FourKit log routing) or entangled with other refactoring. -4. Confirm during Phase 2.3 that `FourKitRuntime.cpp`'s hostfxr loader handles the case where `hostfxr.dll` is absent gracefully (server should warn and continue, not crash): this matters because the self-contained publish puts hostfxr next to the server exe, so the failure mode only fires if a user deletes it. diff --git a/flake.nix b/flake.nix index 7f474e86..0dfdb3da 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "MinecraftConsoles - Minecraft Legacy Console Edition recreation"; + description = "LCE-Revelations - Minecraft Legacy Console Edition recreation"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -503,7 +503,7 @@ XWIN_CACHE = "$HOME/.cache/xwin"; shellHook = '' - echo "MinecraftConsoles development shell" + echo "LCE-Revelations development shell" echo "" echo "Quick build (uses cached SDK):" echo " nix build .#client # Build client package"