Add debug map option to critical error handler - #1121
Conversation
microlith57
left a comment
There was a problem hiding this comment.
lgtm, thanks for the contribution!
|
I'm not a fan of having an option cut off at the bottom and having stuff move around for no reason while navigating the menu when there's plenty of space on screen to hold all of the options. |
We have a drafted #991 that does that. |
The localization isn't my wheelhouse, and I didn't want to mess with the size of the player drawing in case someone else would take issue with that. It's also future-proofing for if more entries are added to the handler. |
|
Went and redid a lot of the UI code as per discussion with Kalobi on Discord. |
|
If I may request, could this be squash-merged if/when it's approved? It should make things cleaner. |
5d73166 to
231a3f3
Compare
Wartori54
left a comment
There was a problem hiding this comment.
Good first PR! Just needs a bit more polish and taking a step back and thinking what is truly needed in here.
| DisplayState state = displayState switch { | ||
| "overlay" => DisplayState.Overlay, | ||
| "cleanscene" => DisplayState.CleanScene, | ||
| _ => DisplayState.Initial, | ||
| }; |
There was a problem hiding this comment.
Apart from having missed the DisplayState.BlueScreen in the switch, this is a bit misleading.
Calling HandleCriticalError with a given DisplayState will advance to the next possible display state, meaning criterror overlay will use a clean scene instead of being an overlay.
I don't think we should add the ability to force a DisplayState but this method should call HandleCriticalError with the greatest DisplayState that comes before the specified displayState, in my opinion.
|
|
||
| // Forcibly saves the game. | ||
| private void ManuallySaveGame() { | ||
| if (SaveData.Instance is null) return; |
There was a problem hiding this comment.
I'd rather throw here rather than return, all callers already check for this.
This could be static too.
| // First, we find how much space we have worst-case | ||
| optMenu.ItemSpacing = 0; | ||
| optMenu.RecalculateSize(); | ||
|
|
||
| if (UsePlayerSprite) { | ||
| optMenu.Position = new Vector2(Celeste.TargetWidth * 0.15f, Celeste.TargetHeight * 0.55f); | ||
| optMenu.Justify = new Vector2(0.5f, 0f); | ||
|
|
||
| // Reduce item spacing if there are too many items | ||
| if (optMenu.Position.Y + optMenu.Height > Celeste.TargetHeight * 0.85f) { | ||
| optMenu.ItemSpacing = 0; | ||
| optMenu.RecalculateSize(); | ||
| PlayerSpriteSize = MaxPlayerSpriteSize; | ||
|
|
||
| float originalSpriteSize = Celeste.TargetHeight * PlayerSpriteSize; | ||
|
|
||
| // Check how much space we have left over | ||
| float verticalDeadSpace = MaxOptionsHeight - optMenu.Height; | ||
| if (UsePlayerSprite) verticalDeadSpace -= originalSpriteSize; | ||
| if (verticalDeadSpace < 0 && UsePlayerSprite) { | ||
| // We need to shrink the player sprite to fit the screen | ||
| PlayerSpriteSize = Math.Min((originalSpriteSize + verticalDeadSpace) / Celeste.TargetHeight, MaxPlayerSpriteSize); | ||
| if (PlayerSpriteSize <= 0) { | ||
| Logger.Warn("crit-error-handler", "Not enough vertical space for player sprite, disabling"); | ||
| disablePlayerSprite = true; | ||
| PlayerSpriteSize = 0; | ||
| } | ||
| } else { | ||
| optMenu.Position = new Vector2(Celeste.TargetWidth * 0.15f, Celeste.TargetHeight * 0.6f); | ||
| optMenu.Justify = new Vector2(0.5f, 0.5f); | ||
|
|
||
| verticalDeadSpace += originalSpriteSize - PlayerSpriteSize * Celeste.TargetHeight; | ||
| } | ||
|
|
||
| int optionCount = ((patch_TextMenu)optMenu).Items.Count; | ||
|
|
||
| float optionSpacing = verticalDeadSpace / optionCount; | ||
|
|
||
| if (optionSpacing < 0) | ||
| // We are completely out of room. RIP. We intentionally don't clamp optionSpacing to >=0 to keep everything on screen even with overlap | ||
| Logger.Warn("crit-error-handler", $"Not enough vertical space for options menu, forcing overlap"); | ||
|
|
||
| optMenu.ItemSpacing = Math.Min(optionSpacing, MaxOptionSpacing); | ||
| optMenu.RecalculateSize(); | ||
|
|
||
| if (optMenu.Height - 1f > MaxOptionsHeight) // -1f to give a leeway for float shenanigans | ||
| Logger.Warn("crit-error-handler", $"Got incorrect height for option menu, is this a bug? (Expected {MaxOptionsHeight}, got {optMenu.Height})"); | ||
|
|
||
| // We center the options within the available space ONLY if the player sprite isn't there, | ||
| // since it looks fine uncentered with the player but awful without | ||
| float availableSpace = MaxOptionsHeight - optMenu.Height; | ||
|
|
||
| float optY = (Celeste.TargetHeight - MaxOptionsHeight) / 2f; | ||
| if (UsePlayerSprite) | ||
| optY += Celeste.TargetHeight * PlayerSpriteSize; | ||
| else | ||
| optY += availableSpace / 2; | ||
|
|
||
| optMenu.Position = new Vector2(Celeste.TargetWidth * 0.15f, optY + optMenu.Height / 2); | ||
| optMenu.Justify = new Vector2(0.5f, 0.5f); |
There was a problem hiding this comment.
Not sure about this, feels like an overly complicated way to auto layout something that will only have two distributions: overworld menu, and in game menu (+ player or no player sprite).
Wouldn't something like the old code with adjusted constants work? Or just a fixed y position for the start of the buttons, and then "smartly" nudge them up if the list would go out of frame?
This menu will have to get rewritten entirely with submenus if we keep adding more buttons anyway.
There was a problem hiding this comment.
I'm really torn on this. On the one hand, I physically can't fit the debug editor option into the old layout without changing the code somewhat, and so if I'm going to be changing the code at all, I'd rather do it well than half-ass it. On the other, though, I'd honestly rather go back to the old source code and shrink the scope of this PR, if at least to get the main feature I want (the debug editor option) merged, with the layout being tackled later on - I'm not the most comfortable working with Everest internals, and given I'm not a maintainer, I feel like anything greater than this in scope, and even what I have now, should fall under, while deserved, very harsh criticism, since ANY change to Everest's internal systems has a very far-reaching impact. I've been laughed out of a project before due to terrible code quality, and I don't want to repeat that.
There was a problem hiding this comment.
If you really want me to, I can completely rewrite the menu to have submenus and such, but as it stands, that feels like the only option apart from rejecting this PR entirely. As I said, I'd rather do it well than half-ass it.
There was a problem hiding this comment.
Not sure about this, feels like an overly complicated way to auto layout something that will only have two distributions: overworld menu, and in game menu (+ player or no player sprite). Wouldn't something like the old code with adjusted constants work? Or just a fixed y position for the start of the buttons, and then "smartly" nudge them up if the list would go out of frame? This menu will have to get rewritten entirely with submenus if we keep adding more buttons anyway.
@Wartori54 ...thinking about it more, I feel I might've gotten the wrong message from this. Could you elaborate on what you want me to do here?
231a3f3 to
fca56d6
Compare
fca56d6 to
33d811a
Compare
|
!? I didn't close the PR... |
|
Here. |
|
Okay... one moment, then. |
|
I've decided that doing anything than just simply adding the debug map option is probably overscoping for this PR, given my lack of experience with Everest's codebase. |
why would you compile my code then |
Are you kidding me? |
Wartori54
left a comment
There was a problem hiding this comment.
LGTM, just two non-blocking comments.
| if (CurrentHandler == this) return; | ||
| RemoveSelf(); // The rest of the coroutine is never run |
There was a problem hiding this comment.
What's the purpose of these two lines?
There was a problem hiding this comment.
Doing what happens after Fade immediately, so that I don't have to hack in a NoFade flag.
There was a problem hiding this comment.
Those two lines ensure there are no duplicate crash handlers, they are run at the end of the Routine as well, but don't affect the fade.
| public static ExceptionDispatchInfo HandleCriticalError(ExceptionDispatchInfo error) { | ||
| // Done so that older mods that use this don't crash due to a linking error | ||
| public static ExceptionDispatchInfo HandleCriticalError(ExceptionDispatchInfo error) => HandleCriticalError(error, DisplayState.Initial); | ||
|
|
||
| public static ExceptionDispatchInfo HandleCriticalError(ExceptionDispatchInfo error, DisplayState defaultDisplayState) { |
There was a problem hiding this comment.
I've checked and the only mod which uses this method directly and would break is FFFFF.
With the change however, it breaks CelesteTAS due to an ambigous match.
I am of course a bit biased, but imo it would make more sense to remove the backwards compat, since:
a) more people are using CelesteTAS and even if an update exists, people are known to not update their mods
b) It reduces tech debt
c) The developer of FFFFF has been made aware that internal Everest changes will not take the resonsbility for keeping the map working
There was a problem hiding this comment.
The counter argument to this is that HandleCriticalError is a public API and we should keep those working.
Also in CelesteTAS you could push out an update now that filters for the overload with the most amount of arguments, and that would work for current Everest and for future Everest once this change lands.
Adding a new overload when you need to introduce a new parameter on a public API is something that the BCL does extensively and I'd say its a good practice even.
There was a problem hiding this comment.
I didn't really consider it a public API. I suppose it's public which is all the qualification it needs?
Adds an option to enter the debug level editor to the critical error handler.