d291dcdc74
agent_api (HTTP server), agent_log (structured logging), agent_events (event bus), agent_console (GameConsole), agent_replay (snapshots), agent_vision (depth/segmentation), agent_fbx (bone remapping), agent_auth (multi-agent), agent_analytics (feature flags + tracking) All modules compile clean with mono. Binary uploaded to S3 v1.0.0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
254 lines
7.5 KiB
C++
254 lines
7.5 KiB
C++
#include "game_console.h"
|
|
|
|
#include "core/io/json.h"
|
|
#include "core/os/os.h"
|
|
#include "core/string/print_string.h"
|
|
|
|
GameConsole *GameConsole::singleton = nullptr;
|
|
|
|
GameConsole::GameConsole() {
|
|
singleton = this;
|
|
_register_builtins();
|
|
}
|
|
|
|
GameConsole::~GameConsole() {
|
|
singleton = nullptr;
|
|
}
|
|
|
|
void GameConsole::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("register_command", "name", "callback", "description", "usage", "is_cheat"), &GameConsole::register_command, DEFVAL(""), DEFVAL(""), DEFVAL(false));
|
|
ClassDB::bind_method(D_METHOD("unregister_command", "name"), &GameConsole::unregister_command);
|
|
ClassDB::bind_method(D_METHOD("has_command", "name"), &GameConsole::has_command);
|
|
ClassDB::bind_method(D_METHOD("execute", "input"), &GameConsole::execute);
|
|
ClassDB::bind_method(D_METHOD("execute_command", "name", "args"), &GameConsole::execute_command);
|
|
ClassDB::bind_method(D_METHOD("set_alias", "alias", "command"), &GameConsole::set_alias);
|
|
ClassDB::bind_method(D_METHOD("remove_alias", "alias"), &GameConsole::remove_alias);
|
|
ClassDB::bind_method(D_METHOD("get_commands"), &GameConsole::get_commands);
|
|
ClassDB::bind_method(D_METHOD("get_history", "count"), &GameConsole::get_history, DEFVAL(50));
|
|
ClassDB::bind_method(D_METHOD("get_output", "count"), &GameConsole::get_output, DEFVAL(100));
|
|
ClassDB::bind_method(D_METHOD("autocomplete", "partial"), &GameConsole::autocomplete);
|
|
ClassDB::bind_method(D_METHOD("set_cheats_enabled", "enabled"), &GameConsole::set_cheats_enabled);
|
|
ClassDB::bind_method(D_METHOD("get_cheats_enabled"), &GameConsole::get_cheats_enabled);
|
|
}
|
|
|
|
void GameConsole::register_command(const String &p_name, const Callable &p_callback, const String &p_description, const String &p_usage, bool p_is_cheat) {
|
|
MutexLock lock(console_mutex);
|
|
Command cmd;
|
|
cmd.name = p_name;
|
|
cmd.callback = p_callback;
|
|
cmd.description = p_description;
|
|
cmd.usage = p_usage;
|
|
cmd.is_cheat = p_is_cheat;
|
|
commands[p_name] = cmd;
|
|
}
|
|
|
|
void GameConsole::unregister_command(const String &p_name) {
|
|
MutexLock lock(console_mutex);
|
|
commands.erase(p_name);
|
|
}
|
|
|
|
bool GameConsole::has_command(const String &p_name) const {
|
|
return commands.has(p_name);
|
|
}
|
|
|
|
String GameConsole::execute(const String &p_input) {
|
|
String input = p_input.strip_edges();
|
|
if (input.is_empty()) {
|
|
return "";
|
|
}
|
|
|
|
// Add to history.
|
|
{
|
|
MutexLock lock(console_mutex);
|
|
command_history.push_back(input);
|
|
if (command_history.size() > MAX_HISTORY) {
|
|
command_history.remove_at(0);
|
|
}
|
|
}
|
|
|
|
// Parse command and arguments.
|
|
Vector<String> parts = input.split(" ", false);
|
|
if (parts.size() == 0) {
|
|
return "";
|
|
}
|
|
|
|
String cmd_name = parts[0];
|
|
|
|
// Check aliases.
|
|
if (aliases.has(cmd_name)) {
|
|
cmd_name = aliases[cmd_name];
|
|
}
|
|
|
|
// Build args array.
|
|
Array args;
|
|
for (int i = 1; i < parts.size(); i++) {
|
|
args.push_back(parts[i]);
|
|
}
|
|
|
|
return execute_command(cmd_name, args);
|
|
}
|
|
|
|
String GameConsole::execute_command(const String &p_name, const Array &p_args) {
|
|
MutexLock lock(console_mutex);
|
|
|
|
if (!commands.has(p_name)) {
|
|
String err = vformat("Unknown command: %s", p_name);
|
|
_add_output(err, "error");
|
|
return err;
|
|
}
|
|
|
|
const Command &cmd = commands[p_name];
|
|
|
|
// Check cheat protection.
|
|
if (cmd.is_cheat && !cheats_enabled) {
|
|
String err = vformat("Command '%s' requires cheats to be enabled", p_name);
|
|
_add_output(err, "error");
|
|
return err;
|
|
}
|
|
|
|
// Execute callback.
|
|
Variant result;
|
|
Callable::CallError call_error;
|
|
const Variant args_var = p_args;
|
|
const Variant *argptrs[1] = { &args_var };
|
|
cmd.callback.callp(argptrs, 1, result, call_error);
|
|
|
|
String result_str;
|
|
if (call_error.error != Callable::CallError::CALL_OK) {
|
|
result_str = vformat("Error executing '%s': call error %d", p_name, call_error.error);
|
|
_add_output(result_str, "error");
|
|
} else {
|
|
result_str = String(result);
|
|
if (!result_str.is_empty()) {
|
|
_add_output(result_str);
|
|
}
|
|
}
|
|
|
|
return result_str;
|
|
}
|
|
|
|
void GameConsole::set_alias(const String &p_alias, const String &p_command) {
|
|
aliases[p_alias] = p_command;
|
|
}
|
|
|
|
void GameConsole::remove_alias(const String &p_alias) {
|
|
aliases.erase(p_alias);
|
|
}
|
|
|
|
Array GameConsole::get_commands() const {
|
|
Array result;
|
|
for (const KeyValue<String, Command> &kv : commands) {
|
|
Dictionary dict;
|
|
dict["name"] = kv.value.name;
|
|
dict["description"] = kv.value.description;
|
|
dict["usage"] = kv.value.usage;
|
|
dict["is_cheat"] = kv.value.is_cheat;
|
|
result.push_back(dict);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Array GameConsole::get_history(int p_count) const {
|
|
Array result;
|
|
int start = MAX(0, command_history.size() - p_count);
|
|
for (int i = start; i < command_history.size(); i++) {
|
|
result.push_back(command_history[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Array GameConsole::get_output(int p_count) const {
|
|
Array result;
|
|
int start = MAX(0, output_log.size() - p_count);
|
|
for (int i = start; i < output_log.size(); i++) {
|
|
Dictionary dict;
|
|
dict["timestamp_msec"] = output_log[i].timestamp_msec;
|
|
dict["text"] = output_log[i].text;
|
|
dict["type"] = output_log[i].type;
|
|
result.push_back(dict);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Array GameConsole::autocomplete(const String &p_partial) const {
|
|
Array result;
|
|
String lower_partial = p_partial.to_lower();
|
|
for (const KeyValue<String, Command> &kv : commands) {
|
|
if (kv.key.to_lower().begins_with(lower_partial)) {
|
|
result.push_back(kv.key);
|
|
}
|
|
}
|
|
// Also check aliases.
|
|
for (const KeyValue<String, String> &kv : aliases) {
|
|
if (kv.key.to_lower().begins_with(lower_partial)) {
|
|
result.push_back(kv.key);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void GameConsole::_add_output(const String &p_text, const String &p_type) {
|
|
OutputEntry entry;
|
|
entry.timestamp_msec = OS::get_singleton()->get_ticks_msec();
|
|
entry.text = p_text;
|
|
entry.type = p_type;
|
|
output_log.push_back(entry);
|
|
if (output_log.size() > MAX_OUTPUT) {
|
|
output_log.remove_at(0);
|
|
}
|
|
}
|
|
|
|
// --- Built-in Commands ---
|
|
|
|
void GameConsole::_register_builtins() {
|
|
register_command("help", callable_mp(this, &GameConsole::_cmd_help), "List all commands", "help [command]");
|
|
register_command("clear", callable_mp(this, &GameConsole::_cmd_clear), "Clear console output", "clear");
|
|
register_command("alias", callable_mp(this, &GameConsole::_cmd_alias), "Create command alias", "alias <name> <command>");
|
|
register_command("cheats", callable_mp(this, &GameConsole::_cmd_cheats), "Enable/disable cheats", "cheats <on|off>");
|
|
}
|
|
|
|
void GameConsole::_cmd_help(const Array &p_args) {
|
|
if (p_args.size() > 0) {
|
|
String cmd_name = p_args[0];
|
|
if (commands.has(cmd_name)) {
|
|
const Command &cmd = commands[cmd_name];
|
|
_add_output(vformat("%s - %s", cmd.name, cmd.description));
|
|
if (!cmd.usage.is_empty()) {
|
|
_add_output(vformat(" Usage: %s", cmd.usage));
|
|
}
|
|
return;
|
|
}
|
|
_add_output(vformat("Unknown command: %s", cmd_name), "error");
|
|
return;
|
|
}
|
|
|
|
_add_output("Available commands:", "info");
|
|
for (const KeyValue<String, Command> &kv : commands) {
|
|
String cheat_tag = kv.value.is_cheat ? " [CHEAT]" : "";
|
|
_add_output(vformat(" %s - %s%s", kv.key, kv.value.description, cheat_tag));
|
|
}
|
|
}
|
|
|
|
void GameConsole::_cmd_clear(const Array &p_args) {
|
|
output_log.clear();
|
|
}
|
|
|
|
void GameConsole::_cmd_alias(const Array &p_args) {
|
|
if (p_args.size() < 2) {
|
|
_add_output("Usage: alias <name> <command>", "error");
|
|
return;
|
|
}
|
|
set_alias(p_args[0], p_args[1]);
|
|
_add_output(vformat("Alias '%s' -> '%s'", String(p_args[0]), String(p_args[1])));
|
|
}
|
|
|
|
void GameConsole::_cmd_cheats(const Array &p_args) {
|
|
if (p_args.size() == 0) {
|
|
_add_output(vformat("Cheats: %s", cheats_enabled ? "ON" : "OFF"));
|
|
return;
|
|
}
|
|
String val = String(p_args[0]).to_lower();
|
|
cheats_enabled = (val == "on" || val == "true" || val == "1");
|
|
_add_output(vformat("Cheats: %s", cheats_enabled ? "ON" : "OFF"));
|
|
}
|