| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- /**
- * The movement module's interface. Every control saves to disk at once, so a change made here
- * survives the next launch with no settings edit.
- */
- #include "movement_panel.h"
- #include <Windows.h>
- #include <array>
- #include <cstdio>
- #include <imgui.h>
- #include "../../../core/ui/components/toggle/ui_toggle_component.h"
- #include "../../movement/movement_settings_store.h"
- namespace sunrise::client::ui::movement {
- namespace {
- /** Lowest and highest virtual keys the picker scans. Zero is not a key. */
- constexpr int kFirstVirtualKey = 1;
- constexpr int kLastVirtualKey = 254;
- /** Mouse buttons are skipped so a click on the picker cannot bind itself. */
- constexpr int kLastMouseKey = 6;
- /** Longest key name Windows returns, plus the null. */
- constexpr std::size_t kKeyNameCapacity = 64;
- enum class CaptureTarget {
- none,
- teleport,
- noclip,
- swordSkate,
- };
- CaptureTarget g_capturing{CaptureTarget::none};
- /**
- * Names one virtual key for display.
- * @param virtualKey Key to name, or zero for no binding.
- * @param output Receives the name.
- */
- void key_name(std::uint32_t virtualKey, std::array<char, kKeyNameCapacity>& output) noexcept {
- if (virtualKey == client::movement::kNoKey) {
- (void)std::snprintf(output.data(), output.size(), "None");
- return;
- }
- const UINT scanCode = MapVirtualKeyW(virtualKey, MAPVK_VK_TO_VSC);
- std::array<wchar_t, kKeyNameCapacity> wide{};
- const int written = scanCode != 0 ? GetKeyNameTextW(static_cast<LONG>(scanCode << 16),
- wide.data(),
- static_cast<int>(wide.size()))
- : 0;
- if (written <= 0
- || WideCharToMultiByte(CP_UTF8,
- 0,
- wide.data(),
- written,
- output.data(),
- static_cast<int>(output.size() - 1),
- nullptr,
- nullptr)
- <= 0) {
- (void)std::snprintf(
- output.data(), output.size(), "Key 0x%02X", static_cast<unsigned>(virtualKey));
- }
- }
- /**
- * Takes the first key held while the picker is armed.
- * @param picked Receives the key, or zero when Escape clears the binding.
- * @return True when this frame ended the capture.
- */
- [[nodiscard]] bool capture_key(std::uint32_t& picked) noexcept {
- if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) {
- picked = client::movement::kNoKey;
- return true;
- }
- for (int key = kFirstVirtualKey; key <= kLastVirtualKey; ++key) {
- if (key <= kLastMouseKey) {
- continue;
- }
- if ((GetAsyncKeyState(key) & 0x8000) != 0) {
- picked = static_cast<std::uint32_t>(key);
- return true;
- }
- }
- return false;
- }
- /**
- * Draws one key picker while keeping capture ownership exclusive.
- * @param id ImGui identity for the button.
- * @param target Binding this picker captures.
- * @param virtualKey Binding value to display and update.
- * @param width Button width.
- * @return True when a new binding was captured.
- */
- [[nodiscard]] bool
- key_picker(const char* id, CaptureTarget target, std::uint32_t& virtualKey, float width) noexcept {
- ImGui::PushID(id);
- if (g_capturing == target) {
- if (ImGui::Button("...", ImVec2(width, 0.0F))) {
- g_capturing = CaptureTarget::none;
- }
- ImGui::PopID();
- std::uint32_t picked = client::movement::kNoKey;
- if (capture_key(picked)) {
- virtualKey = picked;
- g_capturing = CaptureTarget::none;
- return true;
- }
- return false;
- }
- std::array<char, kKeyNameCapacity> name{};
- key_name(virtualKey, name);
- const bool clicked = ImGui::Button(name.data(), ImVec2(width, 0.0F));
- ImGui::PopID();
- if (clicked) {
- g_capturing = target;
- }
- return false;
- }
- } // namespace
- /** Draws the movement module inside the active Core UI frame. */
- void draw() noexcept {
- client::movement::Settings settings = client::movement::get();
- bool changed = false;
- ImGui::TextUnformatted("Teleport");
- ImGui::Separator();
- ImGui::TextWrapped("Teleports you forward in the facing direction. "
- "Cancels vertical momentum.");
- ImGui::Spacing();
- changed =
- core::ui::components::toggle::control("Enabled##teleport", settings.enabled) || changed;
- ImGui::Spacing();
- // One label column and one control column, so the slider and key buttons share both edges.
- const float labelWidth =
- ImGui::CalcTextSize("Toggle key").x + ImGui::GetStyle().ItemSpacing.x * 2;
- const float controlWidth = ImGui::GetContentRegionAvail().x - labelWidth;
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted("Distance");
- ImGui::SameLine(labelWidth);
- ImGui::SetNextItemWidth(controlWidth);
- float distance = settings.distance;
- if (ImGui::SliderFloat("##distance",
- &distance,
- client::movement::kMinimumDistance,
- client::movement::kMaximumDistance,
- "%.0f units")) {
- settings.distance = distance;
- changed = true;
- }
- ImGui::Spacing();
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted("Key");
- ImGui::SameLine(labelWidth);
- changed = key_picker("teleport_key", CaptureTarget::teleport, settings.virtualKey, controlWidth)
- || changed;
- ImGui::Spacing();
- ImGui::Spacing();
- ImGui::TextUnformatted("Noclip");
- ImGui::Separator();
- ImGui::TextWrapped("Uses native horizontal rigid-body velocity while preserving the game's "
- "vertical movement. The bound key turns it on and off in game.");
- ImGui::Spacing();
- changed =
- core::ui::components::toggle::control("Enabled##noclip", settings.noclipEnabled) || changed;
- ImGui::Spacing();
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted("Toggle key");
- ImGui::SameLine(labelWidth);
- changed =
- key_picker("noclip_key", CaptureTarget::noclip, settings.noclipToggleKey, controlWidth)
- || changed;
- ImGui::Spacing();
- ImGui::Spacing();
- ImGui::TextUnformatted("Sword Skate Fix");
- ImGui::Separator();
- ImGui::TextWrapped("A sword's air attack throws you forward, and a glide started while that "
- "throw is still carrying you keeps the speed. The client refuses to start a "
- "glide during the throw. This clears that refusal on the tick you press "
- "jump, and the client's own glide runs from there.");
- ImGui::Spacing();
- changed =
- core::ui::components::toggle::control("Enabled##sword_skate", settings.swordSkateEnabled)
- || changed;
- ImGui::Spacing();
- ImGui::AlignTextToFramePadding();
- ImGui::TextUnformatted("Jump key");
- ImGui::SameLine(labelWidth);
- changed =
- key_picker(
- "sword_skate_key", CaptureTarget::swordSkate, settings.swordSkateJumpKey, controlWidth)
- || changed;
- ImGui::TextWrapped("Must match the key the game jumps on. Nothing happens on any other key.");
- if (changed && !client::movement::publish(settings)) {
- ImGui::Spacing();
- ImGui::TextUnformatted("value out of range, not saved");
- }
- }
- } // namespace sunrise::client::ui::movement
|