mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-19 21:13:05 +01:00
Merge pull request #16685 from IntelOrca/plugin/park-storage
Add Plugin API for storing data in .park files
This commit is contained in:
@@ -86,6 +86,11 @@ void GameState::InitAll(const TileCoordsXY& mapSize)
|
||||
|
||||
CheatsReset();
|
||||
ClearRestrictedScenery();
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
scriptEngine.ClearParkStorage();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "../ride/Vehicle.h"
|
||||
#include "../scenario/Scenario.h"
|
||||
#include "../scenario/ScenarioRepository.h"
|
||||
#include "../scripting/ScriptEngine.h"
|
||||
#include "../world/Climate.h"
|
||||
#include "../world/Entrance.h"
|
||||
#include "../world/Map.h"
|
||||
@@ -86,6 +87,7 @@ namespace OpenRCT2
|
||||
// constexpr uint32_t STAFF = 0x35;
|
||||
constexpr uint32_t CHEATS = 0x36;
|
||||
constexpr uint32_t RESTRICTED_OBJECTS = 0x37;
|
||||
constexpr uint32_t PLUGIN_STORAGE = 0x38;
|
||||
constexpr uint32_t PACKED_OBJECTS = 0x80;
|
||||
// clang-format on
|
||||
}; // namespace ParkFileChunkType
|
||||
@@ -134,6 +136,7 @@ namespace OpenRCT2
|
||||
ReadWriteInterfaceChunk(os);
|
||||
ReadWriteCheatsChunk(os);
|
||||
ReadWriteRestrictedObjectsChunk(os);
|
||||
ReadWritePluginStorageChunk(os);
|
||||
if (os.GetHeader().TargetVersion < 0x4)
|
||||
{
|
||||
UpdateTrackElementsRideType();
|
||||
@@ -167,6 +170,7 @@ namespace OpenRCT2
|
||||
ReadWriteInterfaceChunk(os);
|
||||
ReadWriteCheatsChunk(os);
|
||||
ReadWriteRestrictedObjectsChunk(os);
|
||||
ReadWritePluginStorageChunk(os);
|
||||
ReadWritePackedObjectsChunk(os);
|
||||
}
|
||||
|
||||
@@ -548,6 +552,35 @@ namespace OpenRCT2
|
||||
});
|
||||
}
|
||||
|
||||
void ReadWritePluginStorageChunk(OrcaStream& os)
|
||||
{
|
||||
auto& park = GetContext()->GetGameState()->GetPark();
|
||||
if (os.GetMode() == OrcaStream::Mode::WRITING)
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Dump the plugin storage to JSON (stored in park)
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
park.PluginStorage = scriptEngine.GetParkStorageAsJSON();
|
||||
#endif
|
||||
if (park.PluginStorage.empty() || park.PluginStorage == "{}")
|
||||
{
|
||||
// Don't write the chunk if there is no plugin storage
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
os.ReadWriteChunk(
|
||||
ParkFileChunkType::PLUGIN_STORAGE, [&park](OrcaStream::ChunkStream& cs) { cs.ReadWrite(park.PluginStorage); });
|
||||
|
||||
if (os.GetMode() == OrcaStream::Mode::READING)
|
||||
{
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
scriptEngine.SetParkStorageFromJSON(park.PluginStorage);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ReadWritePackedObjectsChunk(OrcaStream& os)
|
||||
{
|
||||
static constexpr uint8_t DESCRIPTOR_DAT = 0;
|
||||
|
||||
@@ -8,7 +8,7 @@ struct ObjectRepositoryItem;
|
||||
namespace OpenRCT2
|
||||
{
|
||||
// Current version that is saved.
|
||||
constexpr uint32_t PARK_FILE_CURRENT_VERSION = 0x9;
|
||||
constexpr uint32_t PARK_FILE_CURRENT_VERSION = 0xA;
|
||||
|
||||
// The minimum version that is forwards compatible with the current version.
|
||||
constexpr uint32_t PARK_FILE_MIN_VERSION = 0x9;
|
||||
|
||||
@@ -439,6 +439,7 @@ void ScriptEngine::Initialise()
|
||||
_pluginsStarted = false;
|
||||
|
||||
InitSharedStorage();
|
||||
ClearParkStorage();
|
||||
}
|
||||
|
||||
void ScriptEngine::LoadPlugins()
|
||||
@@ -1245,6 +1246,29 @@ void ScriptEngine::SaveSharedStorage()
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptEngine::ClearParkStorage()
|
||||
{
|
||||
duk_push_object(_context);
|
||||
_parkStorage = std::move(DukValue::take_from_stack(_context));
|
||||
}
|
||||
|
||||
std::string ScriptEngine::GetParkStorageAsJSON()
|
||||
{
|
||||
_parkStorage.push();
|
||||
auto json = std::string(duk_json_encode(_context, -1));
|
||||
duk_pop(_context);
|
||||
return json;
|
||||
}
|
||||
|
||||
void ScriptEngine::SetParkStorageFromJSON(std::string_view value)
|
||||
{
|
||||
auto result = DuktapeTryParseJson(_context, value);
|
||||
if (result)
|
||||
{
|
||||
_parkStorage = std::move(*result);
|
||||
}
|
||||
}
|
||||
|
||||
IntervalHandle ScriptEngine::AllocateHandle()
|
||||
{
|
||||
for (size_t i = 0; i < _intervals.size(); i++)
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace OpenRCT2
|
||||
|
||||
namespace OpenRCT2::Scripting
|
||||
{
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 45;
|
||||
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 46;
|
||||
|
||||
// Versions marking breaking changes.
|
||||
static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33;
|
||||
@@ -153,6 +153,7 @@ namespace OpenRCT2::Scripting
|
||||
HookEngine _hookEngine;
|
||||
ScriptExecutionInfo _execInfo;
|
||||
DukValue _sharedStorage;
|
||||
DukValue _parkStorage;
|
||||
|
||||
uint32_t _lastIntervalTimestamp{};
|
||||
std::vector<ScriptInterval> _intervals;
|
||||
@@ -195,11 +196,19 @@ namespace OpenRCT2::Scripting
|
||||
{
|
||||
return _sharedStorage;
|
||||
}
|
||||
DukValue GetParkStorage()
|
||||
{
|
||||
return _parkStorage;
|
||||
}
|
||||
std::vector<std::shared_ptr<Plugin>>& GetPlugins()
|
||||
{
|
||||
return _plugins;
|
||||
}
|
||||
|
||||
void ClearParkStorage();
|
||||
std::string GetParkStorageAsJSON();
|
||||
void SetParkStorageFromJSON(std::string_view value);
|
||||
|
||||
void LoadPlugins();
|
||||
void UnloadPlugins();
|
||||
void Tick();
|
||||
|
||||
@@ -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<DukValue> 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
|
||||
{
|
||||
|
||||
@@ -55,7 +55,59 @@ namespace OpenRCT2::Scripting
|
||||
std::shared_ptr<ScConfiguration> sharedStorage_get()
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
return std::make_shared<ScConfiguration>(scriptEngine.GetSharedStorage());
|
||||
return std::make_shared<ScConfiguration>(ScConfigurationKind::Shared, scriptEngine.GetSharedStorage());
|
||||
}
|
||||
|
||||
std::shared_ptr<ScConfiguration> GetParkStorageForPlugin(std::string_view pluginName)
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
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<ScConfiguration>(ScConfigurationKind::Park, pluginStore);
|
||||
}
|
||||
|
||||
std::shared_ptr<ScConfiguration> getParkStorage(const DukValue& dukPluginName)
|
||||
{
|
||||
auto& scriptEngine = GetContext()->GetScriptEngine();
|
||||
|
||||
std::shared_ptr<ScConfiguration> 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)
|
||||
@@ -381,6 +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_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");
|
||||
|
||||
@@ -258,6 +258,7 @@ money64 Park::GetCompanyValue() const
|
||||
void Park::Initialise()
|
||||
{
|
||||
Name = format_string(STR_UNNAMED_PARK, nullptr);
|
||||
PluginStorage = {};
|
||||
gStaffHandymanColour = COLOUR_BRIGHT_RED;
|
||||
gStaffMechanicColour = COLOUR_LIGHT_BLUE;
|
||||
gStaffSecurityColour = COLOUR_YELLOW;
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace OpenRCT2
|
||||
{
|
||||
public:
|
||||
std::string Name;
|
||||
std::string PluginStorage;
|
||||
|
||||
Park() = default;
|
||||
Park(const Park&) = delete;
|
||||
|
||||
Reference in New Issue
Block a user