From b15a6e843aa340ff4b655b4f1f332269c6bf2eed Mon Sep 17 00:00:00 2001 From: Basssiiie Date: Sun, 15 Jan 2023 21:52:23 +0100 Subject: [PATCH] [Plugin] Wrap callback arguments for custom game actions in event arguments object, fix issue with unloading multiplayer plugins (#19091) * Wrap custom game action arguments in event args object * Update Typescript declaration, documentation and changelog * Pass custom game action by value and remove log messages --- distribution/changelog.txt | 2 ++ distribution/openrct2.d.ts | 10 +++--- distribution/scripting.md | 5 +-- src/openrct2/actions/CustomAction.cpp | 4 +-- src/openrct2/network/NetworkBase.cpp | 2 +- src/openrct2/scripting/ScriptEngine.cpp | 45 ++++++++++++++++++++----- src/openrct2/scripting/ScriptEngine.h | 7 ++-- 7 files changed, 54 insertions(+), 21 deletions(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index e96e4c04b9..c16fc22b38 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -13,11 +13,13 @@ - Improved: [#18975] Add lift sprites for 60 steep hills on the wooden roller coaster. - Improved: [#19044] Added special thanks to RMC and Wiegand to the About page. - Change: [#19018] Renamed actions to fit the naming scheme. +- Change: [#19091] [Plugin] Add game action information to callback arguments of custom actions. - Fix: [#18467] “Selected only” Object Selection filter is active in Track Designs Manager, and cannot be toggled. - Fix: [#18905] Ride Construction window theme is not applied correctly. - Fix: [#18911] Mini Golf station does not draw correctly from all angles. - Fix: [#18971] New Game does not prompt for save before quitting. - Fix: [#19026] Park loan is clamped to a 32-bit integer. +- Fix: [#19091] [Plugin] Remote plugins in multiplayer servers do not unload properly. - Fix: [#19112] Clearing the last character in the Object Selection filter does not properly reset it. - Fix: [#19114] [Plugin] GameActionResult does not comply to API specification. - Fix: [#19136] SV6 saves with experimental RCT1 paths not imported correctly. diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 17dd3c13cd..7cc0da29f2 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -265,10 +265,10 @@ declare global { * @param execute Logic for validating and executing the action. * @throws An error if the action has already been registered by this or another plugin. */ - registerAction( + registerAction( action: string, - query: (args: object) => GameActionResult, - execute: (args: object) => GameActionResult): void; + query: (args: GameActionEventArgs) => GameActionResult, + execute: (args: GameActionEventArgs) => GameActionResult): void; /** * Query the result of running a game action. This allows you to check the outcome and validity of @@ -1278,12 +1278,12 @@ declare global { height: number; } - interface GameActionEventArgs { + interface GameActionEventArgs { readonly player: number; readonly type: number; readonly action: string; readonly isClientOnly: boolean; - readonly args: object; + readonly args: T; result: GameActionResult; } diff --git a/distribution/scripting.md b/distribution/scripting.md index 984748cfe9..a78ebc81f6 100644 --- a/distribution/scripting.md +++ b/distribution/scripting.md @@ -56,8 +56,9 @@ The hot reload feature can be enabled by editing your `config.ini` file and sett ## Breaking changes As of version 34 there are breaking Api changes. -> Version 34 -```Entity.type will now return "guest" or "staff" instead of "peep"``` +- **Version 34:** `Entity.type` will now return `"guest"` or `"staff"` instead of `"peep"`. +- **Version 63:** Accessing G2 sprites by id directly is now deprecated in favor of a future-proof implementation using `IconName` and/or `context.getIcon()`. +- **Version 68:** Custom game actions registered through `context.registerAction()` now wrap the callback arguments in a `GameActionEventArgs`, similar to `context.subscribe()` callbacks. ## Frequently Asked Questions diff --git a/src/openrct2/actions/CustomAction.cpp b/src/openrct2/actions/CustomAction.cpp index 5bb3bc0baa..b573f7c45c 100644 --- a/src/openrct2/actions/CustomAction.cpp +++ b/src/openrct2/actions/CustomAction.cpp @@ -43,13 +43,13 @@ void CustomAction::Serialise(DataSerialiser& stream) GameActions::Result CustomAction::Query() const { auto& scriptingEngine = OpenRCT2::GetContext()->GetScriptEngine(); - return scriptingEngine.QueryOrExecuteCustomGameAction(_id, _json, false); + return scriptingEngine.QueryOrExecuteCustomGameAction(*this, false); } GameActions::Result CustomAction::Execute() const { auto& scriptingEngine = OpenRCT2::GetContext()->GetScriptEngine(); - return scriptingEngine.QueryOrExecuteCustomGameAction(_id, _json, true); + return scriptingEngine.QueryOrExecuteCustomGameAction(*this, true); } #endif diff --git a/src/openrct2/network/NetworkBase.cpp b/src/openrct2/network/NetworkBase.cpp index f543419c46..c57014829f 100644 --- a/src/openrct2/network/NetworkBase.cpp +++ b/src/openrct2/network/NetworkBase.cpp @@ -43,7 +43,7 @@ // It is used for making sure only compatible builds get connected, even within // single OpenRCT2 version. -#define NETWORK_STREAM_VERSION "1" +#define NETWORK_STREAM_VERSION "2" #define NETWORK_STREAM_ID OPENRCT2_VERSION "-" NETWORK_STREAM_VERSION diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 591b96a852..59bf98ebf9 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -1019,8 +1019,13 @@ void ScriptEngine::RemoveNetworkPlugins() auto it = _plugins.begin(); while (it != _plugins.end()) { - if (!(*it)->HasPath()) + auto plugin = (*it); + if (!plugin->HasPath()) { + StopPlugin(plugin); + UnloadPlugin(plugin); + LogPluginInfo(plugin, "Unregistered network plugin"); + it = _plugins.erase(it); } else @@ -1030,16 +1035,16 @@ void ScriptEngine::RemoveNetworkPlugins() } } -GameActions::Result ScriptEngine::QueryOrExecuteCustomGameAction(std::string_view id, std::string_view args, bool isExecute) +GameActions::Result ScriptEngine::QueryOrExecuteCustomGameAction(const CustomAction& customAction, bool isExecute) { - std::string actionz = std::string(id); + std::string actionz = customAction.GetId(); auto kvp = _customActions.find(actionz); if (kvp != _customActions.end()) { - const auto& customAction = kvp->second; + const auto& customActionInfo = kvp->second; // Deserialise the JSON args - std::string argsz(args); + std::string argsz = customAction.GetJson(); auto dukArgs = DuktapeTryParseJson(_context, argsz); if (!dukArgs) @@ -1050,15 +1055,33 @@ GameActions::Result ScriptEngine::QueryOrExecuteCustomGameAction(std::string_vie return action; } + std::vector pluginCallArgs; + if (GetTargetAPIVersion() <= API_VERSION_68_CUSTOM_ACTION_ARGS) + { + pluginCallArgs = { *dukArgs }; + } + else + { + DukObject obj(_context); + obj.Set("action", actionz); + obj.Set("args", *dukArgs); + obj.Set("player", customAction.GetPlayer()); + obj.Set("type", EnumValue(customAction.GetType())); + + auto flags = customAction.GetActionFlags(); + obj.Set("isClientOnly", (flags & GameActions::Flags::ClientOnly) != 0); + pluginCallArgs = { obj.Take() }; + } + // Ready to call plugin handler DukValue dukResult; if (!isExecute) { - dukResult = ExecutePluginCall(customAction.Owner, customAction.Query, { *dukArgs }, false); + dukResult = ExecutePluginCall(customActionInfo.Owner, customActionInfo.Query, pluginCallArgs, false); } else { - dukResult = ExecutePluginCall(customAction.Owner, customAction.Execute, { *dukArgs }, true); + dukResult = ExecutePluginCall(customActionInfo.Owner, customActionInfo.Execute, pluginCallArgs, true); } return DukToGameActionResult(dukResult); } @@ -1469,7 +1492,13 @@ std::unique_ptr ScriptEngine::CreateGameAction(const std::string& ac auto jsonz = duk_json_encode(ctx, -1); auto json = std::string(jsonz); duk_pop(ctx); - return std::make_unique(actionid, json); + auto customAction = std::make_unique(actionid, json); + + if (customAction->GetPlayer() == -1 && network_get_mode() != NETWORK_MODE_NONE) + { + customAction->SetPlayer(network_get_current_player_id()); + } + return customAction; } void ScriptEngine::InitSharedStorage() diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 21d31d2b17..26680b3b60 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -11,6 +11,7 @@ #ifdef ENABLE_SCRIPTING +# include "../actions/CustomAction.h" # include "../common.h" # include "../core/FileWatcher.h" # include "../management/Finance.h" @@ -46,11 +47,12 @@ namespace OpenRCT2 namespace OpenRCT2::Scripting { - static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 68; + static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 69; // Versions marking breaking changes. static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33; static constexpr int32_t API_VERSION_63_G2_REORDER = 63; + static constexpr int32_t API_VERSION_68_CUSTOM_ACTION_ARGS = 68; # ifndef DISABLE_NETWORK class ScSocketBase; @@ -236,8 +238,7 @@ namespace OpenRCT2::Scripting void AddNetworkPlugin(std::string_view code); void RemoveNetworkPlugins(); - [[nodiscard]] GameActions::Result QueryOrExecuteCustomGameAction( - std::string_view id, std::string_view args, bool isExecute); + [[nodiscard]] GameActions::Result QueryOrExecuteCustomGameAction(const CustomAction& action, bool isExecute); bool RegisterCustomAction( const std::shared_ptr& plugin, std::string_view action, const DukValue& query, const DukValue& execute); void RunGameActionHooks(const GameAction& action, GameActions::Result& result, bool isExecute);