From f35e595d0ebcf1109a3f2da6bb2403f7f98ac9ba Mon Sep 17 00:00:00 2001 From: Ted John Date: Fri, 14 Apr 2023 21:57:38 +0100 Subject: [PATCH] Add research plugin API --- distribution/openrct2.d.ts | 99 ++++++- src/openrct2/management/Research.cpp | 19 ++ src/openrct2/management/Research.h | 2 + src/openrct2/scripting/ScriptEngine.cpp | 4 +- .../scripting/bindings/world/ScPark.cpp | 11 + .../scripting/bindings/world/ScPark.hpp | 8 + .../scripting/bindings/world/ScResearch.hpp | 274 ++++++++++++++++++ 7 files changed, 415 insertions(+), 2 deletions(-) create mode 100644 src/openrct2/scripting/bindings/world/ScResearch.hpp diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 3662b291a9..2347b25dba 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -2123,7 +2123,7 @@ declare global { * The track segment adds to inversion counter. Usually applied to the first half of inversions. */ readonly countsAsInversion: boolean; - + /** * Gets a length of the subpositions list for this track segment. */ @@ -3150,6 +3150,103 @@ declare global { postMessage(message: ParkMessageDesc): void; } + interface Research { + /** + * The list of rides and scenery sets that have already been researched. + */ + inventedItems: ResearchItemType[]; + + /** + * The order of rides and scenery sets to be researched. + */ + uninventedItems: ResearchItemType[]; + + /** + * The amount of funding currently spent on research. + */ + funding: ResearchFundingLevel; + + /** + * Flags representing which research categories are enabled. + */ + priorities: number; + + /** + * The current stage for the ride or scenery set being researched. + */ + stage: ResearchFundingStage; + + /** + * The progress for the current stage between 0 and 65535. + * This will increment more quickly the higher the research funding. + */ + progress: number; + + /** + * The expected month the current item being researched will complete. + * Value is between 0 and 7, 0 being March and 7 being October. + * Value is null if there is not yet an expected month. + */ + readonly expectedMonth: number | null; + + /** + * The expected day of the month the current item being researched will complete. + * Value is between 0 and 30, add 1 to it for the human readable date. + * Value is null if there is not yet an expected month. + */ + readonly expectedDay: number | null; + + /** + * Gets whether a particular object has been researched and is available to construct. + * @param type The type of object, e.g. ride, scenery group, or small scenery. + * @param index The object index. + */ + isObjectResearched(type: ObjectType, index: number): boolean; + } + + interface ResearchItem { + /** + * The research category this item belongs in. + * E.g. gentle rides, thrill rides, shops etc. + */ + category: ResearchCategory; + + /** + * Weather the research item is a ride or scenery set. + */ + type: ResearchItemType; + + /** + * The ride or scenery set object index. + */ + object: number; + } + + type ResearchItemType = "scenery" | "ride"; + + enum ResearchCategory { + Transport, + Gentle, + Rollercoaster, + Thrill, + Water, + Shop + } + + enum ResearchFundingLevel { + None, + Minimum, + Normal, + Maximum + } + + type ResearchFundingStage = + "initial_research" | + "designing" | + "completing_design" | + "unknown" | + "finished_all"; + type ScenarioObjectiveType = "none" | "guestsBy" | diff --git a/src/openrct2/management/Research.cpp b/src/openrct2/management/Research.cpp index 4295fbe42a..fefe6bf3a0 100644 --- a/src/openrct2/management/Research.cpp +++ b/src/openrct2/management/Research.cpp @@ -519,6 +519,25 @@ bool ResearchInsertSceneryGroupEntry(ObjectEntryIndex entryIndex, bool researche return false; } +bool ResearchIsInvented(ObjectType objectType, ObjectEntryIndex index) +{ + switch (objectType) + { + case ObjectType::Ride: + return RideEntryIsInvented(index); + case ObjectType::SceneryGroup: + return SceneryGroupIsInvented(index); + case ObjectType::SmallScenery: + case ObjectType::LargeScenery: + case ObjectType::Walls: + case ObjectType::Banners: + case ObjectType::PathBits: + return SceneryIsInvented({ static_cast(objectType), index }); + default: + return true; + } +} + bool RideTypeIsInvented(uint32_t rideType) { return RideTypeIsValid(rideType) ? _researchedRideTypes[rideType] : false; diff --git a/src/openrct2/management/Research.h b/src/openrct2/management/Research.h index 798c4543dd..a0fcdbf3f2 100644 --- a/src/openrct2/management/Research.h +++ b/src/openrct2/management/Research.h @@ -138,6 +138,8 @@ bool ResearchInsertRideEntry(ride_type_t rideType, ObjectEntryIndex entryIndex, void ResearchInsertRideEntry(ObjectEntryIndex entryIndex, bool researched); bool ResearchInsertSceneryGroupEntry(ObjectEntryIndex entryIndex, bool researched); +bool ResearchIsInvented(ObjectType objectType, ObjectEntryIndex index); +bool ResearchSetInvented(ObjectType objectType, ObjectEntryIndex index, bool value); void RideTypeSetInvented(uint32_t rideType); void RideEntrySetInvented(ObjectEntryIndex rideEntryIndex); void ScenerySetInvented(const ScenerySelection& sceneryItem); diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 5808f5f012..8d9f9172a0 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -47,6 +47,7 @@ # include "bindings/world/ScMap.hpp" # include "bindings/world/ScPark.hpp" # include "bindings/world/ScParkMessage.hpp" +# include "bindings/world/ScResearch.hpp" # include "bindings/world/ScScenario.hpp" # include "bindings/world/ScTile.hpp" # include "bindings/world/ScTileElement.hpp" @@ -409,6 +410,7 @@ void ScriptEngine::Initialise() ScPlayer::Register(ctx); ScPlayerGroup::Register(ctx); ScProfiler::Register(ctx); + ScResearch::Register(ctx); ScRide::Register(ctx); ScRideStation::Register(ctx); ScRideObject::Register(ctx); @@ -439,7 +441,7 @@ void ScriptEngine::Initialise() dukglue_register_global(ctx, std::make_shared(), "date"); dukglue_register_global(ctx, std::make_shared(ctx), "map"); dukglue_register_global(ctx, std::make_shared(ctx), "network"); - dukglue_register_global(ctx, std::make_shared(), "park"); + 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"); diff --git a/src/openrct2/scripting/bindings/world/ScPark.cpp b/src/openrct2/scripting/bindings/world/ScPark.cpp index aba155c0fe..0b8c8b6830 100644 --- a/src/openrct2/scripting/bindings/world/ScPark.cpp +++ b/src/openrct2/scripting/bindings/world/ScPark.cpp @@ -44,6 +44,11 @@ namespace OpenRCT2::Scripting { "unlockAllPrices", PARK_FLAGS_UNLOCK_ALL_PRICES }, }); + ScPark::ScPark(duk_context* ctx) + : _context(ctx) + { + } + money64 ScPark::cash_get() const { return gCash; @@ -294,6 +299,11 @@ namespace OpenRCT2::Scripting GfxInvalidateScreen(); } + std::shared_ptr ScPark::research_get() const + { + return std::make_shared(_context); + } + std::vector> ScPark::messages_get() const { std::vector> result; @@ -405,6 +415,7 @@ namespace OpenRCT2::Scripting ctx, &ScPark::constructionRightsPrice_get, &ScPark::constructionRightsPrice_set, "constructionRightsPrice"); dukglue_register_property(ctx, &ScPark::parkSize_get, nullptr, "parkSize"); dukglue_register_property(ctx, &ScPark::name_get, &ScPark::name_set, "name"); + dukglue_register_property(ctx, &ScPark::research_get, nullptr, "research"); dukglue_register_property(ctx, &ScPark::messages_get, &ScPark::messages_set, "messages"); dukglue_register_property(ctx, &ScPark::casualtyPenalty_get, &ScPark::casualtyPenalty_set, "casualtyPenalty"); dukglue_register_method(ctx, &ScPark::getFlag, "getFlag"); diff --git a/src/openrct2/scripting/bindings/world/ScPark.hpp b/src/openrct2/scripting/bindings/world/ScPark.hpp index 439a2539ea..a26cf56b58 100644 --- a/src/openrct2/scripting/bindings/world/ScPark.hpp +++ b/src/openrct2/scripting/bindings/world/ScPark.hpp @@ -15,6 +15,7 @@ # include "../../../common.h" # include "../../Duktape.hpp" # include "ScParkMessage.hpp" +# include "ScResearch.hpp" # include # include @@ -23,7 +24,12 @@ namespace OpenRCT2::Scripting { class ScPark { + private: + duk_context* _context; + public: + ScPark(duk_context* ctx); + money64 cash_get() const; void cash_set(money64 value); @@ -85,6 +91,8 @@ namespace OpenRCT2::Scripting void setFlag(const std::string& key, bool value); + std::shared_ptr research_get() const; + std::vector> messages_get() const; void messages_set(const std::vector& value); diff --git a/src/openrct2/scripting/bindings/world/ScResearch.hpp b/src/openrct2/scripting/bindings/world/ScResearch.hpp new file mode 100644 index 0000000000..a825d03a7f --- /dev/null +++ b/src/openrct2/scripting/bindings/world/ScResearch.hpp @@ -0,0 +1,274 @@ +/***************************************************************************** + * 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. + *****************************************************************************/ + +#pragma once + +#ifdef ENABLE_SCRIPTING + +# include "../../../Context.h" +# include "../../../common.h" +# include "../../../core/String.hpp" +# include "../../../management/Research.h" +# include "../../../ride/RideData.h" +# include "../../Duktape.hpp" +# include "../../ScriptEngine.h" +# include "../object/ScObject.hpp" + +namespace OpenRCT2::Scripting +{ + static const DukEnumMap ResearchStageMap({ + { "initial_research", RESEARCH_STAGE_INITIAL_RESEARCH }, + { "designing", RESEARCH_STAGE_DESIGNING }, + { "completing_design", RESEARCH_STAGE_COMPLETING_DESIGN }, + { "unknown", RESEARCH_STAGE_UNKNOWN }, + { "finished_all", RESEARCH_STAGE_FINISHED_ALL }, + }); + + static const DukEnumMap ResearchCategoryMap({ + { "transport", ResearchCategory::Transport }, + { "gentle", ResearchCategory::Gentle }, + { "rollercoaster", ResearchCategory::Rollercoaster }, + { "thrill", ResearchCategory::Thrill }, + { "water", ResearchCategory::Water }, + { "shop", ResearchCategory::Shop }, + { "scenery_group", ResearchCategory::SceneryGroup }, + }); + + static const DukEnumMap ResearchEntryTypeMap({ + { "ride", Research::EntryType::Ride }, + { "scenery", Research::EntryType::Scenery }, + }); + + template<> inline DukValue ToDuk(duk_context* ctx, const ResearchItem& value) + { + DukObject obj(ctx); + obj.Set("category", ResearchCategoryMap[value.category]); + obj.Set("type", ResearchEntryTypeMap[value.type]); + if (value.type == Research::EntryType::Ride) + { + obj.Set("rideType", value.baseRideType); + } + obj.Set("object", value.entryIndex); + return obj.Take(); + } + + template<> Research::EntryType inline FromDuk(const DukValue& d) + { + if (d.type() == DukValue::STRING) + { + auto it = ResearchEntryTypeMap.find(d.as_string()); + if (it != ResearchEntryTypeMap.end()) + { + return it->second; + } + } + return Research::EntryType::Scenery; + } + + template<> ResearchItem inline FromDuk(const DukValue& d) + { + ResearchItem result; + result.baseRideType = 0; + result.category = ResearchCategory::Transport; + result.flags = 0; + result.type = FromDuk(d["type"]); + auto baseRideType = d["rideType"]; + if (baseRideType.type() == DukValue::NUMBER) + result.baseRideType = baseRideType.as_int(); + result.entryIndex = d["object"].as_int(); + return result; + } + + class ScResearch + { + private: + duk_context* _context; + + public: + ScResearch(duk_context* ctx) + : _context(ctx) + { + } + + uint8_t funding_get() const + { + return gResearchFundingLevel; + } + + void funding_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + gResearchFundingLevel = value; + } + + uint8_t priorities_get() const + { + return gResearchPriorities; + } + + void priorities_set(uint8_t value) + { + ThrowIfGameStateNotMutable(); + gResearchPriorities = value; + } + + std::string stage_get() const + { + return std::string(ResearchStageMap[gResearchProgressStage]); + } + + void stage_set(const std::string& value) + { + ThrowIfGameStateNotMutable(); + auto it = ResearchStageMap.find(value); + if (it != ResearchStageMap.end()) + { + gResearchProgressStage = it->second; + } + } + + uint16_t progress_get() const + { + return gResearchProgress; + } + + void progress_set(uint16_t value) + { + ThrowIfGameStateNotMutable(); + gResearchProgress = value; + } + + DukValue expectedMonth_get() const + { + if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || gResearchExpectedDay == 255) + return ToDuk(_context, nullptr); + return ToDuk(_context, gResearchExpectedMonth); + } + + DukValue expectedDay_get() const + { + if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || gResearchExpectedDay == 255) + return ToDuk(_context, nullptr); + return ToDuk(_context, gResearchExpectedDay); + } + + DukValue lastResearchedItem_get() const + { + if (!gResearchLastItem) + return ToDuk(_context, nullptr); + return ToDuk(_context, *gResearchLastItem); + } + + DukValue expectedItem_get() const + { + if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || !gResearchNextItem) + return ToDuk(_context, nullptr); + return ToDuk(_context, *gResearchNextItem); + } + + std::vector inventedItems_get() const + { + std::vector result; + for (auto& item : gResearchItemsInvented) + { + result.push_back(ToDuk(_context, item)); + } + return result; + } + + void inventedItems_set(const std::vector& value) + { + auto list = ConvertResearchList(value); + gResearchItemsInvented = std::move(list); + ResearchFix(); + } + + std::vector uninventedItems_get() const + { + std::vector result; + for (auto& item : gResearchItemsUninvented) + { + result.push_back(ToDuk(_context, item)); + } + return result; + } + + void uninventedItems_set(const std::vector& value) + { + auto list = ConvertResearchList(value); + gResearchItemsUninvented = std::move(list); + ResearchFix(); + } + + bool isObjectResearched(const std::string& typez, ObjectEntryIndex index) + { + auto result = false; + auto type = ScObject::StringToObjectType(typez); + if (type) + { + result = ResearchIsInvented(*type, index); + } + else + { + duk_error(_context, DUK_ERR_ERROR, "Invalid object type."); + } + return result; + } + + static void Register(duk_context* ctx) + { + dukglue_register_property(ctx, &ScResearch::funding_get, &ScResearch::funding_set, "funding"); + dukglue_register_property(ctx, &ScResearch::priorities_get, &ScResearch::priorities_set, "priorities"); + dukglue_register_property(ctx, &ScResearch::stage_get, &ScResearch::stage_set, "stage"); + dukglue_register_property(ctx, &ScResearch::progress_get, &ScResearch::progress_set, "progress"); + dukglue_register_property(ctx, &ScResearch::expectedMonth_get, nullptr, "expectedMonth"); + dukglue_register_property(ctx, &ScResearch::expectedDay_get, nullptr, "expectedDay"); + dukglue_register_property(ctx, &ScResearch::lastResearchedItem_get, nullptr, "lastResearchedItem"); + dukglue_register_property(ctx, &ScResearch::expectedItem_get, nullptr, "expectedItem"); + dukglue_register_property(ctx, &ScResearch::inventedItems_get, &ScResearch::inventedItems_set, "inventedItems"); + dukglue_register_property( + ctx, &ScResearch::uninventedItems_get, &ScResearch::uninventedItems_set, "uninventedItems"); + dukglue_register_method(ctx, &ScResearch::isObjectResearched, "isObjectResearched"); + } + + static std::vector ConvertResearchList(const std::vector& value) + { + auto& objManager = GetContext()->GetObjectManager(); + std::vector result; + for (auto& item : value) + { + auto researchItem = FromDuk(item); + researchItem.flags = 0; + if (researchItem.type == Research::EntryType::Ride) + { + auto rideEntry = GetRideEntryByIndex(researchItem.entryIndex); + if (rideEntry != nullptr) + { + researchItem.category = GetRideTypeDescriptor(researchItem.baseRideType).GetResearchCategory(); + result.push_back(researchItem); + } + } + else + { + auto sceneryGroup = objManager.GetLoadedObject(ObjectType::SceneryGroup, researchItem.entryIndex); + if (sceneryGroup != nullptr) + { + researchItem.baseRideType = 0; + researchItem.category = ResearchCategory::SceneryGroup; + result.push_back(researchItem); + } + } + } + return result; + } + }; + +} // namespace OpenRCT2::Scripting + +#endif