diff --git a/distribution/changelog.txt b/distribution/changelog.txt
index 5334186c37..21088f56ed 100644
--- a/distribution/changelog.txt
+++ b/distribution/changelog.txt
@@ -26,6 +26,7 @@
- Feature: [#19446] Add new colour options to colour dropdown.
- Feature: [#19547] Add large sloped turns to hybrid coaster and single rail coaster.
- Feature: [#19930] Add plugin APIs for research.
+- Feature: [#19979] Add plugin API for scenery groups.
- Feature: [OpenMusic#25] Added Prehistoric ride music style.
- Feature: [OpenMusic#26] Fairground Organ style 2 with new recordings from Herman's 35er Voigt (Previously known as Bressingham Voigt).
- Feature: [OpenMusic#28] Add Ragtime style 2 ride music.
diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts
index 93c4ad5a5c..3056b508cc 100644
--- a/distribution/openrct2.d.ts
+++ b/distribution/openrct2.d.ts
@@ -41,6 +41,10 @@ declare global {
var climate: Climate;
/** APIs for performance profiling. */
var profiler: Profiler;
+ /**
+ * APIs for getting, loading, and unloading objects.
+ */
+ var objectManager: ObjectManager;
/**
* APIs for creating and editing title sequences.
* These will only be available to clients that are not running headless mode.
@@ -217,19 +221,40 @@ declare global {
captureImage(options: CaptureOptions): void;
/**
- * Gets the loaded object at the given index.
- * @param type The object type.
- * @param index The index.
+ * @deprecated Use {@link ObjectManager.getObject} instead.
*/
getObject(type: ObjectType, index: number): LoadedImageObject;
- getObject(type: "music", index: number): LoadedObject;
+
+ /**
+ * @deprecated Use {@link ObjectManager.getObject} instead.
+ */
getObject(type: "ride", index: number): RideObject;
+
+ /**
+ * @deprecated Use {@link ObjectManager.getObject} instead.
+ */
getObject(type: "small_scenery", index: number): SmallSceneryObject;
+ /**
+ * @deprecated Use {@link ObjectManager.getObject} instead.
+ */
+ getObject(type: "music", index: number): LoadedObject;
+
+ /**
+ * @deprecated Use {@link ObjectManager.getAllObjects} instead.
+ */
getAllObjects(type: ObjectType): LoadedImageObject[];
- getAllObjects(type: "music"): LoadedObject[];
+
+ /**
+ * @deprecated Use {@link ObjectManager.getAllObjects} instead.
+ */
getAllObjects(type: "ride"): RideObject[];
+ /**
+ * @deprecated Use {@link ObjectManager.getAllObjects} instead.
+ */
+ getAllObjects(type: "music"): LoadedObject[];
+
/**
* Gets the {@link TrackSegment} for the given type.
* @param type The track segment type.
@@ -1586,10 +1611,80 @@ declare global {
removeElement(index: number): void;
}
+ type ObjectSourceGame =
+ "rct1" |
+ "added_attractions" |
+ "loopy_landscapes" |
+ "rct2" |
+ "wacky_worlds" |
+ "time_twister" |
+ "custom" |
+ "openrct2_official";
+
+ type ObjectGeneration = "dat" | "json";
+
+ /**
+ * Represents an installed OpenRCT2 object which may or may not be currently loaded into the park.
+ */
+ interface InstalledObject {
+ /**
+ * The full path of the object file.
+ */
+ readonly path: string;
+
+ /**
+ * Whether the object is an original .DAT file, or a .parkobj / .json file.
+ */
+ readonly generation: ObjectGeneration;
+
+ /**
+ * The object type.
+ */
+ readonly type: ObjectType;
+
+ /**
+ * The original game or expansion pack this object first appeared in.
+ */
+ readonly sourceGames: ObjectSourceGame[];
+
+ /**
+ * The unique identifier of the object, e.g. "rct2.burgb".
+ * For legacy DAT objects, the identifier will be in a format similar to "09F55405|DirtGras|B9B19A7F".
+ */
+ readonly identifier: string;
+
+ /**
+ * The original unique identifier of the object, e.g. "BURGB ".
+ * This may have trailing spaces if the name is shorter than 8 characters.
+ * Only .DAT objects or JSON objects based on .DAT objects will have legacy identifiers.
+ */
+ readonly legacyIdentifier: string | null;
+
+ /**
+ * The object version, e.g. "1.5.2-pre".
+ */
+ readonly version: string;
+
+ /**
+ * Gets the list of authors for the object.
+ */
+ readonly authors: string[];
+
+ /**
+ * The name in the user's current language.
+ */
+ readonly name: string;
+ }
+
/**
* Represents the definition of a loaded object (.DAT or .json) such as ride type or scenery item.
*/
interface LoadedObject {
+ /**
+ * Gets a reference to the installed object.
+ */
+ readonly installedObject: InstalledObject;
+
/**
* The object type.
*/
@@ -1602,7 +1697,7 @@ declare global {
/**
* The unique identifier of the object, e.g. "rct2.burgb".
- * Only JSON objects will have an identifier.
+ * For legacy DAT objects, the identifier will be in a format similar to "09F55405|DirtGras|B9B19A7F".
*/
readonly identifier: string;
@@ -1752,10 +1847,19 @@ declare global {
readonly numVerticalFramesOverride: number;
}
+ interface SceneryObject extends LoadedImageObject {
+ /**
+ * A list of scenery groups this object belongs to. This may not contain any
+ * scenery groups that contain this object by default. This is typically
+ * used for custom objects to be part of existing scenery groups.
+ */
+ readonly sceneryGroups: string[];
+ }
+
/**
* Represents the object definition of a small scenery item such a tree.
*/
- interface SmallSceneryObject extends LoadedImageObject {
+ interface SmallSceneryObject extends SceneryObject {
/**
* Raw bit flags that describe characteristics of the scenery item.
*/
@@ -1777,6 +1881,32 @@ declare global {
readonly removalPrice: number;
}
+ interface LargeSceneryObject extends SceneryObject {
+
+ }
+
+ interface WallObject extends SceneryObject {
+
+ }
+
+ interface FootpathAdditionObject extends SceneryObject {
+
+ }
+
+ interface BannerObject extends SceneryObject {
+
+ }
+
+ /**
+ * Represents the object definition of a scenery group.
+ */
+ interface SceneryGroupObject extends LoadedImageObject {
+ /**
+ * The scenery items that belong to this scenery group.
+ */
+ readonly items: string[];
+ }
+
/**
* Represents a ride or stall within the park.
*/
@@ -2337,11 +2467,11 @@ declare global {
* The current tilt of the car in the X/Y axis.
*/
bankRotation: number;
-
- /**
- * Whether the car sprite is reversed or not.
- */
- isReversed: boolean;
+
+ /**
+ * Whether the car sprite is reversed or not.
+ */
+ isReversed: boolean;
/**
* The colour of the car.
@@ -4588,4 +4718,81 @@ declare global {
readonly parents: number[];
readonly children: number[];
}
+
+ interface ObjectManager {
+ /**
+ * Gets all the objects that are installed and can be loaded into the park.
+ */
+ readonly installedObjects: InstalledObject[];
+
+ /**
+ * Gets the installed object with the given identifier, or null
+ * if the object was not found.
+ * @param identifier The object identifier.
+ */
+ getInstalledObject(identifier: string): InstalledObject | null;
+
+ /**
+ * Attempt to load the object into the current park at the given index for the object type.
+ * If an object already exists at the given index, that object will be unloaded and this object
+ * will replace it, providing the object type is the same.
+ * @param identifier The object identifier.
+ * @param index The index to load the object to. If not provided, an empty slot will be used.
+ * @returns The index of the loaded object.
+ */
+ load(identifier: string, index?: number): LoadedObject | null;
+
+ /**
+ * Attempt to load the given objects into the current park, given they are not already loaded.
+ */
+ load(identifiers: string[]): (LoadedObject | null)[];
+
+ /**
+ * Unloads the object, if loaded.
+ * @param identifier The object identifier to unload.
+ */
+ unload(identifier: string): void;
+
+ /**
+ * Unloads the specified objects, if loaded.
+ * @param identifiers The object identifiers to unload.
+ */
+ unload(identifiers: string[]): void;
+
+ /**
+ * Unloads the specified object, if loaded.
+ * @param type The object type.
+ * @param index The index of the slot to unload for the given type.
+ */
+ unload(type: ObjectType, index: number): void;
+
+ /**
+ * Gets the loaded object at the given index.
+ * @param type The object type.
+ * @param index The index.
+ */
+ getObject(type: ObjectType, index: number): LoadedObject;
+ getObject(type: "ride", index: number): RideObject;
+ getObject(type: "small_scenery", index: number): SmallSceneryObject;
+ getObject(type: "large_scenery", index: number): LargeSceneryObject;
+ getObject(type: "wall", index: number): WallObject;
+ getObject(type: "footpath_addition", index: number): FootpathAdditionObject;
+ getObject(type: "banner", index: number): BannerObject;
+ getObject(type: "scenery_group", index: number): SceneryGroupObject;
+ getObject(type: "music", index: number): LoadedObject;
+
+ /**
+ * Gets all the currently loaded objects for a given object type.
+ * @param type The object type.
+ */
+ getAllObjects(type: ObjectType): LoadedObject[];
+ getAllObjects(type: "ride"): RideObject[];
+ getAllObjects(type: "small_scenery"): SmallSceneryObject[];
+ getAllObjects(type: "large_scenery"): LargeSceneryObject[];
+ getAllObjects(type: "wall"): WallObject[];
+ getAllObjects(type: "footpath_addition"): FootpathAdditionObject[];
+ getAllObjects(type: "banner"): BannerObject[];
+ getAllObjects(type: "scenery_group"): SceneryGroupObject[];
+ getAllObjects(type: "music"): LoadedObject[];
+ }
}
diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index e8b49634d1..f502c74a9b 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -514,6 +514,8 @@
+
+
@@ -996,6 +998,7 @@
+
diff --git a/src/openrct2/object/Object.cpp b/src/openrct2/object/Object.cpp
index 94a3aff0e9..c7a9348dd6 100644
--- a/src/openrct2/object/Object.cpp
+++ b/src/openrct2/object/Object.cpp
@@ -80,6 +80,47 @@ std::string_view ObjectEntryDescriptor::GetName() const
return Generation == ObjectGeneration::JSON ? Identifier : Entry.GetName();
}
+std::string ObjectEntryDescriptor::ToString() const
+{
+ if (Generation == ObjectGeneration::DAT)
+ {
+ char buffer[32];
+ std::snprintf(&buffer[0], 9, "%08X", Entry.flags);
+ buffer[8] = '|';
+ std::memcpy(&buffer[9], Entry.name, 8);
+ buffer[17] = '|';
+ std::snprintf(&buffer[18], 9, "%8X", Entry.checksum);
+ return std::string(buffer);
+ }
+ else
+ {
+ return std::string(GetName());
+ }
+}
+
+static uint32_t ParseHex(std::string_view x)
+{
+ assert(x.size() != 8);
+ char buffer[9];
+ std::memcpy(buffer, x.data(), 8);
+ buffer[8] = 0;
+ char* endp{};
+ return static_cast(std::strtol(buffer, &endp, 16));
+}
+
+ObjectEntryDescriptor ObjectEntryDescriptor::Parse(std::string_view identifier)
+{
+ if (identifier.size() == 26 && identifier[8] == '|' && identifier[17] == '|')
+ {
+ RCTObjectEntry entry{};
+ entry.flags = ParseHex(identifier.substr(0, 8));
+ entry.SetName(identifier.substr(9, 8));
+ entry.checksum = ParseHex(identifier.substr(18));
+ return ObjectEntryDescriptor(entry);
+ }
+ return ObjectEntryDescriptor(identifier);
+}
+
bool ObjectEntryDescriptor::operator==(const ObjectEntryDescriptor& rhs) const
{
if (Generation != rhs.Generation)
diff --git a/src/openrct2/object/Object.h b/src/openrct2/object/Object.h
index db1ecd3d57..1eb0de123f 100644
--- a/src/openrct2/object/Object.h
+++ b/src/openrct2/object/Object.h
@@ -139,9 +139,12 @@ struct ObjectEntryDescriptor
bool HasValue() const;
ObjectType GetType() const;
std::string_view GetName() const;
+ std::string ToString() const;
bool operator==(const ObjectEntryDescriptor& rhs) const;
bool operator!=(const ObjectEntryDescriptor& rhs) const;
+
+ static ObjectEntryDescriptor Parse(std::string_view identifier);
};
struct IObjectRepository;
diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp
index 5da40499b9..b680b8ee25 100644
--- a/src/openrct2/object/ObjectManager.cpp
+++ b/src/openrct2/object/ObjectManager.cpp
@@ -175,6 +175,12 @@ public:
return RepositoryItemToObject(ori);
}
+ Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) override
+ {
+ const ObjectRepositoryItem* ori = _objectRepository.FindObject(descriptor);
+ return RepositoryItemToObject(ori, slot);
+ }
+
void LoadObjects(const ObjectList& objectList) override
{
// Find all the required objects
diff --git a/src/openrct2/object/ObjectManager.h b/src/openrct2/object/ObjectManager.h
index 51b6b96757..ffa3c1e79a 100644
--- a/src/openrct2/object/ObjectManager.h
+++ b/src/openrct2/object/ObjectManager.h
@@ -36,6 +36,7 @@ struct IObjectManager
virtual Object* LoadObject(std::string_view identifier) abstract;
virtual Object* LoadObject(const RCTObjectEntry* entry) abstract;
virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor) abstract;
+ virtual Object* LoadObject(const ObjectEntryDescriptor& descriptor, ObjectEntryIndex slot) abstract;
virtual void LoadObjects(const ObjectList& entries) abstract;
virtual void UnloadObjects(const std::vector& entries) abstract;
virtual void UnloadAllTransient() abstract;
diff --git a/src/openrct2/object/ObjectTypes.cpp b/src/openrct2/object/ObjectTypes.cpp
index e9988e7053..af545f07ed 100644
--- a/src/openrct2/object/ObjectTypes.cpp
+++ b/src/openrct2/object/ObjectTypes.cpp
@@ -7,7 +7,7 @@
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
-#include "ObjectTypes.h"
+#include "Object.h"
#include
@@ -20,3 +20,11 @@ bool ObjectTypeIsIntransient(ObjectType type)
{
return std::find(IntransientObjectTypes.begin(), IntransientObjectTypes.end(), type) != std::end(IntransientObjectTypes);
}
+
+size_t GetObjectTypeLimit(ObjectType type)
+{
+ auto index = EnumValue(type);
+ if (index >= EnumValue(ObjectType::Count))
+ return 0;
+ return static_cast(object_entry_group_counts[index]);
+}
diff --git a/src/openrct2/object/ObjectTypes.h b/src/openrct2/object/ObjectTypes.h
index 79acd13033..1daab86d0f 100644
--- a/src/openrct2/object/ObjectTypes.h
+++ b/src/openrct2/object/ObjectTypes.h
@@ -10,6 +10,7 @@
#pragma once
#include
+#include
#include
#include
@@ -78,3 +79,4 @@ constexpr std::array IntransientObjectTypes = { ObjectType::Scena
bool ObjectTypeIsTransient(ObjectType type);
bool ObjectTypeIsIntransient(ObjectType type);
+size_t GetObjectTypeLimit(ObjectType type);
diff --git a/src/openrct2/object/SceneryGroupObject.cpp b/src/openrct2/object/SceneryGroupObject.cpp
index db9c7f775d..b18d24c054 100644
--- a/src/openrct2/object/SceneryGroupObject.cpp
+++ b/src/openrct2/object/SceneryGroupObject.cpp
@@ -243,3 +243,8 @@ uint16_t SceneryGroupObject::GetNumIncludedObjects() const
{
return static_cast(_items.size());
}
+
+const std::vector& SceneryGroupObject::GetItems() const
+{
+ return _items;
+}
diff --git a/src/openrct2/object/SceneryGroupObject.h b/src/openrct2/object/SceneryGroupObject.h
index 9f1307889c..6c95bbb2a8 100644
--- a/src/openrct2/object/SceneryGroupObject.h
+++ b/src/openrct2/object/SceneryGroupObject.h
@@ -41,6 +41,7 @@ public:
void SetRepositoryItem(ObjectRepositoryItem* item) const override;
uint16_t GetNumIncludedObjects() const;
+ const std::vector& GetItems() const;
private:
static std::vector ReadItems(OpenRCT2::IStream* stream);
diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp
index f2ed3c5dce..f9f7ea3449 100644
--- a/src/openrct2/scripting/ScriptEngine.cpp
+++ b/src/openrct2/scripting/ScriptEngine.cpp
@@ -39,7 +39,9 @@
# include "bindings/network/ScPlayer.hpp"
# include "bindings/network/ScPlayerGroup.hpp"
# include "bindings/network/ScSocket.hpp"
+# include "bindings/object/ScInstalledObject.hpp"
# include "bindings/object/ScObject.hpp"
+# include "bindings/object/ScObjectManager.h"
# include "bindings/ride/ScRide.hpp"
# include "bindings/ride/ScRideStation.hpp"
# include "bindings/world/ScClimate.hpp"
@@ -403,8 +405,16 @@ void ScriptEngine::Initialise()
ScDisposable::Register(ctx);
ScMap::Register(ctx);
ScNetwork::Register(ctx);
+ ScObjectManager::Register(ctx);
+ ScInstalledObject::Register(ctx);
ScObject::Register(ctx);
+ ScSceneryObject::Register(ctx);
ScSmallSceneryObject::Register(ctx);
+ ScLargeSceneryObject::Register(ctx);
+ ScWallObject::Register(ctx);
+ ScFootpathAdditionObject::Register(ctx);
+ ScBannerObject::Register(ctx);
+ ScSceneryGroupObject::Register(ctx);
ScPark::Register(ctx);
ScParkMessage::Register(ctx);
ScPlayer::Register(ctx);
@@ -444,6 +454,7 @@ void ScriptEngine::Initialise()
dukglue_register_global(ctx, std::make_shared(ctx), "park");
dukglue_register_global(ctx, std::make_shared(ctx), "profiler");
dukglue_register_global(ctx, std::make_shared(), "scenario");
+ dukglue_register_global(ctx, std::make_shared(), "objectManager");
RegisterConstants();
diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h
index c2bd6a77c9..dbc208ec5c 100644
--- a/src/openrct2/scripting/ScriptEngine.h
+++ b/src/openrct2/scripting/ScriptEngine.h
@@ -47,7 +47,7 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
- static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 77;
+ static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 78;
// Versions marking breaking changes.
static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33;
diff --git a/src/openrct2/scripting/bindings/game/ScContext.hpp b/src/openrct2/scripting/bindings/game/ScContext.hpp
index c8a71a02bc..e8fc832565 100644
--- a/src/openrct2/scripting/bindings/game/ScContext.hpp
+++ b/src/openrct2/scripting/bindings/game/ScContext.hpp
@@ -23,7 +23,7 @@
# include "../../ScriptEngine.h"
# include "../game/ScConfiguration.hpp"
# include "../game/ScDisposable.hpp"
-# include "../object/ScObject.hpp"
+# include "../object/ScObjectManager.h"
# include "../ride/ScTrackSegment.h"
# include
@@ -160,64 +160,18 @@ namespace OpenRCT2::Scripting
}
}
- static DukValue CreateScObject(duk_context* ctx, ObjectType type, int32_t index)
- {
- switch (type)
- {
- case ObjectType::Ride:
- return GetObjectAsDukValue(ctx, std::make_shared(type, index));
- case ObjectType::SmallScenery:
- return GetObjectAsDukValue(ctx, std::make_shared(type, index));
- default:
- return GetObjectAsDukValue(ctx, std::make_shared(type, index));
- }
- }
-
DukValue getObject(const std::string& typez, int32_t index) const
{
- auto ctx = GetContext()->GetScriptEngine().GetContext();
- auto& objManager = GetContext()->GetObjectManager();
-
- auto type = ScObject::StringToObjectType(typez);
- if (type)
- {
- auto obj = objManager.GetLoadedObject(*type, index);
- if (obj != nullptr)
- {
- return CreateScObject(ctx, *type, index);
- }
- }
- else
- {
- duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
- }
- return ToDuk(ctx, nullptr);
+ // deprecated function, moved to ObjectManager.getObject.
+ ScObjectManager objectManager;
+ return objectManager.getObject(typez, index);
}
std::vector getAllObjects(const std::string& typez) const
{
- auto ctx = GetContext()->GetScriptEngine().GetContext();
- auto& objManager = GetContext()->GetObjectManager();
-
- std::vector result;
- auto type = ScObject::StringToObjectType(typez);
- if (type)
- {
- auto count = object_entry_group_counts[EnumValue(*type)];
- for (int32_t i = 0; i < count; i++)
- {
- auto obj = objManager.GetLoadedObject(*type, i);
- if (obj != nullptr)
- {
- result.push_back(CreateScObject(ctx, *type, i));
- }
- }
- }
- else
- {
- duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
- }
- return result;
+ // deprecated function, moved to ObjectManager.getAllObjects.
+ ScObjectManager objectManager;
+ return objectManager.getAllObjects(typez);
}
DukValue getTrackSegment(track_type_t type)
diff --git a/src/openrct2/scripting/bindings/object/ScInstalledObject.hpp b/src/openrct2/scripting/bindings/object/ScInstalledObject.hpp
new file mode 100644
index 0000000000..39b652fade
--- /dev/null
+++ b/src/openrct2/scripting/bindings/object/ScInstalledObject.hpp
@@ -0,0 +1,197 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2020 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.
+ *****************************************************************************/
+
+#pragma once
+
+#ifdef ENABLE_SCRIPTING
+
+# include "../../../Context.h"
+# include "../../../common.h"
+# include "../../../object/ObjectRepository.h"
+# include "../../Duktape.hpp"
+# include "../../ScriptEngine.h"
+
+# include
+
+namespace OpenRCT2::Scripting
+{
+ inline std::string_view ObjectTypeToString(uint8_t type)
+ {
+ static constexpr std::string_view Types[] = {
+ "ride",
+ "small_scenery",
+ "large_scenery",
+ "wall",
+ "banner",
+ "footpath",
+ "footpath_addition",
+ "scenery_group",
+ "park_entrance",
+ "water",
+ "stex",
+ "terrain_surface",
+ "terrain_edge",
+ "station",
+ "music",
+ "footpath_surface",
+ "footpath_railings",
+ };
+ if (type >= std::size(Types))
+ return "unknown";
+ return Types[type];
+ }
+
+ inline std::string_view ObjectSourceGameToString(ObjectSourceGame sourceGame)
+ {
+ static constexpr std::string_view values[] = { "custom", "wacky_worlds", "time_twister", "openrct2_official",
+ "rct1", "added_attractions", "loopy_landscapes", "unknown",
+ "rct2" };
+ if (EnumValue(sourceGame) >= std::size(values))
+ return "unknown";
+ return values[EnumValue(sourceGame)];
+ }
+
+ class ScInstalledObject
+ {
+ protected:
+ size_t _index{};
+
+ public:
+ ScInstalledObject(size_t index)
+ : _index(index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_register_property(ctx, &ScInstalledObject::path_get, nullptr, "path");
+ dukglue_register_property(ctx, &ScInstalledObject::generation_get, nullptr, "generation");
+ dukglue_register_property(ctx, &ScInstalledObject::identifier_get, nullptr, "identifier");
+ dukglue_register_property(ctx, &ScInstalledObject::type_get, nullptr, "type");
+ dukglue_register_property(ctx, &ScInstalledObject::sourceGames_get, nullptr, "sourceGames");
+ dukglue_register_property(ctx, &ScInstalledObject::legacyIdentifier_get, nullptr, "legacyIdentifier");
+ dukglue_register_property(ctx, &ScInstalledObject::authors_get, nullptr, "authors");
+ dukglue_register_property(ctx, &ScInstalledObject::name_get, nullptr, "name");
+ }
+
+ private:
+ std::string path_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ return installedObject->Path;
+ }
+ return {};
+ }
+
+ std::string generation_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ if (installedObject->Generation == ObjectGeneration::DAT)
+ return "dat";
+ else
+ return "json";
+ }
+ return {};
+ }
+
+ std::vector sourceGames_get() const
+ {
+ std::vector result;
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ for (const auto& sourceGame : installedObject->Sources)
+ {
+ result.push_back(std::string(ObjectSourceGameToString(sourceGame)));
+ }
+ }
+ return result;
+ }
+
+ std::string type_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ return std::string(ObjectTypeToString(EnumValue(installedObject->Type)));
+ }
+ return {};
+ }
+
+ std::string identifier_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ if (installedObject->Generation == ObjectGeneration::DAT)
+ {
+ return ObjectEntryDescriptor(installedObject->ObjectEntry).ToString();
+ }
+ else
+ {
+ return installedObject->Identifier;
+ }
+ }
+ return {};
+ }
+
+ DukValue legacyIdentifier_get() const
+ {
+ auto ctx = GetContext()->GetScriptEngine().GetContext();
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ if (!installedObject->ObjectEntry.IsEmpty())
+ {
+ return ToDuk(ctx, installedObject->ObjectEntry.GetName());
+ }
+ }
+ return ToDuk(ctx, nullptr);
+ }
+
+ std::vector authors_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ return installedObject->Authors;
+ }
+ return {};
+ }
+
+ std::string name_get() const
+ {
+ auto installedObject = GetInstalledObject();
+ if (installedObject != nullptr)
+ {
+ return installedObject->Name;
+ }
+ return {};
+ }
+
+ const ObjectRepositoryItem* GetInstalledObject() const
+ {
+ auto context = GetContext();
+ auto& objectRepository = context->GetObjectRepository();
+ auto numObjects = objectRepository.GetNumObjects();
+ if (_index < numObjects)
+ {
+ auto* objects = objectRepository.GetObjects();
+ return &objects[_index];
+ }
+ return nullptr;
+ }
+ };
+} // namespace OpenRCT2::Scripting
+
+#endif
diff --git a/src/openrct2/scripting/bindings/object/ScObject.hpp b/src/openrct2/scripting/bindings/object/ScObject.hpp
index 9a51f7c2e5..bd5a47b86a 100644
--- a/src/openrct2/scripting/bindings/object/ScObject.hpp
+++ b/src/openrct2/scripting/bindings/object/ScObject.hpp
@@ -15,9 +15,11 @@
# include "../../../common.h"
# include "../../../object/ObjectManager.h"
# include "../../../object/RideObject.h"
+# include "../../../object/SceneryGroupObject.h"
# include "../../../object/SmallSceneryObject.h"
# include "../../Duktape.hpp"
# include "../../ScriptEngine.h"
+# include "ScInstalledObject.hpp"
# include
# include
@@ -39,6 +41,7 @@ namespace OpenRCT2::Scripting
static void Register(duk_context* ctx)
{
+ dukglue_register_property(ctx, &ScObject::installedObject_get, nullptr, "installedObject");
dukglue_register_property(ctx, &ScObject::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScObject::index_get, nullptr, "index");
dukglue_register_property(ctx, &ScObject::identifier_get, nullptr, "identifier");
@@ -58,36 +61,25 @@ namespace OpenRCT2::Scripting
return static_cast(i);
}
}
- return ObjectType::None;
- }
-
- static std::string_view ObjectTypeToString(uint8_t type)
- {
- static constexpr std::string_view Types[] = {
- "ride",
- "small_scenery",
- "large_scenery",
- "wall",
- "banner",
- "footpath",
- "footpath_addition",
- "scenery_group",
- "park_entrance",
- "water",
- "stex",
- "terrain_surface",
- "terrain_edge",
- "station",
- "music",
- "footpath_surface",
- "footpath_railings",
- };
- if (type >= std::size(Types))
- return "unknown";
- return Types[type];
+ return std::nullopt;
}
private:
+ std::shared_ptr installedObject_get() const
+ {
+ auto obj = GetObject();
+ if (obj != nullptr)
+ {
+ auto& objectRepository = GetContext()->GetObjectRepository();
+ auto installedObject = objectRepository.FindObject(obj->GetDescriptor());
+ if (installedObject != nullptr)
+ {
+ return std::make_shared(installedObject->Id);
+ }
+ }
+ return {};
+ }
+
std::string type_get() const
{
return std::string(ObjectTypeToString(EnumValue(_type)));
@@ -103,7 +95,14 @@ namespace OpenRCT2::Scripting
auto obj = GetObject();
if (obj != nullptr)
{
- return std::string(obj->GetIdentifier());
+ if (obj->GetGeneration() == ObjectGeneration::DAT)
+ {
+ return obj->GetDescriptor().ToString();
+ }
+ else
+ {
+ return std::string(obj->GetIdentifier());
+ }
}
return {};
}
@@ -791,17 +790,53 @@ namespace OpenRCT2::Scripting
}
};
- class ScSmallSceneryObject : public ScObject
+ class ScSceneryObject : public ScObject
{
public:
- ScSmallSceneryObject(ObjectType type, int32_t index)
+ ScSceneryObject(ObjectType type, int32_t index)
: ScObject(type, index)
{
}
static void Register(duk_context* ctx)
{
- dukglue_set_base_class(ctx);
+ dukglue_set_base_class(ctx);
+ dukglue_register_property(ctx, &ScSceneryObject::sceneryGroups_get, nullptr, "sceneryGroups");
+ }
+
+ private:
+ std::vector sceneryGroups_get() const
+ {
+ std::vector result;
+ auto obj = GetObject();
+ if (obj != nullptr)
+ {
+ auto& scgDescriptor = obj->GetPrimarySceneryGroup();
+ if (scgDescriptor.HasValue())
+ {
+ result.push_back(scgDescriptor.ToString());
+ }
+ }
+ return result;
+ }
+
+ SceneryObject* GetObject() const
+ {
+ return static_cast(ScObject::GetObject());
+ }
+ };
+
+ class ScSmallSceneryObject : public ScSceneryObject
+ {
+ public:
+ ScSmallSceneryObject(ObjectType type, int32_t index)
+ : ScSceneryObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
dukglue_register_property(ctx, &ScSmallSceneryObject::flags_get, nullptr, "flags");
dukglue_register_property(ctx, &ScSmallSceneryObject::height_get, nullptr, "height");
dukglue_register_property(ctx, &ScSmallSceneryObject::price_get, nullptr, "price");
@@ -865,6 +900,99 @@ namespace OpenRCT2::Scripting
return static_cast(ScObject::GetObject());
}
};
+
+ class ScLargeSceneryObject : public ScSceneryObject
+ {
+ public:
+ ScLargeSceneryObject(ObjectType type, int32_t index)
+ : ScSceneryObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
+ }
+ };
+
+ class ScWallObject : public ScSceneryObject
+ {
+ public:
+ ScWallObject(ObjectType type, int32_t index)
+ : ScSceneryObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
+ }
+ };
+
+ class ScFootpathAdditionObject : public ScSceneryObject
+ {
+ public:
+ ScFootpathAdditionObject(ObjectType type, int32_t index)
+ : ScSceneryObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
+ }
+ };
+
+ class ScBannerObject : public ScSceneryObject
+ {
+ public:
+ ScBannerObject(ObjectType type, int32_t index)
+ : ScSceneryObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
+ }
+ };
+
+ class ScSceneryGroupObject : public ScObject
+ {
+ public:
+ ScSceneryGroupObject(ObjectType type, int32_t index)
+ : ScObject(type, index)
+ {
+ }
+
+ static void Register(duk_context* ctx)
+ {
+ dukglue_set_base_class(ctx);
+ dukglue_register_property(ctx, &ScSceneryGroupObject::items_get, nullptr, "items");
+ }
+
+ private:
+ std::vector items_get() const
+ {
+ std::vector result;
+ auto obj = GetObject();
+ if (obj != nullptr)
+ {
+ auto& items = obj->GetItems();
+ for (const auto& item : items)
+ {
+ result.push_back(item.ToString());
+ }
+ }
+ return result;
+ }
+
+ protected:
+ SceneryGroupObject* GetObject() const
+ {
+ return static_cast(ScObject::GetObject());
+ }
+ };
} // namespace OpenRCT2::Scripting
#endif
diff --git a/src/openrct2/scripting/bindings/object/ScObjectManager.cpp b/src/openrct2/scripting/bindings/object/ScObjectManager.cpp
new file mode 100644
index 0000000000..3078f5e117
--- /dev/null
+++ b/src/openrct2/scripting/bindings/object/ScObjectManager.cpp
@@ -0,0 +1,283 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2023 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 ENABLE_SCRIPTING
+
+# include "ScObjectManager.h"
+
+# include "../../../object/ObjectList.h"
+# include "../../../ride/RideData.h"
+# include "../../Duktape.hpp"
+# include "../../ScriptEngine.h"
+
+using namespace OpenRCT2;
+using namespace OpenRCT2::Scripting;
+
+void ScObjectManager::Register(duk_context* ctx)
+{
+ dukglue_register_property(ctx, &ScObjectManager::installedObjects_get, nullptr, "installedObjects");
+ dukglue_register_method(ctx, &ScObjectManager::load, "load");
+ dukglue_register_method(ctx, &ScObjectManager::unload, "unload");
+ dukglue_register_method(ctx, &ScObjectManager::getObject, "getObject");
+ dukglue_register_method(ctx, &ScObjectManager::getAllObjects, "getAllObjects");
+}
+
+std::vector> ScObjectManager::installedObjects_get() const
+{
+ std::vector> result;
+
+ auto context = GetContext();
+ auto& objectManager = context->GetObjectRepository();
+ auto count = objectManager.GetNumObjects();
+ for (size_t i = 0; i < count; i++)
+ {
+ auto installedObject = std::make_shared(i);
+ result.push_back(installedObject);
+ }
+
+ return result;
+}
+
+DukValue ScObjectManager::load(const DukValue& p1, const DukValue& p2)
+{
+ auto context = GetContext();
+ auto& scriptEngine = context->GetScriptEngine();
+ auto& objectRepository = context->GetObjectRepository();
+ auto& objectManager = context->GetObjectManager();
+ auto ctx = scriptEngine.GetContext();
+
+ if (p1.is_array())
+ {
+ // load(identifiers)
+ std::vector descriptors;
+ for (const auto& item : p1.as_array())
+ {
+ if (item.type() != DukValue::STRING)
+ throw DukException() << "Expected string for 'identifier'.";
+
+ const auto& identifier = item.as_string();
+ descriptors.push_back(ObjectEntryDescriptor::Parse(identifier));
+ }
+
+ duk_push_array(ctx);
+ duk_uarridx_t index = 0;
+ for (const auto& descriptor : descriptors)
+ {
+ auto obj = objectManager.LoadObject(descriptor);
+ if (obj != nullptr)
+ {
+ MarkAsResearched(obj);
+ auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
+ auto scLoadedObject = CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
+ scLoadedObject.push();
+ duk_put_prop_index(ctx, -2, index);
+ }
+ else
+ {
+ duk_push_null(ctx);
+ duk_put_prop_index(ctx, -2, index);
+ }
+ index++;
+ }
+ RefreshResearchedItems();
+ return DukValue::take_from_stack(ctx);
+ }
+ else
+ {
+ // load(identifier, index?)
+ if (p1.type() != DukValue::STRING)
+ throw DukException() << "Expected string for 'identifier'.";
+
+ const auto& identifier = p1.as_string();
+ auto descriptor = ObjectEntryDescriptor::Parse(identifier);
+
+ auto installedObject = objectRepository.FindObject(descriptor);
+ if (installedObject != nullptr)
+ {
+ if (p2.type() != DukValue::UNDEFINED)
+ {
+ if (p2.type() != DukValue::NUMBER)
+ throw DukException() << "Expected number for 'index'.";
+
+ auto index = static_cast(p2.as_int());
+ auto limit = GetObjectTypeLimit(installedObject->Type);
+ if (index < limit)
+ {
+ auto loadedObject = objectManager.GetLoadedObject(installedObject->Type, index);
+ if (loadedObject != nullptr)
+ {
+ objectManager.UnloadObjects({ loadedObject->GetDescriptor() });
+ }
+ auto obj = objectManager.LoadObject(descriptor, static_cast(index));
+ if (obj != nullptr)
+ {
+ MarkAsResearched(obj);
+ RefreshResearchedItems();
+ auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
+ return CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
+ }
+ }
+ }
+ else
+ {
+ auto obj = objectManager.LoadObject(descriptor);
+ if (obj != nullptr)
+ {
+ MarkAsResearched(obj);
+ RefreshResearchedItems();
+ auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
+ return CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
+ }
+ }
+ }
+ }
+ return ToDuk(ctx, nullptr);
+}
+
+void ScObjectManager::unload(const DukValue& p1, const DukValue& p2)
+{
+ auto context = GetContext();
+ auto& objectManager = context->GetObjectManager();
+
+ if (p1.type() == DukValue::STRING)
+ {
+ const auto& szP1 = p1.as_string();
+ auto objType = ScObject::StringToObjectType(szP1);
+ if (objType)
+ {
+ // unload(type, index)
+ if (p2.type() != DukValue::NUMBER)
+ throw DukException() << "'index' is invalid.";
+
+ auto objIndex = p2.as_int();
+ auto obj = objectManager.GetLoadedObject(*objType, objIndex);
+ if (obj != nullptr)
+ {
+ objectManager.UnloadObjects({ obj->GetDescriptor() });
+ }
+ }
+ else
+ {
+ // unload(identifier)
+ objectManager.UnloadObjects({ ObjectEntryDescriptor::Parse(szP1) });
+ }
+ }
+ else if (p1.is_array())
+ {
+ // unload(identifiers)
+ auto identifiers = p1.as_array();
+ std::vector descriptors;
+ for (const auto& identifier : identifiers)
+ {
+ if (identifier.type() == DukValue::STRING)
+ {
+ descriptors.push_back(ObjectEntryDescriptor::Parse(identifier.as_string()));
+ }
+ }
+ objectManager.UnloadObjects(descriptors);
+ }
+}
+
+DukValue ScObjectManager::getObject(const std::string& typez, int32_t index) const
+{
+ auto ctx = GetContext()->GetScriptEngine().GetContext();
+ auto& objManager = GetContext()->GetObjectManager();
+
+ auto type = ScObject::StringToObjectType(typez);
+ if (type)
+ {
+ auto obj = objManager.GetLoadedObject(*type, index);
+ if (obj != nullptr)
+ {
+ return CreateScObject(ctx, *type, index);
+ }
+ }
+ else
+ {
+ duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
+ }
+ return ToDuk(ctx, nullptr);
+}
+
+std::vector ScObjectManager::getAllObjects(const std::string& typez) const
+{
+ auto ctx = GetContext()->GetScriptEngine().GetContext();
+ auto& objManager = GetContext()->GetObjectManager();
+
+ std::vector result;
+ auto type = ScObject::StringToObjectType(typez);
+ if (type)
+ {
+ auto count = object_entry_group_counts[EnumValue(*type)];
+ for (int32_t i = 0; i < count; i++)
+ {
+ auto obj = objManager.GetLoadedObject(*type, i);
+ if (obj != nullptr)
+ {
+ result.push_back(CreateScObject(ctx, *type, i));
+ }
+ }
+ }
+ else
+ {
+ duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
+ }
+ return result;
+}
+
+void ScObjectManager::MarkAsResearched(const Object* object)
+{
+ // Defaults selected items to researched (if in-game)
+ auto objectType = object->GetObjectType();
+ auto entryIndex = ObjectManagerGetLoadedObjectEntryIndex(object);
+ if (objectType == ObjectType::Ride)
+ {
+ const auto* rideEntry = GetRideEntryByIndex(entryIndex);
+ auto rideType = rideEntry->GetFirstNonNullRideType();
+ auto category = static_cast(GetRideTypeDescriptor(rideType).Category);
+ ResearchInsertRideEntry(rideType, entryIndex, category, true);
+ }
+ else if (objectType == ObjectType::SceneryGroup)
+ {
+ ResearchInsertSceneryGroupEntry(entryIndex, true);
+ }
+}
+
+void ScObjectManager::RefreshResearchedItems()
+{
+ // Same thing object selection window and inventions window does
+ gSilentResearch = true;
+ ResearchResetCurrentItem();
+ gSilentResearch = false;
+}
+
+DukValue ScObjectManager::CreateScObject(duk_context* ctx, ObjectType type, int32_t index)
+{
+ switch (type)
+ {
+ case ObjectType::Ride:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::SmallScenery:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::LargeScenery:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::Walls:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::PathBits:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::Banners:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ case ObjectType::SceneryGroup:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ default:
+ return GetObjectAsDukValue(ctx, std::make_shared(type, index));
+ }
+}
+
+#endif
diff --git a/src/openrct2/scripting/bindings/object/ScObjectManager.h b/src/openrct2/scripting/bindings/object/ScObjectManager.h
new file mode 100644
index 0000000000..f6ce560602
--- /dev/null
+++ b/src/openrct2/scripting/bindings/object/ScObjectManager.h
@@ -0,0 +1,43 @@
+/*****************************************************************************
+ * Copyright (c) 2014-2020 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.
+ *****************************************************************************/
+
+#pragma once
+
+#ifdef ENABLE_SCRIPTING
+
+# include "../../Duktape.hpp"
+# include "../../ScriptEngine.h"
+# include "ScInstalledObject.hpp"
+# include "ScObject.hpp"
+
+# include
+
+namespace OpenRCT2::Scripting
+{
+ class ScObjectManager
+ {
+ public:
+ static void Register(duk_context* ctx);
+
+ std::vector> installedObjects_get() const;
+
+ DukValue load(const DukValue& p1, const DukValue& p2);
+ void unload(const DukValue& p1, const DukValue& p2);
+
+ DukValue getObject(const std::string& typez, int32_t index) const;
+ std::vector getAllObjects(const std::string& typez) const;
+
+ private:
+ static void MarkAsResearched(const Object* object);
+ static void RefreshResearchedItems();
+ static DukValue CreateScObject(duk_context* ctx, ObjectType type, int32_t index);
+ };
+} // namespace OpenRCT2::Scripting
+
+#endif