diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index a5ee8c1103..ec84c0d072 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -186,15 +186,18 @@ declare global { sharedStorage: Configuration; /** - * Shared generic storage for all plugins. Data is persisted for the current - * loaded park, and is stored inside the .park file. Any references to objects, - * or arrays are copied by reference. If these arrays, objects, or any other - * arrays, or objects that they reference change without a subsequent call to + * Gets the storage for the current plugin if no name is specified. + * If a plugin name is specified, the storage for the plugin with that name will be returned. + * Data is persisted for the current loaded park, and is stored inside the .park file. + * Any references to objects, or arrays are copied by reference. If these arrays, objects, + * or any other arrays, or objects that they reference change without a subsequent call to * the `set` method, their new state will still be serialised. * Keep in mind that all data here will be serialised every time the park is * saved, including when the park is periodically saved automatically. + * @param pluginName The name of the plugin to get a store for. If undefined, the + * current plugin's name will be used. Plugin names are case sensitive. */ - parkStorage: Configuration; + getParkStorage(pluginName?: string): Configuration; /** * Render the current state of the map and save to disc. @@ -313,7 +316,7 @@ declare global { } interface Configuration { - getAll(namespace: string): { [name: string]: any }; + getAll(namespace?: string): { [name: string]: any }; get(key: string): T | undefined; get(key: string, defaultValue: T): T; set(key: string, value: T): void; diff --git a/distribution/scripting.md b/distribution/scripting.md index 715acffff9..e780647960 100644 --- a/distribution/scripting.md +++ b/distribution/scripting.md @@ -172,7 +172,7 @@ if (!h) { All plugins have access to the same shared storage. -If you want to only store data specific to the current park that is loaded, use `context.parkStorage`. Any data stored here will be written to the .park file. +If you want to only store data specific to the current park that is loaded, use `context.getParkStorage`. Any data stored here will be written to the .park file. > Can plugins communicate with other processes, or the internet? diff --git a/src/openrct2/scripting/bindings/game/ScConfiguration.hpp b/src/openrct2/scripting/bindings/game/ScConfiguration.hpp index a7d1477653..9a70c2762d 100644 --- a/src/openrct2/scripting/bindings/game/ScConfiguration.hpp +++ b/src/openrct2/scripting/bindings/game/ScConfiguration.hpp @@ -19,22 +19,30 @@ namespace OpenRCT2::Scripting { + enum class ScConfigurationKind + { + User, + Shared, + Park + }; + class ScConfiguration { private: - bool _isUserConfig{}; + ScConfigurationKind _kind; DukValue _backingObject; public: // context.configuration ScConfiguration() - : _isUserConfig(true) + : _kind(ScConfigurationKind::User) { } - // context.sharedStorage - ScConfiguration(const DukValue& backingObject) - : _backingObject(backingObject) + // context.sharedStorage / context.getParkStorage + ScConfiguration(ScConfigurationKind kind, const DukValue& backingObject) + : _kind(kind) + , _backingObject(backingObject) { } @@ -68,15 +76,18 @@ namespace OpenRCT2::Scripting std::optional GetNamespaceObject(std::string_view ns) const { auto store = _backingObject; - auto k = ns; - bool end; - do + if (!ns.empty()) { - auto [next, remainder] = GetNextNamespace(k); - store = store[next]; - k = remainder; - end = store.type() == DukValue::Type::UNDEFINED || remainder.empty(); - } while (!end); + auto k = ns; + bool end; + do + { + auto [next, remainder] = GetNextNamespace(k); + store = store[next]; + k = remainder; + end = store.type() == DukValue::Type::UNDEFINED || remainder.empty(); + } while (!end); + } return store.type() == DukValue::OBJECT ? std::make_optional(store) : std::nullopt; } @@ -112,17 +123,26 @@ namespace OpenRCT2::Scripting bool IsValidNamespace(std::string_view ns) const { - if (ns.empty() || ns[0] == '.' || ns[ns.size() - 1] == '.') + if (!ns.empty() && (ns[0] == '.' || ns[ns.size() - 1] == '.')) { return false; } - for (size_t i = 1; i < ns.size() - 1; i++) + + if (_kind != ScConfigurationKind::Park) { - if (ns[i - 1] == '.' && ns[i] == '.') + if (ns.empty()) { return false; } + for (size_t i = 1; i < ns.size() - 1; i++) + { + if (ns[i - 1] == '.' && ns[i] == '.') + { + return false; + } + } } + return true; } @@ -131,13 +151,24 @@ namespace OpenRCT2::Scripting return !key.empty() && key.find('.') == std::string_view::npos; } - DukValue getAll(const std::string& ns) const + DukValue getAll(const DukValue& dukNamespace) const { DukValue result; auto ctx = GetContext()->GetScriptEngine().GetContext(); + + std::string ns = ""; + if (dukNamespace.type() == DukValue::Type::STRING) + { + ns = dukNamespace.as_string(); + } + else if (dukNamespace.type() != DukValue::Type::UNDEFINED) + { + duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid."); + } + if (IsValidNamespace(ns)) { - if (_isUserConfig) + if (_kind == ScConfigurationKind::User) { DukObject obj(ctx); if (ns == "general") @@ -163,7 +194,7 @@ namespace OpenRCT2::Scripting DukValue get(const std::string& key, const DukValue& defaultValue) const { auto ctx = GetContext()->GetScriptEngine().GetContext(); - if (_isUserConfig) + if (_kind == ScConfigurationKind::User) { if (key == "general.language") { @@ -214,7 +245,7 @@ namespace OpenRCT2::Scripting { auto& scriptEngine = GetContext()->GetScriptEngine(); auto ctx = scriptEngine.GetContext(); - if (_isUserConfig) + if (_kind == ScConfigurationKind::User) { try { diff --git a/src/openrct2/scripting/bindings/game/ScContext.hpp b/src/openrct2/scripting/bindings/game/ScContext.hpp index 654808dcf8..f65d366f9f 100644 --- a/src/openrct2/scripting/bindings/game/ScContext.hpp +++ b/src/openrct2/scripting/bindings/game/ScContext.hpp @@ -55,13 +55,59 @@ namespace OpenRCT2::Scripting std::shared_ptr sharedStorage_get() { auto& scriptEngine = GetContext()->GetScriptEngine(); - return std::make_shared(scriptEngine.GetSharedStorage()); + return std::make_shared(ScConfigurationKind::Shared, scriptEngine.GetSharedStorage()); } - std::shared_ptr parkStorage_get() + std::shared_ptr GetParkStorageForPlugin(std::string_view pluginName) { auto& scriptEngine = GetContext()->GetScriptEngine(); - return std::make_shared(scriptEngine.GetParkStorage()); + auto parkStore = scriptEngine.GetParkStorage(); + auto pluginStore = parkStore[pluginName]; + + // Create if it doesn't exist + if (pluginStore.type() != DukValue::Type::OBJECT) + { + auto* ctx = scriptEngine.GetContext(); + parkStore.push(); + duk_push_object(ctx); + duk_put_prop_lstring(ctx, -2, pluginName.data(), pluginName.size()); + duk_pop(ctx); + + pluginStore = parkStore[pluginName]; + } + + return std::make_shared(ScConfigurationKind::Park, pluginStore); + } + + std::shared_ptr getParkStorage(const DukValue& dukPluginName) + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + + std::shared_ptr result; + if (dukPluginName.type() == DukValue::Type::STRING) + { + auto& pluginName = dukPluginName.as_string(); + if (pluginName.empty()) + { + duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name is empty"); + } + result = GetParkStorageForPlugin(pluginName); + } + else if (dukPluginName.type() == DukValue::Type::UNDEFINED) + { + auto plugin = _execInfo.GetCurrentPlugin(); + if (plugin == nullptr) + { + duk_error( + scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name must be specified when used from console."); + } + result = GetParkStorageForPlugin(plugin->GetMetadata().Name); + } + else + { + duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid plugin name."); + } + return result; } void captureImage(const DukValue& options) @@ -387,7 +433,7 @@ namespace OpenRCT2::Scripting dukglue_register_property(ctx, &ScContext::apiVersion_get, nullptr, "apiVersion"); dukglue_register_property(ctx, &ScContext::configuration_get, nullptr, "configuration"); dukglue_register_property(ctx, &ScContext::sharedStorage_get, nullptr, "sharedStorage"); - dukglue_register_property(ctx, &ScContext::parkStorage_get, nullptr, "parkStorage"); + dukglue_register_method(ctx, &ScContext::getParkStorage, "getParkStorage"); dukglue_register_method(ctx, &ScContext::captureImage, "captureImage"); dukglue_register_method(ctx, &ScContext::getObject, "getObject"); dukglue_register_method(ctx, &ScContext::getAllObjects, "getAllObjects");