1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-24 23:34:37 +01:00

Emscripten: Reimplement save/load

This commit is contained in:
Ethan O'Brien
2025-08-07 08:34:26 -05:00
parent 52e3c774bc
commit 1eff246b1a
11 changed files with 410 additions and 61 deletions

View File

@@ -12,7 +12,7 @@ if (EMSCRIPTEN)
endif ()
set(SHARED_FLAGS "-fexceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${USE_FLAGS} ${SHARED_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LDFLAGS} --bind ${SHARED_FLAGS} -s EXPORTED_FUNCTIONS=_GetVersion,_main --js-library ${ROOT_DIR}/emscripten/deps.js")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LDFLAGS} --bind ${SHARED_FLAGS} -sEXPORTED_FUNCTIONS=${EMSCRIPTEN_EXPORTED_FUNCTIONS} --js-library ${ROOT_DIR}/emscripten/deps.js")
find_package(SpeexDSP REQUIRED)
elseif (MSVC)
find_package(SDL2 REQUIRED)

View File

@@ -28,6 +28,13 @@
#include <openrct2/ui/WindowManager.h>
#include <openrct2/windows/Intent.h>
#ifdef __EMSCRIPTEN__
extern "C" {
extern void EmscriptenLoadGame(LoadSaveType type);
extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, bool saveAs, LoadSaveType type);
}
#endif
namespace OpenRCT2::Ui::FileBrowser
{
static LoadSaveCallback _loadSaveCallback;
@@ -37,6 +44,19 @@ namespace OpenRCT2::Ui::FileBrowser
{
RegisterCallback(callback);
#ifdef __EMSCRIPTEN__
if (action == LoadSaveAction::save)
{
Select("/save.park", action, type, trackDesign);
EmscriptenSaveGame(type == LoadSaveType::track, false, true, type);
}
else
{
EmscriptenLoadGame(type);
}
return nullptr;
#endif
auto hasFilePicker = OpenRCT2::GetContext()->GetUiContext().HasFilePicker();
auto& config = Config::Get().general;
@@ -526,3 +546,10 @@ namespace OpenRCT2::Ui::FileBrowser
return ContextOpenCommonFileDialog(desc);
}
} // namespace OpenRCT2::Ui::FileBrowser
#ifdef __EMSCRIPTEN__
extern "C" void LoadGameCallback(const char* path, LoadSaveType action)
{
OpenRCT2::Ui::FileBrowser::Select(path, LoadSaveAction::load, action, nullptr);
}
#endif

View File

@@ -66,3 +66,7 @@ namespace OpenRCT2::Ui::FileBrowser
WindowBase* OpenPreferred(
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign);
} // namespace OpenRCT2::Ui::FileBrowser
#ifdef __EMSCRIPTEN__
extern "C" void LoadGameCallback(const char* path, LoadSaveType action);
#endif

View File

@@ -48,6 +48,13 @@
#include <openrct2/world/Scenery.h>
#include <string>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
extern "C" {
extern void EmscriptenLoadGame();
}
#endif
using namespace OpenRCT2::Numerics;
namespace OpenRCT2::Ui::Windows

View File

@@ -182,7 +182,7 @@ target_link_libraries(libopenrct2 Threads::Threads)
# For some reason, these flags break the check for pthreads. Add them after.
if (EMSCRIPTEN)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS=_GetVersion,_main --js-library ${ROOT_DIR}/emscripten/deps.js")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXPORTED_FUNCTIONS=${EMSCRIPTEN_EXPORTED_FUNCTIONS} --js-library ${ROOT_DIR}/emscripten/deps.js")
endif()
if (NOT MINGW AND NOT MSVC AND NOT EMSCRIPTEN)

View File

@@ -76,6 +76,13 @@
#include <iterator>
#include <memory>
#ifdef __EMSCRIPTEN__
extern "C" {
extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, bool saveAs, LoadSaveType type);
extern void EmscriptenResetAutosave();
}
#endif
using namespace OpenRCT2;
uint16_t gCurrentDeltaTime;
@@ -455,8 +462,14 @@ void SaveGame()
{
if (!gFirstTimeSaving && !gIsAutosaveLoaded)
{
#ifndef __EMSCRIPTEN__
const auto savePath = Path::WithExtension(gScenarioSavePath, ".park");
SaveGameWithName(savePath);
#else
const auto savePath = Path::WithExtension("save", ".park");
SaveGameWithName(savePath);
EmscriptenSaveGame(false, false, false, LoadSaveType::park);
#endif
}
else
{
@@ -512,6 +525,7 @@ void SaveGameAs()
ContextOpenIntent(intent.get());
}
#ifndef __EMSCRIPTEN__
static void LimitAutosaveCount(const size_t numberOfFilesToKeep, bool processLandscapeFolder)
{
size_t autosavesCount = 0;
@@ -613,6 +627,14 @@ void GameAutosave()
if (!ScenarioSave(gameState, path, saveFlags))
Console::Error::WriteLine("Could not autosave the scenario. Is the save folder writeable?");
}
#else
void GameAutosave()
{
const auto savePath = Path::WithExtension("save", ".park");
SaveGameWithName(savePath);
EmscriptenSaveGame(false, true, false, LoadSaveType::park);
}
#endif // __EMSCRIPTEN__
static void GameLoadOrQuitNoSavePromptCallback(ModalResult result, const utf8* path)
{
@@ -643,6 +665,9 @@ static void NewGameWindowCallback(const utf8* path)
GetContext()->LoadParkFromFile(path, false, true);
GameLoadScripts();
GameNotifyMapChanged();
#ifdef __EMSCRIPTEN__
EmscriptenResetAutosave();
#endif
}
/**
@@ -685,6 +710,9 @@ void GameLoadOrQuitNoSavePrompt()
gFirstTimeSaving = true;
GameNotifyMapChange();
GameUnloadScripts();
#ifdef __EMSCRIPTEN__
EmscriptenResetAutosave();
#endif
auto* context = OpenRCT2::GetContext();
context->SetActiveScene(context->GetTitleScene());

View File

@@ -0,0 +1,138 @@
/*****************************************************************************
* Copyright (c) 2014-2025 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#ifdef __EMSCRIPTEN__
#include "Platform.h"
#include "../Diagnostic.h"
#include "../GameState.h"
#include "../core/Compression.h"
#include "../core/File.h"
#include "../core/Guard.hpp"
#include "../core/MemoryStream.h"
#include "../localisation/Language.h"
#include "../park/ParkFile.h"
#include <emscripten.h>
extern "C" {
extern char* GetLocaleCurrencyCode();
}
namespace OpenRCT2::Platform
{
std::string GetFolderPath(SpecialFolder folder)
{
switch (folder)
{
case SpecialFolder::userCache:
case SpecialFolder::userConfig:
case SpecialFolder::userData:
case SpecialFolder::userHome:
return "/persistent";
default:
return std::string();
}
}
std::string GetDocsPath()
{
return std::string();
}
std::string GetInstallPath()
{
return "/OpenRCT2";
}
std::string GetCurrentExecutablePath()
{
return std::string();
}
u8string StrDecompToPrecomp(u8string_view input)
{
return u8string(input);
}
bool HandleSpecialCommandLineArgument(const char* argument)
{
return false;
}
uint16_t GetLocaleLanguage()
{
auto locale = reinterpret_cast<char*>(EM_ASM_PTR({
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
return stringToNewUTF8(locale);
}));
auto languageId = LanguageGetIDFromLocale(locale);
free(locale);
return languageId;
}
CurrencyType GetLocaleCurrency()
{
auto localeCurrencyCode = GetLocaleCurrencyCode();
auto currency = Platform::GetCurrencyValue(localeCurrencyCode);
free(localeCurrencyCode);
return currency;
}
MeasurementFormat GetLocaleMeasurementFormat()
{
int isImperial = EM_ASM_INT({
// There is no way to get this directly from javascript! Assume based off of user country
const country = Intl.DateTimeFormat().resolvedOptions().locale.split("-").pop();
return ([ "LR", "MM", "US" ]).includes(country) ? 1 : 0;
});
return isImperial == 1 ? MeasurementFormat::Imperial : MeasurementFormat::Metric;
}
std::string GetSteamPath()
{
return {};
}
u8string GetRCT1SteamDir()
{
return {};
}
u8string GetRCT2SteamDir()
{
return {};
}
u8string GetRCTClassicSteamDir()
{
return {};
}
#ifndef DISABLE_TTF
std::string GetFontPath(const TTFFontDescriptor& font)
{
return {};
}
#endif
std::vector<std::string_view> GetSearchablePathsRCT1()
{
return { "/RCT" };
}
std::vector<std::string_view> GetSearchablePathsRCT2()
{
return { "/RCT" };
}
} // namespace OpenRCT2::Platform
#endif

View File

@@ -7,7 +7,7 @@
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#if defined(__unix__) && !defined(__ANDROID__) && !defined(__APPLE__)
#if defined(__unix__) && !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__EMSCRIPTEN__)
#include "../Diagnostic.h"
@@ -201,7 +201,7 @@ namespace OpenRCT2::Platform
{
LOG_FATAL("failed to get process path");
}
#elif defined(__OpenBSD__) || defined(__EMSCRIPTEN__)
#elif defined(__OpenBSD__)
// There is no way to get the path name of a running executable.
// If you are not using the port or package, you may have to change this line!
strlcpy(exePath, "/usr/local/bin/", sizeof(exePath));