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