Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
aefe959
basic hacky mouse support
lightmanLP Jan 3, 2025
00814e6
fix
lightmanLP Feb 4, 2025
d2e7e1f
column update
lightmanLP Feb 4, 2025
4607448
disabled -> hidden
lightmanLP Feb 4, 2025
40c00a3
typo
lightmanLP Feb 4, 2025
f98098d
fix inverts
lightmanLP Feb 5, 2025
e89c871
remove shield flag check
lightmanLP Feb 6, 2025
d2a134a
shield invert cvars
lightmanLP Feb 6, 2025
7637dcd
mouse shield control
lightmanLP Feb 6, 2025
db111ce
bunch of renames for better understanding
lightmanLP Feb 6, 2025
834b73d
change cvar default value
lightmanLP Feb 9, 2025
03c6d20
some messy math
lightmanLP Feb 15, 2025
d3f59bc
reset shielding logic
lightmanLP Feb 15, 2025
0f03e5d
separate custom shielding handle
lightmanLP Feb 15, 2025
2278e71
shielding handle update
lightmanLP Feb 15, 2025
6b298e4
small rename
lightmanLP Feb 22, 2025
a8f0450
update settings
lightmanLP Feb 22, 2025
1d2408f
note
lightmanLP Feb 22, 2025
1cfea7b
some fixes
lightmanLP Feb 22, 2025
21d59c9
more fixes
lightmanLP Feb 22, 2025
c84d209
fix inverts
lightmanLP Feb 22, 2025
20b274f
note
lightmanLP Feb 22, 2025
34dc663
moving around
lightmanLP Apr 16, 2025
2a1e721
freelook in z-target triggering fix
lightmanLP Apr 16, 2025
a550b41
ztarget tweak
lightmanLP Apr 16, 2025
03f0cf5
temporary shielding mouse pos reset
lightmanLP Apr 17, 2025
07b0391
mouse cursor hint
lightmanLP Apr 18, 2025
40df3aa
basic soh copy-pasted quickspin
lightmanLP Apr 19, 2025
9e08f50
fix
lightmanLP Apr 30, 2025
6ca2fe1
move condition
lightmanLP May 11, 2025
d6a7e08
disable capture in kaleido
lightmanLP May 11, 2025
f7b9e80
small fixes
lightmanLP May 11, 2025
7383dc5
fix mouse capture state buffering
lightmanLP May 11, 2025
c4da7a8
small rename
lightmanLP May 11, 2025
10c6e35
🔧
lightmanLP May 11, 2025
8142d04
[temporary] capture toggle for ben menu
lightmanLP May 11, 2025
06af00d
compat1
lightmanLP Jul 24, 2025
f281d40
tmp
lightmanLP Dec 25, 2025
f38ca12
3.1+ bump
lightmanLP Dec 25, 2025
6b86196
better auto mouse capture proto
lightmanLP Jan 26, 2026
64743fb
Some capture behavior tweaks
lightmanLP Jan 26, 2026
9adab6b
small fix
lightmanLP Jan 26, 2026
be94751
another small fix
lightmanLP Jan 26, 2026
6c75000
fix overshoulder mouse aim
lightmanLP Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mm/2s2h/BenGui/BenGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ void SetupGuiElements() {
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press - to access enhancements menu");
#else
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F1 to access enhancements menu");
gui->GetGameOverlay()->TextDrawNotification(30.0f, true, "Press F2 to toggle mouse cursor");
#endif
}

Expand Down
64 changes: 64 additions & 0 deletions mm/2s2h/BenGui/BenMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,43 @@ void BenMenu::AddEnhancements() {
.Min(0.1f)
.Max(3.0f));

path.column = SECTION_COLUMN_3;
AddWidget(path, "Mouse", WIDGET_SEPARATOR_TEXT);
AddWidget(path, "Mouse Enabled", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Camera.Mouse.Enabled")
.Options(CheckboxOptions().DefaultValue(false))
.Callback(
[](WidgetInfo& info) {
bool enabled = CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0) && CVarGetInteger("gEnhancements.Camera.Mouse.AutoCapture", 1);
Ship::Context::GetInstance()->GetWindow()->SetAutoCaptureMouse(enabled);
}
);
AddWidget(path, "Auto Capture Mouse Input", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Camera.Mouse.AutoCapture")
.Callback(
[](WidgetInfo& info) {
bool enabled = CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0) && CVarGetInteger("gEnhancements.Camera.Mouse.AutoCapture", 1);
Ship::Context::GetInstance()->GetWindow()->SetAutoCaptureMouse(enabled);
}
).Options(
CheckboxOptions().Tooltip(
"When Mouse Controls are enabled, this toggles whether the program will automatically "
"hide the cursor and capture mouse input when closing the menu."
)
);
AddWidget(path, "Mouse Shielding Enabled", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Mouse.Shielding.Enabled")
.Options(CheckboxOptions().DefaultValue(false))
.PreFunc([](WidgetInfo& info) {
info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_MOUSE_OFF).active;
});
AddWidget(path, "Mouse Quickspin", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Mouse.Quickspin.Enable")
.Options(CheckboxOptions().DefaultValue(false))
.PreFunc([](WidgetInfo& info) {
info.isHidden = mBenMenu->disabledMap.at(DISABLE_FOR_MOUSE_OFF).active;
});

path = { "Enhancements", "Cheats", SECTION_COLUMN_1 };
AddSidebarEntry("Enhancements", "Cheats", 2);
AddWidget(path, "Infinite Health", WIDGET_CVAR_CHECKBOX)
Expand Down Expand Up @@ -1033,6 +1070,10 @@ void BenMenu::AddEnhancements() {
.CVar("gEnhancements.Equipment.ChuDrops")
.Options(
CheckboxOptions().Tooltip("When a bomb drop is spawned, it has a 50% chance to be a Bombchu instead."));
AddWidget(path, "Invert Shield X Axis", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Equipment.InvertShieldX")
.Options(CheckboxOptions().Tooltip(
"Invert the X axis while holding the shield."));
AddWidget(path, "Invert Shield Y Axis", WIDGET_CVAR_CHECKBOX)
.CVar("gEnhancements.Equipment.InvertShieldY")
.Options(CheckboxOptions().Tooltip(
Expand Down Expand Up @@ -2020,6 +2061,12 @@ void BenMenu::InitElement() {
{ DISABLE_FOR_FREE_LOOK_OFF,
{ [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Camera.FreeLook.Enable", 0); },
"Free Look is Disabled" } },
{ DISABLE_FOR_MOUSE_ON,
{ [](disabledInfo& info) -> bool { return CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0); },
"Mouse is Enabled" } },
{ DISABLE_FOR_MOUSE_OFF,
{ [](disabledInfo& info) -> bool { return !CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0); },
"Mouse is Disabled" } },
{ DISABLE_FOR_GYRO_OFF,
{ [](disabledInfo& info) -> bool {
return !CVarGetInteger("gEnhancements.Camera.FirstPerson.GyroEnabled", 0);
Expand Down Expand Up @@ -2129,4 +2176,21 @@ void BenMenu::Draw() {
void BenMenu::DrawElement() {
Ship::Menu::DrawElement();
}

void BenMenu::SetVisibility(bool visible) {
bool wasVisible = IsVisible();
Ship::Menu::SetVisibility(visible);

static bool captureBuffer = false;
if (wasVisible == visible) {
return;
}
std::shared_ptr<Ship::Window> window = Ship::Context::GetInstance()->GetWindow();
if (visible) {
captureBuffer = window->IsMouseCaptured();
window->SetMouseCapture(false);
} else {
window->SetMouseCapture(captureBuffer);
}
};
} // namespace BenGui
3 changes: 3 additions & 0 deletions mm/2s2h/BenGui/BenMenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class BenMenu : public Ship::Menu {
void AddSettings();
void AddEnhancements();
void AddDevTools();

protected:
void SetVisibility(bool visible) override;
};
} // namespace BenGui

Expand Down
2 changes: 2 additions & 0 deletions mm/2s2h/BenGui/MenuTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ typedef enum {
DISABLE_FOR_DEBUG_CAM_OFF,
DISABLE_FOR_FREE_LOOK_ON,
DISABLE_FOR_FREE_LOOK_OFF,
DISABLE_FOR_MOUSE_ON,
DISABLE_FOR_MOUSE_OFF,
DISABLE_FOR_GYRO_OFF,
DISABLE_FOR_GYRO_ON,
DISABLE_FOR_RIGHT_STICK_OFF,
Expand Down
10 changes: 9 additions & 1 deletion mm/2s2h/BenPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ CrowdControl* CrowdControl::Instance;
#include <ship/window/gui/resource/FontFactory.h>
#include "2s2h/Enhancements/Audio/AudioCollection.h"
#include "BenGui/BenInputEditorWindow.h"
#include "2s2h/Enhancements/Controls/Mouse/LocalMouseCaptureManager.h"

OTRGlobals* OTRGlobals::Instance;
GameInteractor* GameInteractor::Instance;
Expand Down Expand Up @@ -202,8 +203,15 @@ OTRGlobals::OTRGlobals() {

auto benInputEditorWindow = std::make_shared<BenInputEditorWindow>("gWindows.BenInputEditor", "2S2H Input Editor");
auto benFast3dWindow =
std::make_shared<Fast::Fast3dWindow>(std::vector<std::shared_ptr<Ship::GuiWindow>>({ benInputEditorWindow }));
std::make_shared<Fast::Fast3dWindow>(
std::make_shared<Ship::Gui>(std::vector<std::shared_ptr<Ship::GuiWindow>>({ benInputEditorWindow })),
std::make_shared<LocalMouseCaptureManager>()
);
context->InitWindow(benFast3dWindow);
benFast3dWindow->SetAutoCaptureMouse(
CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0)
&& CVarGetInteger("gEnhancements.Camera.Mouse.AutoCapture", 1)
);

// Override LUS defaults
auto overlay = context->GetInstance()->GetWindow()->GetGui()->GetGameOverlay();
Expand Down
55 changes: 45 additions & 10 deletions mm/2s2h/Enhancements/Camera/FreeLook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h/ShipInit.hpp"
#include "CameraUtils.h"
#include "2s2h/Enhancements/Controls/Mouse/Mouse.h"

extern "C" {
#include "macros.h"
Expand Down Expand Up @@ -95,13 +96,23 @@ bool Camera_FreeLook(Camera* camera) {

Camera_ResetActionFuncState(camera, camera->mode);

f32 yawDiff = -sCamPlayState->state.input[0].cur.right_stick_x * 10.0f *
(CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.X", 1.0f));
f32 pitchDiff = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f *
(CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f));
f32 yawDiff = -sCamPlayState->state.input[0].cur.right_stick_x * 10.0f;
f32 pitchDiff = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f;

yaw += yawDiff * GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_X);
pitch += pitchDiff * -GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_Y);
if (Mouse_IsCaptured() && CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0)
// Disable mouse camera control when holding up a shield
&& !(CVarGetInteger("gEnhancements.Mouse.Shielding.Enabled", 0) && player->stateFlags1 & PLAYER_STATE1_400000)
) {
MouseCoords mouseDelta = Mouse_GetDelta();
yawDiff -= mouseDelta.x * 40.0f;
pitchDiff -= mouseDelta.y * 40.0f;
}

yawDiff *= CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.X", 1.0f) * GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_X);
pitchDiff *= CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f) * -GameInteractor_InvertControl(GI_INVERT_CAMERA_RIGHT_STICK_Y);

yaw += yawDiff;
pitch += pitchDiff;

s16 maxPitch = DEG_TO_BINANG(CVarGetFloat("gEnhancements.Camera.FreeLook.MaxPitch", 72.0f));
s16 minPitch = DEG_TO_BINANG(CVarGetFloat("gEnhancements.Camera.FreeLook.MinPitch", -49.0f));
Expand All @@ -114,6 +125,7 @@ bool Camera_FreeLook(Camera* camera) {
}

f32 distTarget = CVarGetInteger("gEnhancements.Camera.FreeLook.MaxCameraDistance", roData->unk_04);
// TODO: try with different transition
f32 transitionSpeed = CVarGetInteger("gEnhancements.Camera.FreeLook.TransitionSpeed", 25);
// Smooth step camera away to max camera distance. Camera collision is calculated later
camera->dist = Camera_ScaledStepToCeilF(distTarget, camera->dist,
Expand Down Expand Up @@ -141,11 +153,34 @@ bool Camera_FreeLook(Camera* camera) {
}

bool Camera_CanFreeLook(Camera* camera) {
f32 camX = sCamPlayState->state.input[0].cur.right_stick_x * 10.0f;
f32 camY = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f;
if (!sCanFreeLook && (fabsf(camX) >= 15.0f || fabsf(camY) >= 15.0f)) {
sCanFreeLook = true;
if (!sCanFreeLook && Mouse_IsCaptured() && CVarGetInteger("gEnhancements.Camera.Mouse.Enabled", 0)) {
MouseCoords mouseDelta = Mouse_GetDelta();
Player* player = GET_PLAYER(gPlayState);
if (mouseDelta.x != 0 || mouseDelta.y != 0) {
// TODO: why auto? consider
if (player->autoLockOnActor == NULL) {
sCanFreeLook = true;
} else if (
CVarGetInteger("gEnhancements.Camera.Mouse.ZTargetFreeLookEnabled", 1)
&& (
abs(mouseDelta.x) * CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.X", 1.0f) > 30.0f
|| abs(mouseDelta.y) * CVarGetFloat("gEnhancements.Camera.RightStick.CameraSensitivity.Y", 1.0f) > 30.0f
)
) {
sCanFreeLook = true;
}
}
}

if (!sCanFreeLook) {
f32 camX = sCamPlayState->state.input[0].cur.right_stick_x * 10.0f;
f32 camY = sCamPlayState->state.input[0].cur.right_stick_y * 10.0f;
if (fabsf(camX) >= 15.0f || fabsf(camY) >= 15.0f) {
sCanFreeLook = true;
}
}

// TODO: check persistent Z-target freelook variant
// Pressing Z will "Reset" Camera
if (CHECK_BTN_ALL(sCamPlayState->state.input[0].press.button, BTN_Z)) {
sCanFreeLook = false;
Expand Down
17 changes: 17 additions & 0 deletions mm/2s2h/Enhancements/Controls/Mouse/LocalMouseCaptureManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "LocalMouseCaptureManager.h"

#include <fast/Fast3dWindow.h>
#include "ship/Context.h"
#include "Mouse.h"

void LocalMouseCaptureManager::ToggleMouseCaptureOverride() {
Mouse_ForceToggleCapture();
}

void LocalMouseCaptureManager::UpdateMouseCapture() {
Mouse_UpdateCaptureByState();
auto wnd = std::dynamic_pointer_cast<Fast::Fast3dWindow>(Ship::Context::GetInstance()->GetWindow());
if (wnd->IsMouseCaptured()) {
SetCursorVisibleTicks(GetCursorVisibilityTime() * wnd->GetTargetFps());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <ship/window/MouseCaptureManager.h>

class LocalMouseCaptureManager : public Ship::MouseCaptureManager {
void ToggleMouseCaptureOverride() override;
void UpdateMouseCapture() override;
};
132 changes: 132 additions & 0 deletions mm/2s2h/Enhancements/Controls/Mouse/Mouse.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include "Mouse.h"

#include "ship/Context.h"
#include "ShipInit.hpp"
#include "GameInteractor/GameInteractor.h"
#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h"
#include <libultraship/bridge/consolevariablebridge.h>

static MouseCoords current;
static MouseCaptureGameState gameState;

#ifdef __cplusplus
extern "C" {
#endif

void Mouse_Update() {
Ship::Coords coords = Ship::Context::GetInstance()->GetWindow()->GetMouseDelta();
current.x = coords.x;
current.y = coords.y;
}

MouseCoords Mouse_GetDelta() {
return current;
}

MouseCoords Mouse_GetPos() {
Ship::Coords coords = Ship::Context::GetInstance()->GetWindow()->GetMousePos();
return { coords.x, coords.y };
}

void Mouse_SetCursorPos(s32 x, s32 y) {
Ship::Context::GetInstance()->GetWindow()->SetMousePos({ x, y });
}

bool Mouse_IsCaptured() {
return Ship::Context::GetInstance()->GetWindow()->IsMouseCaptured();
}

bool InferCaptureFromState(MouseCaptureGameState state) {
// TODO: forced on app start?
bool capture;
bool inBenMenu = Ship::Context::GetInstance()->GetWindow()->GetGui()->GetMenuOrMenubarVisible();

if (state.isCaptureForced) {
capture = state.forcedCaptureState;
} else if (inBenMenu || state.inKaleido) {
capture = false;
} else if (!state.gameStarted) {
capture = false;
} else {
capture = true;
}
return capture;
}

void Mouse_ForceToggleCapture() {
if (gameState.isCaptureForced) {
MouseCaptureGameState unforcedState = gameState;
unforcedState.isCaptureForced = false;
if (gameState.forcedCaptureState == InferCaptureFromState(unforcedState)) {
gameState.forcedCaptureState = !gameState.forcedCaptureState;
} else {
gameState.isCaptureForced = false;
}
} else {
gameState.isCaptureForced = true;
gameState.forcedCaptureState = !Ship::Context::GetInstance()->GetWindow()->IsMouseCaptured();
}
Mouse_UpdateCaptureByState();
}

void Mouse_UpdateCaptureByState() {
bool capture = InferCaptureFromState(gameState);

if (
gameState.isCaptureForced
|| !capture
|| Ship::Context::GetInstance()->GetWindow()->GetMouseCaptureManager()->ShouldAutoCaptureMouse()
) {
Ship::Context::GetInstance()->GetWindow()->SetMouseCapture(capture);
}
}

void HandleKaleidoCapture(PauseContext* pauseCtx) {
bool prev = gameState.inKaleido;
switch (pauseCtx->state) {
case PAUSE_STATE_MAIN: {
gameState.inKaleido = true;
break;
}
case PAUSE_STATE_UNPAUSE_SETUP: {
gameState.inKaleido = false;
break;
}
}
if (prev != gameState.inKaleido) {
Mouse_UpdateCaptureByState();
}
}

void RegisterMouseRelatedHooks() {
COND_HOOK(
OnKaleidoUpdate,
true,
HandleKaleidoCapture
);
COND_HOOK(
OnSaveLoad,
true,
[](s16 fileNum) {
gameState.gameStarted = true;
Mouse_UpdateCaptureByState();
}
);
COND_HOOK(
OnConsoleLogoUpdate,
true,
[]() {
if (gameState.gameStarted) {
gameState.gameStarted = false;
gameState.inKaleido = false;
Mouse_UpdateCaptureByState();
}
}
);
}

static RegisterShipInitFunc initFunc(RegisterMouseRelatedHooks, {});

#ifdef __cplusplus
} // extern "C"
#endif
Loading
Loading