mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 17:27:19 +02:00
Binds for first person, the HUD, voice chat, time of day, No Bail, challenges, nametags and dots
Binds for first person, the HUD, voice chat, time of day, No Bail, challenges, nametags and dots
Eight more actions can be put on a keyboard key, a chord or a controller
combo, in the binds list beside the others. None is bound until the player
binds it.
- First person, Hide HUD, Voice chat on / off, Next time of day, No Bail,
Show / hide challenges, Player nametags, Player dots.
- Each bind runs the console switch for it, so it follows the same rules as
typing it: No Bail still answers to a server that has it off, and the time
of day goes to the server for an admin where the server sets it.
- They are one table (action_binds): a bind is its name in the profile, the
name "bind <name>" takes, its label and its command.
- "bind firstperson|hidehud|voicechat|tod|nobail|challenges|nametags|
nametagdots <n>" sets one from the console; 0 clears it.
- They are not read while the menu, the console or the chat box is open.
Console, which the binds use and anyone can type:
- Every on/off switch takes "toggle" ("nobail toggle", "hideui toggle").
- "firstperson 0|1", which was only in the menu.
- "tod next": the time of day after the current one.
- "mp voice-chat on|off|toggle": the player's own voice chat, leaving the
rest of their voice settings; it says which way it went in their chat.
This commit is contained in:
1 parent
46cf65df0a
commit
574860da72
15 files changed
+113
-5
No files matched your search
@@ -215,6 +215,8 @@ template <class Context> class Registry {
|
||||
token += ' ';
|
||||
token += args[index++];
|
||||
}
|
||||
// "toggle" on an on/off switch: the other of what it is now.
|
||||
if (arg.type == Type::boolean && equal(token, "toggle")) token = current.value && *current.value == "1" ? "0" : "1";
|
||||
auto value = parse_value(arg.type, token);
|
||||
if (!value) {
|
||||
output("error: Invalid " + arg.name + ": '" + token + "'. Usage: " + usage(*entry));
|
||||
|
||||
@@ -84,6 +84,21 @@ struct ControllerInput {
|
||||
ControllerStyle style{};
|
||||
std::array<std::uint64_t, 4> keys{};
|
||||
};
|
||||
// Switches a player can put on a button or key: each runs a console command when pressed.
|
||||
// `key` is its name in the profile, `name` what "bind <name>" takes.
|
||||
struct ActionBind {
|
||||
std::string_view key, name, label, command;
|
||||
};
|
||||
inline constexpr std::array<ActionBind, 8> action_binds{{
|
||||
{"first_person", "firstperson", "First person", "firstperson toggle"},
|
||||
{"hide_hud", "hidehud", "Hide HUD", "hideui toggle"},
|
||||
{"voice_chat", "voicechat", "Voice chat on / off", "mp voice-chat toggle"},
|
||||
{"time_of_day", "tod", "Next time of day", "tod next"},
|
||||
{"no_bail", "nobail", "No Bail", "nobail toggle"},
|
||||
{"challenges", "challenges", "Show / hide challenges", "challenges toggle"},
|
||||
{"nametags", "nametags", "Player nametags", "mp nametags toggle"},
|
||||
{"nametag_dots", "nametagdots", "Player dots", "mp nametag-dots toggle"},
|
||||
}};
|
||||
struct ControllerBindingsModel {
|
||||
bool available{};
|
||||
bool freecam_controller{};
|
||||
@@ -95,6 +110,7 @@ struct ControllerBindingsModel {
|
||||
std::uint32_t up_velocity_combo{};
|
||||
std::uint32_t offboard_up_velocity_combo{};
|
||||
std::uint32_t vote_yes_combo{}, vote_no_combo{}; // answering a dedicated server's vote
|
||||
std::array<std::uint32_t, action_binds.size()> action_combos{}; // by action_binds
|
||||
std::string status;
|
||||
};
|
||||
inline std::string controller_combo_label(std::uint32_t value, ControllerStyle style = ControllerStyle::xbox) {
|
||||
|
||||
@@ -616,7 +616,7 @@ bool queue_command(std::string_view action, std::string_view argument, std::stri
|
||||
if ((action != "host" && action != "host-config" && action != "join" && action != "join-lobby" && action != "join-friend-lobby" && action != "stop" &&
|
||||
action != "distances" && action != "object-placement" && action != "object-limit" && action != "kick" && action != "clear-objects" &&
|
||||
action != "nametags" && action != "chat-visible" && action != "chat-filter" &&
|
||||
action != "nametag-distance" && action != "nametag-dots" && action != "nametags-friends" && action != "player-distance" && action != "direct-connections" && action != "pose-dump" && action != "vote" &&
|
||||
action != "nametag-distance" && action != "nametag-dots" && action != "nametags-friends" && action != "player-distance" && action != "direct-connections" && action != "pose-dump" && action != "vote" && action != "voice-chat" &&
|
||||
action != "chat-bubbles" && action != "chat-bubbles-own" && action != "chat-bubbles-distance" &&
|
||||
action != "chat-bubbles-duration" && action != "chat-bubbles-history" &&
|
||||
!own_mark_command(action) &&
|
||||
@@ -741,6 +741,18 @@ std::string command(std::string_view action, std::string_view argument, std::str
|
||||
publish(s);
|
||||
return value.enabled ? (value.open_mic ? "Open microphone enabled." : "Push-to-talk voice enabled.") : "Voice chat disabled.";
|
||||
}
|
||||
if (action == "voice-chat") {
|
||||
// The player's own voice chat, on or off; the rest of their voice settings stay.
|
||||
if (argument != "on" && argument != "off" && argument != "toggle") return "Choose on, off or toggle.";
|
||||
auto value = s.voice_settings;
|
||||
value.enabled = argument == "toggle" ? !value.enabled : argument == "on";
|
||||
s.voice_settings = value;
|
||||
s.voice.configure(value);
|
||||
publish(s);
|
||||
const std::string result = value.enabled ? "Voice chat on." : "Voice chat off.";
|
||||
add_chat(s, 0, "ReSkate", result);
|
||||
return result;
|
||||
}
|
||||
if (action == "tp") return teleport_self(s, argument);
|
||||
if (action == "tpall" || action == "tphere") return teleport_players(s, action, argument);
|
||||
if (action == "voice-allow") {
|
||||
|
||||
@@ -33,6 +33,7 @@ void register_multiplayer_commands(Commands ®istry) {
|
||||
Command{"nametags", "Show or hide player nametags (on, off, toggle)"},
|
||||
Command{"pose-dump", "Research: record your own poses for a number of seconds (1-600) to logs/poses-*.bin"},
|
||||
Command{"vote", "Answer the vote a dedicated server is running (yes, no)"},
|
||||
Command{"voice-chat", "Your own voice chat (on, off, toggle)"},
|
||||
Command{"direct-connections", "Connect straight to servers that offer it, not through Steam's relays (on, off, toggle)"},
|
||||
Command{"player-distance", "How far away another player still gets a skater, in metres (50-1000; 1000: every player). Past it: a nametag or dot"},
|
||||
Command{"nametag-distance", "How far away a player's name still shows, in metres (10-500); past it they are a dot"},
|
||||
@@ -61,6 +62,7 @@ void register_multiplayer_commands(Commands ®istry) {
|
||||
std::string_view(c.name) == "chat-bubbles" || std::string_view(c.name) == "chat-bubbles-own" ||
|
||||
std::string_view(c.name) == "nametag-dots" || std::string_view(c.name) == "nametags-friends" ||
|
||||
std::string_view(c.name) == "direct-connections" || std::string_view(c.name) == "vote" ||
|
||||
std::string_view(c.name) == "voice-chat" ||
|
||||
std::string_view(c.name) == "score-check")
|
||||
args.push_back(argument("choice"));
|
||||
if (std::string_view(c.name) == "chat-bubbles-distance" || std::string_view(c.name) == "nametag-distance")
|
||||
|
||||
@@ -117,6 +117,8 @@ bool freecam_controller(const Snapshot&);
|
||||
std::uint32_t freecam_binding(const Snapshot&);
|
||||
std::uint32_t tp_to_freecam_binding(const Snapshot&);
|
||||
std::uint32_t vote_yes_binding(const Snapshot&);
|
||||
// One of action_binds, by its key; 0: not bound.
|
||||
std::uint32_t action_binding(const Snapshot&, std::string_view key);
|
||||
std::uint32_t vote_no_binding(const Snapshot&);
|
||||
std::uint32_t noclip_binding(const Snapshot&);
|
||||
std::uint32_t forward_velocity_binding(const Snapshot&);
|
||||
@@ -195,6 +197,8 @@ public:
|
||||
void save_tp_to_freecam_binding(std::uint32_t);
|
||||
std::uint32_t vote_binding(bool yes) const;
|
||||
void save_vote_binding(bool yes, std::uint32_t);
|
||||
std::uint32_t action_binding(std::string_view key) const;
|
||||
void save_action_binding(std::string_view key, std::uint32_t);
|
||||
std::uint32_t noclip_binding() const;
|
||||
void save_noclip_binding(std::uint32_t);
|
||||
std::uint32_t forward_velocity_binding() const;
|
||||
|
||||
@@ -19,6 +19,7 @@ ControllerBindingsModel local_profile_controller_bindings() {
|
||||
result.offboard_up_velocity_combo = s.store->offboard_up_velocity_binding();
|
||||
result.vote_yes_combo = s.store->vote_binding(true);
|
||||
result.vote_no_combo = s.store->vote_binding(false);
|
||||
for (std::size_t i = 0; i < action_binds.size(); ++i) result.action_combos[i] = s.store->action_binding(action_binds[i].key);
|
||||
result.available = true;
|
||||
return result;
|
||||
}
|
||||
@@ -68,6 +69,18 @@ bool set_local_freecam_binding(std::uint32_t combo) {
|
||||
} catch (...) { binding_feedback = "Could not save the binding. See the console log."; return false; }
|
||||
}
|
||||
|
||||
bool set_local_action_binding(std::size_t index, std::uint32_t combo) {
|
||||
auto& s = local_runtime(); std::lock_guard lock(s.native_mutex);
|
||||
if (!s.active || !s.store || index >= action_binds.size() || !valid_action_binding(combo)) return false;
|
||||
try {
|
||||
const auto& slot = action_binds[index];
|
||||
s.store->save_action_binding(slot.key, combo);
|
||||
binding_feedback = std::string(slot.label) + (combo ? " binding saved." : " binding cleared.");
|
||||
dingosdk::logging::event(dingosdk::logging::Channel::profile, dingosdk::Json{{"event","controller_binding_saved"},{"action",std::string(slot.key)},{"combo",combo}}.dump().c_str());
|
||||
return true;
|
||||
} catch (...) { binding_feedback = "Could not save the binding. See the console log."; return false; }
|
||||
}
|
||||
|
||||
bool set_local_vote_binding(bool yes, std::uint32_t combo) {
|
||||
auto& s = local_runtime(); std::lock_guard lock(s.native_mutex);
|
||||
if (!s.active || !s.store || !valid_action_binding(combo)) return false;
|
||||
|
||||
@@ -67,6 +67,8 @@ bool set_local_freecam_binding(std::uint32_t);
|
||||
bool set_local_tp_to_freecam_binding(std::uint32_t);
|
||||
// The player's binding for Yes (or No) in a dedicated server's vote; 0 clears it.
|
||||
bool set_local_vote_binding(bool yes, std::uint32_t);
|
||||
// One of action_binds (controller_bindings.h), by its place there; 0 clears it.
|
||||
bool set_local_action_binding(std::size_t index, std::uint32_t);
|
||||
bool set_local_noclip_binding(std::uint32_t);
|
||||
bool set_local_forward_velocity_binding(std::uint32_t);
|
||||
bool set_local_up_velocity_binding(std::uint32_t);
|
||||
|
||||
@@ -217,6 +217,18 @@ void Store::save_forward_velocity_binding(std::uint32_t combo) {
|
||||
update.commit();
|
||||
}
|
||||
|
||||
std::uint32_t Store::action_binding(std::string_view key) const {
|
||||
std::lock_guard lock(mutex_);
|
||||
return profile::action_binding(value_, key);
|
||||
}
|
||||
void Store::save_action_binding(std::string_view key, std::uint32_t combo) {
|
||||
require(valid_action_binding(combo), "Unsupported controller combo or keyboard key");
|
||||
const std::string name(key);
|
||||
std::lock_guard lock(mutex_);
|
||||
Update update(*this);
|
||||
update.json(value_.settings, {"bindings", name.c_str()}) = combo;
|
||||
update.commit();
|
||||
}
|
||||
std::uint32_t Store::vote_binding(bool yes) const {
|
||||
std::lock_guard lock(mutex_);
|
||||
return yes ? profile::vote_yes_binding(value_) : profile::vote_no_binding(value_);
|
||||
|
||||
@@ -70,6 +70,7 @@ void Store::Update::encode(Json& patch) {
|
||||
(void)world_controls(value); (void)graphics_controls(value); (void)profile::freecam_controller(value); (void)profile::freecam_controller_binding(value); (void)profile::freecam_binding(value); (void)profile::tp_to_freecam_binding(value); (void)profile::noclip_binding(value);
|
||||
(void)profile::forward_velocity_binding(value);
|
||||
(void)profile::vote_yes_binding(value); (void)profile::vote_no_binding(value);
|
||||
for (const auto& slot : action_binds) (void)profile::action_binding(value, slot.key);
|
||||
(void)profile::up_velocity_binding(value);
|
||||
(void)profile::offboard_up_velocity_binding(value);
|
||||
remove("player_settings", [](const Json& row) { return row[0] != "options"; });
|
||||
|
||||
@@ -95,6 +95,7 @@ static std::uint32_t vote_binding(const Snapshot& s, const char* key, std::uint3
|
||||
"Unsupported controller or keyboard key in a vote binding");
|
||||
return static_cast<std::uint32_t>(mask);
|
||||
}
|
||||
std::uint32_t action_binding(const Snapshot& s, std::string_view key) { return vote_binding(s, std::string(key).c_str(), 0); }
|
||||
std::uint32_t vote_yes_binding(const Snapshot& s) { return vote_binding(s, "vote_yes", default_vote_yes_binding); }
|
||||
std::uint32_t vote_no_binding(const Snapshot& s) { return vote_binding(s, "vote_no", default_vote_no_binding); }
|
||||
std::uint32_t offboard_up_velocity_binding(const Snapshot& s) {
|
||||
|
||||
@@ -20,6 +20,8 @@ void register_movement_commands(Commands ®istry) {
|
||||
&overlay::DebugModel::no_bail, &overlay::DebugModel::no_bail_available},
|
||||
{"freecam", "debug.freecam", "Move the camera independently of the skater", Debug::set_free_camera,
|
||||
&overlay::DebugModel::free_camera, &overlay::DebugModel::camera_available},
|
||||
{"firstperson", "debug.first_person", "See through the skater's eyes", Debug::set_first_person,
|
||||
&overlay::DebugModel::first_person, &overlay::DebugModel::camera_available},
|
||||
{"hideui", "debug.game_ui_hidden", "Hide the game's interface", Debug::set_game_ui_hidden,
|
||||
&overlay::DebugModel::game_ui_hidden, &overlay::DebugModel::ui_available}};
|
||||
for (const auto &setting : settings) {
|
||||
@@ -247,6 +249,21 @@ void register_movement_commands(Commands ®istry) {
|
||||
};
|
||||
registry.add(std::move(vote_bind));
|
||||
}
|
||||
// The switches a player can put on a button (action_binds): each bind runs its command.
|
||||
for (std::size_t i = 0; i < action_binds.size(); ++i) {
|
||||
const auto &slot = action_binds[i];
|
||||
auto entry = action("bind " + std::string(slot.name),
|
||||
"Bind a controller combo or keyboard key to: " + std::string(slot.label) + " (" + std::string(slot.command) + "); 0 clears the binding",
|
||||
Group::movement, {mask});
|
||||
entry.inspect = [](const Model &m) {
|
||||
return State{m.bindings.available, {}, "Controller bindings are unavailable.", {}, false};
|
||||
};
|
||||
entry.run = [i](const Model &, const Values &args, const Output &out) {
|
||||
const bool saved = set_local_action_binding(i, static_cast<std::uint32_t>(std::get<std::uint64_t>(args[0])));
|
||||
out(saved ? local_profile_controller_bindings().status : "error: Invalid binding or save failed.");
|
||||
};
|
||||
registry.add(std::move(entry));
|
||||
}
|
||||
}
|
||||
void register_ai_commands(Commands ®istry) {
|
||||
const auto ready = [](const Model &m) {
|
||||
|
||||
@@ -72,7 +72,7 @@ void binds_page(SkateMenu& menu, const Model& model, const CallbacksV3& callback
|
||||
const auto save = [&](int action, std::uint32_t combo) {
|
||||
std::array<char, 512> result{};
|
||||
const auto command = std::string("bind ") +
|
||||
(action == 1 ? "freecamcontroller " : action == 2 ? "freecam " : action == 3 ? "noclip " : action == 4 ? "forwardvelocity " : action == 5 ? "upvelocity " : action == 6 ? "tptofreecam " : action == 8 ? "voteyes " : action == 9 ? "voteno " : "offboardupvelocity ") + std::to_string(combo);
|
||||
(action >= 10 ? std::string(action_binds[static_cast<std::size_t>(action - 10)].name) + " " : action == 1 ? "freecamcontroller " : action == 2 ? "freecam " : action == 3 ? "noclip " : action == 4 ? "forwardvelocity " : action == 5 ? "upvelocity " : action == 6 ? "tptofreecam " : action == 8 ? "voteyes " : action == 9 ? "voteno " : "offboardupvelocity ") + std::to_string(combo);
|
||||
const bool queued = callbacks.queue_console_command(callbacks.user, command.c_str(), result.data(), result.size());
|
||||
result.back() = '\0';
|
||||
feedback(menu, result[0] ? result.data() : queued ? "Saving binding..." : "Could not queue binding.");
|
||||
@@ -129,6 +129,8 @@ void binds_page(SkateMenu& menu, const Model& model, const CallbacksV3& callback
|
||||
row(7, "Off-board Up Boost", model.bindings.offboard_up_velocity_combo);
|
||||
row(8, "Vote yes", model.bindings.vote_yes_combo);
|
||||
row(9, "Vote no", model.bindings.vote_no_combo);
|
||||
for (std::size_t i = 0; i < action_binds.size(); ++i)
|
||||
row(10 + static_cast<int>(i), action_binds[i].label.data(), model.bindings.action_combos[i]);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
@@ -190,8 +190,9 @@ void register_world_commands(Commands ®istry) {
|
||||
// and the rest off, or all seven go back to the level's own choice.
|
||||
static constexpr std::array<std::string_view, 8> times{"default", "morning", "noon", "afternoon",
|
||||
"evening", "night", "weatherday", "weathernight"};
|
||||
auto time = argument("default|morning|noon|afternoon|evening|night|weatherday|weathernight");
|
||||
auto time = argument("default|morning|noon|afternoon|evening|night|weatherday|weathernight|next");
|
||||
time.choices.assign(times.begin(), times.end());
|
||||
time.choices.emplace_back("next"); // the one after the current: for a button that steps through them
|
||||
auto tod = variable("tod", "Time of day on the current map", Group::world, time);
|
||||
// The current map's time layers in slot order, or empty when it has none.
|
||||
const auto time_layers = [](const Model &m) {
|
||||
@@ -219,7 +220,18 @@ void register_world_commands(Commands ®istry) {
|
||||
"Saved for this map", value != "default"};
|
||||
};
|
||||
tod.run = [time_layers](const Model &m, const Values &args, const Output &out) {
|
||||
const auto wanted = lower(std::get<std::string>(args[0]));
|
||||
auto wanted = lower(std::get<std::string>(args[0]));
|
||||
if (wanted == "next") {
|
||||
// As inspect reads it: the one layer forced on, or the level's own.
|
||||
const auto now_layers = time_layers(m);
|
||||
std::size_t forced{};
|
||||
for (std::size_t i = 0; i < now_layers.size(); ++i) {
|
||||
const auto &choice = m.world.choices[now_layers[i]];
|
||||
if (choice == "on") forced = forced ? times.size() : i + 1;
|
||||
else if (choice != "off") forced = times.size();
|
||||
}
|
||||
wanted = std::string(times[forced >= times.size() ? 1 : (forced + 1) % times.size()]);
|
||||
}
|
||||
const auto found = std::find(times.begin(), times.end(), wanted);
|
||||
if (found == times.end()) { out("error: Choose default, morning, noon, afternoon, evening, night, weatherday or weathernight."); return; }
|
||||
const auto slot = static_cast<std::size_t>(found - times.begin());
|
||||
|
||||
+12
-1
@@ -385,6 +385,8 @@ void update_model(std::uintptr_t client, TickState& frame) {
|
||||
std::uint32_t freecam_controller_combo{}, freecam_combo{}, tp_to_freecam_combo{}, noclip_combo{}, forward_velocity_combo{}, up_velocity_combo{}, offboard_up_velocity_combo{};
|
||||
// Yes and No in a dedicated server's vote: read only while one is running.
|
||||
std::uint32_t vote_yes_combo{}, vote_no_combo{};
|
||||
// The switches on buttons (action_binds): each runs its console command.
|
||||
std::array<std::uint32_t, dingosdk::action_binds.size()> action_combos{};
|
||||
const bool vote_open = dingosdk::multiplayer::server_vote_open();
|
||||
bool freecam_controller = dingosdk::local_freecam_controller();
|
||||
{
|
||||
@@ -398,6 +400,7 @@ void update_model(std::uintptr_t client, TickState& frame) {
|
||||
forward_velocity_combo = r.model.bindings.available ? r.model.bindings.forward_velocity_combo : 0;
|
||||
up_velocity_combo = r.model.bindings.available ? r.model.bindings.up_velocity_combo : 0;
|
||||
offboard_up_velocity_combo = r.model.bindings.available ? r.model.bindings.offboard_up_velocity_combo : 0;
|
||||
if (r.model.bindings.available) action_combos = r.model.bindings.action_combos;
|
||||
if (vote_open && r.model.bindings.available) {
|
||||
vote_yes_combo = r.model.bindings.vote_yes_combo;
|
||||
vote_no_combo = r.model.bindings.vote_no_combo;
|
||||
@@ -409,7 +412,15 @@ void update_model(std::uintptr_t client, TickState& frame) {
|
||||
DingoSDKOverlaySetFreecamInputCapture(dingosdk::client_free_camera_active() && freecam_controller);
|
||||
dingosdk::ControllerInput controller;
|
||||
if (freecam_controller_combo || freecam_combo || tp_to_freecam_combo || noclip_combo || forward_velocity_combo || up_velocity_combo || offboard_up_velocity_combo ||
|
||||
vote_yes_combo || vote_no_combo) DingoSDKOverlayReadControllerInput(&controller);
|
||||
vote_yes_combo || vote_no_combo || std::ranges::any_of(action_combos, [](auto combo) { return combo != 0; }))
|
||||
DingoSDKOverlayReadControllerInput(&controller);
|
||||
for (std::size_t i = 0; i < action_combos.size(); ++i)
|
||||
if (r.action_bind_latches[i].update(action_combos[i], controller, r.observer_failed)) {
|
||||
std::array<char, 256> result{};
|
||||
const std::string command(dingosdk::action_binds[i].command);
|
||||
if (queue_console_command(nullptr, command.c_str(), result.data(), result.size()))
|
||||
record(("{\"event\":\"controller_binding_triggered\",\"action\":\"" + std::string(dingosdk::action_binds[i].key) + "\"}").c_str());
|
||||
}
|
||||
// (The input reads as nothing while the menu, the console or the chat box is open, so typing
|
||||
// a bound key answers no vote.)
|
||||
if (r.vote_yes_bind_latch.update(vote_yes_combo, controller, !vote_open) && dingosdk::multiplayer::queue_command("vote", "yes", ""))
|
||||
|
||||
@@ -77,6 +77,7 @@ struct Runtime {
|
||||
dingosdk::ControllerComboLatch up_velocity_bind_latch;
|
||||
dingosdk::ControllerComboLatch offboard_up_velocity_bind_latch;
|
||||
dingosdk::ControllerComboLatch vote_yes_bind_latch, vote_no_bind_latch;
|
||||
std::array<dingosdk::ControllerComboLatch, dingosdk::action_binds.size()> action_bind_latches;
|
||||
ULONGLONG next_offline{};
|
||||
bool transition_seen{}, initialized{}, catalog_logged{},
|
||||
native_tick_ready{}, native_loading_logging{}, fixed_stop_entitlement_route_ready{}, menu_load_queued{}, startup_load_queued{};
|
||||
|
||||
Reference in new issue
Block a user