Files
scummvm-cursorfix/engines/ags/engine/main/quit.cpp
2026-02-02 04:50:13 +01:00

252 lines
7.6 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
//
// 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