1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-23 06:44:38 +01:00

Implement registering game actions

This commit is contained in:
Ted John
2020-03-02 23:55:18 +00:00
parent 4e4379e6ef
commit 2ad37db817
5 changed files with 149 additions and 63 deletions

View File

@@ -80,12 +80,6 @@ declare global {
expenditureType: ExpenditureType;
}
interface GameActionDesc {
id: string;
query: (args: object) => GameActionResult;
execute: (args: object) => GameActionResult;
}
interface NetworkEventArgs {
readonly player: number;
}
@@ -122,8 +116,15 @@ declare global {
/**
* Registers a new game action that allows clients to interact with the game.
* @param action The unique name of the action.
* @param query Logic for validating and returning a price for an action.
* @param execute Logic for validating and executing the action.
* @throws An error if the action has already been registered by this or another plugin.
*/
registerGameAction(desc: GameActionDesc): void;
registerGameAction(
action: string,
query: (args: object) => GameActionResult,
execute: (args: object) => GameActionResult): void;
/**
* Query the result of running a game action. This allows you to check the outcome and validity of
@@ -156,8 +157,7 @@ declare global {
subscribe(hook: "network.leave", callback: (e: NetworkEventArgs) => void): IDisposable;
}
interface IntentDesc
{
interface IntentDesc {
key: string;
title?: string;
shortcut?: string;

View File

@@ -111,7 +111,7 @@ rct_window* window_error_open(rct_string_id title, rct_string_id message)
gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM;
width = gfx_get_string_width_new_lined(_window_error_text);
width = std::min(196, width);
width = std::clamp(width, 64, 196);
gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM;
gfx_wrap_string(_window_error_text, width + 1, &numLines, &fontHeight);

View File

@@ -79,46 +79,31 @@ namespace OpenRCT2::Scripting
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
if (args.type() == DukValue::Type::OBJECT)
try
{
if (callback.is_function())
auto action = CreateGameAction(actionid, args);
if (action != nullptr)
{
try
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (isExecute)
{
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.");
}
action->SetCallback([this, plugin, callback](const GameAction*, const GameActionResult* res) -> void {
HandleGameActionResult(plugin, *res, callback);
});
GameActions::Execute(action.get());
}
catch (DukException&)
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid action parameters.");
auto res = GameActions::Query(action.get());
HandleGameActionResult(plugin, *res, callback);
}
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Callback was not a function.");
duk_error(ctx, DUK_ERR_ERROR, "Unknown action.");
}
}
else
catch (DukException&)
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid action parameters.");
}
@@ -148,7 +133,14 @@ namespace OpenRCT2::Scripting
{
// Serialise args to json so that it can be sent
auto ctx = args.context();
args.push();
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);
@@ -185,8 +177,30 @@ namespace OpenRCT2::Scripting
auto args = DukValue::take_from_stack(ctx);
// Call the plugin callback and pass the result object
scriptEngine.ExecutePluginCall(plugin, callback, { args }, false);
if (callback.is_function())
{
// Call the plugin callback and pass the result object
scriptEngine.ExecutePluginCall(plugin, callback, { args }, false);
}
}
void registerGameAction(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:
@@ -196,6 +210,7 @@ namespace OpenRCT2::Scripting
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::registerGameAction, "registerGameAction");
}
};
} // namespace OpenRCT2::Scripting

View File

@@ -459,6 +459,7 @@ void ScriptEngine::StopPlugin(std::shared_ptr<Plugin> plugin)
{
if (plugin->HasStarted())
{
RemoveCustomGameActions(plugin);
_hookEngine.UnsubscribeAll(plugin);
for (auto callback : _pluginStoppedSubscriptions)
{
@@ -653,7 +654,7 @@ std::future<void> ScriptEngine::Eval(const std::string& s)
return future;
}
bool ScriptEngine::ExecutePluginCall(
DukValue ScriptEngine::ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args, bool isGameStateMutable)
{
if (func.is_function())
@@ -667,9 +668,7 @@ bool ScriptEngine::ExecutePluginCall(
auto result = duk_pcall(_context, static_cast<duk_idx_t>(args.size()));
if (result == DUK_EXEC_SUCCESS)
{
// TODO allow result to be returned as a DukValue
duk_pop(_context);
return true;
return DukValue::take_from_stack(_context);
}
else
{
@@ -678,7 +677,7 @@ bool ScriptEngine::ExecutePluginCall(
duk_pop(_context);
}
}
return false;
return DukValue();
}
void ScriptEngine::LogPluginInfo(const std::shared_ptr<Plugin>& plugin, const std::string_view& message)
@@ -695,27 +694,82 @@ void ScriptEngine::AddNetworkPlugin(const std::string_view& code)
}
std::unique_ptr<GameActionResult> ScriptEngine::QueryOrExecuteCustomGameAction(
const std::string_view& id,
const std::string_view& args,
bool isExecute)
const std::string_view& id, const std::string_view& args, bool isExecute)
{
// Deserialise the JSON args
std::string argsz(args);
duk_push_string(_context, argsz.c_str());
duk_json_decode(_context, -1);
auto dukArgs = DukValue::take_from_stack(_context);
// Ready to call plugin handler
if (isExecute)
std::string actionz = std::string(id);
auto kvp = _customActions.find(actionz);
if (kvp != _customActions.end())
{
std::printf("EXECUTE: %s(%s)\n", std::string(id).c_str(), std::string(args).c_str());
const auto& customAction = kvp->second;
// Deserialise the JSON args
std::string argsz(args);
duk_push_string(_context, argsz.c_str());
duk_json_decode(_context, -1);
auto dukArgs = DukValue::take_from_stack(_context);
// Ready to call plugin handler
DukValue dukResult;
if (!isExecute)
{
dukResult = ExecutePluginCall(customAction.Plugin, customAction.Query, { dukArgs }, false);
}
else
{
dukResult = ExecutePluginCall(customAction.Plugin, customAction.Execute, { dukArgs }, true);
}
return DukToGameActionResult(dukResult);
}
else
{
std::printf("QUERY: %s(%s)\n", std::string(id).c_str(), std::string(args).c_str());
auto action = std::make_unique<GameActionResult>();
action->Error = GA_ERROR::UNKNOWN;
action->ErrorTitle = OBJECT_ERROR_UNKNOWN;
return action;
}
}
std::unique_ptr<GameActionResult> ScriptEngine::DukToGameActionResult(const DukValue& d)
{
auto result = std::make_unique<GameActionResult>();
result->Error = d["error"].type() == DukValue::Type::NUMBER ? static_cast<GA_ERROR>(d["error"].as_int()) : GA_ERROR::OK;
auto errorTitle = d["errorTitle"].type() == DukValue::Type::STRING ? d["errorTitle"].as_string() : std::string();
auto errorMessage = d["errorMessage"].type() == DukValue::Type::STRING ? d["errorMessage"].as_string() : std::string();
result->Cost = d["cost"].type() == DukValue::Type::NUMBER ? d["cost"].as_int() : 0;
return result;
}
bool ScriptEngine::RegisterCustomAction(
const std::shared_ptr<Plugin>& plugin, const std::string_view& action, const DukValue& query, const DukValue& execute)
{
std::string actionz = std::string(action);
if (_customActions.find(actionz) != _customActions.end())
{
return false;
}
return std::make_unique<GameActionResult>();
CustomAction customAction;
customAction.Plugin = plugin;
customAction.Name = std::move(actionz);
customAction.Query = query;
customAction.Execute = execute;
_customActions[customAction.Name] = std::move(customAction);
return true;
}
void ScriptEngine::RemoveCustomGameActions(const std::shared_ptr<Plugin>& plugin)
{
for (auto it = _customActions.begin(); it != _customActions.end();)
{
if (it->second.Plugin == plugin)
{
it = _customActions.erase(it);
}
else
{
it++;
}
}
}
std::string OpenRCT2::Scripting::Stringify(const DukValue& val)

View File

@@ -21,6 +21,7 @@
# include <mutex>
# include <queue>
# include <string>
# include <unordered_map>
# include <unordered_set>
# include <vector>
@@ -118,6 +119,16 @@ namespace OpenRCT2::Scripting
std::mutex _changedPluginFilesMutex;
std::vector<std::function<void(std::shared_ptr<Plugin>)>> _pluginStoppedSubscriptions;
struct CustomAction
{
std::shared_ptr<Plugin> Plugin;
std::string Name;
DukValue Query;
DukValue Execute;
};
std::unordered_map<std::string, CustomAction> _customActions;
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
ScriptEngine(ScriptEngine&) = delete;
@@ -143,7 +154,7 @@ namespace OpenRCT2::Scripting
void UnloadPlugins();
void Update();
std::future<void> Eval(const std::string& s);
bool ExecutePluginCall(
DukValue ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args,
bool isGameStateMutable);
@@ -156,7 +167,11 @@ namespace OpenRCT2::Scripting
void AddNetworkPlugin(const std::string_view& code);
std::unique_ptr<GameActionResult> QueryOrExecuteCustomGameAction(const std::string_view& id, const std::string_view& args, bool isExecute);
std::unique_ptr<GameActionResult> QueryOrExecuteCustomGameAction(
const std::string_view& id, const std::string_view& args, bool isExecute);
bool RegisterCustomAction(
const std::shared_ptr<Plugin>& plugin, const std::string_view& action, const DukValue& query,
const DukValue& execute);
private:
void Initialise();
@@ -170,6 +185,8 @@ namespace OpenRCT2::Scripting
void SetupHotReloading();
void AutoReloadPlugins();
void ProcessREPL();
void RemoveCustomGameActions(const std::shared_ptr<Plugin>& plugin);
std::unique_ptr<GameActionResult> DukToGameActionResult(const DukValue& d);
};
bool IsGameStateMutable();