Files
GabsPuNs-Project_Zenith_Main/Minecraft.Client/Common/UI/UIScene_LoadCreateJoinMenu.cpp

1935 lines
51 KiB
C++

// Non-RmlUi includes that depend on Windows macros
#include "UI.h"
#include "UIScene_LoadCreateJoinMenu.h"
#include "../../../Minecraft.World/StringHelpers.h"
#include "../../../Minecraft.World/net.minecraft.world.item.h"
#include "../../../Minecraft.World/net.minecraft.world.level.h"
#include "../../../Minecraft.World/net.minecraft.world.level.chunk.storage.h"
#include "../../../Minecraft.World/ConsoleSaveFile.h"
#include "../../../Minecraft.World/ConsoleSaveFileOriginal.h"
#include "../../ProgressRenderer.h"
#include "../../MinecraftServer.h"
#include "../../TexturePackRepository.h"
#include "../../TexturePack.h"
#include "../Network/SessionInfo.h"
#include "../../../Minecraft.World/NbtIo.h"
#include "../../../Minecraft.World/compression.h"
#include "../../Windows64/KeyboardMouseInput.h"
#include "UISplitScreenHelpers.h"
#include <vector>
// RmlUi includes (Windows macros temporarily suppressed)
#pragma push_macro("byte")
#pragma push_macro("GetNextSibling")
#pragma push_macro("GetFirstChild")
#undef byte
#undef GetNextSibling
#undef GetFirstChild
#include "RmlManager.h"
#include "../../Windows64/KeyboardMouseInput.h"
#include <RmlUi/Core/Input.h>
#include <RmlUi/Core/Context.h>
#include <RmlUi/Core/Element.h>
#pragma pop_macro("GetFirstChild")
#pragma pop_macro("GetNextSibling")
#pragma pop_macro("byte")
#ifdef _WINDOWS64
static wstring GetPreferredLevelGenSaveTitle(LevelGenerationOptions* levelGen)
{
if (levelGen == nullptr)
return L"";
if (levelGen->isTutorial())
return app.GetString(IDS_TUTORIALSAVENAME);
LPCWSTR displayName = levelGen->getDisplayName();
if (displayName != nullptr && displayName[0] != L'\0')
return displayName;
wstring defaultSaveName = levelGen->getDefaultSaveName();
if (!defaultSaveName.empty())
return defaultSaveName;
LPCWSTR worldName = levelGen->getWorldName();
if (worldName != nullptr && worldName[0] != L'\0')
return worldName;
return L"";
}
static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
{
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
if (hFile == INVALID_HANDLE_VALUE) return L"";
DWORD fileSize = GetFileSize(hFile, nullptr);
if (fileSize < 12 || fileSize == INVALID_FILE_SIZE)
{
CloseHandle(hFile);
return L"";
}
std::vector<uint8_t> rawData(fileSize);
DWORD bytesRead = 0;
if (!ReadFile(hFile, rawData.data(), fileSize, &bytesRead, nullptr) || bytesRead != fileSize)
{
CloseHandle(hFile);
return L"";
}
CloseHandle(hFile);
std::vector<uint8_t> decompressedData;
uint8_t* saveData = nullptr;
unsigned int saveSize = 0;
if (*reinterpret_cast<unsigned int*>(rawData.data()) == 0)
{
unsigned int decompSize = *reinterpret_cast<unsigned int*>(rawData.data() + 4);
if (decompSize == 0 || decompSize > 128 * 1024 * 1024)
{
return L"";
}
decompressedData.resize(decompSize);
Compression::getCompression()->Decompress(decompressedData.data(), &decompSize, rawData.data() + 8, fileSize - 8);
saveData = decompressedData.data();
saveSize = decompSize;
}
else
{
saveData = rawData.data();
saveSize = fileSize;
}
wstring result = L"";
if (saveSize >= 12)
{
unsigned int headerOffset = *reinterpret_cast<unsigned int*>(saveData);
unsigned int numEntries = *reinterpret_cast<unsigned int*>(saveData + 4);
const unsigned int entrySize = sizeof(FileEntrySaveData);
if (headerOffset < saveSize && numEntries > 0 && numEntries < 10000 &&
headerOffset + numEntries * entrySize <= saveSize)
{
auto* table = reinterpret_cast<FileEntrySaveData*>(saveData + headerOffset);
for (unsigned int i = 0; i < numEntries; i++)
{
if (wcscmp(table[i].filename, L"level.dat") == 0)
{
unsigned int off = table[i].startOffset;
unsigned int len = table[i].length;
if (off >= 12 && off + len <= saveSize && len > 0 && len < 4 * 1024 * 1024)
{
byteArray ba;
ba.data = saveData + off;
ba.length = len;
std::unique_ptr<CompoundTag> root(NbtIo::decompress(ba));
if (root != nullptr)
{
CompoundTag* dataTag = root->getCompound(L"Data");
if (dataTag != nullptr)
result = dataTag->getString(L"LevelName");
}
}
break;
}
}
}
}
if (result == L"world") result = L"";
return result;
}
#endif
#define JOIN_LOAD_ONLINE_TIMER_ID 0
#define JOIN_LOAD_ONLINE_TIMER_TIME 100
// -- RmlUi helper methods --
void UIScene_LoadCreateJoinMenu::ShowTabPanel(const char* panelId, bool show)
{
if (!m_document) return;
auto* el = m_document->GetElementById(panelId);
if (el) el->SetProperty("display", show ? "flex" : "none");
}
void UIScene_LoadCreateJoinMenu::SetTabButtonState(int tabIndex)
{
if (!m_document) return;
const char* tabIds[] = { "tab_load", "tab_create", "tab_join" };
for (int i = 0; i < 3; i++)
{
auto* btn = m_document->GetElementById(tabIds[i]);
if (btn)
btn->SetClass("selected", i == tabIndex);
}
}
void UIScene_LoadCreateJoinMenu::ClearList(const char* listId)
{
if (!m_document) return;
auto* container = m_document->GetElementById(listId);
if (!container) return;
while (container->GetNumChildren() > 0)
container->RemoveChild(container->GetChild(0));
}
void UIScene_LoadCreateJoinMenu::AddListItem(const char* listId, const wstring& text, const wchar_t* textureName)
{
if (!m_document) return;
auto* container = m_document->GetElementById(listId);
if (!container) return;
Rml::ElementPtr itemPtr = m_document->CreateElement("div");
Rml::Element* item = itemPtr.get();
item->SetClass("list_item", true);
Rml::ElementPtr btnPtr = m_document->CreateElement("button");
Rml::Element* btn = btnPtr.get();
if (textureName && wcslen(textureName) > 0)
{
Rml::ElementPtr imgPtr = m_document->CreateElement("img");
Rml::Element* img = imgPtr.get();
img->SetClass("thumb", true);
char texBuf[512];
WideCharToMultiByte(CP_UTF8, 0, textureName, -1, texBuf, sizeof(texBuf), nullptr, nullptr);
img->SetAttribute("src", texBuf);
btn->AppendChild(std::move(imgPtr));
}
// Convert wide string to narrow for RmlUi
char narrowBuf[512];
int charsWritten = WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, narrowBuf, sizeof(narrowBuf), nullptr, nullptr);
if (charsWritten > 0)
{
Rml::ElementPtr spanPtr = m_document->CreateElement("span");
Rml::Element* span = spanPtr.get();
span->SetInnerRML(narrowBuf);
btn->AppendChild(std::move(spanPtr));
}
// Set tabindex and unique ID for keyboard navigation and event identification
btn->SetAttribute("tabindex", "-1");
int index = container->GetNumChildren();
char btnId[64];
sprintf_s(btnId, "%s_btn_%d", listId, index);
btn->SetId(btnId);
btn->AddEventListener(Rml::EventId::Click, this);
item->AppendChild(std::move(btnPtr));
container->AppendChild(std::move(itemPtr));
}
int UIScene_LoadCreateJoinMenu::GetListItemCount(const char* listId)
{
if (!m_document) return 0;
auto* container = m_document->GetElementById(listId);
if (!container) return 0;
return container->GetNumChildren();
}
void UIScene_LoadCreateJoinMenu::SetListTexture(int itemIndex, const char* listId, const wchar_t* textureName)
{
// RmlUi dynamic texture assignment would go here if needed.
// Currently thumbnails are not rendered via RmlUi decorators.
(void)itemIndex; (void)listId; (void)textureName;
}
// -- Construction / Destruction --
UIScene_LoadCreateJoinMenu::UIScene_LoadCreateJoinMenu(int iPad, void* initData, UILayer* parentLayer) : UIScene_RmlBase(iPad, parentLayer)
{
constexpr uint64_t MAXIMUM_SAVE_STORAGE = 4LL * 1024LL * 1024LL * 1024LL;
// Initialize all members before any function calls
m_iRequestingThumbnailId = 0;
m_iSaveInfoC = 0;
m_bIgnoreInput = false;
m_bShowingPartyGamesOnly = false;
m_bInParty = false;
m_activeTab = eTab_Load;
m_bPendingSaveSizeBarRefresh = false;
m_bPendingJoinTabAvailabilityRefresh = false;
m_bPendingJoinVisualRefresh = false;
m_bRebuildingJoinVisual = false;
#ifdef _WINDOWS64
m_lastHoverMouseX = -1;
m_lastHoverMouseY = -1;
m_mouseHoverTracked = false;
m_hoverBaseIndexLoad = 0;
m_hoverBaseIndexCreate = 0;
m_hoverBaseIndexJoin = 0;
#endif
m_currentSessions = nullptr;
m_iState = e_SavesIdle;
m_bHasNoGamesLabel = false;
m_bUpdateSaveSize = false;
m_bAllLoaded = false;
m_bRetrievingSaveThumbnails = false;
m_bSaveThumbnailReady = false;
m_bExitScene = false;
m_pSaveDetails = nullptr;
m_bSavesDisplayed = false;
m_saveDetails = nullptr;
m_iSaveDetailsCount = 0;
m_iTexturePacksNotInstalled = 0;
m_bCopying = false;
m_bCopyingCancelled = false;
m_eAction = eAction_None;
// Load RmlUi document
Rml::Context* ctx = RmlManager::Get().GetContext();
if (ctx)
{
m_document = ctx->LoadDocument("LoadCreateJoinMenu.rml");
if (m_document)
{
m_document->Show();
// Register tab button click listeners
auto registerTab = [&](const char* id) {
auto* el = m_document->GetElementById(id);
if (el) el->AddEventListener(Rml::EventId::Click, this);
};
registerTab("tab_load");
registerTab("tab_create");
registerTab("tab_join");
// Start on Load tab
SetActiveTab(eTab_Load, false);
// Focus the load tab initially for keyboard navigation
auto* tabLoad = m_document->GetElementById("tab_load");
if (tabLoad) tabLoad->Focus();
ShowTabPanel("load_timer", true);
ShowTabPanel("create_timer", false);
ShowTabPanel("join_timer", false);
}
}
app.SetLiveLinkRequired(true);
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive(m_iPad) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
int iLB = -1;
StorageManager.ClearSavesInfo();
Initialise();
UpdateGamesList();
g_NetworkManager.SetSessionsUpdatedCallback(&UpdateGamesListCallback, this);
m_initData = new JoinMenuInitData();
MinecraftServer::resetFlags();
if (!m_bIgnoreInput)
app.m_dlcManager.checkForCorruptDLCAndAlert();
}
UIScene_LoadCreateJoinMenu::~UIScene_LoadCreateJoinMenu()
{
g_NetworkManager.SetSessionsUpdatedCallback(nullptr, nullptr);
app.SetLiveLinkRequired(false);
if (m_currentSessions)
{
for (const auto& it : *m_currentSessions)
delete it;
delete m_currentSessions;
m_currentSessions = nullptr;
}
if (m_saveDetails)
{
for (int i = 0; i < m_iSaveDetailsCount; ++i)
delete m_saveDetails[i].pbThumbnailData;
delete [] m_saveDetails;
}
// RmlUi cleanup
if (m_document)
{
auto removeTab = [&](const char* id) {
auto* el = m_document->GetElementById(id);
if (el) el->RemoveEventListener(Rml::EventId::Click, this);
};
removeTab("tab_load");
removeTab("tab_create");
removeTab("tab_join");
m_document->Close();
m_document = nullptr;
}
}
void UIScene_LoadCreateJoinMenu::ProcessEvent(Rml::Event& event)
{
if (m_bIgnoreInput)
return;
const Rml::String& id = event.GetCurrentElement()->GetId();
if (event == Rml::EventId::Click)
{
if (id == "tab_load") { ui.PlayUISFX(eSFX_Press); SetActiveTab(eTab_Load, true); }
else if (id == "tab_create") { ui.PlayUISFX(eSFX_Press); SetActiveTab(eTab_Create, true); }
else if (id == "tab_join") { ui.PlayUISFX(eSFX_Press); SetActiveTab(eTab_Join, true); }
else
{
// List item button clicks: pattern "load_list_btn_0", "create_list_btn_1", "join_list_btn_2"
size_t suffixPos = id.find("_btn_");
if (suffixPos != Rml::String::npos)
{
Rml::String listId = id.substr(0, suffixPos);
int index = atoi(id.c_str() + suffixPos + 5);
EControls control;
if (listId == "load_list")
control = eControl_SavesList;
else if (listId == "create_list")
control = eControl_NewGamesList;
else if (listId == "join_list")
control = eControl_GamesList;
else
return;
handlePress(static_cast<F64>(control), static_cast<F64>(index));
}
}
}
}
// -- Update / Tooltips / Components --
void UIScene_LoadCreateJoinMenu::updateTooltips()
{
int iRB = -1;
int iY = -1;
int iLB = -1;
int iX = -1;
int iLS = IDS_TOOLTIPS_NAVIGATE;
bool savesListHasFocus = (m_activeTab == eTab_Load);
bool hasSaves = (m_pSaveDetails != nullptr) && (m_pSaveDetails->iSaveC > 0) && (GetListItemCount("load_list") > 0);
if ((m_activeTab == eTab_Load) && hasSaves)
{
if (StorageManager.GetSaveDisabled())
iY = IDS_TOOLTIPS_DELETESAVE;
else if (StorageManager.EnoughSpaceForAMinSaveGame())
iY = IDS_TOOLTIPS_SAVEOPTIONS;
else
iY = IDS_TOOLTIPS_DELETESAVE;
}
else if (DoesMashUpWorldHaveFocus())
iY = IDS_TOOLTIPS_HIDE;
if (ProfileManager.IsFullVersion() == false)
iRB = -1;
else if (StorageManager.GetSaveDisabled())
{
#ifdef _XBOX
iX = IDS_TOOLTIPS_SELECTDEVICE;
#endif
}
else
iX = IDS_TOOLTIPS_CHANGEDEVICE;
ui.SetTooltips(DEFAULT_XUI_MENU_USER, IDS_TOOLTIPS_SELECT, IDS_TOOLTIPS_BACK, iX, iY, -1, -1, iLB, iRB, iLS, -1, -1,
true);
}
void UIScene_LoadCreateJoinMenu::updateComponents()
{
m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, true);
m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, false);
}
UIControl* UIScene_LoadCreateJoinMenu::GetMainPanel()
{
return nullptr;
}
void UIScene_LoadCreateJoinMenu::handleDestroy()
{
#if ( defined __PS3__ || defined __ORBIS__ || defined _DURANGO)
InputManager.DestroyKeyboard();
#endif
}
void UIScene_LoadCreateJoinMenu::handleGainFocus(bool navBack)
{
UIScene::handleGainFocus(navBack);
updateTooltips();
SyncMovieTab();
m_bPendingSaveSizeBarRefresh = true;
m_bPendingJoinTabAvailabilityRefresh = true;
addTimer(JOIN_LOAD_ONLINE_TIMER_ID,JOIN_LOAD_ONLINE_TIMER_TIME);
if (navBack)
{
app.SetLiveLinkRequired(true);
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive(m_iPad) && ProfileManager.
AllowedToPlayMultiplayer(m_iPad);
m_bIgnoreInput = false;
if (!m_bMultiplayerAllowed)
{
ClearList("join_list");
ShowTabPanel("join_timer", false);
}
if (app.GetCorruptSaveDeleted())
{
m_iState = e_SavesRepopulateAfterDelete;
app.SetCorruptSaveDeleted(false);
}
}
}
// -- Initialise --
void UIScene_LoadCreateJoinMenu::Initialise()
{
m_iSaveListIndex = 0;
m_iGameListIndex = 0;
#ifdef _WINDOWS64
m_addServerPhase = eAddServer_Idle;
#endif
m_iDefaultButtonsC = 0;
m_iMashUpButtonsC = 0;
if (ProfileManager.IsFullVersion() == false)
{
AddDefaultButtons();
}
else if (StorageManager.GetSaveDisabled())
{
{
GetSaveInfo();
ShowTabPanel("load_timer", true);
}
}
else
{
bool bCanRename = StorageManager.EnoughSpaceForAMinSaveGame();
GetSaveInfo();
ShowTabPanel("load_timer", true);
}
m_bIgnoreInput = true;
app.m_dlcManager.checkForCorruptDLCAndAlert();
}
// -- Thumbnail / Save callbacks --
int UIScene_LoadCreateJoinMenu::LoadSaveDataThumbnailReturned(LPVOID lpParam, PBYTE pbThumbnail, DWORD dwThumbnailBytes)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(lpParam);
if(pbThumbnail && dwThumbnailBytes)
{
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].pbThumbnailData = new BYTE[dwThumbnailBytes];
memcpy(pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].pbThumbnailData, pbThumbnail, dwThumbnailBytes);
pClass->m_saveDetails[pClass->m_iRequestingThumbnailId].dwThumbnailSize = dwThumbnailBytes;
}
else
pClass->m_bSaveThumbnailReady = true;
return 0;
}
int UIScene_LoadCreateJoinMenu::LoadSaveCallback(LPVOID lpParam, bool bRes)
{
return 0;
}
// -- Timer --
void UIScene_LoadCreateJoinMenu::handleTimerComplete(int id)
{
switch (id)
{
case JOIN_LOAD_ONLINE_TIMER_ID:
{
bool bMultiplayerAllowed = ProfileManager.IsSignedInLive(m_iPad) && ProfileManager.
AllowedToPlayMultiplayer(m_iPad);
if (bMultiplayerAllowed != m_bMultiplayerAllowed)
{
if (bMultiplayerAllowed)
{
}
else
{
m_bInParty = false;
ClearList("join_list");
ShowTabPanel("join_timer", false);
m_bHasNoGamesLabel = false;
}
m_bMultiplayerAllowed = bMultiplayerAllowed;
}
}
break;
}
}
// -- Input Handling --
void UIScene_LoadCreateJoinMenu::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool& handled)
{
if (m_bIgnoreInput)
return;
if (!m_bSavesDisplayed)
return;
Rml::Context* ctx = RmlManager::Get().GetContext();
if (!ctx)
return;
ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released);
auto openSaveOptions = [&]() -> bool
{
if (!pressed || repeat || !ProfileManager.IsFullVersion() || m_activeTab != eTab_Load)
return false;
if ((m_pSaveDetails != nullptr) &&
(m_pSaveDetails->iSaveC > 0) &&
(GetListItemCount("load_list") > 0) &&
(m_iSaveListIndex >= 0) &&
(m_iSaveListIndex < GetListItemCount("load_list")))
{
m_bIgnoreInput = true;
if (StorageManager.EnoughSpaceForAMinSaveGame() && !StorageManager.GetSaveDisabled())
{
UINT uiIDA[3];
uiIDA[0] = IDS_CONFIRM_CANCEL;
uiIDA[1] = IDS_TITLE_RENAMESAVE;
uiIDA[2] = IDS_TOOLTIPS_DELETESAVE;
ui.RequestAlertMessage(IDS_TOOLTIPS_SAVEOPTIONS, IDS_TEXT_SAVEOPTIONS, uiIDA, 3, iPad,
&UIScene_LoadCreateJoinMenu::SaveOptionsDialogReturned, this);
}
else
{
UINT uiIDA[2];
uiIDA[0] = IDS_CONFIRM_CANCEL;
uiIDA[1] = IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, iPad,
&UIScene_LoadCreateJoinMenu::DeleteSaveDialogReturned, this);
}
ui.PlayUISFX(eSFX_Press);
return true;
}
return false;
};
auto hideFocusedMashupWorld = [&]() -> bool
{
if (!pressed || repeat || !ProfileManager.IsFullVersion() || !DoesMashUpWorldHaveFocus())
return false;
const int lGenID = m_iNewGameListIndex - m_iDefaultButtonsC;
if ((lGenID < 0) || (lGenID >= static_cast<int>(m_generators.size())))
return false;
LevelGenerationOptions* levelGen = m_generators.at(lGenID);
if (!levelGen || levelGen->isTutorial() || !levelGen->requiresTexturePack())
return false;
const int oldIndex = m_iNewGameListIndex;
m_bIgnoreInput = true;
app.HideMashupPackWorld(m_iPad, levelGen->getRequiredTexturePackId());
AddDefaultButtons();
if (GetListItemCount("create_list") <= 0)
m_iNewGameListIndex = 0;
else if (oldIndex >= GetListItemCount("create_list"))
m_iNewGameListIndex = GetListItemCount("create_list") - 1;
else if (oldIndex >= 0)
m_iNewGameListIndex = oldIndex;
updateTooltips();
m_iState = e_SavesIdle;
ui.PlayUISFX(eSFX_Press);
m_bIgnoreInput = false;
return true;
};
switch (key)
{
case ACTION_MENU_CANCEL:
if (pressed)
{
navigateBack();
handled = true;
}
break;
case ACTION_MENU_Y:
#ifdef _WINDOWS64
if (hideFocusedMashupWorld())
{
handled = true;
break;
}
if (openSaveOptions())
{
handled = true;
break;
}
#endif
break;
case ACTION_MENU_RIGHT_SCROLL:
case ACTION_MENU_RIGHT:
if (pressed && !repeat)
{
ui.PlayUISFX(eSFX_Focus);
if (m_activeTab == eTab_Load)
SetActiveTab(eTab_Create, true);
else if (m_activeTab == eTab_Create)
SetActiveTab(eTab_Join, true);
else
SetActiveTab(eTab_Load, true);
handled = true;
}
break;
case ACTION_MENU_LEFT_SCROLL:
case ACTION_MENU_LEFT:
if (pressed && !repeat)
{
ui.PlayUISFX(eSFX_Focus);
if (m_activeTab == eTab_Load)
SetActiveTab(eTab_Join, true);
else if (m_activeTab == eTab_Create)
SetActiveTab(eTab_Load, true);
else
SetActiveTab(eTab_Create, true);
handled = true;
}
break;
case ACTION_MENU_OK:
case ACTION_MENU_UP:
case ACTION_MENU_DOWN:
case ACTION_MENU_PAGEUP:
case ACTION_MENU_PAGEDOWN:
if (pressed)
{
switch (key)
{
case ACTION_MENU_UP: ctx->ProcessKeyDown(Rml::Input::KI_UP, 0); break;
case ACTION_MENU_DOWN: ctx->ProcessKeyDown(Rml::Input::KI_DOWN, 0); break;
case ACTION_MENU_OK: ctx->ProcessKeyDown(Rml::Input::KI_RETURN, 0); break;
case ACTION_MENU_PAGEUP: ctx->ProcessKeyDown(Rml::Input::KI_PRIOR, 0); break;
case ACTION_MENU_PAGEDOWN: ctx->ProcessKeyDown(Rml::Input::KI_NEXT, 0); break;
}
handled = true;
}
break;
case ACTION_MENU_OTHER_STICK_UP:
if (pressed)
{
ctx->ProcessKeyDown(Rml::Input::KI_UP, 0);
handled = true;
}
break;
case ACTION_MENU_OTHER_STICK_DOWN:
if (pressed)
{
ctx->ProcessKeyDown(Rml::Input::KI_DOWN, 0);
handled = true;
}
break;
}
}
// -- Focus --
void UIScene_LoadCreateJoinMenu::handleFocusChange(F64 controlId, F64 childId)
{
constexpr int visibleRows = 7;
const ELoadCreateJoinTab oldTab = m_activeTab;
switch (static_cast<int>(controlId))
{
case eControl_TabLoad:
m_activeTab = eTab_Load;
break;
case eControl_TabCreate:
m_activeTab = eTab_Create;
break;
case eControl_TabJoin:
m_activeTab = eTab_Join;
break;
case eControl_GamesList:
m_activeTab = eTab_Join;
m_iGameListIndex = childId;
if (childId < m_hoverBaseIndexJoin)
m_hoverBaseIndexJoin = static_cast<int>(childId);
else if (childId >= m_hoverBaseIndexJoin + visibleRows)
m_hoverBaseIndexJoin = static_cast<int>(childId) - visibleRows + 1;
{
const int maxJoinBase = (GetListItemCount("join_list") - visibleRows > 0)
? (GetListItemCount("join_list") - visibleRows)
: 0;
if (m_hoverBaseIndexJoin < 0)
m_hoverBaseIndexJoin = 0;
else if (m_hoverBaseIndexJoin > maxJoinBase)
m_hoverBaseIndexJoin = maxJoinBase;
}
break;
case eControl_SavesList:
m_activeTab = eTab_Load;
m_iSaveListIndex = childId;
m_bUpdateSaveSize = true;
if (childId < m_hoverBaseIndexLoad)
m_hoverBaseIndexLoad = static_cast<int>(childId);
else if (childId >= m_hoverBaseIndexLoad + visibleRows)
m_hoverBaseIndexLoad = static_cast<int>(childId) - visibleRows + 1;
{
const int maxLoadBase = (GetListItemCount("load_list") - visibleRows > 0)
? (GetListItemCount("load_list") - visibleRows)
: 0;
if (m_hoverBaseIndexLoad < 0)
m_hoverBaseIndexLoad = 0;
else if (m_hoverBaseIndexLoad > maxLoadBase)
m_hoverBaseIndexLoad = maxLoadBase;
}
break;
case eControl_NewGamesList:
m_activeTab = eTab_Create;
m_iNewGameListIndex = childId;
if (childId < m_hoverBaseIndexCreate)
m_hoverBaseIndexCreate = static_cast<int>(childId);
else if (childId >= m_hoverBaseIndexCreate + visibleRows)
m_hoverBaseIndexCreate = static_cast<int>(childId) - visibleRows + 1;
{
const int maxCreateBase = (GetListItemCount("create_list") - visibleRows > 0)
? (GetListItemCount("create_list") - visibleRows)
: 0;
if (m_hoverBaseIndexCreate < 0)
m_hoverBaseIndexCreate = 0;
else if (m_hoverBaseIndexCreate > maxCreateBase)
m_hoverBaseIndexCreate = maxCreateBase;
}
break;
}
updateTooltips();
if (m_activeTab != oldTab)
{
SetActiveTab(m_activeTab, false);
m_bPendingSaveSizeBarRefresh = true;
m_bPendingJoinTabAvailabilityRefresh = true;
}
}
void UIScene_LoadCreateJoinMenu::handleInitFocus(F64 controlId, F64 childId)
{
SetActiveTab(m_activeTab, false);
m_bPendingSaveSizeBarRefresh = true;
m_bPendingJoinTabAvailabilityRefresh = true;
}
// -- Games List --
void UIScene_LoadCreateJoinMenu::UpdateGamesListCallback(LPVOID pParam)
{
if (pParam != nullptr)
{
auto pScene = static_cast<UIScene_LoadCreateJoinMenu*>(pParam);
pScene->UpdateGamesList();
}
}
void UIScene_LoadCreateJoinMenu::tick()
{
UIScene_RmlBase::tick();
if (m_bExitScene)
if (!m_bRetrievingSaveThumbnails)
navigateBack();
if (hasFocus(m_iPad))
{
if (m_bUpdateSaveSize)
{
UpdateSaveSizeBarVisibility();
m_bUpdateSaveSize = false;
}
if (m_bPendingSaveSizeBarRefresh)
{
UpdateSaveSizeBarVisibility();
m_bPendingSaveSizeBarRefresh = false;
}
if (m_bPendingJoinTabAvailabilityRefresh)
{
UpdateJoinTabAvailability();
m_bPendingJoinTabAvailabilityRefresh = false;
}
if (!m_bSavesDisplayed)
{
m_pSaveDetails = StorageManager.ReturnSavesInfo();
if (m_pSaveDetails != nullptr)
{
ClearList("load_list");
AddDefaultButtons();
m_bSavesDisplayed = true;
UpdateGamesList();
m_bIgnoreInput = false;
if (m_saveDetails != nullptr)
{
for (unsigned int i = 0; i < m_iSaveDetailsCount; ++i)
if (m_saveDetails[i].pbThumbnailData != nullptr)
delete m_saveDetails[i].pbThumbnailData;
delete m_saveDetails;
}
m_saveDetails = new SaveListDetails[m_pSaveDetails->iSaveC];
m_iSaveDetailsCount = m_pSaveDetails->iSaveC;
#ifdef _WINDOWS64
auto sortedIdx = new int[m_pSaveDetails->iSaveC];
for (int si = 0; si < m_pSaveDetails->iSaveC; ++si)
sortedIdx[si] = si;
for (int si = 1; si < m_pSaveDetails->iSaveC; ++si)
{
int key = sortedIdx[si];
int sj = si - 1;
while (sj >= 0 && CompareFileTime(&m_pSaveDetails->SaveInfoA[sortedIdx[sj]].lastWriteTime, &m_pSaveDetails->SaveInfoA[key].lastWriteTime) < 0)
{
sortedIdx[sj + 1] = sortedIdx[sj];
--sj;
}
sortedIdx[sj + 1] = key;
}
#endif
for (unsigned int i = 0; i < m_pSaveDetails->iSaveC; ++i)
{
#ifdef _WINDOWS64
int origIdx = sortedIdx[i];
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
ZeroMemory(wFilename, sizeof(wFilename));
mbstowcs_s(nullptr, wFilename, MAX_SAVEFILENAME_LENGTH, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, _TRUNCATE);
wchar_t wSaveTitle[MAX_DISPLAYNAME_LENGTH];
ZeroMemory(wSaveTitle, sizeof(wSaveTitle));
mbstowcs_s(nullptr, wSaveTitle, MAX_DISPLAYNAME_LENGTH, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, _TRUNCATE);
wstring filePath = wstring(L"Data/Saves/") + wstring(wFilename) + std::wstring(wSaveTitle) + L".ms";
wstring iconPath = wstring(L"Data/Saves/") + wstring(wFilename) + L"/icon.png";
{
wstring levelName = ReadLevelNameFromSaveFile(filePath);
//TODO: Add default icon (images/lce/icon/MinecraftIcon.png) for saves without icon.png
if (!levelName.empty())
{
AddListItem("load_list", levelName, iconPath.c_str());
wcstombs_s(nullptr, m_saveDetails[i].UTF8SaveName, 127, levelName.c_str(), _TRUNCATE);
m_saveDetails[i].UTF8SaveName[127] = '\0';
}
else
{
AddListItem("load_list", wstring(m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle + 127), iconPath.c_str());
memcpy(m_saveDetails[i].UTF8SaveName, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, 128);
}
m_saveDetails[i].saveId = origIdx;
memcpy(m_saveDetails[i].UTF8SaveFilename, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH);
}
#endif
}
#ifdef _WINDOWS64
delete[] sortedIdx;
#endif
ShowTabPanel("load_timer", false);
updateTooltips();
}
}
if (!m_bExitScene && m_bSavesDisplayed && !m_bRetrievingSaveThumbnails && !m_bAllLoaded)
{
if (m_iRequestingThumbnailId < GetListItemCount("load_list"))
{
m_bRetrievingSaveThumbnails = true;
PSAVE_DETAILS pSaveDetails = StorageManager.ReturnSavesInfo();
C4JStorage::ESaveGameState eLoadStatus = StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
if (eLoadStatus != C4JStorage::ESaveGame_GetSaveThumbnail)
{
m_bRetrievingSaveThumbnails = false;
m_bAllLoaded = true;
}
}
}
else if (m_bSavesDisplayed && m_bSaveThumbnailReady)
{
m_bSaveThumbnailReady = false;
if (!m_bExitScene)
{
// Thumbnail data received - store it for potential use
// RmlUi texture registration would go here for dynamic thumbnails
++m_iRequestingThumbnailId;
if (m_iRequestingThumbnailId < GetListItemCount("load_list"))
{
PSAVE_DETAILS pSaveDetails = StorageManager.ReturnSavesInfo();
C4JStorage::ESaveGameState eLoadStatus = StorageManager.LoadSaveDataThumbnail(&pSaveDetails->SaveInfoA[m_saveDetails[m_iRequestingThumbnailId].saveId],&LoadSaveDataThumbnailReturned,this);
if (eLoadStatus != C4JStorage::ESaveGame_GetSaveThumbnail)
{
m_bRetrievingSaveThumbnails = false;
m_bAllLoaded = true;
}
}
else
{
m_bRetrievingSaveThumbnails = false;
m_bAllLoaded = true;
}
}
else
{
m_bRetrievingSaveThumbnails = false;
}
}
}
switch (m_iState)
{
case e_SavesIdle:
break;
case e_SavesRepopulate:
m_bIgnoreInput = false;
m_iState = e_SavesIdle;
m_bAllLoaded = false;
m_bRetrievingSaveThumbnails = false;
m_iRequestingThumbnailId = 0;
GetSaveInfo();
break;
case e_SavesRepopulateAfterMashupHide:
m_bIgnoreInput = false;
m_iRequestingThumbnailId = 0;
m_bAllLoaded = false;
m_bRetrievingSaveThumbnails = false;
m_bSavesDisplayed = false;
m_iSaveInfoC = 0;
ClearList("load_list");
GetSaveInfo();
m_iState = e_SavesIdle;
break;
case e_SavesRepopulateAfterDelete:
case e_SavesRepopulateAfterTransferDownload:
m_bIgnoreInput = false;
m_iRequestingThumbnailId = 0;
m_bAllLoaded = false;
m_bRetrievingSaveThumbnails = false;
m_bSavesDisplayed = false;
m_iSaveInfoC = 0;
ClearList("load_list");
StorageManager.ClearSavesInfo();
GetSaveInfo();
m_iState = e_SavesIdle;
break;
}
}
// -- Get Save Info --
void UIScene_LoadCreateJoinMenu::GetSaveInfo()
{
unsigned int uiSaveC = 0;
if (app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
{
uiSaveC = 0;
File savesDir(L"Saves");
if (savesDir.exists())
{
m_saves = savesDir.listFiles();
uiSaveC = static_cast<unsigned int>(m_saves->size());
}
unsigned int listItems = uiSaveC;
AddDefaultButtons();
for (unsigned int i = 0; i < listItems; i++)
{
wstring wName = m_saves->at(i)->getName();
AddListItem("load_list", wName);
}
m_bSavesDisplayed = true;
m_bAllLoaded = true;
m_bIgnoreInput = false;
}
else
{
m_bSavesDisplayed = false;
ClearList("load_list");
m_iSaveInfoC = 0;
ShowTabPanel("load_timer", true);
m_pSaveDetails = StorageManager.ReturnSavesInfo();
if (m_pSaveDetails == nullptr)
{
char savename[] = "save";
C4JStorage::ESaveGameState eSGIStatus = StorageManager.GetSavesInfo(m_iPad, nullptr, this, savename);
}
}
}
// -- Update Games List --
void UIScene_LoadCreateJoinMenu::UpdateGamesList()
{
if (m_bIgnoreInput)
return;
if (Minecraft::GetInstance()->skins->getSelected()->isLoadingData() || (Minecraft::GetInstance()->skins->
needsUIUpdate() || ui.IsReloadingSkin()))
return;
if (!m_bSavesDisplayed)
return;
ShowTabPanel("join_timer", false);
int iRB = -1;
int iY = -1;
int iX = -1;
vector<FriendSessionInfo*>* newSessions = g_NetworkManager.GetSessionList(m_iPad, 1, m_bShowingPartyGamesOnly);
if (m_currentSessions != nullptr && m_currentSessions->size() == newSessions->size())
{
bool same = true;
for (size_t i = 0; i < newSessions->size(); i++)
if (memcmp(&(*m_currentSessions)[i]->sessionId, &(*newSessions)[i]->sessionId, sizeof(SessionID)) != 0 ||
wcscmp((*m_currentSessions)[i]->displayLabel ? (*m_currentSessions)[i]->displayLabel : L"",
(*newSessions)[i]->displayLabel ? (*newSessions)[i]->displayLabel : L"") != 0)
{
same = false;
break;
}
if (same)
{
for (auto& it : *newSessions)
delete it;
delete newSessions;
return;
}
}
if (m_currentSessions)
{
for (auto& it : *m_currentSessions)
delete it;
delete m_currentSessions;
}
m_currentSessions = newSessions;
unsigned int filteredListSize = static_cast<unsigned int>(m_currentSessions->size());
if (filteredListSize > 0)
{
m_bHasNoGamesLabel = false;
ShowTabPanel("join_timer", false);
}
else
{
ShowTabPanel("join_timer", false);
}
if (m_activeTab == eTab_Join)
{
RebuildJoinGamesListVisual(true);
m_bPendingJoinVisualRefresh = false;
}
else
m_bPendingJoinVisualRefresh = true;
updateTooltips();
m_bPendingJoinTabAvailabilityRefresh = true;
}
// -- Add Default Buttons --
void UIScene_LoadCreateJoinMenu::AddDefaultButtons()
{
app.EnableMashupPackWorlds(m_iPad);
m_iDefaultButtonsC = 0;
m_iMashUpButtonsC = 0;
m_iNewGameListIndex = 0;
ClearList("create_list");
m_generators.clear();
AddListItem("create_list", app.GetString(IDS_CREATE_NEW_WORLD), L"images/lce/icon/CreateWorldIcon.png");
m_iDefaultButtonsC++;
int i = 0;
for (LevelGenerationOptions* levelGen : *app.getLevelGenerators())
{
unsigned int uiTexturePackID = levelGen->getRequiredTexturePackId();
if (uiTexturePackID != 0)
{
unsigned int uiMashUpWorldsBitmask = app.GetMashupPackWorlds(m_iPad);
if ((uiMashUpWorldsBitmask & (1 << (uiTexturePackID - 1024))) == 0)
continue;
}
LPCWSTR wstr = levelGen->getWorldName();
if (levelGen->isTutorial())
AddListItem("create_list", wstr, L"images/lce/icon/TutorialIcon.png");
else
AddListItem("create_list", wstr); //TODO: Add DLCs icons
m_generators.push_back(levelGen);
++i;
}
m_iDefaultButtonsC += i;
}
// -- Focus queries --
bool UIScene_LoadCreateJoinMenu::DoesSavesListHaveFocus()
{
return (m_activeTab == eTab_Load);
}
bool UIScene_LoadCreateJoinMenu::DoesMashUpWorldHaveFocus()
{
if (m_activeTab != eTab_Create)
return false;
const int lGenID = m_iNewGameListIndex - m_iDefaultButtonsC;
if (lGenID < 0 || lGenID >= static_cast<int>(m_generators.size()))
return false;
LevelGenerationOptions* levelGen = m_generators.at(lGenID);
return levelGen && !levelGen->isTutorial() && levelGen->requiresTexturePack();
}
bool UIScene_LoadCreateJoinMenu::DoesGamesListHaveFocus()
{
return (m_activeTab == eTab_Join);
}
bool UIScene_LoadCreateJoinMenu::DoesNewGamesListHaveFocus()
{
return (m_activeTab == eTab_Create);
}
int UIScene_LoadCreateJoinMenu::GetMovieTabFromFocus()
{
if (DoesGamesListHaveFocus())
return 2;
if (DoesNewGamesListHaveFocus())
return 1;
if (DoesSavesListHaveFocus())
return 0;
return 0;
}
void UIScene_LoadCreateJoinMenu::SyncMovieTab()
{
SetMovieTab(GetMovieTabFromFocus());
}
void UIScene_LoadCreateJoinMenu::SetMovieTab(int tab)
{
SetTabButtonState(tab);
}
void UIScene_LoadCreateJoinMenu::SetActiveTab(ELoadCreateJoinTab tab, bool setFocus)
{
const bool enteringJoin = (m_activeTab != eTab_Join) && (tab == eTab_Join);
m_activeTab = tab;
if (m_activeTab == eTab_Join && !m_bRebuildingJoinVisual && (enteringJoin || m_bPendingJoinVisualRefresh))
{
RebuildJoinGamesListVisual(true);
m_bPendingJoinVisualRefresh = false;
}
SetMovieTab(tab);
ApplyTabVisibility(setFocus);
}
void UIScene_LoadCreateJoinMenu::ApplyTabVisibility(bool setFocus)
{
const bool showLoad = (m_activeTab == eTab_Load);
const bool showCreate = (m_activeTab == eTab_Create);
const bool showJoin = (m_activeTab == eTab_Join);
ShowTabPanel("panel_load", showLoad);
ShowTabPanel("panel_create", showCreate);
ShowTabPanel("panel_join", showJoin);
if (setFocus && m_document)
{
auto focusFirstButton = [](Rml::Element* list) {
if (!list || list->GetNumChildren() == 0) return;
auto* item = list->GetChild(0);
if (item && item->GetNumChildren() > 0)
item->GetChild(0)->Focus();
};
if (showLoad)
focusFirstButton(m_document->GetElementById("load_list"));
else if (showCreate)
focusFirstButton(m_document->GetElementById("create_list"));
else if (showJoin)
focusFirstButton(m_document->GetElementById("join_list"));
}
updateTooltips();
}
void UIScene_LoadCreateJoinMenu::RebuildJoinGamesListVisual(bool syncFocus)
{
if (m_bRebuildingJoinVisual)
return;
m_bRebuildingJoinVisual = true;
FriendSessionInfo* pSelectedSession = nullptr;
ClearList("join_list");
#ifdef _WINDOWS64
AddListItem("join_list", app.GetString(IDS_SERVER_ADD));
#endif
if (m_currentSessions == nullptr || m_currentSessions->empty())
{
m_bRebuildingJoinVisual = false;
return;
}
unsigned int sessionIndex = 0;
for (FriendSessionInfo* sessionInfo : *m_currentSessions)
{
AddListItem("join_list", sessionInfo->displayLabel);
++sessionIndex;
}
m_bRebuildingJoinVisual = false;
}
void UIScene_LoadCreateJoinMenu::UpdateJoinTabAvailability()
{
// RmlUi equivalent: no Iggy call needed; tab availability is handled by button state
}
void UIScene_LoadCreateJoinMenu::UpdateSaveSizeBarVisibility()
{
// RmlUi equivalent: show/hide the save size bar element
const bool showSaveSizeBar = (m_activeTab == eTab_Load);
ShowTabPanel("save_size_bar", showSaveSizeBar);
}
wstring UIScene_LoadCreateJoinMenu::getMoviePath()
{
return L"";
}
// -- Keyboard / Press / LevelGen --
int UIScene_LoadCreateJoinMenu::KeyboardCompleteWorldNameCallback(LPVOID lpParam, bool bRes)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(lpParam);
pClass->m_bIgnoreInput = false;
if (bRes)
{
uint16_t ui16Text[128] = {};
Win64_GetKeyboardText(ui16Text, 128);
if (ui16Text[0] != 0)
{
int displayIdx = pClass->m_iSaveListIndex;
auto pSaveInfo = &pClass->m_pSaveDetails->SaveInfoA[pClass->m_saveDetails[displayIdx].saveId];
StorageManager.RenameSaveData(pSaveInfo, ui16Text, &UIScene_LoadCreateJoinMenu::RenameSaveDataReturned, pClass);
}
else
{
pClass->m_bIgnoreInput = false;
pClass->updateTooltips();
}
}
else
{
pClass->m_bIgnoreInput = false;
pClass->updateTooltips();
}
return 0;
}
void UIScene_LoadCreateJoinMenu::handlePress(F64 controlId, F64 childId)
{
switch (static_cast<int>(controlId))
{
case eControl_TabLoad:
ui.PlayUISFX(eSFX_Press);
SetActiveTab(eTab_Load, true);
return;
case eControl_TabCreate:
ui.PlayUISFX(eSFX_Press);
SetActiveTab(eTab_Create, true);
return;
case eControl_TabJoin:
ui.PlayUISFX(eSFX_Press);
SetActiveTab(eTab_Join, true);
return;
case eControl_NewGamesList:
{
m_bIgnoreInput = true;
ui.PlayUISFX(eSFX_Press);
if (static_cast<int>(childId) == 0)
{
app.SetTutorialMode(false);
ShowTabPanel("join_timer", false);
app.SetCorruptSaveDeleted(false);
auto params = new CreateWorldMenuInitData();
params->iPad = m_iPad;
ui.NavigateToScene(m_iPad, eUIScene_CreateWorldMenu, params);
}
else
{
int lGenID = static_cast<int>(childId) - 1;
if (lGenID < static_cast<int>(m_generators.size()))
{
LevelGenerationOptions* levelGen = m_generators.at(lGenID);
app.SetTutorialMode(levelGen->isTutorial());
app.SetAutosaveTimerTime();
if (levelGen->isTutorial())
LoadLevelGen(levelGen);
else
{
auto params = new LoadMenuInitData();
params->iPad = m_iPad;
params->iSaveGameInfoIndex = -1;
params->levelGen = levelGen;
params->saveDetails = nullptr;
ui.NavigateToScene(m_iPad, eUIScene_LoadMenu, params);
}
}
}
}
break;
case eControl_SavesList:
{
m_bIgnoreInput = true;
ui.PlayUISFX(eSFX_Press);
int saveIndex = static_cast<int>(childId);
if (app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
LoadSaveFromDisk(m_saves->at(saveIndex));
else
{
auto params = new LoadMenuInitData();
params->iPad = m_iPad;
params->iSaveGameInfoIndex = m_saveDetails[saveIndex].saveId;
params->levelGen = nullptr;
params->saveDetails = &m_saveDetails[saveIndex];
ui.NavigateToScene(m_iPad, eUIScene_LoadMenu, params);
}
}
break;
case eControl_GamesList:
{
#ifdef _WINDOWS64
if (static_cast<int>(childId) == ADD_SERVER_BUTTON_INDEX)
{
ui.PlayUISFX(eSFX_Press);
BeginAddServer();
break;
}
#endif
m_bIgnoreInput = true;
m_eAction = eAction_JoinGame;
ui.PlayUISFX(eSFX_Press);
int nIndex = static_cast<int>(childId);
#ifdef _WINDOWS64
nIndex -= 1;
#endif
m_iGameListIndex = nIndex;
CheckAndJoinGame(nIndex);
}
break;
}
}
void UIScene_LoadCreateJoinMenu::LoadLevelGen(LevelGenerationOptions* levelGen)
{
app.ClearTerrainFeaturePosition();
StorageManager.ResetSaveData();
const wstring preferredSaveTitle = GetPreferredLevelGenSaveTitle(levelGen);
StorageManager.SetSaveTitle(preferredSaveTitle.empty()
? levelGen->getDefaultSaveName().c_str()
: preferredSaveTitle.c_str());
bool isClientSide = false;
bool isPrivate = false;
int maxPlayers = 8;
if (app.GetTutorialMode())
{
isClientSide = false;
maxPlayers = 4;
}
g_NetworkManager.HostGame(0, isClientSide, isPrivate, maxPlayers, 0);
auto param = new NetworkGameInitData();
param->seed = 0;
param->saveData = nullptr;
param->settings = app.GetGameHostOption(eGameHostOption_Tutorial);
param->levelGen = levelGen;
param->levelName = preferredSaveTitle;
if (levelGen->requiresTexturePack())
{
param->texturePackId = levelGen->getRequiredTexturePackId();
Minecraft* pMinecraft = Minecraft::GetInstance();
pMinecraft->skins->selectTexturePackById(param->texturePackId);
}
#ifndef _XBOX
g_NetworkManager.FakeLocalPlayerJoined();
#endif
auto loadingParams = new LoadingInputParams();
loadingParams->func = &CGameNetworkManager::RunNetworkGameThreadProc;
loadingParams->lpParam = static_cast<LPVOID>(param);
auto completionData = new UIFullscreenProgressCompletionData();
completionData->bShowBackground = TRUE;
completionData->bShowLogo = TRUE;
completionData->type = e_ProgressCompletion_CloseAllPlayersUIScenes;
completionData->iPad = DEFAULT_XUI_MENU_USER;
loadingParams->completionData = completionData;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(), eUIScene_FullscreenProgress, loadingParams);
}
void UIScene_LoadCreateJoinMenu::LoadSaveFromDisk(File* saveFile,
ESavePlatform savePlatform /*= SAVE_FILE_PLATFORM_LOCAL*/)
{
StorageManager.ResetSaveData();
StorageManager.SetSaveTitle(saveFile->getName().c_str());
int64_t fileSize = saveFile->length();
FileInputStream fis(*saveFile);
byteArray ba(static_cast<unsigned int>(fileSize));
fis.read(ba);
fis.close();
bool isClientSide = false;
bool isPrivate = false;
int maxPlayers = MINECRAFT_NET_MAX_PLAYERS;
if (app.GetTutorialMode())
{
isClientSide = false;
maxPlayers = 4;
}
app.SetGameHostOption(eGameHostOption_GameType, GameType::CREATIVE->getId());
g_NetworkManager.HostGame(0, isClientSide, isPrivate, maxPlayers, 0);
auto saveData = new LoadSaveDataThreadParam(ba.data, ba.length, saveFile->getName());
auto param = new NetworkGameInitData();
param->seed = 0;
param->saveData = saveData;
param->settings = app.GetGameHostOption(eGameHostOption_All);
param->savePlatform = savePlatform;
#ifndef _XBOX
g_NetworkManager.FakeLocalPlayerJoined();
#endif
auto loadingParams = new LoadingInputParams();
loadingParams->func = &CGameNetworkManager::RunNetworkGameThreadProc;
loadingParams->lpParam = static_cast<LPVOID>(param);
auto completionData = new UIFullscreenProgressCompletionData();
completionData->bShowBackground = TRUE;
completionData->bShowLogo = TRUE;
completionData->type = e_ProgressCompletion_CloseAllPlayersUIScenes;
completionData->iPad = DEFAULT_XUI_MENU_USER;
loadingParams->completionData = completionData;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(), eUIScene_FullscreenProgress, loadingParams);
}
// -- DLC / Join / Save callbacks --
void UIScene_LoadCreateJoinMenu::HandleDLCMountingComplete()
{
Initialise();
}
void UIScene_LoadCreateJoinMenu::CheckAndJoinGame(int gameIndex)
{
if (GetListItemCount("join_list") > 0 && gameIndex < m_currentSessions->size())
{
m_initData->iPad = 0;
m_initData->selectedSession = m_currentSessions->at(gameIndex);
#ifdef _WINDOWS64
{
int serverDbCount = 0;
FILE* dbFile = fopen("servers.db", "rb");
if (dbFile)
{
char magic[4] = {};
if (fread(magic, 1, 4, dbFile) == 4 && memcmp(magic, "MCSV", 4) == 0)
{
uint32_t version = 0, count = 0;
fread(&version, sizeof(uint32_t), 1, dbFile);
fread(&count, sizeof(uint32_t), 1, dbFile);
if (version == 1)
serverDbCount = static_cast<int>(count);
}
fclose(dbFile);
}
int lanCount = static_cast<int>(m_currentSessions->size()) - serverDbCount;
if (gameIndex >= lanCount && lanCount >= 0)
m_initData->serverIndex = gameIndex - lanCount;
else
m_initData->serverIndex = -1;
}
#endif
if (m_initData->selectedSession->data.texturePackParentId != 0)
{
int texturePacksCount = Minecraft::GetInstance()->skins->getTexturePackCount();
bool bHasTexturePackInstalled = false;
for (int i = 0; i < texturePacksCount; i++)
{
TexturePack* tp = Minecraft::GetInstance()->skins->getTexturePackByIndex(i);
if (tp->getDLCParentPackId() == m_initData->selectedSession->data.texturePackParentId)
{
bHasTexturePackInstalled = true;
break;
}
}
if (bHasTexturePackInstalled == false) //TODO: Add a Error Message for Missing Texture Pack
return;
}
ShowTabPanel("join_timer", false);
m_bIgnoreInput = true;
ui.NavigateToScene(ProfileManager.GetPrimaryPad(), eUIScene_JoinMenu, m_initData);
}
}
// -- Dialog callbacks --
int UIScene_LoadCreateJoinMenu::DeleteSaveDialogReturned(void *pParam,int iPad,C4JStorage::EMessageResult result)
{
auto* pClass = static_cast<UIScene_LoadCreateJoinMenu*>(pParam);
if(result==C4JStorage::EMessage_ResultDecline)
{
if(app.DebugSettingsOn() && app.GetLoadSavesFromFolderEnabled())
pClass->m_bIgnoreInput=false;
else
{
int displayIdx = pClass->m_iSaveListIndex;
if (pClass->m_saveDetails && displayIdx >= 0 && pClass->m_saveDetails[displayIdx].UTF8SaveFilename[0])
{
auto pSaveInfo = &pClass->m_pSaveDetails->SaveInfoA[pClass->m_saveDetails[displayIdx].saveId];
StorageManager.DeleteSaveData(pSaveInfo, &UIScene_LoadCreateJoinMenu::DeleteSaveDataReturned, (LPVOID)pClass->GetCallbackUniqueId());
}
pClass->ShowTabPanel("load_timer", true);
}
}
else
pClass->m_bIgnoreInput=false;
return 0;
}
int UIScene_LoadCreateJoinMenu::DeleteSaveDataReturned(LPVOID lpParam, bool bRes)
{
ui.EnterCallbackIdCriticalSection();
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(ui.GetSceneFromCallbackId((size_t)lpParam));
if (pClass)
{
if (bRes)
pClass->m_iState = e_SavesRepopulateAfterDelete;
else
pClass->m_bIgnoreInput = false;
pClass->updateTooltips();
}
ui.LeaveCallbackIdCriticalSection();
return 0;
}
int UIScene_LoadCreateJoinMenu::RenameSaveDataReturned(LPVOID lpParam, bool bRes)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(lpParam);
if (bRes)
pClass->m_iState = e_SavesRepopulate;
else
pClass->m_bIgnoreInput = false;
pClass->updateTooltips();
return 0;
}
int UIScene_LoadCreateJoinMenu::SaveOptionsDialogReturned(void* pParam, int iPad, C4JStorage::EMessageResult result)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(pParam);
switch (result)
{
case C4JStorage::EMessage_ResultDecline: // rename
{
pClass->m_bIgnoreInput = true;
#ifdef _WINDOWS64
{
wchar_t wSaveName[128];
ZeroMemory(wSaveName, 128 * sizeof(wchar_t));
mbstowcs_s(nullptr, wSaveName, 128, pClass->m_saveDetails[pClass->m_iSaveListIndex].UTF8SaveName, _TRUNCATE);
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_RENAME_WORLD_TITLE);
kbData.defaultText = wSaveName;
kbData.maxChars = 25;
kbData.callback = &UIScene_LoadCreateJoinMenu::KeyboardCompleteWorldNameCallback;
kbData.lpParam = pClass;
kbData.pcMode = g_KBMInput.IsKBMActive();
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
}
#endif
}
break;
case C4JStorage::EMessage_ResultThirdOption: // delete
{
UINT uiIDA[2];
uiIDA[0] = IDS_CONFIRM_CANCEL;
uiIDA[1] = IDS_CONFIRM_OK;
ui.RequestAlertMessage(IDS_TOOLTIPS_DELETESAVE, IDS_TEXT_DELETE_SAVE, uiIDA, 2, iPad,
&UIScene_LoadCreateJoinMenu::DeleteSaveDialogReturned, pClass);
}
break;
case C4JStorage::EMessage_Cancelled:
default:
{
pClass->updateTooltips();
pClass->m_bIgnoreInput = false;
}
break;
}
return 0;
}
int UIScene_LoadCreateJoinMenu::TexturePackDialogReturned(void* pParam, int iPad, C4JStorage::EMessageResult result)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(pParam);
if (result == C4JStorage::EMessage_ResultAccept)
XBackgroundDownloadSetMode(XBACKGROUND_DOWNLOAD_MODE_ALWAYS_ALLOW);
pClass->m_bIgnoreInput = false;
return 0;
}
#ifdef _WINDOWS64
void UIScene_LoadCreateJoinMenu::BeginAddServer()
{
m_addServerPhase = eAddServer_IP;
m_addServerIP.clear();
m_addServerPort.clear();
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_ADDRESS);
kbData.defaultText = L"";
kbData.maxChars = 64;
kbData.callback = &UIScene_LoadCreateJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = this;
kbData.pcMode = false;
ui.NavigateToScene(m_iPad, eUIScene_Keyboard, &kbData);
}
void UIScene_LoadCreateJoinMenu::AppendServerToFile(const wstring& ip, const wstring& port, const wstring& name)
{
FILE* f = fopen("servers.db", "ab");
if (!f) return;
uint32_t version = 1;
uint32_t count = 1;
char ipBuf[128], portBuf[16], nameBuf[128];
WideCharToMultiByte(CP_UTF8, 0, ip.c_str(), -1, ipBuf, sizeof(ipBuf), nullptr, nullptr);
WideCharToMultiByte(CP_UTF8, 0, port.c_str(), -1, portBuf, sizeof(portBuf), nullptr, nullptr);
WideCharToMultiByte(CP_UTF8, 0, name.c_str(), -1, nameBuf, sizeof(nameBuf), nullptr, nullptr);
fwrite(ipBuf, 1, strlen(ipBuf) + 1, f);
fwrite(portBuf, 1, strlen(portBuf) + 1, f);
fwrite(nameBuf, 1, strlen(nameBuf) + 1, f);
fclose(f);
}
int UIScene_LoadCreateJoinMenu::AddServerKeyboardCallback(LPVOID lpParam, bool bRes)
{
auto pClass = static_cast<UIScene_LoadCreateJoinMenu*>(lpParam);
if (!bRes)
{
pClass->m_addServerPhase = eAddServer_Idle;
return 0;
}
uint16_t text[128] = {};
Win64_GetKeyboardText(text, 128);
switch (pClass->m_addServerPhase)
{
case eAddServer_IP:
{
char ipBuf[128];
WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)text, -1, ipBuf, sizeof(ipBuf), nullptr, nullptr);
pClass->m_addServerIP = (wchar_t*)text;
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_PORT);
kbData.defaultText = L"19132";
kbData.maxChars = 6;
kbData.callback = &UIScene_LoadCreateJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = false;
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
pClass->m_addServerPhase = eAddServer_Port;
}
break;
case eAddServer_Port:
{
pClass->m_addServerPort = (wchar_t*)text;
UIKeyboardInitData kbData;
kbData.title = app.GetString(IDS_SERVER_NAME);
kbData.defaultText = L"";
kbData.maxChars = 64;
kbData.callback = &UIScene_LoadCreateJoinMenu::AddServerKeyboardCallback;
kbData.lpParam = pClass;
kbData.pcMode = false;
ui.NavigateToScene(pClass->m_iPad, eUIScene_Keyboard, &kbData);
pClass->m_addServerPhase = eAddServer_Name;
}
break;
case eAddServer_Name:
{
wstring serverName = (wchar_t*)text;
pClass->AppendServerToFile(pClass->m_addServerIP, pClass->m_addServerPort, serverName);
pClass->m_addServerPhase = eAddServer_Idle;
pClass->UpdateGamesList();
}
break;
}
return 0;
}
#endif