mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 18:45:57 +02:00
Pull request 326 split into general plugin APIs and contrib/plugins/fit-check: - reskate.capture (start, stop, discard, state, compare, kept, save): the screen recorded before the overlay draws, cut out by the game's depth or by colour, saved as GIF, APNG, PNG or WebM with an optional card; the "capture" permission. - reskate.room.turn: a stand-in pad's stick held over to turn the menu's preview. - reskate.menu.game and reskate.menu.grid: what the game's menu and its item grid show. - reskate.outfit.worn, board and title: what the skater wears, by name. - reskate.files.reveal: a data folder opened in Explorer. The plugin finds where the turn comes round, eases the stick off, picks evenly spaced frames and saves them with the board's parts or the outfit on the card.
239 lines
11 KiB
C++
239 lines
11 KiB
C++
#include "commands.h"
|
|
#include "Engine/Core/Log/logging.h"
|
|
#include "Engine/Core/Platform/path_text.h"
|
|
#include <memory>
|
|
|
|
namespace dingosdk::console {
|
|
Argument argument(std::string name, Type type, bool optional) {
|
|
Argument result;
|
|
result.name = std::move(name);
|
|
result.type = type;
|
|
result.optional = optional;
|
|
return result;
|
|
}
|
|
Entry action(std::string name, std::string description, Group group, std::vector<Argument> args) {
|
|
Entry entry;
|
|
entry.name = std::move(name);
|
|
entry.description = std::move(description);
|
|
entry.group = group;
|
|
entry.arguments = std::move(args);
|
|
return entry;
|
|
}
|
|
Entry variable(std::string name, std::string description, Group group, Argument arg) {
|
|
auto entry = action(std::move(name), std::move(description), group, {std::move(arg)});
|
|
entry.kind = Kind::variable;
|
|
return entry;
|
|
}
|
|
State boolean_state(bool available, bool value, std::string reason, std::string detail) {
|
|
return {available, available ? std::optional<std::string>(value ? "1" : "0") : std::nullopt, std::move(reason),
|
|
std::move(detail), false};
|
|
}
|
|
namespace {
|
|
void register_console_commands(Commands ®istry) {
|
|
auto topic = argument("command", Type::text, true);
|
|
topic.rest = true;
|
|
topic.complete = [®istry](const Model &, auto) {
|
|
std::vector<std::string> names;
|
|
for (const auto &e : registry.entries())
|
|
names.push_back(e.name);
|
|
return names;
|
|
};
|
|
auto help = action("help", "Show command syntax, values, restrictions, and availability", Group::console, {topic});
|
|
help.execution = Execution::local;
|
|
help.run = [®istry](const Model &model, const Values &args, const Output &out) {
|
|
if (args.empty()) {
|
|
out("Enter a variable by itself to read it, or follow it with a value: "
|
|
"noclip 1, nobail 0, flyspeed 60.");
|
|
out("Use commands [prefix] to list entries, help <command> for details and the commands under it "
|
|
"(help perf, help mp, help trainer), and Tab to complete names and arguments.");
|
|
out("Groups: Movement, Gameplay, World, Graphics, Progression, Objects, "
|
|
"Engine, Console.");
|
|
return;
|
|
}
|
|
const auto &name = std::get<std::string>(args[0]);
|
|
const auto *entry = registry.find(name);
|
|
const auto below = registry.under(entry ? entry->name : name);
|
|
const auto list_below = [&below, &out] {
|
|
for (const auto *item : below)
|
|
out(Commands::usage(*item) + (item->description.empty() ? "" : " - " + item->description));
|
|
};
|
|
if (!entry) {
|
|
if (below.empty())
|
|
out("error: Unknown help topic.");
|
|
list_below();
|
|
return;
|
|
}
|
|
out(Commands::usage(*entry));
|
|
out(registry.describe(*entry, model));
|
|
out(std::string("Group: ") + group_name(entry->group));
|
|
for (const auto &arg : entry->arguments) {
|
|
if (!arg.choices.empty()) {
|
|
std::string choices = arg.name + ": ";
|
|
for (const auto &choice : arg.choices) {
|
|
if (choices.back() != ' ')
|
|
choices += ", ";
|
|
choices += choice;
|
|
}
|
|
out(choices);
|
|
}
|
|
if (arg.minimum || arg.maximum)
|
|
out(arg.name + " range: " + (arg.minimum ? value_text(*arg.minimum) : "unbounded") + " to " +
|
|
(arg.maximum ? value_text(*arg.maximum) : "unbounded"));
|
|
}
|
|
if (entry->reset)
|
|
out("Restore: reset " + entry->name);
|
|
// `perf` is a command of its own and the first word of `perf hud`, `perf status` and the rest.
|
|
if (!below.empty())
|
|
out(entry->name + " commands:");
|
|
list_below();
|
|
};
|
|
registry.add(std::move(help));
|
|
auto list = action("commands", "List commands and variables, optionally filtered by prefix", Group::console,
|
|
{argument("prefix", Type::text, true)});
|
|
list.execution = Execution::local;
|
|
list.run = [®istry](const Model &model, const Values &args, const Output &out) {
|
|
const auto prefix = args.empty() ? std::string{} : std::get<std::string>(args[0]);
|
|
std::vector<const Entry *> rows;
|
|
for (const auto &e : registry.entries())
|
|
if (ascii_case_insensitive_starts_with(e.name, prefix))
|
|
rows.push_back(&e);
|
|
std::sort(rows.begin(), rows.end(), [](auto *a, auto *b) { return lower(a->name) < lower(b->name); });
|
|
for (const auto *e : rows)
|
|
out(registry.describe(*e, model));
|
|
out(std::to_string(rows.size()) + " matching entries.");
|
|
};
|
|
registry.add(std::move(list));
|
|
for (const auto &name : {"clear", "history"}) {
|
|
auto entry =
|
|
action(name, equal(name, "clear") ? "Clear console scrollback" : "Show recent commands", Group::console);
|
|
entry.execution = Execution::local;
|
|
entry.echo_input = !equal(name, "clear");
|
|
entry.run = [clear = equal(name, "clear")](const Model &, const Values &, const Output &out) {
|
|
const auto &callback = clear ? out.clear : out.history;
|
|
if (callback)
|
|
callback();
|
|
else
|
|
out("This command belongs to the overlay console.");
|
|
};
|
|
registry.add(std::move(entry));
|
|
}
|
|
auto text = argument("text", Type::text, true);
|
|
text.rest = true;
|
|
auto echo = action("echo", "Print text to the console", Group::console, {text});
|
|
echo.execution = Execution::local;
|
|
echo.run = [](const Model &, const Values &args, const Output &out) {
|
|
out(args.empty() ? "" : std::get<std::string>(args[0]));
|
|
};
|
|
registry.add(std::move(echo));
|
|
auto status = action("status", "Show current game state", Group::console);
|
|
status.execution = Execution::local;
|
|
status.run = [](const Model &model, const Values &, const Output &out) {
|
|
out("state: " + model.state);
|
|
out("freecam=" + value_text(model.debug.free_camera) + " noclip=" + value_text(model.debug.noclip) +
|
|
" nobail=" + value_text(model.debug.no_bail) +
|
|
" flyspeed=" + value_text(static_cast<double>(model.debug.camera_speed)));
|
|
if (!model.offline.status.empty())
|
|
out(model.offline.status);
|
|
};
|
|
registry.add(std::move(status));
|
|
auto reset_arg = argument("command");
|
|
reset_arg.rest = true;
|
|
reset_arg.complete = [®istry](const Model &, auto) {
|
|
std::vector<std::string> names{"all", "movement", "features", "engine"};
|
|
for (const auto &e : registry.entries())
|
|
if (e.reset)
|
|
names.push_back(e.name);
|
|
return names;
|
|
};
|
|
auto reset = action("reset", "Restore a variable or a group of session overrides", Group::console, {reset_arg});
|
|
reset.run = [®istry](const Model &model, const Values &args, const Output &out) {
|
|
const auto &name = std::get<std::string>(args[0]);
|
|
const auto *entry = registry.find(name);
|
|
if (!entry || !entry->reset) {
|
|
out("error: This entry has no restore operation. Use help reset.");
|
|
return;
|
|
}
|
|
entry->reset(model, out);
|
|
};
|
|
registry.add(std::move(reset));
|
|
for (const auto &name : {"movement", "features", "engine", "all"}) {
|
|
auto entry = action(std::string("reset ") + name, "Restore the selected runtime overrides", Group::console);
|
|
entry.run = [scope = std::string(name)](const Model &model, const Values &, const Output &out) {
|
|
// Restore native fields before queuing other changes; the runtime
|
|
// reservation intentionally rejects competing setting transactions.
|
|
if (scope == "engine" || scope == "all") {
|
|
out(request_reset_named_settings());
|
|
if (scope == "engine")
|
|
for (const auto &v : model.offline.variables)
|
|
if (v.override_active)
|
|
request_engine_variable(v.name, true, false);
|
|
}
|
|
if (scope == "movement" || scope == "all")
|
|
request_debug(overlay::DebugAction::restore_debug);
|
|
if (scope == "features" || scope == "all")
|
|
request_feature(overlay::OfflineFeatureGroup::restore_all, false);
|
|
};
|
|
if (equal(name, "movement"))
|
|
entry.aliases = {"reset debug"};
|
|
registry.add(std::move(entry));
|
|
}
|
|
auto log = action("log", "Show logging level and output path", Group::console);
|
|
log.execution = Execution::local;
|
|
log.aliases = {"log status"};
|
|
log.run = [](const Model &, const Values &, const Output &out) {
|
|
const auto s = logging::status();
|
|
out(std::string("Logging level: ") + std::string(logging::name(s.level)));
|
|
out(path_utf8(s.directory / L"ReSkate.log"));
|
|
};
|
|
registry.add(std::move(log));
|
|
auto level = argument("level");
|
|
level.choices = {"trace", "debug", "info", "warning", "error", "critical", "off"};
|
|
auto loglevel = variable("loglevel", "Set the shared logging level", Group::console, level);
|
|
loglevel.execution = Execution::local;
|
|
loglevel.aliases = {"log level"};
|
|
loglevel.inspect = [](const Model &) {
|
|
return State{true, std::string(logging::name(logging::status().level)), {}, {}, false};
|
|
};
|
|
loglevel.run = [](const Model &, const Values &args, const Output &out) {
|
|
const auto &text = std::get<std::string>(args[0]);
|
|
const auto parsed = logging::parse_level(text);
|
|
if (parsed) {
|
|
logging::set_level(*parsed);
|
|
out("Logging level: " + text);
|
|
}
|
|
};
|
|
registry.add(std::move(loglevel));
|
|
auto flush = action("log flush", "Flush the log file", Group::console);
|
|
flush.execution = Execution::local;
|
|
flush.run = [](const Model &, const Values &, const Output &out) {
|
|
logging::flush();
|
|
out("Log flushed.");
|
|
};
|
|
registry.add(std::move(flush));
|
|
}
|
|
} // namespace
|
|
const Commands &game_commands() {
|
|
static const auto registry = [] {
|
|
auto result = std::make_unique<Commands>();
|
|
register_console_commands(*result);
|
|
register_movement_commands(*result);
|
|
register_ai_commands(*result);
|
|
register_settings_commands(*result);
|
|
register_world_commands(*result);
|
|
register_graphics_commands(*result);
|
|
register_progression_commands(*result);
|
|
register_object_commands(*result);
|
|
register_park_editor_commands(*result);
|
|
register_multiplayer_commands(*result);
|
|
register_perf_commands(*result);
|
|
register_item_commands(*result);
|
|
register_capture_commands(*result);
|
|
register_trainer_commands(*result);
|
|
register_replay_keyframes_commands(*result);
|
|
register_plugin_commands(*result);
|
|
return result;
|
|
}();
|
|
return *registry;
|
|
}
|
|
} // namespace dingosdk::console
|