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

1279 lines
45 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/>.
*
*/
//
// Engine initialization
//
#include "ags/shared/core/platform.h"
#include "ags/lib/allegro.h" // allegro_install and _exit
#include "ags/engine/ac/asset_helper.h"
#include "ags/shared/ac/common.h"
#include "ags/engine/ac/character.h"
#include "ags/engine/ac/character_extras.h"
#include "ags/shared/ac/character_info.h"
#include "ags/engine/ac/draw.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/global_character.h"
#include "ags/engine/ac/global_game.h"
#include "ags/engine/ac/gui.h"
#include "ags/engine/ac/lip_sync.h"
#include "ags/engine/ac/path_helper.h"
#include "ags/engine/ac/route_finder.h"
#include "ags/engine/ac/sys_events.h"
#include "ags/engine/ac/room_status.h"
#include "ags/engine/ac/speech.h"
#include "ags/shared/ac/sprite_cache.h"
#include "ags/engine/ac/translation.h"
#include "ags/engine/ac/view_frame.h"
#include "ags/engine/ac/dynobj/script_object.h"
#include "ags/engine/ac/dynobj/script_system.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/engine/debugging/debug_log.h"
#include "ags/engine/debugging/debugger.h"
#include "ags/shared/debugging/out.h"
#include "ags/engine/device/mouse_w32.h"
#include "ags/shared/font/ags_font_renderer.h"
#include "ags/shared/font/fonts.h"
#include "ags/shared/gfx/image.h"
#include "ags/engine/gfx/graphics_driver.h"
#include "ags/engine/gfx/gfx_driver_factory.h"
#include "ags/engine/gfx/ddb.h"
#include "ags/engine/main/config.h"
#include "ags/engine/main/game_file.h"
#include "ags/engine/main/game_start.h"
#include "ags/engine/main/engine.h"
#include "ags/engine/main/engine_setup.h"
#include "ags/engine/main/graphics_mode.h"
#include "ags/engine/main/main.h"
#include "ags/engine/platform/base/sys_main.h"
#include "ags/engine/platform/base/ags_platform_driver.h"
#include "ags/shared/util/directory.h"
#include "ags/shared/util/error.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_utils.h"
#include "ags/ags.h"
#include "ags/globals.h"
namespace AGS3 {
using namespace AGS::Shared;
using namespace AGS::Engine;
bool engine_init_backend() {
set_our_eip(-199);
_G(platform)->PreBackendInit();
// Initialize SDL
Debug::Printf(kDbgMsg_Info, "Initializing backend libs");
if (sys_main_init()) {
const char *user_hint = _G(platform)->GetBackendFailUserHint();
_G(platform)->DisplayAlert("Unable to initialize SDL library.\n\n%s",
user_hint);
return false;
}
// Initialize stripped allegro library
if (install_allegro()) {
_G(platform)->DisplayAlert("Internal error: unable to initialize stripped Allegro 4 library.");
return false;
}
_G(platform)->PostBackendInit();
return true;
}
void winclosehook() {
_G(want_exit) = true;
_G(abort_engine) = true;
_G(check_dynamic_sprites_at_exit) = 0;
AbortGame();
}
void engine_setup_window() {
Debug::Printf(kDbgMsg_Info, "Setting up window");
set_our_eip(-198);
sys_window_set_title(_GP(game).gamename.GetCStr());
sys_window_set_icon();
sys_evt_set_quit_callback(winclosehook);
set_our_eip(-197);
}
// Fills map with game settings, to e.g. let setup application(s)
// display correct properties to the user
static void fill_game_properties(StringOrderMap &map) {
map["title"] = _GP(game).gamename;
map["guid"] = _GP(game).guid;
map["legacy_uniqueid"] = StrUtil::IntToString(_GP(game).uniqueid);
map["legacy_resolution"] = StrUtil::IntToString(_GP(game).GetResolutionType());
map["legacy_letterbox"] = StrUtil::IntToString(_GP(game).options[OPT_LETTERBOX]);
map["resolution_width"] = StrUtil::IntToString(_GP(game).GetDefaultRes().Width);
map["resolution_height"] = StrUtil::IntToString(_GP(game).GetDefaultRes().Height);
map["resolution_bpp"] = StrUtil::IntToString(_GP(game).GetColorDepth());
map["render_at_screenres"] = StrUtil::IntToString(
_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined ? -1 :
(_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled ? 1 : 0));
}
// Starts up setup application, if capable.
// Returns TRUE if should continue running the game, otherwise FALSE.
bool engine_run_setup(const ConfigTree &cfg) {
#if AGS_PLATFORM_OS_WINDOWS
{
Debug::Printf(kDbgMsg_Info, "Running Setup");
ConfigTree cfg_with_meta = cfg;
fill_game_properties(cfg_with_meta["gameproperties"]);
ConfigTree cfg_out;
SetupReturnValue res = _G(platform)->RunSetup(cfg_with_meta, cfg_out);
if (res != kSetup_Cancel) {
String cfg_file = PreparePathForWriting(GetGameUserConfigDir(), DefaultConfigFileName);
if (cfg_file.IsEmpty()) {
_G(platform)->DisplayAlert("Unable to write into directory '%s'.\n%s",
GetGameUserConfigDir().FullDir.GetCStr(), _G(platform)->GetDiskWriteAccessTroubleshootingText());
} else if (!IniUtil::Merge(cfg_file, cfg_out)) {
_G(platform)->DisplayAlert("Unable to write to the configuration file (error code 0x%08X).\n%s",
_G(platform)->GetLastSystemError(), _G(platform)->GetDiskWriteAccessTroubleshootingText());
}
}
if (res != kSetup_RunGame)
return false;
// Start the game in the new process, and close the current one afterwards
String args = String::FromFormat("\"%s\"", appPath.GetCStr());
_spawnl(_P_NOWAIT, appPath.GetCStr(), args.GetCStr(), NULL);
return false;
}
#endif
return true;
}
// Scans given directory for the AGS game config. If such config exists
// and it contains data file name, then returns one.
// Otherwise returns empty string.
static String find_game_data_in_config(const String &path) {
// First look for config
ConfigTree cfg;
String def_cfg_file = Path::ConcatPaths(path, DefaultConfigFileName);
if (IniUtil::Read(def_cfg_file, cfg)) {
String data_file = CfgReadString(cfg, "misc", "datafile");
Debug::Printf("Found game config: %s", def_cfg_file.GetCStr());
Debug::Printf(" Cfg: data file: %s", data_file.GetCStr());
// Only accept if it's a relative path
if (!data_file.IsEmpty() && Path::IsRelativePath(data_file))
return Path::ConcatPaths(path, data_file);
}
return ""; // not found in config
}
// Scans for game data in several common locations.
// When it does so, it first looks for game config file, which contains
// explicit directions to game data in its settings.
// If such config is not found, it scans same location for *any* game data instead.
String search_for_game_data_file(String &was_searching_in) {
Debug::Printf("Looking for the game data.\n Cwd: %s\n Path arg: %s",
Directory::GetCurrentDirectory().GetCStr(),
_G(cmdGameDataPath).GetCStr());
// 1. From command line argument, which may be a directory or actual file
if (!_G(cmdGameDataPath).IsEmpty()) {
if (File::IsFile(_G(cmdGameDataPath)))
return _G(cmdGameDataPath); // this path is a file
if (!File::IsDirectory(_G(cmdGameDataPath)))
return ""; // path is neither file nor directory
was_searching_in = _G(cmdGameDataPath);
Debug::Printf("Searching in (cmd arg): %s", was_searching_in.GetCStr());
// first scan for config
String data_path = find_game_data_in_config(_G(cmdGameDataPath));
if (!data_path.IsEmpty())
return data_path;
// if not found in config, lookup for data in same dir
return FindGameData(_G(cmdGameDataPath));
}
// 2. Look in other known locations
// 2.1. Look for attachment in the running executable
if (!_G(appPath).IsEmpty() && Shared::AssetManager::IsDataFile(_G(appPath))) {
Debug::Printf("Found game data embedded in executable");
was_searching_in = Path::GetDirectoryPath(_G(appPath));
return _G(appPath);
}
// 2.2 Look in current working directory
String cur_dir = Directory::GetCurrentDirectory();
was_searching_in = cur_dir;
Debug::Printf("Searching in (cwd): %s", was_searching_in.GetCStr());
// first scan for config
String data_path = find_game_data_in_config(cur_dir);
if (!data_path.IsEmpty())
return data_path;
// if not found in config, lookup for data in same dir
data_path = FindGameData(cur_dir);
if (!data_path.IsEmpty())
return data_path;
// 2.3 Look in executable's directory (if it's different from current dir)
if (Path::ComparePaths(_G(appDirectory), cur_dir) == 0)
return ""; // no luck
was_searching_in = _G(appDirectory);
Debug::Printf("Searching in (exe dir): %s", was_searching_in.GetCStr());
// first scan for config
data_path = find_game_data_in_config(_G(appDirectory));
if (!data_path.IsEmpty())
return data_path;
// if not found in config, lookup for data in same dir
return FindGameData(_G(appDirectory));
}
void engine_init_fonts() {
Debug::Printf(kDbgMsg_Info, "Initializing TTF renderer");
init_font_renderer();
}
void engine_init_mouse() {
int res = _GP(mouse).GetButtonCount();
if (res < 0)
Debug::Printf(kDbgMsg_Info, "Initializing mouse: failed");
else
Debug::Printf(kDbgMsg_Info, "Initializing mouse: number of buttons reported is %d", res);
_GP(mouse).SetSpeed(_GP(usetup).mouse_speed);
}
void engine_locate_speech_pak() {
init_voicepak("");
}
void engine_locate_audio_pak() {
String music_file = _GP(game).GetAudioVOXName();
String music_filepath = find_assetlib(music_file);
if (!music_filepath.IsEmpty()) {
if (_GP(AssetMgr)->AddLibrary(music_filepath) == kAssetNoError) {
Debug::Printf(kDbgMsg_Info, "%s found and initialized.", music_file.GetCStr());
_GP(ResPaths).AudioPak.Name = music_file;
_GP(ResPaths).AudioPak.Path = music_filepath;
} else {
_G(platform)->DisplayAlert("Unable to initialize digital audio pack '%s', file could be corrupt or of unsupported format.",
music_file.GetCStr());
}
} else if (!_GP(ResPaths).AudioDir2.IsEmpty() &&
Path::ComparePaths(_GP(ResPaths).DataDir, _GP(ResPaths).AudioDir2) != 0) {
Debug::Printf(kDbgMsg_Info, "Audio pack was not found, but explicit audio directory is defined.");
}
}
// Assign asset locations to the AssetManager
void engine_assign_assetpaths() {
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).GamePak.Path, ",audio"); // main pack may have audio bundled too
// The asset filters are currently a workaround for limiting search to certain locations;
// this is both an optimization and to prevent unexpected behavior.
// - empty filter is for regular files
// audio - audio clips
// voice - voice-over clips
// NOTE: we add extra optional directories first because they should have higher priority
// TODO: maybe change AssetManager library order to stack-like later (last added = top priority)?
if (!_GP(ResPaths).DataDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).DataDir2, _GP(ResPaths).DataDir) != 0)
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).DataDir2, ",audio,voice"); // dir may have anything
if (!_GP(ResPaths).AudioDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).AudioDir2, _GP(ResPaths).DataDir) != 0)
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).AudioDir2, "audio");
if (!_GP(ResPaths).VoiceDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).VoiceDir2, _GP(ResPaths).DataDir) != 0)
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).VoiceDir2, "voice");
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).DataDir, ",audio,voice"); // dir may have anything
if (!_GP(ResPaths).AudioPak.Path.IsEmpty())
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).AudioPak.Path, "audio");
if (!_GP(ResPaths).SpeechPak.Path.IsEmpty())
_GP(AssetMgr)->AddLibrary(_GP(ResPaths).SpeechPak.Path, "voice");
}
void engine_init_keyboard() {
/* do nothing */
}
void engine_init_audio() {
#if !AGS_PLATFORM_SCUMMVM
if (usetup.audio_backend != 0) {
Debug::Printf("Initializing audio");
try {
audio_core_init(); // audio core system
} catch (std::runtime_error ex) {
Debug::Printf(kDbgMsg_Error, "Failed to initialize audio: %s", ex.what());
usetup.audio_backend = 0;
}
}
#endif
if (!_GP(usetup).audio_enabled) {
// all audio is disabled
Debug::Printf(kDbgMsg_Info, "Audio is disabled");
}
}
void engine_init_debug() {
if (_GP(usetup).show_fps)
_G(display_fps) = kFPS_Forced;
if ((_G(debug_flags) & (~DBG_DEBUGMODE)) > 0) {
_G(platform)->DisplayAlert("Engine debugging enabled.\n"
"\nNOTE: You have selected to enable one or more engine debugging options.\n"
"These options cause many parts of the game to behave abnormally, and you\n"
"may not see the game as you are used to it. The point is to test whether\n"
"the engine passes a point where it is crashing on you normally.\n"
"[Debug flags enabled: 0x%02X]", _G(debug_flags));
}
}
void engine_init_pathfinder() {
init_pathfinder(_G(loaded_game_file_version));
}
void engine_pre_init_gfx() {
//Debug::Printf("Initialize gfx");
//_G(platform)->InitialiseAbufAtStartup();
}
int engine_load_game_data() {
Debug::Printf("Load game data");
set_our_eip(-17);
HError err = load_game_file();
if (!err) {
_G(proper_exit) = 1;
display_game_file_error(err);
return EXIT_ERROR;
}
return 0;
}
// Replace special tokens inside a user path option
static void resolve_configured_path(String &option) {
option.Replace(Shared::String("$GAMENAME$"), _GP(game).gamename);
}
// Setup paths and directories that may be affected by user configuration
void engine_init_user_directories() {
resolve_configured_path(_GP(usetup).user_data_dir);
resolve_configured_path(_GP(usetup).shared_data_dir);
if (!_GP(usetup).user_conf_dir.IsEmpty())
Debug::Printf(kDbgMsg_Info, "User config directory: %s", _GP(usetup).user_conf_dir.GetCStr());
if (!_GP(usetup).user_data_dir.IsEmpty())
Debug::Printf(kDbgMsg_Info, "User data directory: %s", _GP(usetup).user_data_dir.GetCStr());
if (!_GP(usetup).shared_data_dir.IsEmpty())
Debug::Printf(kDbgMsg_Info, "Shared data directory: %s", _GP(usetup).shared_data_dir.GetCStr());
// Initialize default save directory early, for we'll need it to set restart point
SetDefaultSaveDirectory();
}
#if AGS_PLATFORM_OS_ANDROID
extern char android_base_directory[256];
#endif // AGS_PLATFORM_OS_ANDROID
// TODO: remake/remove this nonsense
int check_write_access() {
#if AGS_PLATFORM_SCUMMVM
return true;
#else
set_our_eip(-1895);
// The Save Game Dir is the only place that we should write to
String svg_dir = get_save_game_directory();
if (platform->GetDiskFreeSpaceMB(svg_dir) < 2)
return 0;
String tempPath = String::FromFormat("%s""tmptest.tmp", svg_dir.GetCStr());
Stream *temp_s = Shared::File::CreateFile(tempPath);
if (!temp_s)
// TODO: The fallback should be done on all platforms, and there's
// already similar procedure found in SetSaveGameDirectoryPath.
// If Android has extra dirs to fallback to, they should be provided
// by platform driver's method, not right here!
#if AGS_PLATFORM_OS_ANDROID
{
put_backslash(android_base_directory);
tempPath.Format("%s""tmptest.tmp", android_base_directory);
temp_s = Shared::File::CreateFile(tempPath);
if (temp_s == NULL) return 0;
else SetCustomSaveParent(android_base_directory);
}
#else
return 0;
#endif // AGS_PLATFORM_OS_ANDROID
set_our_eip(-1896);
temp_s->Write("just to test the drive free space", 30);
delete temp_s;
set_our_eip(-1897);
if (File::DeleteFile(tempPath))
return 0;
return 1;
#endif
}
int engine_check_disk_space() {
Debug::Printf(kDbgMsg_Info, "Checking for disk space");
if (check_write_access() == 0) {
_G(platform)->DisplayAlert("Unable to write in the savegame directory.\n%s", _G(platform)->GetDiskWriteAccessTroubleshootingText());
_G(proper_exit) = 1;
return EXIT_ERROR;
}
return 0;
}
int engine_check_font_was_loaded() {
if (!font_first_renderer_loaded()) {
_G(platform)->DisplayAlert("No game fonts found. At least one font is required to run the _GP(game).");
_G(proper_exit) = 1;
return EXIT_ERROR;
}
return 0;
}
// Do the preload graphic if available
void show_preload() {
RGB temppal[256];
Bitmap *splashsc = BitmapHelper::CreateRawBitmapOwner(load_pcx("preload.pcx", temppal));
if (splashsc != nullptr) {
Debug::Printf("Displaying preload image");
if (splashsc->GetColorDepth() == 8)
set_palette_range(temppal, 0, 255, 0);
if (_G(gfxDriver)->UsesMemoryBackBuffer())
_G(gfxDriver)->GetMemoryBackBuffer()->Clear();
const Rect &view = _GP(play).GetMainViewport();
Bitmap *tsc = BitmapHelper::CreateBitmapCopy(splashsc, _GP(game).GetColorDepth());
if (!_G(gfxDriver)->HasAcceleratedTransform() && view.GetSize() != tsc->GetSize()) {
Bitmap *stretched = new Bitmap(view.GetWidth(), view.GetHeight(), tsc->GetColorDepth());
stretched->StretchBlt(tsc, RectWH(0, 0, view.GetWidth(), view.GetHeight()));
delete tsc;
tsc = stretched;
}
IDriverDependantBitmap *ddb = _G(gfxDriver)->CreateDDBFromBitmap(tsc, false, true);
ddb->SetStretch(view.GetWidth(), view.GetHeight());
_G(gfxDriver)->ClearDrawLists();
_G(gfxDriver)->BeginSpriteBatch(view);
_G(gfxDriver)->DrawSprite(0, 0, ddb);
_G(gfxDriver)->EndSpriteBatch();
render_to_screen();
_G(gfxDriver)->DestroyDDB(ddb);
delete splashsc;
delete tsc;
_G(platform)->Delay(500);
}
}
int engine_init_sprites() {
Debug::Printf(kDbgMsg_Info, "Initialize sprites");
HError err = _GP(spriteset).InitFile(SpriteFile::DefaultSpriteFileName, SpriteFile::DefaultSpriteIndexName);
if (!err) {
sys_main_shutdown();
allegro_exit();
_G(proper_exit) = 1;
_G(platform)->DisplayAlert("Could not load sprite set file %s\n%s",
SpriteFile::DefaultSpriteFileName,
err->FullMessage().GetCStr());
return EXIT_ERROR;
}
if (_GP(usetup).SpriteCacheSize > 0)
_GP(spriteset).SetMaxCacheSize(_GP(usetup).SpriteCacheSize * 1024);
Debug::Printf("Sprite cache set: %zu KB", _GP(spriteset).GetMaxCacheSize() / 1024);
return 0;
}
// TODO: this should not be a part of "engine_" function group,
// move this elsewhere (InitGameState?).
void engine_init_game_settings() {
set_our_eip(-7);
Debug::Printf("Initialize game settings");
// Initialize randomizer
_GP(play).randseed = g_system->getMillis();
::AGS::g_vm->setRandomNumberSeed(_GP(play).randseed);
if (_GP(usetup).audio_enabled) {
_GP(play).separate_music_lib = !_GP(ResPaths).AudioPak.Name.IsEmpty();
_GP(play).voice_avail = _GP(ResPaths).VoiceAvail;
} else {
_GP(play).voice_avail = false;
_GP(play).separate_music_lib = false;
}
// Setup a text encoding mode depending on the game data hint
if (_GP(game).options[OPT_GAMETEXTENCODING] == 65001) // utf-8 codepage number
set_uformat(U_UTF8);
else
set_uformat(U_ASCII);
int ee;
for (ee = 0; ee < 256; ee++) {
if (_GP(game).paluses[ee] != PAL_BACKGROUND)
_G(palette)[ee] = _GP(game).defpal[ee];
}
for (ee = 0; ee < _GP(game).numcursors; ee++) {
// The cursor graphics are assigned to mousecurs[] and so cannot
// be removed from memory
if (_GP(game).mcurs[ee].pic >= 0)
_GP(spriteset).PrecacheSprite(_GP(game).mcurs[ee].pic);
// just in case they typed an invalid view number in the editor
if (_GP(game).mcurs[ee].view >= _GP(game).numviews)
_GP(game).mcurs[ee].view = -1;
if (_GP(game).mcurs[ee].view >= 0)
precache_view(_GP(game).mcurs[ee].view);
}
// may as well preload the character gfx
if (_G(playerchar)->view >= 0)
precache_view(_G(playerchar)->view, 0, Character_GetDiagonalWalking(_G(playerchar)) ? 8 : 4);
set_our_eip(-6);
for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++) {
_G(scrObj)[ee].id = ee;
}
for (ee = 0; ee < _GP(game).numcharacters; ee++) {
memset(&_GP(game).chars[ee].inv[0], 0, MAX_INV * sizeof(short));
_GP(game).chars[ee].activeinv = -1;
_GP(game).chars[ee].following = -1;
_GP(game).chars[ee].followinfo = 97 | (10 << 8);
if (_G(loaded_game_file_version) < kGameVersion_360)
_GP(game).chars[ee].idletime = 20; // default to 20 seconds _GP(game).chars[ee].idleleft = _GP(game).chars[ee].idletime;
_GP(game).chars[ee].transparency = 0;
_GP(game).chars[ee].baseline = -1;
_GP(game).chars[ee].walkwaitcounter = 0;
_GP(game).chars[ee].z = 0;
_GP(charextra)[ee].xwas = INVALID_X;
_GP(charextra)[ee].zoom = 100;
if (_GP(game).chars[ee].view >= 0) {
// set initial loop to 0
_GP(game).chars[ee].loop = 0;
// or to 1 if they don't have up/down frames
if (_GP(views)[_GP(game).chars[ee].view].loops[0].numFrames < 1)
_GP(game).chars[ee].loop = 1;
}
_GP(charextra)[ee].process_idle_this_time = 0;
_GP(charextra)[ee].invorder_count = 0;
_GP(charextra)[ee].slow_move_counter = 0;
_GP(charextra)[ee].animwait = 0;
}
set_our_eip(-5);
for (ee = 0; ee < _GP(game).numinvitems; ee++) {
if (_GP(game).invinfo[ee].flags & IFLG_STARTWITH) _G(playerchar)->inv[ee] = 1;
else _G(playerchar)->inv[ee] = 0;
}
//
// TODO: following big initialization sequence could be in GameState ctor
_GP(play).score = 0;
_GP(play).sierra_inv_color = 7;
// copy the value set by the editor
if (_GP(game).options[OPT_GLOBALTALKANIMSPD] >= 0) {
_GP(play).talkanim_speed = _GP(game).options[OPT_GLOBALTALKANIMSPD];
_GP(game).options[OPT_GLOBALTALKANIMSPD] = 1;
} else {
_GP(play).talkanim_speed = -_GP(game).options[OPT_GLOBALTALKANIMSPD] - 1;
_GP(game).options[OPT_GLOBALTALKANIMSPD] = 0;
}
_GP(play).inv_item_wid = 40;
_GP(play).inv_item_hit = 22;
_GP(play).messagetime = -1;
_GP(play).disabled_user_interface = 0;
_GP(play).gscript_timer = -1;
_GP(play).debug_mode = _GP(game).options[OPT_DEBUGMODE];
_GP(play).inv_top = 0;
_GP(play).inv_numdisp = 0;
_GP(play).inv_numorder = 0;
_GP(play).text_speed = 15;
_GP(play).text_min_display_time_ms = 1000;
_GP(play).ignore_user_input_after_text_timeout_ms = 500;
_GP(play).ClearIgnoreInput();
_GP(play).lipsync_speed = 15;
_GP(play).close_mouth_speech_time = 10;
_GP(play).disable_antialiasing = 0;
_GP(play).rtint_enabled = false;
_GP(play).rtint_level = 0;
_GP(play).rtint_light = 0;
_GP(play).text_speed_modifier = 0;
_GP(play).text_align = kHAlignLeft;
// Make the default alignment to the right with right-to-left text
if (_GP(game).options[OPT_RIGHTLEFTWRITE])
_GP(play).text_align = kHAlignRight;
_GP(play).speech_bubble_width = get_fixed_pixel_size(100);
_GP(play).bg_frame = 0;
_GP(play).bg_frame_locked = 0;
_GP(play).bg_anim_delay = 0;
_GP(play).anim_background_speed = 0;
_GP(play).mouse_cursor_hidden = 0;
_GP(play).silent_midi = 0;
_GP(play).current_music_repeating = 0;
_GP(play).skip_until_char_stops = -1;
_GP(play).get_loc_name_last_time = -1;
_GP(play).get_loc_name_save_cursor = -1;
_GP(play).restore_cursor_mode_to = -1;
_GP(play).restore_cursor_image_to = -1;
_GP(play).ground_level_areas_disabled = 0;
_GP(play).next_screen_transition = -1;
_GP(play).temporarily_turned_off_character = -1;
_GP(play).inv_backwards_compatibility = 0;
_GP(play).gamma_adjustment = 100;
_GP(play).music_queue_size = 0;
_GP(play).shakesc_length = 0;
_GP(play).wait_counter = 0;
_GP(play).SetWaitSkipResult(SKIP_NONE);
_GP(play).key_skip_wait = SKIP_NONE;
_GP(play).cur_music_number = -1;
_GP(play).music_repeat = 1;
_GP(play).music_master_volume = 100 + LegacyMusicMasterVolumeAdjustment;
_GP(play).digital_master_volume = 100;
_GP(play).screen_flipped = 0;
_GP(play).speech_mode = kSpeech_VoiceText;
_GP(play).speech_skip_style = user_to_internal_skip_speech((SkipSpeechStyle)_GP(game).options[OPT_NOSKIPTEXT]);
_GP(play).sound_volume = 255;
_GP(play).speech_volume = 255;
_GP(play).normal_font = 0;
_GP(play).speech_font = 1;
_GP(play).speech_text_shadow = 16;
_GP(play).screen_tint = -1;
_GP(play).bad_parsed_word[0] = 0;
_GP(play).swap_portrait_side = 0;
_GP(play).swap_portrait_lastchar = -1;
_GP(play).swap_portrait_lastlastchar = -1;
_GP(play).in_conversation = 0;
_GP(play).skip_display = 3;
_GP(play).no_multiloop_repeat = 0;
_GP(play).in_cutscene = 0;
_GP(play).fast_forward = 0;
_GP(play).totalscore = _GP(game).totalscore;
_GP(play).roomscript_finished = 0;
_GP(play).no_textbg_when_voice = 0;
_GP(play).max_dialogoption_width = get_fixed_pixel_size(180);
_GP(play).no_hicolor_fadein = 0;
_GP(play).bgspeech_game_speed = 0;
_GP(play).bgspeech_stay_on_display = 0;
_GP(play).unfactor_speech_from_textlength = 0;
_GP(play).mp3_loop_before_end = 70;
_GP(play).speech_music_drop = 60;
_GP(play).room_changes = 0;
_GP(play).check_interaction_only = 0;
_GP(play).replay_hotkey_unused = -1; // StartRecording: not supported.
_GP(play).dialog_options_x = 0;
_GP(play).dialog_options_y = 0;
_GP(play).min_dialogoption_width = 0;
_GP(play).disable_dialog_parser = 0;
_GP(play).ambient_sounds_persist = 0;
_GP(play).screen_is_faded_out = 0;
_GP(play).player_on_region = 0;
_GP(play).top_bar_backcolor = 8;
_GP(play).top_bar_textcolor = 16;
_GP(play).top_bar_bordercolor = 8;
_GP(play).top_bar_borderwidth = 1;
_GP(play).top_bar_ypos = 25;
_GP(play).top_bar_font = -1;
_GP(play).screenshot_width = 160;
_GP(play).screenshot_height = 100;
_GP(play).speech_text_align = kHAlignCenter;
_GP(play).auto_use_walkto_points = 1;
_GP(play).inventory_greys_out = 0;
_GP(play).skip_speech_specific_key = 0;
_GP(play).abort_key = 324; // Alt+X
_GP(play).fade_to_red = 0;
_GP(play).fade_to_green = 0;
_GP(play).fade_to_blue = 0;
_GP(play).show_single_dialog_option = 0;
_GP(play).keep_screen_during_instant_transition = 0;
_GP(play).read_dialog_option_colour = -1;
_GP(play).stop_dialog_at_end = DIALOG_NONE;
_GP(play).speech_portrait_placement = 0;
_GP(play).speech_portrait_x = 0;
_GP(play).speech_portrait_y = 0;
_GP(play).speech_display_post_time_ms = 0;
_GP(play).dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT;
_GP(play).speech_has_voice = false;
_GP(play).speech_voice_blocking = false;
_GP(play).speech_in_post_state = false;
_GP(play).complete_overlay_on = 0;
_GP(play).text_overlay_on = 0;
_GP(play).narrator_speech = _GP(game).playercharacter;
_GP(play).crossfading_out_channel = 0;
_GP(play).speech_textwindow_gui = _GP(game).options[OPT_TWCUSTOM];
if (_GP(play).speech_textwindow_gui == 0)
_GP(play).speech_textwindow_gui = -1;
_GP(play).game_name = _GP(game).gamename;
_GP(play).lastParserEntry[0] = 0;
_GP(play).follow_change_room_timer = 150;
for (ee = 0; ee < MAX_ROOM_BGFRAMES; ee++)
_GP(play).raw_modified[ee] = 0;
_GP(play).game_speed_modifier = 0;
if (_G(debug_flags) & DBG_DEBUGMODE)
_GP(play).debug_mode = 1;
_GP(play).shake_screen_yoff = 0;
GUI::Options.DisabledStyle = static_cast<GuiDisableStyle>(_GP(game).options[OPT_DISABLEOFF]);
GUI::Options.ClipControls = _GP(game).options[OPT_CLIPGUICONTROLS] != 0;
// Force GUI metrics recalculation, accommodating for loaded fonts
GUI::MarkForFontUpdate(-1);
memset(&_GP(play).walkable_areas_on[0], 1, MAX_WALK_AREAS);
memset(&_GP(play).script_timers[0], 0, MAX_TIMERS * sizeof(int));
memset(&_GP(play).default_audio_type_volumes[0], -1, MAX_AUDIO_TYPES * sizeof(int));
if (!_GP(usetup).translation.IsEmpty())
Game_ChangeTranslation(_GP(usetup).translation.GetCStr());
update_invorder();
_G(displayed_room) = -10;
set_our_eip(-4);
_G(mousey) = 100; // stop icon bar popping up
// We use same variable to read config and be used at runtime for now,
// so update it here with regards to game design option
_GP(usetup).RenderAtScreenRes =
(_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined && _GP(usetup).RenderAtScreenRes) ||
_GP(game).options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled;
}
void engine_setup_scsystem_auxiliary() {
// ScriptSystem::aci_version is only 10 chars long
snprintf(_GP(scsystem).aci_version, sizeof(_GP(scsystem).aci_version), "%s", _G(EngineVersion).LongString.GetCStr());
if (_GP(usetup).override_script_os >= 0) {
_GP(scsystem).os = _GP(usetup).override_script_os;
} else {
_GP(scsystem).os = _G(platform)->GetSystemOSID();
}
}
void engine_prepare_to_start_game() {
Debug::Printf("Prepare to start game");
engine_setup_scsystem_auxiliary();
if (_GP(usetup).load_latest_save) {
#ifndef AGS_PLATFORM_SCUMMVM
int slot = GetLastSaveSlot();
if (slot >= 0)
loadSaveGameOnStartup = get_save_game_path(slot);
#endif
}
}
// Define location of the game data either using direct settings or searching
// for the available resource packs in common locations.
// Returns two paths:
// - startup_dir: this is where engine found game config and/or data;
// - data_path: full path of the main data pack;
// data_path's directory (may or not be eq to startup_dir) should be considered data directory,
// and this is where engine look for all game data.
HError define_gamedata_location_checkall(String &data_path, String &startup_dir) {
// First try if they provided a startup option
if (!_G(cmdGameDataPath).IsEmpty()) {
// If not a valid path - bail out
if (!File::IsFileOrDir(_G(cmdGameDataPath)))
return new Error(String::FromFormat("Provided game location is not a valid path.\n Cwd: %s\n Path: %s",
Directory::GetCurrentDirectory().GetCStr(),
_G(cmdGameDataPath).GetCStr()));
// If it's a file, then keep it and proceed
if (File::IsFile(_G(cmdGameDataPath))) {
Debug::Printf("Using provided game data path: %s", _G(cmdGameDataPath).GetCStr());
startup_dir = Path::GetDirectoryPath(_G(cmdGameDataPath));
data_path = _G(cmdGameDataPath);
return HError::None();
}
}
#if AGS_SEARCH_FOR_GAME_ON_LAUNCH
// No direct filepath provided, search in common locations.
data_path = search_for_game_data_file(startup_dir);
if (data_path.IsEmpty()) {
return new Error("Engine was not able to find any compatible game data.",
startup_dir.IsEmpty() ? String() : String::FromFormat("Searched in: %s", startup_dir.GetCStr()));
}
data_path = Path::MakeAbsolutePath(data_path);
Debug::Printf(kDbgMsg_Info, "Located game data pak: %s", data_path.GetCStr());
return HError::None();
#else
// No direct filepath provided, bail out.
return new Error("The game location was not defined by startup settings.");
#endif
}
// Define location of the game data
bool define_gamedata_location() {
String data_path, startup_dir;
HError err = define_gamedata_location_checkall(data_path, startup_dir);
if (!err) {
_G(platform)->DisplayAlert("ERROR: Unable to determine game data.\n%s", err->FullMessage().GetCStr());
main_print_help();
return false;
}
// On success: set all the necessary path and filename settings
_GP(usetup).startup_dir = startup_dir;
_GP(usetup).main_data_file = data_path;
_GP(usetup).main_data_dir = Path::GetDirectoryPath(data_path);
return true;
}
// Find and preload main game data
bool engine_init_gamedata() {
Debug::Printf(kDbgMsg_Info, "Initializing game data");
// First, find data location
if (!define_gamedata_location())
return false;
// Try init game lib
AssetError asset_err = _GP(AssetMgr)->AddLibrary(_GP(usetup).main_data_file);
if (asset_err != kAssetNoError) {
_G(platform)->DisplayAlert("ERROR: The game data is missing, is of unsupported format or corrupt.\nFile: '%s'", _GP(usetup).main_data_file.GetCStr());
return false;
}
// Pre-load game name and savegame folder names from data file
// TODO: research if that is possible to avoid this step and just
// read the full head game data at this point. This might require
// further changes of the order of initialization.
HError err = preload_game_data();
if (!err) {
display_game_file_error(err);
return false;
}
// Setup _GP(ResPaths), so that we know out main locations further
_GP(ResPaths).GamePak.Path = _GP(usetup).main_data_file;
_GP(ResPaths).GamePak.Name = Path::GetFilename(_GP(usetup).main_data_file);
_GP(ResPaths).DataDir = _GP(usetup).install_dir.IsEmpty() ? _GP(usetup).main_data_dir : Path::MakeAbsolutePath(_GP(usetup).install_dir);
_GP(ResPaths).DataDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_data_dir);
_GP(ResPaths).AudioDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_audio_dir);
_GP(ResPaths).VoiceDir2 = Path::MakeAbsolutePath(_GP(usetup).opt_voice_dir);
Debug::Printf(kDbgMsg_Info, "Startup directory: %s", _GP(usetup).startup_dir.GetCStr());
Debug::Printf(kDbgMsg_Info, "Data directory: %s", _GP(ResPaths).DataDir.GetCStr());
if (!_GP(ResPaths).DataDir2.IsEmpty())
Debug::Printf(kDbgMsg_Info, "Opt data directory: %s", _GP(ResPaths).DataDir2.GetCStr());
if (!_GP(ResPaths).AudioDir2.IsEmpty())
Debug::Printf(kDbgMsg_Info, "Opt audio directory: %s", _GP(ResPaths).AudioDir2.GetCStr());
if (!_GP(ResPaths).VoiceDir2.IsEmpty())
Debug::Printf(kDbgMsg_Info, "Opt voice-over directory: %s", _GP(ResPaths).VoiceDir2.GetCStr());
return true;
}
void engine_read_config(ConfigTree &cfg) {
if (!_GP(usetup).conf_path.IsEmpty()) {
IniUtil::Read(_GP(usetup).conf_path, cfg);
return;
}
// Read default configuration file
String def_cfg_file = find_default_cfg_file();
IniUtil::Read(def_cfg_file, cfg);
// Disabled on Windows because people were afraid that this config could be mistakenly
// created by some installer and screw up their games. Until any kind of solution is found.
String user_global_cfg_file;
// Read user global configuration file
user_global_cfg_file = find_user_global_cfg_file();
if (Path::ComparePaths(user_global_cfg_file, def_cfg_file) != 0)
IniUtil::Read(user_global_cfg_file, cfg);
// Handle directive to search for the user config inside the custom directory;
// this option may come either from command line or default/global config.
if (_GP(usetup).user_conf_dir.IsEmpty())
_GP(usetup).user_conf_dir = CfgReadString(cfg, "misc", "user_conf_dir");
if (_GP(usetup).user_conf_dir.IsEmpty()) // also try deprecated option
_GP(usetup).user_conf_dir = CfgReadBoolInt(cfg, "misc", "localuserconf") ? "." : "";
// Test if the file is writeable, if it is then both engine and setup
// applications may actually use it fully as a user config, otherwise
// fallback to default behavior.
if (!_GP(usetup).user_conf_dir.IsEmpty()) {
resolve_configured_path(_GP(usetup).user_conf_dir);
if (Path::IsRelativePath(_GP(usetup).user_conf_dir))
_GP(usetup).user_conf_dir = Path::ConcatPaths(_GP(usetup).startup_dir, _GP(usetup).user_conf_dir);
if (!Directory::CreateDirectory(_GP(usetup).user_conf_dir) ||
!File::TestWriteFile(Path::ConcatPaths(_GP(usetup).user_conf_dir, DefaultConfigFileName))) {
Debug::Printf(kDbgMsg_Warn, "Write test failed at user config dir '%s', using default path.",
_GP(usetup).user_conf_dir.GetCStr());
_GP(usetup).user_conf_dir = "";
}
}
// Handle directive to search for the user config inside the game directory;
// this option may come either from command line or default/global config.
_GP(usetup).local_user_conf |= CfgReadInt(cfg, "misc", "localuserconf", 0) != 0;
if (_GP(usetup).local_user_conf) { // Test if the file is writeable, if it is then both engine and setup
// applications may actually use it fully as a user config, otherwise
// fallback to default behavior.
_GP(usetup).local_user_conf = File::TestWriteFile(def_cfg_file);
}
// Read user configuration file
String user_cfg_file = find_user_cfg_file();
if (Path::ComparePaths(user_cfg_file, def_cfg_file) != 0 &&
Path::ComparePaths(user_cfg_file, user_global_cfg_file) != 0)
IniUtil::Read(user_cfg_file, cfg);
// Apply overriding options from platform settings
// TODO: normally, those should be instead stored in the same config file in a uniform way
override_config_ext(cfg);
}
// Gathers settings from all available sources into single ConfigTree
void engine_prepare_config(ConfigTree &cfg, const ConfigTree &startup_opts) {
Debug::Printf(kDbgMsg_Info, "Setting up game configuration");
// Read configuration files
engine_read_config(cfg);
// Merge startup options in
for (const auto &sectn : startup_opts)
for (const auto &opt : sectn._value)
cfg[sectn._key][opt._key] = opt._value;
}
// Applies configuration to the running game
void engine_set_config(const ConfigTree cfg) {
config_defaults();
apply_config(cfg);
post_config();
}
static bool print_info_needs_game(const std::set<String> &keys) {
return keys.count("all") > 0 || keys.count("config") > 0 || keys.count("configpath") > 0 ||
keys.count("data") > 0 || keys.count("filepath") > 0 || keys.count("gameproperties") > 0;
}
static void engine_print_info(const std::set<String> &keys, ConfigTree *user_cfg) {
const bool all = keys.count("all") > 0;
ConfigTree data;
if (all || keys.count("engine") > 0) {
data["engine"]["name"] = get_engine_name();
data["engine"]["version"] = get_engine_version();
}
if (all || keys.count("graphicdriver") > 0) {
StringV drv;
AGS::Engine::GetGfxDriverFactoryNames(drv);
for (size_t i = 0; i < drv.size(); ++i) {
data["graphicdriver"][String::FromFormat("%zu", i)] = drv[i];
}
}
if (all || keys.count("configpath") > 0) {
String def_cfg_file = find_default_cfg_file();
String gl_cfg_file = find_user_global_cfg_file();
String user_cfg_file = find_user_cfg_file();
data["configpath"]["default"] = def_cfg_file;
data["configpath"]["global"] = gl_cfg_file;
data["configpath"]["user"] = user_cfg_file;
}
if ((all || keys.count("config") > 0) && user_cfg) {
for (const auto &sectn : *user_cfg) {
String cfg_sectn = String::FromFormat("config@%s", sectn._key.GetCStr());
for (const auto &opt : sectn._value)
data[cfg_sectn][opt._key] = opt._value;
}
}
if (all || keys.count("data") > 0) {
data["data"]["gamename"] = _GP(game).gamename;
data["data"]["version"] = StrUtil::IntToString(_G(loaded_game_file_version));
data["data"]["compiledwith"] = _GP(game).compiled_with;
data["data"]["basepack"] = _GP(ResPaths).GamePak.Path;
}
if (all || keys.count("gameproperties") > 0) {
fill_game_properties(data["gameproperties"]);
}
if (all || keys.count("filepath") > 0) {
data["filepath"]["exe"] = _G(appPath);
data["filepath"]["cwd"] = Directory::GetCurrentDirectory();
data["filepath"]["datadir"] = Path::MakePathNoSlash(_GP(ResPaths).DataDir);
if (!_GP(ResPaths).DataDir2.IsEmpty()) {
data["filepath"]["datadir2"] = Path::MakePathNoSlash(_GP(ResPaths).DataDir2);
data["filepath"]["audiodir2"] = Path::MakePathNoSlash(_GP(ResPaths).AudioDir2);
data["filepath"]["voicedir2"] = Path::MakePathNoSlash(_GP(ResPaths).VoiceDir2);
}
data["filepath"]["savegamedir"] = Path::MakePathNoSlash(GetGameUserDataDir().FullDir);
data["filepath"]["appdatadir"] = Path::MakePathNoSlash(GetGameAppDataDir().FullDir);
}
String full;
IniUtil::WriteToString(full, data);
_G(platform)->WriteStdOut("%s", full.GetCStr());
}
// TODO: this function is still a big mess, engine/system-related initialization
// is mixed with game-related data adjustments. Divide it in parts, move game
// data init into either InitGameState() or other game method as appropriate.
int initialize_engine(const ConfigTree &startup_opts) {
_G(proper_exit) = false;
if (_G(engine_pre_init_callback)) {
_G(engine_pre_init_callback)();
}
//-----------------------------------------------------
// Install backend
if (!engine_init_backend())
return EXIT_ERROR;
//-----------------------------------------------------
// Locate game data and assemble game config
if (_G(justTellInfo) && !print_info_needs_game(_G(tellInfoKeys))) {
engine_print_info(_G(tellInfoKeys), nullptr);
return EXIT_NORMAL;
}
if (!engine_init_gamedata())
return EXIT_ERROR;
ConfigTree cfg;
engine_prepare_config(cfg, startup_opts);
// Test if need to run built-in setup program (where available)
if (!_G(justTellInfo) && _G(justRunSetup)) {
if (!engine_run_setup(cfg))
return EXIT_NORMAL;
}
// Set up game options from user config
engine_set_config(cfg);
if (_G(justTellInfo)) {
engine_print_info(_G(tellInfoKeys), &cfg);
return EXIT_NORMAL;
}
set_our_eip(-190);
//-----------------------------------------------------
// Init auxiliary data files and other directories, initialize asset manager
engine_init_user_directories();
set_our_eip(-191);
engine_locate_speech_pak();
set_our_eip(-192);
engine_locate_audio_pak();
set_our_eip(-193);
engine_assign_assetpaths();
//-----------------------------------------------------
// Begin setting up systems
set_our_eip(-194);
engine_init_fonts();
set_our_eip(-195);
engine_init_keyboard();
set_our_eip(-196);
engine_init_mouse();
set_our_eip(-198);
engine_init_audio();
set_our_eip(-199);
engine_init_debug();
set_our_eip(-10);
engine_init_pathfinder();
set_game_speed(40);
set_our_eip(-20);
set_our_eip(-19);
int res = engine_load_game_data();
if (res != 0)
return res;
set_our_eip(-189);
res = engine_check_disk_space();
if (res != 0)
return res;
// Make sure that at least one font was loaded in the process of loading
// the game data.
// TODO: Fold this check into engine_load_game_data()
res = engine_check_font_was_loaded();
if (res != 0)
return res;
set_our_eip(-179);
engine_adjust_for_rotation_settings();
// Attempt to initialize graphics mode
if (!engine_try_set_gfxmode_any(_GP(usetup).Screen))
return EXIT_ERROR;
// Configure game window after renderer was initialized
engine_setup_window();
SetMultitasking(_GP(usetup).multitasking);
sys_window_show_cursor(false); // hide the system cursor
show_preload();
res = engine_init_sprites();
if (res != 0)
return res;
engine_init_game_settings();
engine_prepare_to_start_game();
initialize_start_and_play_game(_G(override_start_room), _G(loadSaveGameOnStartup));
return EXIT_NORMAL;
}
bool engine_try_set_gfxmode_any(const DisplayModeSetup &setup) {
const DisplayMode old_dm = _G(gfxDriver) ? _G(gfxDriver)->GetDisplayMode() : DisplayMode();
engine_shutdown_gfxmode();
sys_renderer_set_output(_GP(usetup).software_render_driver);
const Size init_desktop = get_desktop_size();
bool res = graphics_mode_init_any(GraphicResolution(_GP(game).GetGameRes(), _GP(game).color_depth * 8),
setup, ColorDepthOption(_GP(game).GetColorDepth()));
if (res)
engine_post_gfxmode_setup(init_desktop, old_dm);
// Make sure that we don't receive window events queued during init
sys_flush_events();
return res;
}
bool engine_try_switch_windowed_gfxmode() {
if (!_G(gfxDriver) || !_G(gfxDriver)->IsModeSet())
return false;
// Keep previous mode in case we need to revert back
DisplayMode old_dm = _G(gfxDriver)->GetDisplayMode();
FrameScaleDef old_frame = graphics_mode_get_render_frame();
// Release engine resources that depend on display mode
engine_pre_gfxmode_release();
Size init_desktop = get_desktop_size();
bool windowed = !old_dm.IsWindowed();
ActiveDisplaySetting setting = graphics_mode_get_last_setting(windowed);
DisplayMode last_opposite_mode = setting.Dm;
FrameScaleDef frame = setting.Frame;
// Apply vsync in case it has been toggled at runtime
last_opposite_mode.Vsync = _GP(usetup).Screen.Params.VSync;
// If there are saved parameters for given mode (fullscreen/windowed),
// *and* if the window is on the same display where it's been last time,
// then use old params, otherwise - get default setup for the new mode.
bool res;
if (last_opposite_mode.IsValid() && (setting.DisplayIndex == sys_get_window_display_index())) {
res = graphics_mode_set_dm(last_opposite_mode);
} else {
WindowSetup ws = windowed ? _GP(usetup).Screen.WinSetup : _GP(usetup).Screen.FsSetup;
frame = windowed ? _GP(usetup).Screen.WinGameFrame : _GP(usetup).Screen.FsGameFrame;
res = graphics_mode_set_dm_any(_GP(game).GetGameRes(), ws, old_dm.ColorDepth,
frame, _GP(usetup).Screen.Params);
}
// Apply corresponding frame render method
if (res)
res = graphics_mode_set_render_frame(frame);
if (!res) {
// If failed, try switching back to previous gfx mode
res = graphics_mode_set_dm(old_dm) &&
graphics_mode_set_render_frame(old_frame);
if (!res)
quitprintf("Failed to restore graphics mode.");
}
// If succeeded (with any case), update engine objects that rely on
// active display mode.
if (!_G(gfxDriver)->GetDisplayMode().IsRealFullscreen())
init_desktop = get_desktop_size();
engine_post_gfxmode_setup(init_desktop, old_dm);
// Make sure that we don't receive window events queued during init
sys_flush_events();
return res;
}
void engine_on_window_changed(const Size &sz) {
graphics_mode_on_window_changed(sz);
on_coordinates_scaling_changed();
invalidate_screen();
}
void engine_shutdown_gfxmode() {
if (!_G(gfxDriver))
return;
engine_pre_gfxsystem_shutdown();
graphics_mode_shutdown();
}
const char *get_engine_name() {
return "Adventure Game Studio run-time engine";
}
const char *get_engine_version() {
return _G(EngineVersion).LongString.GetCStr();
}
String get_engine_version_and_build() {
const char *bit = (AGS_PLATFORM_64BIT) ? "64-bit" : "32-bit";
const char *end = (AGS_PLATFORM_ENDIAN_LITTLE) ? "LE" : "BE";
#ifdef BUILD_STR
return String::FromFormat("%s (Build: %s), %s %s",
_G(EngineVersion).LongString.GetCStr(), EngineVersion.BuildInfo.GetCStr(),
bit, end);
#else
return String::FromFormat("%s, %s %s",
_G(EngineVersion).LongString.GetCStr(),
bit, end);
#endif
}
void engine_set_pre_init_callback(t_engine_pre_init_callback callback) {
_G(engine_pre_init_callback) = callback;
}
} // namespace AGS3