1279 lines
45 KiB
C++
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 §n : 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 §n : *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
|