From 069a2b319286df7e7669eb6ca3a9f3f3435fee74 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 29 Apr 2023 17:39:35 +0100 Subject: [PATCH] Implement object manager for plugins --- distribution/openrct2.d.ts | 54 +++- src/openrct2/libopenrct2.vcxproj | 3 + src/openrct2/object/ObjectManager.cpp | 6 + src/openrct2/object/ObjectManager.h | 1 + src/openrct2/object/ObjectTypes.cpp | 10 +- src/openrct2/object/ObjectTypes.h | 1 + src/openrct2/scripting/ScriptEngine.cpp | 3 + .../scripting/bindings/game/ScContext.hpp | 70 +---- .../bindings/object/ScObjectManager.cpp | 270 ++++++++++++++++++ .../bindings/object/ScObjectManager.h | 43 +++ 10 files changed, 392 insertions(+), 69 deletions(-) create mode 100644 src/openrct2/scripting/bindings/object/ScObjectManager.cpp create mode 100644 src/openrct2/scripting/bindings/object/ScObjectManager.h diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 8838eb9888..b71ddc47c0 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,9 +221,7 @@ 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: "ride", index: number): RideObject; @@ -231,6 +233,9 @@ declare global { getObject(type: "scenery_group", index: number): SceneryGroupObject; getObject(type: "music", index: number): LoadedObject; + /** + * @deprecated Use {@link ObjectManager.getAllObjects} instead. + */ getAllObjects(type: ObjectType): LoadedImageObject[]; getAllObjects(type: "ride"): RideObject[]; getAllObjects(type: "small_scenery"): SmallSceneryObject[]; @@ -4706,8 +4711,18 @@ declare global { } 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 @@ -4716,12 +4731,12 @@ declare global { * @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): number; + load(identifier: string, index?: number): LoadedObject; /** * Attempt to load the given objects into the current park, given they are not already loaded. */ - load(identifiers: string[]): void; + load(identifiers: string[]): LoadedObject[]; /** * Unloads the object, if loaded. @@ -4741,5 +4756,34 @@ declare global { * @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): LoadedImageObject; + 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): LoadedImageObject[]; + 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 51a3fccbb8..acf63387ce 100644 --- a/src/openrct2/libopenrct2.vcxproj +++ b/src/openrct2/libopenrct2.vcxproj @@ -509,6 +509,8 @@ + + @@ -986,6 +988,7 @@ + diff --git a/src/openrct2/object/ObjectManager.cpp b/src/openrct2/object/ObjectManager.cpp index fd79b209a7..516dce81ca 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 05444663a9..12ab8a4dbc 100644 --- a/src/openrct2/object/ObjectTypes.h +++ b/src/openrct2/object/ObjectTypes.h @@ -78,3 +78,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/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index f55c94b143..d2f7fdaa55 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -41,6 +41,7 @@ # 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" @@ -404,6 +405,7 @@ void ScriptEngine::Initialise() ScDisposable::Register(ctx); ScMap::Register(ctx); ScNetwork::Register(ctx); + ScObjectManager::Register(ctx); ScInstalledObject::Register(ctx); ScObject::Register(ctx); ScSceneryObject::Register(ctx); @@ -452,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/bindings/game/ScContext.hpp b/src/openrct2/scripting/bindings/game/ScContext.hpp index d7f32223b7..1b3b411d8e 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,74 +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)); - 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)); - } - } - 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/ScObjectManager.cpp b/src/openrct2/scripting/bindings/object/ScObjectManager.cpp new file mode 100644 index 0000000000..6ac67415b1 --- /dev/null +++ b/src/openrct2/scripting/bindings/object/ScObjectManager.cpp @@ -0,0 +1,270 @@ +#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.emplace_back(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(); + ObjectEntryDescriptor descriptor(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 = 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, 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(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.emplace_back(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)); + } +} 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