From 07ed0f5c0ec2b498c6c527c532f63f2dc2ba2ebc Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 1 Mar 2020 15:52:18 +0000 Subject: [PATCH] Implement query and execute of game actions --- distribution/openrct2.d.ts | 24 +++++- src/openrct2/scripting/ScContext.hpp | 121 +++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 4 deletions(-) diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 3a8f60f3d3..188c7d16a1 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -73,8 +73,8 @@ declare global { interface GameActionResult { error: string; - errorTitle: string; - errorMessage: string; + errorTitle?: string; + errorMessage?: string; position: Coord3; cost: number; expenditureType: ExpenditureType; @@ -125,6 +125,24 @@ declare global { */ registerGameAction(desc: GameActionDesc): void; + /** + * Query the result of running a game action. This allows you to check the outcome and validity of + * an action without actually executing it. + * @param action The name of the action. + * @param args The action parameters. + * @param callback The function to be called with the result of the action. + */ + queryAction(action: string, args: object, callback: (result: GameActionResult) => void): void; + + /** + * Executes a game action. In a network game, this will send a request to the server and wait + * for the server to reply. + * @param action The name of the action. + * @param args The action parameters. + * @param callback The function to be called with the result of the action. + */ + executeAction(action: string, args: object, callback: (result: GameActionResult) => void): void; + /** * Subscribes to the given hook. */ @@ -663,8 +681,6 @@ declare global { kickPlayer(index: number): void; sendMessage(message: string): void; sendMessage(message: string, players: number[]): void; - sendQueryAction(action: string, args: object, callback: (result: GameActionResult) => void): void; - sendExecuteAction(action: string, args: object, callback: (result: GameActionResult) => void): void; } interface GameDate { diff --git a/src/openrct2/scripting/ScContext.hpp b/src/openrct2/scripting/ScContext.hpp index e5528ac443..87ba11772c 100644 --- a/src/openrct2/scripting/ScContext.hpp +++ b/src/openrct2/scripting/ScContext.hpp @@ -11,6 +11,8 @@ #ifdef __ENABLE_SCRIPTING__ +# include "../actions/ParkSetNameAction.hpp" +# include "../actions/SmallSceneryPlaceAction.hpp" # include "Duktape.hpp" # include "HookEngine.h" # include "ScDisposable.hpp" @@ -34,6 +36,7 @@ namespace OpenRCT2::Scripting { } + private: void registerIntent(const DukValue& desc) { } @@ -61,10 +64,128 @@ namespace OpenRCT2::Scripting 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(); + if (args.type() == DukValue::Type::OBJECT) + { + if (callback.is_function()) + { + 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."); + } + } + else + { + duk_error(ctx, DUK_ERR_ERROR, "Callback was not a function."); + } + } + else + { + 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); + } + return {}; + } + + 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 = format_string(res.ErrorTitle, nullptr); + duk_push_string(ctx, title.c_str()); + duk_put_prop_string(ctx, objIdx, "errorTitle"); + + auto message = format_string(res.ErrorMessage, res.ErrorMessageArgs.data()); + 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); + + // Call the plugin callback and pass the result object + scriptEngine.ExecutePluginCall(plugin, callback, { args }, false); + } + + public: static void Register(duk_context* ctx) { dukglue_register_method(ctx, &ScContext::registerIntent, "registerIntent"); dukglue_register_method(ctx, &ScContext::subscribe, "subscribe"); + dukglue_register_method(ctx, &ScContext::queryAction, "queryAction"); + dukglue_register_method(ctx, &ScContext::executeAction, "executeAction"); } }; } // namespace OpenRCT2::Scripting