diff --git a/src/openrct2-ui/windows/TitleMenu.cpp b/src/openrct2-ui/windows/TitleMenu.cpp index b7d3f3f5c6..4db6d11eea 100644 --- a/src/openrct2-ui/windows/TitleMenu.cpp +++ b/src/openrct2-ui/windows/TitleMenu.cpp @@ -123,6 +123,7 @@ rct_window* window_title_menu_open() static void window_title_menu_scenarioselect_callback(const utf8* path) { context_load_park_from_file(path); + game_load_scripts(); } static void window_title_menu_mouseup(rct_window* w, rct_widgetindex widgetIndex) diff --git a/src/openrct2/Context.cpp b/src/openrct2/Context.cpp index 487dc9f3a5..7dd8edf040 100644 --- a/src/openrct2/Context.cpp +++ b/src/openrct2/Context.cpp @@ -810,7 +810,11 @@ namespace OpenRCT2 } network_begin_server(gNetworkStartPort, gNetworkStartAddress); } + else #endif // DISABLE_NETWORK + { + game_load_scripts(); + } break; } case STARTUP_ACTION_EDIT: diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index d19a9937db..7526bb5d2c 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -588,7 +588,10 @@ void game_load_init() audio_stop_title_music(); gGameSpeed = 1; +} +void game_load_scripts() +{ GetContext()->GetScriptEngine().LoadPlugins(); } @@ -812,6 +815,7 @@ static void game_load_or_quit_no_save_prompt_callback(int32_t result, const utf8 game_finish(); window_close_by_class(WC_EDITOR_OBJECT_SELECTION); context_load_park_from_file(path); + game_load_scripts(); } } diff --git a/src/openrct2/Game.h b/src/openrct2/Game.h index 29f06e0c55..a1167563f2 100644 --- a/src/openrct2/Game.h +++ b/src/openrct2/Game.h @@ -156,6 +156,7 @@ void update_palette_effects(); void game_load_or_quit_no_save_prompt(); void load_from_sv6(const char* path); void game_load_init(); +void game_load_scripts(); void game_finish(); void pause_toggle(); bool game_is_paused(); diff --git a/src/openrct2/network/Network.cpp b/src/openrct2/network/Network.cpp index 1a5724e5cd..182c6c2153 100644 --- a/src/openrct2/network/Network.cpp +++ b/src/openrct2/network/Network.cpp @@ -194,6 +194,7 @@ public: void Client_Send_GAMEINFO(); void Client_Send_OBJECTS(const std::vector& objects); void Server_Send_OBJECTS(NetworkConnection& connection, const std::vector& objects) const; + void Server_Send_SCRIPTS(NetworkConnection& connection) const; NetworkStats_t GetStats() const; json_t* GetServerInfoAsJson() const; @@ -309,6 +310,7 @@ private: void Client_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet); void Server_Handle_TOKEN(NetworkConnection& connection, NetworkPacket& packet); void Client_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket& packet); + void Client_Handle_SCRIPTS(NetworkConnection& connection, NetworkPacket& packet); void Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet); void Server_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket& packet); @@ -344,6 +346,7 @@ Network::Network() client_command_handlers[NETWORK_COMMAND_GAMEINFO] = &Network::Client_Handle_GAMEINFO; client_command_handlers[NETWORK_COMMAND_TOKEN] = &Network::Client_Handle_TOKEN; client_command_handlers[NETWORK_COMMAND_OBJECTS] = &Network::Client_Handle_OBJECTS; + client_command_handlers[NETWORK_COMMAND_SCRIPTS] = &Network::Client_Handle_SCRIPTS; client_command_handlers[NETWORK_COMMAND_GAMESTATE] = &Network::Client_Handle_GAMESTATE; server_command_handlers.resize(NETWORK_COMMAND_MAX, nullptr); server_command_handlers[NETWORK_COMMAND_AUTH] = &Network::Server_Handle_AUTH; @@ -624,6 +627,8 @@ bool Network::BeginServer(uint16_t port, const std::string& address) _serverState.gamestateSnapshotsEnabled = gConfigNetwork.desync_debugging; _advertiser = CreateServerAdvertiser(listening_port); + game_load_scripts(); + return true; } @@ -1465,6 +1470,37 @@ void Network::Server_Send_OBJECTS(NetworkConnection& connection, const std::vect connection.QueuePacket(std::move(packet)); } +void Network::Server_Send_SCRIPTS(NetworkConnection& connection) const +{ + using namespace OpenRCT2::Scripting; + + auto& scriptEngine = GetContext()->GetScriptEngine(); + const auto& plugins = scriptEngine.GetPlugins(); + std::vector> pluginsToSend; + for (const auto& plugin : plugins) + { + const auto& metadata = plugin->GetMetadata(); + if (metadata.Type == OpenRCT2::Scripting::PluginType::ServerClient) + { + pluginsToSend.push_back(plugin); + } + } + + log_verbose("Server sends %u scripts", pluginsToSend.size()); + std::unique_ptr packet(NetworkPacket::Allocate()); + *packet << (uint32_t)NETWORK_COMMAND_SCRIPTS << (uint32_t)pluginsToSend.size(); + for (const auto& plugin : pluginsToSend) + { + const auto& metadata = plugin->GetMetadata(); + log_verbose("Script %s", metadata.Name.c_str()); + + const auto& code = plugin->GetCode(); + *packet << (uint32_t)code.size(); + packet->Write((const uint8_t*)code.c_str(), code.size()); + } + connection.QueuePacket(std::move(packet)); +} + NetworkStats_t Network::GetStats() const { NetworkStats_t stats = {}; @@ -2339,6 +2375,7 @@ void Network::Server_Client_Joined(const char* name, const std::string& keyhash, auto& objManager = context->GetObjectManager(); auto objects = objManager.GetPackableObjects(); Server_Send_OBJECTS(connection, objects); + Server_Send_SCRIPTS(connection); // Log player joining event std::string playerNameHash = player->Name + " (" + keyhash + ")"; @@ -2398,6 +2435,21 @@ void Network::Client_Handle_OBJECTS(NetworkConnection& connection, NetworkPacket Client_Send_OBJECTS(requested_objects); } +void Network::Client_Handle_SCRIPTS(NetworkConnection& connection, NetworkPacket& packet) +{ + auto& scriptEngine = GetContext()->GetScriptEngine(); + + uint32_t numScripts{}; + packet >> numScripts; + for (uint32_t i = 0; i < numScripts; i++) + { + uint32_t codeLength{}; + packet >> codeLength; + auto code = std::string_view((const char*)packet.Read(codeLength), codeLength); + scriptEngine.AddNetworkPlugin(code); + } +} + void Network::Client_Handle_GAMESTATE(NetworkConnection& connection, NetworkPacket& packet) { uint32_t tick; @@ -2683,6 +2735,7 @@ void Network::Client_Handle_MAP([[maybe_unused]] NetworkConnection& connection, if (LoadMap(&ms)) { game_load_init(); + game_load_scripts(); _serverState.tick = gCurrentTicks; // window_network_status_open("Loaded new map from network"); _serverState.state = NETWORK_SERVER_STATE_OK; diff --git a/src/openrct2/network/NetworkTypes.h b/src/openrct2/network/NetworkTypes.h index 3837c72da9..a2bf68150a 100644 --- a/src/openrct2/network/NetworkTypes.h +++ b/src/openrct2/network/NetworkTypes.h @@ -68,6 +68,7 @@ enum NETWORK_COMMAND NETWORK_COMMAND_PLAYERINFO, NETWORK_COMMAND_REQUEST_GAMESTATE, NETWORK_COMMAND_GAMESTATE, + NETWORK_COMMAND_SCRIPTS, NETWORK_COMMAND_MAX, NETWORK_COMMAND_INVALID = -1 }; diff --git a/src/openrct2/scripting/Plugin.cpp b/src/openrct2/scripting/Plugin.cpp index 9e97028e46..cc9ea3ce62 100644 --- a/src/openrct2/scripting/Plugin.cpp +++ b/src/openrct2/scripting/Plugin.cpp @@ -24,28 +24,29 @@ Plugin::Plugin(duk_context* context, const std::string& path) { } +void Plugin::SetCode(const std::string_view& code) +{ + _code = code; +} + void Plugin::Load() { + if (!_path.empty()) + { + LoadCodeFromFile(); + } + std::string projectedVariables = "console,context,date,map,network,park"; if (!gOpenRCT2Headless) { projectedVariables += ",ui"; } - std::string code; - { - std::ifstream fs(_path); - if (fs.is_open()) - { - fs.seekg(0, std::ios::end); - code.reserve(fs.tellg()); - fs.seekg(0, std::ios::beg); - code.assign(std::istreambuf_iterator(fs), std::istreambuf_iterator()); - } - } + // Wrap the script in a function and pass the global objects as variables // so that if the script modifies them, they are not modified for other scripts. // clang-format off + auto code = _code; code = " (function(" + projectedVariables + ") {" " var __metadata__ = null;" @@ -99,6 +100,20 @@ void Plugin::Update() { } +void Plugin::LoadCodeFromFile() +{ + std::string code; + std::ifstream fs(_path); + if (fs.is_open()) + { + fs.seekg(0, std::ios::end); + code.reserve(fs.tellg()); + fs.seekg(0, std::ios::beg); + code.assign(std::istreambuf_iterator(fs), std::istreambuf_iterator()); + } + _code = std::move(code); +} + static std::string TryGetString(const DukValue& value, const std::string& message) { if (value.type() != DukValue::Type::STRING) @@ -144,8 +159,8 @@ PluginType Plugin::ParsePluginType(const std::string_view& type) if (type == "server") return PluginType::Server; if (type == "client") - return PluginType::Server; + return PluginType::Client; if (type == "server_client") - return PluginType::Server; + return PluginType::ServerClient; throw std::invalid_argument("Unknown plugin type."); } diff --git a/src/openrct2/scripting/Plugin.h b/src/openrct2/scripting/Plugin.h index b005cba4b3..956840d55a 100644 --- a/src/openrct2/scripting/Plugin.h +++ b/src/openrct2/scripting/Plugin.h @@ -54,6 +54,7 @@ namespace OpenRCT2::Scripting duk_context* _context{}; std::string _path; PluginMetadata _metadata; + std::string _code; bool _hasStarted{}; public: @@ -62,11 +63,21 @@ namespace OpenRCT2::Scripting return _path; }; + bool HasPath() const + { + return _path.empty(); + } + const PluginMetadata& GetMetadata() const { return _metadata; } + const std::string& GetCode() const + { + return _code; + } + bool HasStarted() const { return _hasStarted; @@ -79,12 +90,15 @@ namespace OpenRCT2::Scripting Plugin(const Plugin&) = delete; Plugin(Plugin&&) = delete; + void SetCode(const std::string_view& code); void Load(); void Start(); void Stop(); void Update(); private: + void LoadCodeFromFile(); + static PluginMetadata GetMetadata(const DukValue& dukMetadata); static PluginType ParsePluginType(const std::string_view& type); }; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 19a23ec4ef..163b8e5b74 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -113,7 +113,7 @@ void ScriptEngine::LoadPlugins() } } - if (gConfigPlugin.enable_hot_reloading) + if (gConfigPlugin.enable_hot_reloading && network_get_mode() == NETWORK_MODE_NONE) { SetupHotReloading(); } @@ -123,10 +123,15 @@ void ScriptEngine::LoadPlugins() } void ScriptEngine::LoadPlugin(const std::string& path) +{ + auto plugin = std::make_shared(_context, path); + LoadPlugin(plugin); +} + +void ScriptEngine::LoadPlugin(std::shared_ptr& plugin) { try { - auto plugin = std::make_shared(_context, path); ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); plugin->Load(); @@ -240,7 +245,7 @@ void ScriptEngine::StartPlugins() { for (auto& plugin : _plugins) { - if (!plugin->HasStarted()) + if (!plugin->HasStarted() && ShouldStartPlugin(plugin)) { ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); try @@ -257,12 +262,36 @@ void ScriptEngine::StartPlugins() _pluginsStarted = true; } +bool ScriptEngine::ShouldStartPlugin(const std::shared_ptr& plugin) +{ + auto networkMode = network_get_mode(); + if (networkMode == NETWORK_MODE_CLIENT) + { + // Only client plugins and plugins downloaded from server should be started + const auto& metadata = plugin->GetMetadata(); + if (metadata.Type == PluginType::Server) + { + LogPluginInfo(plugin, "Server plugin not started"); + return false; + } + else if (metadata.Type == PluginType::ServerClient && plugin->HasPath()) + { + LogPluginInfo(plugin, "Server / client plugin not started"); + return false; + } + } + return true; +} + void ScriptEngine::StopPlugins() { for (auto& plugin : _plugins) { - StopPlugin(plugin); - LogPluginInfo(plugin, "Stopped"); + if (plugin->HasStarted()) + { + StopPlugin(plugin); + LogPluginInfo(plugin, "Stopped"); + } } _pluginsStarted = false; } @@ -332,6 +361,13 @@ void ScriptEngine::LogPluginInfo(const std::shared_ptr& plugin, const st _console.WriteLine("[" + pluginName + "] " + std::string(message)); } +void ScriptEngine::AddNetworkPlugin(const std::string_view& code) +{ + auto plugin = std::make_shared(_context, std::string()); + plugin->SetCode(code); + LoadPlugin(plugin); +} + static std::string Stringify(duk_context* ctx, duk_idx_t idx) { auto type = duk_get_type(ctx, idx); diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index c83ba577af..4156c13825 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -123,6 +123,10 @@ namespace OpenRCT2::Scripting { return _execInfo; } + std::vector>& GetPlugins() + { + return _plugins; + } void LoadPlugins(); void UnloadPlugins(); @@ -136,13 +140,17 @@ namespace OpenRCT2::Scripting _pluginStoppedSubscriptions.push_back(callback); } + void AddNetworkPlugin(const std::string_view& code); + private: void Initialise(); void StartPlugins(); void StopPlugins(); void LoadPlugin(const std::string& path); + void LoadPlugin(std::shared_ptr& plugin); void StopPlugin(std::shared_ptr plugin); bool ShouldLoadScript(const std::string& path); + bool ShouldStartPlugin(const std::shared_ptr &plugin); void SetupHotReloading(); void AutoReloadPlugins(); void ProcessREPL();