/***************************************************************************** * 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 "../actions/CustomAction.hpp" # include "../actions/ParkSetNameAction.hpp" # include "../actions/SmallSceneryPlaceAction.hpp" # include "../object/ObjectManager.h" # include "../scenario/Scenario.h" # include "Duktape.hpp" # include "HookEngine.h" # include "ScConfiguration.hpp" # include "ScDisposable.hpp" # include "ScObject.hpp" # include "ScriptEngine.h" # include # include namespace OpenRCT2::Scripting { class ScContext { private: ScriptExecutionInfo& _execInfo; HookEngine& _hookEngine; public: ScContext(ScriptExecutionInfo& execInfo, HookEngine& hookEngine) : _execInfo(execInfo) , _hookEngine(hookEngine) { } private: std::shared_ptr configuration_get() { return std::make_shared(); } std::shared_ptr sharedStorage_get() { auto& scriptEngine = GetContext()->GetScriptEngine(); return std::make_shared(scriptEngine.GetSharedStorage()); } static DukValue CreateScObject(duk_context* ctx, uint8_t type, int32_t index) { switch (type) { case OBJECT_TYPE_RIDE: return GetObjectAsDukValue(ctx, std::make_shared(type, index)); case OBJECT_TYPE_SMALL_SCENERY: 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 {}; } 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[*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; } int32_t getRandom(int32_t min, int32_t max) { ThrowIfGameStateNotMutable(); if (min >= max) return min; int32_t range = max - min; return min + scenario_rand_max(range); } std::shared_ptr subscribe(const std::string& hook, const DukValue& callback) { auto hookType = GetHookType(hook); if (hookType == HOOK_TYPE::UNDEFINED) { throw DukException() << "Unknown hook type: " << hook; } if (!callback.is_function()) { throw DukException() << "Expected function for callback"; } auto owner = _execInfo.GetCurrentPlugin(); if (owner == nullptr) { throw DukException() << "Not in a plugin context"; } auto cookie = _hookEngine.Subscribe(hookType, owner, callback); return std::make_shared([this, hookType, cookie]() { _hookEngine.Unsubscribe(hookType, cookie); }); } void queryAction(const std::string& action, const DukValue& args, const DukValue& callback) { QueryOrExecuteAction(action, args, callback, false); } void executeAction(const std::string& action, const DukValue& args, const DukValue& callback) { QueryOrExecuteAction(action, args, callback, true); } void QueryOrExecuteAction(const std::string& actionid, const DukValue& args, const DukValue& callback, bool isExecute) { auto& scriptEngine = GetContext()->GetScriptEngine(); auto ctx = scriptEngine.GetContext(); try { auto action = CreateGameAction(actionid, args); if (action != nullptr) { auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); if (isExecute) { action->SetCallback([this, plugin, callback](const GameAction*, const GameActionResult* res) -> void { HandleGameActionResult(plugin, *res, callback); }); GameActions::Execute(action.get()); } else { auto res = GameActions::Query(action.get()); HandleGameActionResult(plugin, *res, callback); } } else { duk_error(ctx, DUK_ERR_ERROR, "Unknown action."); } } catch (DukException&) { duk_error(ctx, DUK_ERR_ERROR, "Invalid action parameters."); } } std::unique_ptr CreateGameAction(const std::string& actionid, const DukValue& args) { if (actionid == "parksetname") { auto name = args["name"].as_string(); return std::make_unique(name); } else if (actionid == "smallsceneryplace") { CoordsXYZD loc; loc.x = args["x"].as_int(); loc.y = args["y"].as_int(); loc.z = args["z"].as_int(); loc.direction = args["direction"].as_int(); uint8_t quadrant = args["quadrant"].as_int(); uint8_t sceneryType = args["object"].as_int(); uint8_t primaryColour = args["primaryColour"].as_int(); uint8_t secondaryColour = args["secondaryColour"].as_int(); return std::make_unique(loc, quadrant, sceneryType, primaryColour, secondaryColour); } else { // Serialise args to json so that it can be sent auto ctx = args.context(); if (args.type() == DukValue::Type::OBJECT) { args.push(); } else { duk_push_object(ctx); } auto jsonz = duk_json_encode(ctx, -1); auto json = std::string(jsonz); duk_pop(ctx); return std::make_unique(actionid, json); } } void HandleGameActionResult( const std::shared_ptr& plugin, const GameActionResult& res, const DukValue& callback) { // Construct result object auto& scriptEngine = GetContext()->GetScriptEngine(); auto ctx = scriptEngine.GetContext(); auto objIdx = duk_push_object(ctx); duk_push_int(ctx, static_cast(res.Error)); duk_put_prop_string(ctx, objIdx, "error"); if (res.Error != GA_ERROR::OK) { auto title = res.GetErrorTitle(); duk_push_string(ctx, title.c_str()); duk_put_prop_string(ctx, objIdx, "errorTitle"); auto message = res.GetErrorMessage(); duk_push_string(ctx, message.c_str()); duk_put_prop_string(ctx, objIdx, "errorMessage"); } duk_push_int(ctx, static_cast(res.Cost)); duk_put_prop_string(ctx, objIdx, "cost"); duk_push_int(ctx, static_cast(res.Expenditure)); duk_put_prop_string(ctx, objIdx, "expenditureType"); auto args = DukValue::take_from_stack(ctx); if (callback.is_function()) { // Call the plugin callback and pass the result object scriptEngine.ExecutePluginCall(plugin, callback, { args }, false); } } void registerAction(const std::string& action, const DukValue& query, const DukValue& execute) { auto& scriptEngine = GetContext()->GetScriptEngine(); auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); auto ctx = scriptEngine.GetContext(); if (!query.is_function()) { duk_error(ctx, DUK_ERR_ERROR, "query was not a function."); } else if (!execute.is_function()) { duk_error(ctx, DUK_ERR_ERROR, "execute was not a function."); } else if (!scriptEngine.RegisterCustomAction(plugin, action, query, execute)) { duk_error(ctx, DUK_ERR_ERROR, "action has already been registered."); } } public: static void Register(duk_context* ctx) { dukglue_register_property(ctx, &ScContext::configuration_get, nullptr, "configuration"); dukglue_register_property(ctx, &ScContext::sharedStorage_get, nullptr, "sharedStorage"); dukglue_register_method(ctx, &ScContext::getObject, "getObject"); dukglue_register_method(ctx, &ScContext::getAllObjects, "getAllObjects"); dukglue_register_method(ctx, &ScContext::getRandom, "getRandom"); dukglue_register_method(ctx, &ScContext::subscribe, "subscribe"); dukglue_register_method(ctx, &ScContext::queryAction, "queryAction"); dukglue_register_method(ctx, &ScContext::executeAction, "executeAction"); dukglue_register_method(ctx, &ScContext::registerAction, "registerAction"); } }; } // namespace OpenRCT2::Scripting #endif