Skip to content
Merged
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# QuickMap

Hold the gamepad **Start button** for a configurable duration to open the **world map**. A short press opens the Journal Menu (or Tween Menu) as normal.
Hold the gamepad **Start button** for a configurable duration to open the **world map**. A short press opens whatever Start is bound to in your controls.
Comment thread
codepuncher marked this conversation as resolved.
Outdated

> **Gamepad only.** Vanilla behaviour is fully preserved.
> **Gamepad only.** Default behaviour is fully preserved.
Comment thread
codepuncher marked this conversation as resolved.

Inspired by Red Dead Redemption 2's hold-Start-to-open-map mechanic.

Expand Down
2 changes: 1 addition & 1 deletion docs/nexus-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Hold the gamepad Start button to open the world map. Short press opens the Journ

[size=4][b]Overview[/b][/size]

Hold the gamepad [b]Start button[/b] for a configurable duration to open the [b]world map[/b] directly. A short press opens the Journal Menu (or Tween Menu) as normal — vanilla behaviour is fully preserved.
Hold the gamepad [b]Start button[/b] for a configurable duration to open the [b]world map[/b] directly. A short press opens whatever Start is bound to in your controls.
Comment thread
codepuncher marked this conversation as resolved.
Outdated

[color=#ffcc00][b]Gamepad only.[/b][/color]

Expand Down
59 changes: 33 additions & 26 deletions src/InputHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,18 @@ void InputHandler::UpdateShortPressMenu()
auto* controlMap = RE::ControlMap::GetSingleton();
if (!controlMap) {
logger::error("ControlMap unavailable — short press will have no effect");
shortPressMenuName.clear();
shortPressUserEvent = "";
return;
}

constexpr auto kStart = static_cast<std::uint32_t>(RE::BSWin32GamepadDevice::Key::kStart);
const auto userEvent = controlMap->GetUserEventName(kStart, RE::INPUT_DEVICE::kGamepad);
shortPressUserEvent = controlMap->GetUserEventName(kStart, RE::INPUT_DEVICE::kGamepad);

if (userEvent == "Tween Menu") {
shortPressMenuName = RE::TweenMenu::MENU_NAME;
} else if (userEvent == "Journal") {
shortPressMenuName = RE::JournalMenu::MENU_NAME;
if (shortPressUserEvent.empty()) {
logger::warn("Start has no binding in kGameplay context — short press disabled");
} else {
if (userEvent.empty()) {
logger::warn("Start has no binding in kGameplay context — short press disabled");
} else {
logger::warn("Unknown user event '{}' for Start — short press disabled", userEvent);
}
shortPressMenuName.clear();
logger::info("Start short press user event: '{}'", shortPressUserEvent);
}

logger::info("Start short press bound to '{}'", shortPressMenuName.empty() ? "(none)" : shortPressMenuName);
}

RE::BSEventNotifyControl InputHandler::ProcessEvent(
Expand Down Expand Up @@ -97,12 +88,12 @@ bool InputHandler::ProcessStartButton(const RE::ButtonEvent* btn)

if (btn->IsHeld() && _pressTime) {
if (!_mapTriggered && btn->HeldDuration() >= holdDuration) {
_mapTriggered = true;
auto* uiQueue = RE::UIMessageQueue::GetSingleton();
if (!uiQueue) {
logger::error("UIMessageQueue unavailable — hold threshold reached but map not opened");
} else {
uiQueue->AddMessage(RE::MapMenu::MENU_NAME, RE::UI_MESSAGE_TYPE::kShow, nullptr);
_mapTriggered = true;
}
}
return true;
Expand Down Expand Up @@ -132,22 +123,38 @@ void InputHandler::DispatchShortPress(float held) const
return;
}

auto* uiQueue = RE::UIMessageQueue::GetSingleton();
if (!uiQueue) {
logger::error("UIMessageQueue unavailable — Start press consumed but no menu opened");
if (shortPressUserEvent.empty()) {
logger::warn("Start short press has no binding — press consumed but no menu opened");
return;
}

if (shortPressMenuName.empty()) {
logger::warn("Start short press has no known binding — press consumed but no menu opened");
auto* menuControls = RE::MenuControls::GetSingleton();
if (!menuControls || !menuControls->menuOpenHandler) {
logger::error("MenuControls or menuOpenHandler unavailable — short press consumed but no menu opened");
return;
}

if (shortPressMenuName == RE::JournalMenu::MENU_NAME) {
using func_t = void (*)(bool);
static REL::Relocation<func_t> openJournal{ RELOCATION_ID(52428, 53327) };
openJournal(true);
} else {
uiQueue->AddMessage(shortPressMenuName, RE::UI_MESSAGE_TYPE::kShow, nullptr);
constexpr auto kStart = static_cast<std::uint32_t>(RE::BSWin32GamepadDevice::Key::kStart);
auto* syntheticEvent = RE::ButtonEvent::Create(
RE::INPUT_DEVICE::kGamepad,
shortPressUserEvent,
kStart,
1.0F, // value=1.0 → IsPressed()=true, IsDown()=true
0.0F // heldDownSecs=0.0 → IsDown()=true
);
Comment thread
codepuncher marked this conversation as resolved.
Outdated
if (!syntheticEvent) {
logger::error("Failed to allocate synthetic ButtonEvent — short press consumed but no menu opened");
return;
}

if (!menuControls->menuOpenHandler->CanProcess(syntheticEvent)) {
logger::warn("MenuOpenHandler rejected short press — press consumed but no menu opened");
syntheticEvent->~ButtonEvent();
RE::free(syntheticEvent);
return;
}

menuControls->menuOpenHandler->ProcessButton(syntheticEvent);
Comment thread
codepuncher marked this conversation as resolved.
Outdated
syntheticEvent->~ButtonEvent();
RE::free(syntheticEvent);
}
4 changes: 2 additions & 2 deletions src/InputHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class InputHandler :

static constexpr float kDefaultHoldDuration{ 0.5F };

float holdDuration{ kDefaultHoldDuration };
std::string shortPressMenuName;
float holdDuration{ kDefaultHoldDuration };
RE::BSFixedString shortPressUserEvent;

// Queries ControlMap for the menu bound to Start and caches it in shortPressMenuName.
Comment thread
codepuncher marked this conversation as resolved.
Outdated
// Call at kInputLoaded, kPostLoadGame, and kNewGame.
Comment thread
codepuncher marked this conversation as resolved.
Expand Down