/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ // // Quit game procedure // #include "ags/shared/core/platform.h" #include "ags/engine/ac/cd_audio.h" #include "ags/engine/ac/game.h" #include "ags/engine/ac/game_setup.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/engine/ac/game_state.h" #include "ags/engine/ac/room_status.h" #include "ags/engine/ac/route_finder.h" #include "ags/engine/ac/translation.h" #include "ags/engine/ac/dynobj/dynobj_manager.h" #include "ags/engine/debugging/ags_editor_debugger.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/debugging/debugger.h" #include "ags/shared/debugging/out.h" #include "ags/shared/font/fonts.h" #include "ags/engine/main/config.h" #include "ags/engine/main/engine.h" #include "ags/engine/main/main.h" #include "ags/engine/main/quit.h" #include "ags/shared/ac/sprite_cache.h" #include "ags/engine/gfx/graphics_driver.h" #include "ags/shared/gfx/bitmap.h" #include "ags/shared/core/asset_manager.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/engine/platform/base/sys_main.h" #include "ags/plugins/plugin_engine.h" #include "ags/shared/script/cc_common.h" #include "ags/engine/media/audio/audio_system.h" #include "ags/globals.h" #include "ags/ags.h" namespace AGS3 { using namespace AGS::Shared; using namespace AGS::Engine; void quit_tell_editor_debugger(const String &qmsg, QuitReason qreason) { if (_G(editor_debugging_initialized)) { if (qreason & kQuitKind_GameException) _G(handledErrorInEditor) = send_exception_to_debugger(qmsg.GetCStr()); send_state_to_debugger("EXIT"); _G(editor_debugger)->Shutdown(); } } void quit_stop_cd() { if (_G(need_to_stop_cd)) cd_manager(3, 0); } void quit_check_dynamic_sprites(QuitReason qreason) { if ((qreason & kQuitKind_NormalExit) && _G(check_dynamic_sprites_at_exit) && (_GP(game).options[OPT_DEBUGMODE] != 0)) { // Check that the dynamic sprites have been deleted; // ignore those that are owned by the game objects. for (size_t i = 1; i < _GP(spriteset).GetSpriteSlotCount(); i++) { if ((_GP(game).SpriteInfos[i].Flags & SPF_DYNAMICALLOC) && ((_GP(game).SpriteInfos[i].Flags & SPF_OBJECTOWNED) == 0)) { debug_script_warn("Dynamic sprite %d was never deleted", i); } } } } void quit_shutdown_audio() { set_our_eip(9917); _GP(game).options[OPT_CROSSFADEMUSIC] = 0; shutdown_sound(); } // Parses the quit message; returns: // * QuitReason - which is a code of the reason we're quitting (game error, etc); // * errmsg - a pure error message (extracted from the parsed string). // * alertis - a complex message to post into the engine output (stdout, log); QuitReason quit_check_for_error_state(const char *qmsg, String &errmsg, String &alertis) { if (qmsg[0] == '|') { return kQuit_GameRequest; } else if (qmsg[0] == '!') { QuitReason qreason; qmsg++; if (qmsg[0] == '|') { qreason = kQuit_UserAbort; alertis = "Abort key pressed.\n\n"; } else if (qmsg[0] == '?') { qmsg++; qreason = kQuit_ScriptAbort; alertis = "A fatal error has been generated by the script using the AbortGame function. Please contact the game author for support.\n\n"; } else { qreason = kQuit_GameError; alertis.Format("An error has occurred. Please contact the game author for support, as this " "is likely to be a scripting error and not a bug in AGS.\n" "(Engine version %s)\n\n", _G(EngineVersion).LongString.GetCStr()); } alertis.Append(cc_get_err_callstack()); if (qreason != kQuit_UserAbort) { alertis.AppendFmt("\nError: %s", qmsg); errmsg = qmsg; Debug::Printf(kDbgMsg_Fatal, "ERROR: %s\n%s", qmsg, cc_get_error().CallStack.GetCStr()); } return qreason; } else if (qmsg[0] == '%') { qmsg++; alertis.Format("A warning has been generated. This is not normally fatal, but you have selected " "to treat warnings as errors.\n" "(Engine version %s)\n\n%s\n%s", _G(EngineVersion).LongString.GetCStr(), cc_get_err_callstack().GetCStr(), qmsg); errmsg = qmsg; return kQuit_GameWarning; } else { alertis.Format("An internal error has occurred. Please note down the following information.\n" "(Engine version %s)\n" "\nError: %s", _G(EngineVersion).LongString.GetCStr(), qmsg); return kQuit_FatalError; } } void quit_delete_temp_files() { #ifdef TODO al_ffblk dfb; int dun = al_findfirst("~ac*.tmp", &dfb, FA_SEARCH); while (!dun) { File::DeleteFile(dfb.name); dun = al_findnext(&dfb); } al_findclose(&dfb); #endif } // quit - exits the engine, shutting down everything gracefully // The parameter is the message to print. If this message begins with // an '!' character, then it is printed as a "contact game author" error. // If it begins with a '|' then it is treated as a "thanks for playing" type // message. If it begins with anything else, it is treated as an internal // error. // "!|" is a special code used to mean that the player has aborted (Alt+X) void quit(const char *quitmsg) { if (!_G(abort_engine)) { strncpy(_G(quit_message), quitmsg, sizeof(_G(quit_message)) - 1); _G(quit_message)[sizeof(_G(quit_message)) - 1] = '\0'; _G(abort_engine) = true; } } void quit_free() { if (strlen(_G(quit_message)) == 0) Common::strcpy_s(_G(quit_message), "|bye!"); const char *quitmsg = _G(quit_message); Debug::Printf(kDbgMsg_Info, "Quitting the game..."); // NOTE: we must not use the quitmsg pointer past this step, // as it may be from a plugin and we're about to free plugins String errmsg, fullmsg; QuitReason qreason = quit_check_for_error_state(quitmsg, errmsg, fullmsg); if (qreason & kQuitKind_NormalExit) save_config_file(); _G(handledErrorInEditor) = false; quit_tell_editor_debugger(errmsg, qreason); set_our_eip(9900); quit_stop_cd(); set_our_eip(9020); // Be sure to unlock mouse on exit, or users will hate us sys_window_lock_mouse(false); set_our_eip(9016); quit_check_dynamic_sprites(qreason); if (_G(use_cdplayer)) _G(platform)->ShutdownCDPlayer(); set_our_eip(9019); quit_shutdown_audio(); set_our_eip(9901); _GP(spriteset).Reset(); set_our_eip(9908); shutdown_pathfinder(); unload_game(); engine_shutdown_gfxmode(); _G(platform)->PreBackendExit(); // On abnormal exit: display the message (at this point the window still exists) if ((qreason & kQuitKind_NormalExit) == 0 && !_G(handledErrorInEditor)) { _G(platform)->DisplayAlert("%s", fullmsg.GetCStr()); } // release backed library // WARNING: no Allegro objects should remain in memory after this, // if their destruction is called later, program will crash! shutdown_font_renderer(); allegro_exit(); sys_main_shutdown(); _G(platform)->PostAllegroExit(); set_our_eip(9903); quit_delete_temp_files(); _G(proper_exit) = 1; Debug::Printf(kDbgMsg_Alert, "***** ENGINE HAS SHUTDOWN"); shutdown_debug(); set_our_eip(9904); } } // namespace AGS3