diff --git a/src/openrct2/libopenrct2.vcxproj b/src/openrct2/libopenrct2.vcxproj
index e9293bae1b..51a3fccbb8 100644
--- a/src/openrct2/libopenrct2.vcxproj
+++ b/src/openrct2/libopenrct2.vcxproj
@@ -993,6 +993,7 @@
+
diff --git a/src/openrct2/scripting/bindings/world/ScResearch.cpp b/src/openrct2/scripting/bindings/world/ScResearch.cpp
new file mode 100644
index 0000000000..4e6995c4ae
--- /dev/null
+++ b/src/openrct2/scripting/bindings/world/ScResearch.cpp
@@ -0,0 +1,286 @@
+/*****************************************************************************
+ * 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 "ScResearch.hpp"
+
+# 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", 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 = {}; // We ignore category because it will be derived from ride type
+ 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;
+ }
+
+ ScResearch::ScResearch(duk_context* ctx)
+ : _context(ctx)
+ {
+ }
+
+ uint8_t ScResearch::funding_get() const
+ {
+ return gResearchFundingLevel;
+ }
+
+ void ScResearch::funding_set(uint8_t value)
+ {
+ ThrowIfGameStateNotMutable();
+ gResearchFundingLevel = std::clamp(value, RESEARCH_FUNDING_NONE, RESEARCH_FUNDING_MAXIMUM);
+ }
+
+ std::vector ScResearch::priorities_get() const
+ {
+ std::vector result;
+ for (auto i = EnumValue(ResearchCategory::Transport); i <= EnumValue(ResearchCategory::SceneryGroup); i++)
+ {
+ auto category = static_cast(i);
+ if (gResearchPriorities & EnumToFlag(category))
+ {
+ result.emplace_back(ResearchCategoryMap[category]);
+ }
+ }
+ return result;
+ }
+
+ void ScResearch::priorities_set(const std::vector& values)
+ {
+ ThrowIfGameStateNotMutable();
+
+ auto priorities = 0;
+ for (const auto& value : values)
+ {
+ auto category = ResearchCategoryMap.TryGet(value);
+ if (category)
+ {
+ priorities |= EnumToFlag(*category);
+ }
+ }
+ gResearchPriorities = priorities;
+ }
+
+ std::string ScResearch::stage_get() const
+ {
+ return std::string(ResearchStageMap[gResearchProgressStage]);
+ }
+
+ void ScResearch::stage_set(const std::string& value)
+ {
+ ThrowIfGameStateNotMutable();
+ auto it = ResearchStageMap.find(value);
+ if (it != ResearchStageMap.end())
+ {
+ gResearchProgressStage = it->second;
+ }
+ }
+
+ uint16_t ScResearch::progress_get() const
+ {
+ return gResearchProgress;
+ }
+
+ void ScResearch::progress_set(uint16_t value)
+ {
+ ThrowIfGameStateNotMutable();
+ gResearchProgress = value;
+ }
+
+ DukValue ScResearch::expectedMonth_get() const
+ {
+ if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || gResearchExpectedDay == 255)
+ return ToDuk(_context, nullptr);
+ return ToDuk(_context, gResearchExpectedMonth);
+ }
+
+ DukValue ScResearch::expectedDay_get() const
+ {
+ if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || gResearchExpectedDay == 255)
+ return ToDuk(_context, nullptr);
+ return ToDuk(_context, gResearchExpectedDay + 1);
+ }
+
+ DukValue ScResearch::lastResearchedItem_get() const
+ {
+ if (!gResearchLastItem)
+ return ToDuk(_context, nullptr);
+ return ToDuk(_context, *gResearchLastItem);
+ }
+
+ DukValue ScResearch::expectedItem_get() const
+ {
+ if (gResearchProgressStage == RESEARCH_STAGE_INITIAL_RESEARCH || !gResearchNextItem)
+ return ToDuk(_context, nullptr);
+ return ToDuk(_context, *gResearchNextItem);
+ }
+
+ std::vector ScResearch::inventedItems_get() const
+ {
+ std::vector result;
+ for (auto& item : gResearchItemsInvented)
+ {
+ result.push_back(ToDuk(_context, item));
+ }
+ return result;
+ }
+
+ void ScResearch::inventedItems_set(const std::vector& value)
+ {
+ ThrowIfGameStateNotMutable();
+ auto list = ConvertResearchList(value);
+ gResearchItemsInvented = std::move(list);
+ ResearchFix();
+ }
+
+ std::vector ScResearch::uninventedItems_get() const
+ {
+ std::vector result;
+ for (auto& item : gResearchItemsUninvented)
+ {
+ result.push_back(ToDuk(_context, item));
+ }
+ return result;
+ }
+
+ void ScResearch::uninventedItems_set(const std::vector& value)
+ {
+ ThrowIfGameStateNotMutable();
+ auto list = ConvertResearchList(value);
+ gResearchItemsUninvented = std::move(list);
+ ResearchFix();
+ }
+
+ bool ScResearch::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;
+ }
+
+ void ScResearch::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");
+ }
+
+ std::vector ScResearch::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
diff --git a/src/openrct2/scripting/bindings/world/ScResearch.hpp b/src/openrct2/scripting/bindings/world/ScResearch.hpp
index daa3433d27..652d2eebae 100644
--- a/src/openrct2/scripting/bindings/world/ScResearch.hpp
+++ b/src/openrct2/scripting/bindings/world/ScResearch.hpp
@@ -11,281 +11,47 @@
#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", 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 = {}; // We ignore category because it will be derived from ride type
- 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)
- {
- }
+ ScResearch(duk_context* ctx);
- uint8_t funding_get() const
- {
- return gResearchFundingLevel;
- }
+ static void Register(duk_context* ctx);
- void funding_set(uint8_t value)
- {
- ThrowIfGameStateNotMutable();
- gResearchFundingLevel = std::clamp(value, RESEARCH_FUNDING_NONE, RESEARCH_FUNDING_MAXIMUM);
- }
+ private:
+ uint8_t funding_get() const;
+ void funding_set(uint8_t value);
- std::vector priorities_get() const
- {
- std::vector result;
- for (auto i = EnumValue(ResearchCategory::Transport); i <= EnumValue(ResearchCategory::SceneryGroup); i++)
- {
- auto category = static_cast(i);
- if (gResearchPriorities & EnumToFlag(category))
- {
- result.emplace_back(ResearchCategoryMap[category]);
- }
- }
- return result;
- }
+ std::vector priorities_get() const;
+ void priorities_set(const std::vector& values);
- void priorities_set(const std::vector& values)
- {
- ThrowIfGameStateNotMutable();
+ std::string stage_get() const;
+ void stage_set(const std::string& value);
- auto priorities = 0;
- for (const auto& value : values)
- {
- auto category = ResearchCategoryMap.TryGet(value);
- if (category)
- {
- priorities |= EnumToFlag(*category);
- }
- }
- gResearchPriorities = priorities;
- }
+ uint16_t progress_get() const;
+ void progress_set(uint16_t value);
- std::string stage_get() const
- {
- return std::string(ResearchStageMap[gResearchProgressStage]);
- }
+ DukValue expectedMonth_get() const;
+ DukValue expectedDay_get() const;
+ DukValue lastResearchedItem_get() const;
+ DukValue expectedItem_get() const;
- void stage_set(const std::string& value)
- {
- ThrowIfGameStateNotMutable();
- auto it = ResearchStageMap.find(value);
- if (it != ResearchStageMap.end())
- {
- gResearchProgressStage = it->second;
- }
- }
+ std::vector inventedItems_get() const;
+ void inventedItems_set(const std::vector& value);
- uint16_t progress_get() const
- {
- return gResearchProgress;
- }
+ std::vector uninventedItems_get() const;
+ void uninventedItems_set(const std::vector& value);
- void progress_set(uint16_t value)
- {
- ThrowIfGameStateNotMutable();
- gResearchProgress = value;
- }
+ bool isObjectResearched(const std::string& typez, ObjectEntryIndex index);
- 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 + 1);
- }
-
- 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;
- }
+ static std::vector ConvertResearchList(const std::vector& value);
};
} // namespace OpenRCT2::Scripting