Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project uses version tags that match the mod version in `everest.yaml`, whi

### Added

- Add **Menu Mouse** to the Interface tab. Hold `Left Alt` (or the bind in its popup) in a Celeste menu to point and click: hover selects, left click confirms or steps the value under the cursor, right click goes back, and the wheel scrolls. Works in the pause menu, options and mod options, the main menu, file select, chapter select, the chapter panel, and the journal. On by default.
- Add Automatic Error Reports for selected Akron startup, overlay, settings and StartPos failures. Reporting is on by default when a Sentry destination is configured, with an opt-out in the Interface tab. Existing saved Off choices are preserved. Reports send only bounded Akron stack details and the mod version. Local logging and Send diagnostics keep their existing behavior.

### Changed
Expand All @@ -16,6 +17,10 @@ This project uses version tags that match the mod version in `everest.yaml`, whi
- Turning **Enabled** off removes Akron's overlay, HUD, hotkeys, interop, error reports, and debug console commands on the next launch. Only its mod options entry stays, so it can be turned back on there.
- Add a **Hold** bind row to the Fast Lookout popup, set with its **Bind** button. The hold starts unbound, and Everest's keyboard and controller config used to be the only place to set it. Keyboard keys in this hold now act as one chord, like Akron's other binds: a hold saved as two separate keys needs both held, so bind it again if you meant either key. Controller and mouse buttons still work on their own.

### Fixed

- Ignore Click Teleport clicks while the game is paused, including the click that unpauses it, so a click on the pause menu never moves Madeline or toggles Cursor Zoom. Cursor Tools, Cursor Zoom, Entity Inspector and Free Camera mouse control also stand down while Menu Mouse is driving a menu.

## Akron Beta 84

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Source/Commands/akron-qa-commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2096,7 +2096,7 @@ public static void QaCursorToolsState(string clickAction = "") {
AkronModule.Settings.CursorToolsClickAction = AkronCursorToolsClickAction.ClickTeleport;
}

bool simulatedHold = AkronModule.ShouldUseCursorToolsHold(AkronModule.Settings.CursorTools, true, false);
bool simulatedHold = AkronModule.ShouldUseCursorToolsHold(AkronModule.Settings.CursorTools, true, false, false);
AkronCursorToolsClickAction normalizedClickAction = AkronModuleSettings.NormalizeCursorToolsClickAction(AkronModule.Settings.CursorToolsClickAction);
Log("qa-cursor-tools-enabled: " + AkronModule.Settings.CursorTools.ToString().ToLowerInvariant());
Log("qa-cursor-tools-simulated-hold: " + simulatedHold.ToString().ToLowerInvariant());
Expand Down
2 changes: 2 additions & 0 deletions Source/Commands/akron-status-commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public static void Status() {
Log("safe-mode: " + AkronModule.Settings.SafeMode.ToString().ToLowerInvariant());
Log("consume-menu-input: " + AkronModule.Settings.ConsumeGameplayInputInMenu.ToString().ToLowerInvariant());
Log("pause-gameplay-in-menu: " + AkronModule.Settings.PauseGameplayInMenu.ToString().ToLowerInvariant());
Log("menu-mouse: " + AkronModule.Settings.MenuMouse.ToString().ToLowerInvariant());
Log("menu-mouse-hold: " + AkronModuleSettings.DescribeBinding(AkronModule.Settings.MenuMouseHold));
Log("overlay-opacity: " + AkronModule.Settings.OverlayOpacity.ToString(CultureInfo.InvariantCulture));
Log("overlay-theme: " + AkronOverlayThemes.CurrentDisplayName());
Log("allow-pause-buffering: " + AkronModule.Settings.AllowPauseBuffering.ToString().ToLowerInvariant());
Expand Down
9 changes: 9 additions & 0 deletions Source/Module/AkronModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ public override void Load() {
Everest.Events.Level.OnUnpause += LevelOnUnpause;
Everest.Events.Level.OnExit += LevelOnExit;
AkronEntityInspector.LoadInspectorPin();
AkronMenuMouse.Load();
MethodInfo dashCoroutineMethod = ResolvePlayerDashCoroutineMethod();
if (dashCoroutineMethod != null) {
dashCoroutineHook = new ILHook(dashCoroutineMethod, PlayerDashCoroutineIlHook);
Expand Down Expand Up @@ -399,6 +400,7 @@ public override void Unload() {
Everest.Events.Level.OnUnpause -= LevelOnUnpause;
Everest.Events.Level.OnExit -= LevelOnExit;
AkronEntityInspector.UnloadInspectorPin();
AkronMenuMouse.Unload();
dashCoroutineHook?.Dispose();
dashCoroutineHook = null;
lookoutRoutineHook?.Dispose();
Expand Down Expand Up @@ -1523,7 +1525,14 @@ private static void MiniTextboxOnRender(On.Celeste.MiniTextbox.orig_Render orig,

private static void TextMenuOnUpdate(On.Celeste.TextMenu.orig_Update orig, TextMenu self) {
ReplacePauseMenuButtonActionIfNeeded(self?.Current as TextMenu.Button);
// Menu Mouse scrolls with the wheel, so the native auto-scroll must not move a
// menu the pointer is driving (see AkronMenuMouse.HoldsScroll).
bool holdScroll = AkronMenuMouse.HoldsScroll(self);
float heldY = holdScroll ? self.Y : 0f;
orig(self);
if (holdScroll) {
self.Y = heldY;
}
KeepNativeTextMenuInsideViewport(self);
}

Expand Down
6 changes: 6 additions & 0 deletions Source/Module/AkronModuleSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ public void ApplySubmissionMode(bool enabled) {
public int IndicatorOffsetY { get; set; }
public bool ConsumeGameplayInputInMenu { get; set; } = true;
public bool PauseGameplayInMenu { get; set; }
// Menu Mouse lets the cursor drive Celeste's own menus while MenuMouseHold is held.
// On by default: it does nothing until the hold bind is pressed in a menu.
public bool MenuMouse { get; set; } = true;
public int OverlayOpacity { get; set; } = 96;
public AkronOverlayThemePreset OverlayThemePreset { get; set; } = AkronOverlayThemePreset.Default;
public int OverlayScale { get; set; } = 100;
Expand Down Expand Up @@ -321,6 +324,9 @@ public void ApplySubmissionMode(bool enabled) {
[DefaultButtonBinding(0, Keys.LeftAlt)]
public ButtonBinding CursorToolsHold { get; set; }

[DefaultButtonBinding(0, Keys.LeftAlt)]
public ButtonBinding MenuMouseHold { get; set; }

public bool ReducedVisualNoise { get; set; }
public bool NoParticles { get; set; }
public bool NoTrails { get; set; }
Expand Down
21 changes: 18 additions & 3 deletions Source/Module/akron-module-overlay-input.cs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ private static void UpdateOverlayCursorState() {
ShouldShowEntityInspectorCursor() ||
ShouldShowClickTeleportCursor() ||
ShouldShowCursorZoomCursor() ||
ShouldShowFreeCameraMouseCursor();
ShouldShowFreeCameraMouseCursor() ||
AkronMenuMouse.ShowsCursor;
if (shouldShowCursor) {
ShowManagedCursorForTransientUi();
return;
Expand Down Expand Up @@ -471,6 +472,7 @@ private static void CaptureCursorVisibilityIfNeeded() {
private static bool ShouldShowClickTeleportCursor() {
return Engine.Scene is Level &&
Overlay?.Visible != true &&
!AkronMenuMouse.ShowsCursor &&
Comment thread
Microck marked this conversation as resolved.
IsClickTeleportCursorActive() &&
AkronPolicy.CanUse(AkronFeatureKind.ClickTeleport).Allowed;
}
Expand All @@ -481,17 +483,30 @@ internal static bool ShouldShowEntityInspectorCursor() {
Settings.EntityInspector || Settings.CursorTools,
IsEntityInspectorCursorHoldActive() || IsCursorToolsInspectorPinActive(),
Overlay?.Visible == true,
AkronPolicy.CanUse(AkronFeatureKind.EntityInspector).Allowed);
AkronPolicy.CanUse(AkronFeatureKind.EntityInspector).Allowed,
AkronMenuMouse.ShowsCursor);
}

internal static bool IsEntityInspectorCursorHoldActive() {
return IsButtonBindingHeld(AkronModuleSettings.ResolveEntityInspectorCursorHoldBinding(Settings));
}

internal static bool ShouldShowEntityInspectorCursor(bool entityInspector, bool cursorHoldBindingHeld, bool overlayVisible, bool policyAllowed) {
// The Akron overlay and its placement modes own the mouse while they are up, so
// Menu Mouse only runs when neither is active.
internal static bool IsMenuMouseHoldActive() {
return Settings.MenuMouse &&
Overlay?.Visible != true &&
Overlay?.IsTransientMouseUiActive != true &&
IsButtonBindingHeld(Settings.MenuMouseHold);
Comment thread
Microck marked this conversation as resolved.
}

// Like the overlay, Menu Mouse driving a menu owns the shared Left Alt hold, so the
// inspector neither previews nor pins entities behind that menu.
internal static bool ShouldShowEntityInspectorCursor(bool entityInspector, bool cursorHoldBindingHeld, bool overlayVisible, bool policyAllowed, bool menuMouseOwnsCursor) {
return entityInspector &&
cursorHoldBindingHeld &&
!overlayVisible &&
!menuMouseOwnsCursor &&
policyAllowed;
}

Expand Down
1 change: 1 addition & 0 deletions Source/Module/akron-module-settings-label-rows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ private static void EnsureCurrentButtonBindingDefaults(AkronModuleSettings setti
settings.ClickTeleportCursor = EnsureButtonBinding(settings.ClickTeleportCursor, CreateLeftAltHoldBinding);
settings.CursorZoomHold = EnsureButtonBinding(settings.CursorZoomHold, CreateLeftAltHoldBinding);
settings.CursorToolsHold = EnsureButtonBinding(settings.CursorToolsHold, CreateLeftAltHoldBinding);
settings.MenuMouseHold = EnsureButtonBinding(settings.MenuMouseHold, CreateLeftAltHoldBinding);
}

private static ButtonBinding EnsureButtonBinding(ButtonBinding binding, Func<ButtonBinding> defaultFactory) {
Expand Down
68 changes: 40 additions & 28 deletions Source/Module/akron-module-visual-runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
namespace Celeste.Mod.Akron;

public partial class AkronModule {
internal static bool ShouldUseCursorToolsHold(bool cursorTools, bool cursorToolsHoldBindingHeld, bool overlayVisible) {
return cursorTools && cursorToolsHoldBindingHeld && !overlayVisible;
// Cursor Tools and Menu Mouse share the Left Alt default. While Menu Mouse drives a
// menu it owns the cursor, so the hold must not also pan, zoom or freeze the level
// behind it (freezing would stop the pause menu from updating at all).
internal static bool ShouldUseCursorToolsHold(bool cursorTools, bool cursorToolsHoldBindingHeld, bool overlayVisible, bool menuMouseOwnsCursor) {
return cursorTools && cursorToolsHoldBindingHeld && !overlayVisible && !menuMouseOwnsCursor;
}

internal static bool IsCursorToolsHoldActive() {
return ShouldUseCursorToolsHold(
Settings.CursorTools,
Settings.CursorToolsHold?.Check ?? false,
IsOverlayVisible);
IsOverlayVisible,
AkronMenuMouse.ShowsCursor);
Comment thread
Microck marked this conversation as resolved.
}

internal static bool IsClickTeleportEffectiveEnabled() {
Expand Down Expand Up @@ -52,7 +56,8 @@ internal static bool IsFreeCameraMouseControlEffectiveEnabled() {
return IsFreeCameraMouseControlEffectiveEnabled(
Settings.FreeCameraMouseControl,
IsCursorToolsHoldActive(),
Settings.CursorToolsFreeCamera);
Settings.CursorToolsFreeCamera,
AkronMenuMouse.ShowsCursor);
}

internal static bool IsCursorToolEffectiveEnabled(bool savedToolEnabled, bool cursorToolsHoldActive, bool cursorToolsOptionEnabled) {
Expand All @@ -64,10 +69,13 @@ internal static bool IsClickTeleportCursorActive(bool clickTeleportEnabled, bool
cursorToolsHoldActive && cursorToolsClickTeleportEnabled;
}

internal static bool IsFreeCameraMouseControlEffectiveEnabled(bool freeCameraMouseControlEnabled, bool cursorToolsHoldActive, bool cursorToolsFreeCameraEnabled) {
return freeCameraMouseControlEnabled ||
cursorToolsHoldActive &&
cursorToolsFreeCameraEnabled;
// Free Camera pans every level update, paused or not, so pointing at a menu row
// with Menu Mouse would otherwise drag the camera behind the menu.
internal static bool IsFreeCameraMouseControlEffectiveEnabled(bool freeCameraMouseControlEnabled, bool cursorToolsHoldActive, bool cursorToolsFreeCameraEnabled, bool menuMouseOwnsCursor) {
return !menuMouseOwnsCursor &&
(freeCameraMouseControlEnabled ||
cursorToolsHoldActive &&
cursorToolsFreeCameraEnabled);
}

internal static bool IsCursorToolsFreezeGameplayEffectiveEnabled() {
Expand Down Expand Up @@ -101,21 +109,17 @@ private static void ApplyStartPosMousePlacement(Level level, Player player) {

private static void CaptureClickTeleportTargetBeforeCameraMovement(Level level, Player player) {
pendingClickTeleportTarget = null;
if (!IsClickTeleportEffectiveEnabled() ||
Settings.StartPosMousePlacement ||
player == null ||
player.Dead ||
IsOverlayVisible ||
!ShouldShowClickTeleportCursor()) {
clickTeleportLastLeftDown = false;
return;
}

MouseState mouse = Mouse.GetState();
bool leftDown = mouse.LeftButton == ButtonState.Pressed;
bool pressed = leftDown && !clickTeleportLastLeftDown;
clickTeleportLastLeftDown = leftDown;
if (!pressed) {
// A click while paused belongs to the pause menu (Menu Mouse shares the Left Alt
// default), never to the room behind it.
bool gated = !IsClickTeleportEffectiveEnabled() ||
Settings.StartPosMousePlacement ||
level.Paused ||
player == null ||
player.Dead ||
IsOverlayVisible ||
!ShouldShowClickTeleportCursor();
if (!TrackFreshPress(mouse.LeftButton == ButtonState.Pressed, gated, ref clickTeleportLastLeftDown)) {
return;
}

Expand All @@ -125,6 +129,15 @@ private static void CaptureClickTeleportTargetBeforeCameraMovement(Level level,
pendingClickTeleportTarget = target;
}

// A fresh press is one that starts while the feature is live. The input is tracked
// while gated too: the Alt+click on Resume is still held on the first unpaused frame,
// and must not count as a new press (teleporting Madeline or toggling Cursor Zoom).
internal static bool TrackFreshPress(bool down, bool gated, ref bool lastDown) {
bool pressed = !gated && down && !lastDown;
lastDown = down;
return pressed;
}

private static void ApplyClickTeleport(Level level, Player player) {
if (!IsClickTeleportEffectiveEnabled() ||
Settings.StartPosMousePlacement ||
Expand Down Expand Up @@ -231,20 +244,19 @@ internal static void MoveHairForTeleport(Player player, Vector2 delta) {
private static void UpdateCursorZoom(Level level) {
bool cursorToolsHoldActive = IsCursorToolsHoldActive();
bool cursorZoomEnabled = IsCursorZoomEffectiveEnabled();
if (!cursorZoomEnabled || IsOverlayVisible || !AkronPolicy.CanUse(AkronFeatureKind.CursorZoom).Allowed) {
bool bindDown = Settings.CursorZoomHold?.Check ?? false;
// Menu Mouse reads the same wheel; like the overlay, it pauses zoom input.
bool gated = !cursorZoomEnabled || IsOverlayVisible || AkronMenuMouse.ShowsCursor || !AkronPolicy.CanUse(AkronFeatureKind.CursorZoom).Allowed;
bool bindPressed = TrackFreshPress(Settings.CursorZoom && bindDown, gated, ref cursorZoomLastBindDown);
if (gated) {
cursorZoomHadScrollSample = false;
cursorZoomLastBindDown = false;
if (!cursorZoomEnabled) {
cursorZoomToggleActive = false;
DeactivateCursorZoom(level);
}
return;
}

bool bindDown = Settings.CursorZoomHold?.Check ?? false;
bool bindPressed = Settings.CursorZoom && bindDown && !cursorZoomLastBindDown;
cursorZoomLastBindDown = Settings.CursorZoom ? bindDown : false;

AkronCursorZoomActivationMode activationMode = AkronModuleSettings.NormalizeCursorZoomActivationMode(Settings.CursorZoomActivationMode);
bool active = cursorToolsHoldActive || bindDown;
if (activationMode == AkronCursorZoomActivationMode.Toggle) {
Expand Down
2 changes: 2 additions & 0 deletions Source/Overlay/akron-overlay-bindable-actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ private static string DescribeBindingForAction(string label) {
"Click Teleport" => "Hold " + AkronModuleSettings.DescribeBinding(settings.ClickTeleportCursor) + " and click",
"Cursor Zoom" => "Hold " + AkronModuleSettings.DescribeBinding(settings.CursorZoomHold) + " and scroll",
"Cursor Tools" => "Hold " + AkronModuleSettings.DescribeBinding(settings.CursorToolsHold),
"Menu Mouse" => "Hold " + AkronModuleSettings.DescribeBinding(settings.MenuMouseHold) + " in menus",
"Reload Room" => AkronModuleSettings.DescribeBinding(settings.ReloadRoom),
"Reload Chapter" => AkronModuleSettings.DescribeBinding(settings.ReloadChapter),
"Open Debug Map" => AkronModuleSettings.DescribeBinding(settings.OpenDebugMap),
Expand Down Expand Up @@ -768,6 +769,7 @@ private static ButtonBinding ResolveDefaultButtonBinding(AkronModuleSettings set
"Player/Click Teleport" => settings.ClickTeleportCursor,
"Creator/Cursor Zoom" => settings.CursorZoomHold,
"Creator/Cursor Tools" => settings.CursorToolsHold,
"Interface/Menu Mouse" => settings.MenuMouseHold,
"Creator/Entity Inspector" => settings.ToggleEntityInspector,
"Level/Show Hitboxes" => settings.ToggleHitboxes,
"Level/Freeze Gameplay" => settings.FreezeGameplay,
Expand Down
2 changes: 2 additions & 0 deletions Source/Overlay/akron-overlay-copy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ public sealed partial class AkronOverlay {
["Hide Akron HUD"] = new[] { "hud", "hide", "akron", "labels" },
["Trail Visibility"] = new[] { "trail", "dash trail", "ghost trail", "classic wave trail", "always trail", "no trail", "hide trail", "cut rate" },
["Block Gameplay Input"] = new[] { "input", "consume", "madeline", "overlay", "menu" },
["Menu Mouse"] = new[] { "mouse", "cursor", "click", "scroll", "menu", "pause menu", "chapter select", "file select", "alt" },
["Stamina Widget"] = new[] { "stamina", "widget", "text", "hud" },
["Speed Widget"] = new[] { "speed", "widget", "text", "hud" },
["Dash Widget"] = new[] { "dashes", "widget", "text", "hud" },
Expand Down Expand Up @@ -408,6 +409,7 @@ public sealed partial class AkronOverlay {
["Hide Akron HUD"] = "Hide every Akron label and indicator while keeping their settings.",
["Trail Visibility"] = "Choose Vanilla, Hidden, or Always for Madeline's trail. The popup sets the cut rate for Always.",
["Block Gameplay Input"] = "Keep gameplay input from reaching Madeline while the overlay is open. Off, she keeps moving behind the menu.",
["Menu Mouse"] = "Hold the cursor hotkey in a Celeste menu to point and click: hover selects, left click confirms, right click goes back, the wheel scrolls.",
["Stamina Widget"] = "Show Madeline's current stamina as a text line in the label stack.",
["Speed Widget"] = "Show Madeline's current speed as a text line in the label stack.",
["Dash Widget"] = "Show Madeline's current dash count as a text line in the label stack.",
Expand Down
1 change: 1 addition & 0 deletions Source/Overlay/akron-overlay-entries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ private static List<OverlayEntry> BuildEntriesForTab(string tab, Level level) {
UploadPackRow(level),
Toggle("Pause While Open", () => AkronModule.Settings.PauseGameplayInMenu, value => AkronModule.Settings.PauseGameplayInMenu = value),
Toggle("Block Gameplay Input", () => AkronModule.Settings.ConsumeGameplayInputInMenu, value => AkronModule.Settings.ConsumeGameplayInputInMenu = value, "input", "madeline", "overlay", "consume"),
Toggle("Menu Mouse", () => AkronModule.Settings.MenuMouse, value => AkronModule.Settings.MenuMouse = value, "mouse", "cursor", "click", "menu", "pause menu", "chapter select", "file select", "alt"),
Toggle("Streamer Mode", () => AkronModule.Settings.StreamerMode, value => AkronModule.Settings.StreamerMode = value),
LoggingToggle(),
ErrorReportingToggle(),
Expand Down
Loading
Loading