mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 08:05:15 +02:00
A slam can break the local skater's board at the nose, in the middle or at the tail. Half the time the grip tape holds the pieces together and the board folds or an end hangs; otherwise a piece comes off, flies and lies where it lands. The board is whole again in hand, on riding and on a respawn. The pieces are four stretches of the deck in a content mod's copy of the game's one deck model, each on a spare bone of the board's rig. The boards' render pose hook only moves bones, because the replay records where a bone is and not its size: a broken board's deck goes away and its stretches go to where the pieces lie, and a whole board's stretches go away. The deck the player chose loads first and other mods that bring a deck model are set aside for the run, without touching mods.json. Another deck is chosen while the game runs, through a live apply of the mods. contrib/board-break.md says how it works and how a deck pack is made.
236 lines
11 KiB
C++
236 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 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);
|
|
if (!entry) {
|
|
bool found = false;
|
|
for (const auto &item : registry.entries())
|
|
if (ascii_case_insensitive_starts_with(item.name, name + " ")) {
|
|
out(Commands::usage(item) + (item.description.empty() ? "" : " - " + item.description));
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
out("error: Unknown help topic.");
|
|
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);
|
|
};
|
|
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_trainer_commands(*result);
|
|
register_hall_of_meat_commands(*result);
|
|
register_road_rash_commands(*result);
|
|
register_board_break_commands(*result);
|
|
return result;
|
|
}();
|
|
return *registry;
|
|
}
|
|
} // namespace dingosdk::console
|