Skip to content
Open
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
18 changes: 18 additions & 0 deletions Celeste.Mod.mm/Content/Dialog/English.txt
Original file line number Diff line number Diff line change
Expand Up @@ -345,3 +345,21 @@
ASSETRELOADHELPER_LOADINGNEWMOD= Loading new mod:
ASSETRELOADHELPER_RELOADINGMODASSEMBLY= Reloading mod assembly:
ASSETRELOADHELPER_RELOADINGMOUNTAIN= Reloading mountain

# Critical Error Handler
CRITICALERRORHANDLER_OOOOPS= Ooops! :(
CRITICALERRORHANDLER_OOOOPS_COLON_THREE= Ooops! :3
CRITICALERRORHANDLER_CRITICAL_ERROR= Celeste/Everest encountered a critical error.
CRITICALERRORHANDLER_DISCORD= Please report this in the Celeste discord!
CRITICALERRORHANDLER_DISCORD_LINK= discord.gg/celeste - channel #modding_help
CRITICALERRORHANDLER_LOG_FILE_BACKED_UP= Your log file has been backed up; please attach it to your bug report.
CRITICALERRORHANDLER_ADDITIONAL_ERRORS= Additional errors have occurred since the initial crash!
CRITICALERRORHANDLER_ERROR_DETAILS= Error Details

CRITICALERRORHANDLER_OPEN_LOG_FILE_FOLDER= Open log file folder
CRITICALERRORHANDLER_RETRY_LEVEL= Retry level
CRITICALERRORHANDLER_SAVE_QUIT= Save & Quit
CRITICALERRORHANDLER_SAVE_PROGRESS= Save current progress
CRITICALERRORHANDLER_RETURN_TO_MENU= Return to main menu
CRITICALERRORHANDLER_EXIT_GAME= Exit Game
CRITICALERRORHANDLER_RESTART_GAME= Restart Game
3 changes: 3 additions & 0 deletions Celeste.Mod.mm/Mod/Core/CoreModuleSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,9 @@ public bool CodeReload {
[SettingIgnore]
public bool CrashHandlerAlwaysTeabag { get; set; } = false; // The world is a cruel place, so we can't turn this on by default... ._.

[SettingIgnore]
public bool CrashHandlerAlwaysColonThree { get; set; } = false;

[SettingIgnore]
public string CurrentVersion { get; set; }

Expand Down
81 changes: 64 additions & 17 deletions Celeste.Mod.mm/Mod/UI/CriticalErrorHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,29 @@ private set {
private VirtualRenderTarget playerRenderTarget;
private bool UsePlayerSprite => !disablePlayerSprite && State != DisplayState.BlueScreen;

private static bool canColonThree;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name canColonThree is not very clear, another name like alreadyCrashed could be clearer.


private bool playerShouldTeabag;
private bool isCrouched;
private float crouchTimer;
private bool isColonThree;

private string textOooops = "Ooops! :(";
private string textOooopsColonThree = "Ooops! :3";
private string textCriticalError = "Celeste/Everest encountered a critical error.";
private string textDiscord = "Please report this in the Celeste discord!";
private string textDiscordLink = "discord.gg/celeste - channel #modding_help";
private string textLogFileBackedUp = "Your log file has been backed up; please attach it to your bug report.";
private string textAdditionalErrors = "Additional errors have occurred since the initial crash!";
private string textErrorDetails = "Error Details";

private string textOpenLogFileFolder = "Open log file folder";
private string textRetryLevel = "Retry level";
private string textSaveQuit = "Save & Quit";
private string textSaveProgress = "Save current progress";
private string textReturnToMenu = "Return to main menu";
private string textExitGame = "Exit Game";
private string textRestartGame = "Restart Game";

private CriticalErrorHandler(ExceptionDispatchInfo error, string logFile, string logFileError) {
Depth += 100; // Render below other overlays
Expand All @@ -347,13 +367,41 @@ private CriticalErrorHandler(ExceptionDispatchInfo error, string logFile, string
LogFile = logFile;
LogFileError = string.IsNullOrEmpty(logFile) ? logFileError : null;

playerShouldTeabag = CoreModule.Settings.CrashHandlerAlwaysTeabag || (!(Settings.Instance?.DisableFlashes ?? true) && new Random().Next(0, 10) == 0);
Random rng = new();
playerShouldTeabag = CoreModule.Settings.CrashHandlerAlwaysTeabag || (!(Settings.Instance?.DisableFlashes ?? true) && rng.Next(0, 10) == 0);
isColonThree = CoreModule.Settings.CrashHandlerAlwaysColonThree || (canColonThree && rng.Next(0, 10) == 0);
canColonThree = true;

try {
PopulateDialog();
} catch {
// Something is wrong with the dialog; so fall back to the hardcoded defaults.
}

beforeRenderInterceptor = new BeforeRenderInterceptor(BeforeRender);
Add(new Coroutine(Routine()));
Logger.Info("crit-error-handler", $"Created critical error handler for exception {errorType}: {errorMessage}");
}

private void PopulateDialog() {
textOooops = Dialog.Get("CRITICALERRORHANDLER_OOOOPS");
textOooopsColonThree = Dialog.Get("CRITICALERRORHANDLER_OOOOPS_COLON_THREE");
textCriticalError = Dialog.Get("CRITICALERRORHANDLER_CRITICAL_ERROR");
textDiscord = Dialog.Get("CRITICALERRORHANDLER_DISCORD");
textDiscordLink = Dialog.Get("CRITICALERRORHANDLER_DISCORD_LINK");
textLogFileBackedUp = Dialog.Get("CRITICALERRORHANDLER_LOG_FILE_BACKED_UP");
textAdditionalErrors = Dialog.Get("CRITICALERRORHANDLER_ADDITIONAL_ERRORS");
textErrorDetails = Dialog.Get("CRITICALERRORHANDLER_ERROR_DETAILS");

textOpenLogFileFolder = Dialog.Get("CRITICALERRORHANDLER_OPEN_LOG_FILE_FOLDER");
textRetryLevel = Dialog.Get("CRITICALERRORHANDLER_RETRY_LEVEL");
textSaveQuit = Dialog.Get("CRITICALERRORHANDLER_SAVE_QUIT");
textSaveProgress = Dialog.Get("CRITICALERRORHANDLER_SAVE_PROGRESS");
textReturnToMenu = Dialog.Get("CRITICALERRORHANDLER_RETURN_TO_MENU");
textExitGame = Dialog.Get("CRITICALERRORHANDLER_EXIT_GAME");
textRestartGame = Dialog.Get("CRITICALERRORHANDLER_RESTART_GAME");
}

public void Dispose() {
playerRenderTarget?.Dispose();
playerRenderTarget = null;
Expand All @@ -369,7 +417,7 @@ private IEnumerator Routine() {
// Create the options menu
optMenu = new TextMenu() { AutoScroll = false };

optMenu.Add(new TextMenu.Button("Open log file folder") { Disabled = LogFileError != null }.Pressed(() => {
optMenu.Add(new TextMenu.Button(textOpenLogFileFolder) { Disabled = LogFileError != null }.Pressed(() => {
string openProg =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "explorer.exe" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "xdg-open" :
Expand All @@ -385,19 +433,19 @@ private IEnumerator Routine() {

UserChoice? choice = null;
if (Session != null) {
optMenu.Add(new TextMenu.Button("Retry level") { Disabled = !CanExecuteChoice(UserChoice.RetryLevel) }.Pressed(() => choice = UserChoice.RetryLevel));
optMenu.Add(new TextMenu.Button("Save & Quit") { Disabled = !CanExecuteChoice(UserChoice.SaveAndQuit) }.Pressed(() => choice = UserChoice.SaveAndQuit));
optMenu.Add(new TextMenu.Button(textRetryLevel) { Disabled = !CanExecuteChoice(UserChoice.RetryLevel) }.Pressed(() => choice = UserChoice.RetryLevel));
optMenu.Add(new TextMenu.Button(textSaveQuit) { Disabled = !CanExecuteChoice(UserChoice.SaveAndQuit) }.Pressed(() => choice = UserChoice.SaveAndQuit));
}

if (SaveData.Instance != null && !hasFlushedSaveData)
optMenu.Add(new TextMenu.Button("Save current progress") { Disabled = !CanExecuteChoice(UserChoice.FlushSaveData) }.Pressed(() => choice = UserChoice.FlushSaveData));
optMenu.Add(new TextMenu.Button(textSaveProgress) { Disabled = !CanExecuteChoice(UserChoice.FlushSaveData) }.Pressed(() => choice = UserChoice.FlushSaveData));

optMenu.Add(new TextMenu.Button("Return to main menu") { Disabled = !CanExecuteChoice(UserChoice.ReturnToMainMenu) }.Pressed(() => choice = UserChoice.ReturnToMainMenu));
optMenu.Add(new TextMenu.Button(textReturnToMenu) { Disabled = !CanExecuteChoice(UserChoice.ReturnToMainMenu) }.Pressed(() => choice = UserChoice.ReturnToMainMenu));

optMenu.Add(new TextMenu.Button("Exit Game").Pressed(() => {
optMenu.Add(new TextMenu.Button(textExitGame).Pressed(() => {
Scene.OnEndOfFrame += static () => Engine.Instance.Exit();
}));
optMenu.Add(new TextMenu.Button("Restart Game").Pressed(() => {
optMenu.Add(new TextMenu.Button(textRestartGame).Pressed(() => {
Everest.Events.Celeste.OnShutdown += static () => BOOT.StartCelesteProcess();
Scene.OnEndOfFrame += static () => Engine.Instance.Exit();
}));
Expand Down Expand Up @@ -649,7 +697,6 @@ private void BeforeRender() {
/// ended.
/// </summary>
public static event Action OnAfterPlayerRender;

public override void Render() {
// Draw the background
switch (State) {
Expand Down Expand Up @@ -695,7 +742,7 @@ public override void Render() {

// Draw the error UI
Vector2 textPos = new Vector2(Celeste.TargetWidth * 0.3f, Celeste.TargetHeight * 0.35f);
ActiveFont.Draw("Oooops! :(", textPos, new Vector2(0, 1), new Vector2(3), Color.White * Fade);
ActiveFont.Draw(isColonThree ? textOooopsColonThree : textOooops, textPos, new Vector2(0, 1), new Vector2(3), Color.White * Fade);
textPos.X += 50;

void DrawLineWrap(string text, float scale, Color color, Vector2 posOff = default) {
Expand Down Expand Up @@ -730,23 +777,23 @@ void DrawLineWrap(string text, float scale, Color color, Vector2 posOff = defaul
}
}

DrawLineWrap("Celeste/Everest encountered a critical error.", 0.7f, Color.LightGray);
DrawLineWrap("Please report this in the Celeste discord!", 0.7f, Color.LightGray);
DrawLineWrap("discord.gg/celeste - channel #modding_help", 0.5f, Color.Gray, Vector2.UnitX * 50);
DrawLineWrap("Your log file has been backed up; please attach it to your bug report.", 0.7f, Color.LightGray);
DrawLineWrap(textCriticalError, 0.7f, Color.LightGray);
DrawLineWrap(textDiscord, 0.7f, Color.LightGray);
DrawLineWrap(textDiscordLink, 0.5f, Color.Gray, Vector2.UnitX * 50);
DrawLineWrap(textLogFileBackedUp, 0.7f, Color.LightGray);
if (string.IsNullOrEmpty(LogFileError))
DrawLineWrap(LogFile, 0.4f, Color.Gray, Vector2.UnitX * 50);
else
DrawLineWrap(LogFileError, 0.5f, Color.OrangeRed, Vector2.UnitX * 50);

if (EncounteredAdditionalErrors) {
textPos.Y += 20;
DrawLineWrap("Additional errors have occurred since the initial crash!", 0.7f, Color.IndianRed);
DrawLineWrap(textAdditionalErrors, 0.7f, Color.IndianRed);
}

textPos.Y += 20;

DrawLineWrap($"Error Details: {errorType}: {errorMessage}", 0.7f, Color.LightGray);
DrawLineWrap($"{textErrorDetails}: {errorType}: {errorMessage}", 0.7f, Color.LightGray);
textPos.X += 50;
string[] btLines = (errorStackTrace ?? string.Empty)
.Split('\n')
Expand All @@ -765,4 +812,4 @@ void DrawLineWrap(string text, float scale, Color color, Vector2 posOff = defaul
}

}
}
}
5 changes: 5 additions & 0 deletions Celeste.Mod.mm/Patches/Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,10 @@ private static void CmdOpenLog() {
else
Engine.Commands.Log($"{pathLog} does not exist");
}

[Command("criticalerrorhandler", "open the critical error handler screen")]
private static void CmdCriticalErrorHandler() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow changing the exception message?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't think this is necessary; besides, if this accepted a string message then the message could only be one word long

Engine.Scene.OnEndOfFrame += static () => throw new Exception("Pretend error thrown to open the critical error handler screen");
}
}
}