2024-08-14 18:36:42 +03:00
/*
* EntryPoint . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
// EntryPoint.cpp : Defines the entry point for the console application.
2024-08-27 03:59:09 +03:00
# include "StdInc.h"
# include "../Global.h"
# include "../client/ClientCommandManager.h"
# include "../client/CMT.h"
# include "../client/CPlayerInterface.h"
# include "../client/CServerHandler.h"
2025-02-10 21:49:23 +00:00
# include "../client/GameEngine.h"
2025-02-11 15:23:33 +00:00
# include "../client/GameInstance.h"
2024-08-27 03:59:09 +03:00
# include "../client/gui/CursorHandler.h"
# include "../client/gui/WindowHandler.h"
# include "../client/mainmenu/CMainMenu.h"
# include "../client/render/Graphics.h"
# include "../client/render/IRenderHandler.h"
# include "../client/windows/CMessage.h"
# include "../client/windows/InfoWindows.h"
2024-08-14 18:36:42 +03:00
2025-03-12 14:18:44 +00:00
# include "../lib/AsyncRunner.h"
2025-02-28 11:37:56 +00:00
# include "../lib/CConsoleHandler.h"
2025-03-03 21:45:58 +00:00
# include "../lib/CConfigHandler.h"
2024-08-14 18:36:42 +03:00
# include "../lib/CThreadHelper.h"
# include "../lib/ExceptionsCommon.h"
# include "../lib/filesystem/Filesystem.h"
# include "../lib/logging/CBasicLogConfigurator.h"
2025-01-01 20:59:16 +00:00
# include "../lib/modding/IdentifierStorage.h"
# include "../lib/modding/CModHandler.h"
# include "../lib/modding/ModDescription.h"
# include "../lib/texts/MetaString.h"
2025-02-14 16:23:37 +00:00
# include "../lib/GameLibrary.h"
2024-08-27 03:59:09 +03:00
# include "../lib/VCMIDirs.h"
2024-08-14 18:36:42 +03:00
# include <boost/program_options.hpp>
# include <vstd/StringUtils.h>
# include <SDL_main.h>
# include <SDL.h>
# ifdef VCMI_ANDROID
# include "../lib/CAndroidVMHelper.h"
# include <SDL_system.h>
# endif
# if __MINGW32__
# undef main
# endif
namespace po = boost : : program_options ;
namespace po_style = boost : : program_options : : command_line_style ;
2024-08-27 03:59:09 +03:00
static std : : atomic < bool > headlessQuit = false ;
static std : : optional < std : : string > criticalInitializationError ;
2024-08-14 18:36:42 +03:00
2025-01-01 20:59:16 +00:00
static void init ( )
2024-08-14 18:36:42 +03:00
{
try
{
2025-03-03 17:20:34 +00:00
CStopWatch tmh ;
LIBRARY - > initializeLibrary ( ) ;
logGlobal - > info ( " Initializing VCMI_Lib: %d ms " , tmh . getDiff ( ) ) ;
2024-08-14 18:36:42 +03:00
}
catch ( const DataLoadingException & e )
{
criticalInitializationError = e . what ( ) ;
return ;
}
// Debug code to load all maps on start
//ClientCommandManager commandController;
2025-01-19 12:40:41 +00:00
//commandController.processCommand("translate maps", false);
2024-08-14 18:36:42 +03:00
}
2025-01-01 20:59:16 +00:00
static void checkForModLoadingFailure ( )
{
2025-02-14 16:23:37 +00:00
const auto & brokenMods = LIBRARY - > identifiersHandler - > getModsWithFailedRequests ( ) ;
2025-01-01 20:59:16 +00:00
if ( ! brokenMods . empty ( ) )
{
MetaString messageText ;
messageText . appendTextID ( " vcmi.client.errors.modLoadingFailure " ) ;
for ( const auto & modID : brokenMods )
{
2025-02-14 16:23:37 +00:00
messageText . appendRawString ( LIBRARY - > modh - > getModInfo ( modID ) . getName ( ) ) ;
2025-01-01 20:59:16 +00:00
messageText . appendEOL ( ) ;
}
CInfoWindow : : showInfoDialog ( messageText . toString ( ) , { } ) ;
}
}
2024-08-14 18:36:42 +03:00
static void prog_version ( )
{
printf ( " %s \n " , GameConstants : : VCMI_VERSION . c_str ( ) ) ;
std : : cout < < VCMIDirs : : get ( ) . genHelpString ( ) ;
}
static void prog_help ( const po : : options_description & opts )
{
auto time = std : : time ( nullptr ) ;
printf ( " %s - A Heroes of Might and Magic 3 clone \n " , GameConstants : : VCMI_VERSION . c_str ( ) ) ;
printf ( " Copyright (C) 2007-%d VCMI dev team - see AUTHORS file \n " , std : : localtime ( & time ) - > tm_year + 1900 ) ;
printf ( " This is free software; see the source for copying conditions. There is NO \n " ) ;
printf ( " warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \n " ) ;
printf ( " \n " ) ;
std : : cout < < opts ;
}
# if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
int wmain ( int argc , wchar_t * argv [ ] )
# elif defined(VCMI_MOBILE)
int SDL_main ( int argc , char * argv [ ] )
# else
int main ( int argc , char * argv [ ] )
# endif
{
# ifdef VCMI_ANDROID
CAndroidVMHelper : : initClassloader ( SDL_AndroidGetJNIEnv ( ) ) ;
// boost will crash without this
setenv ( " LANG " , " C " , 1 ) ;
# endif
# if !defined(VCMI_MOBILE)
// Correct working dir executable folder (not bundle folder) so we can use executable relative paths
boost : : filesystem : : current_path ( boost : : filesystem : : system_complete ( argv [ 0 ] ) . parent_path ( ) ) ;
# endif
std : : cout < < " Starting... " < < std : : endl ;
po : : options_description opts ( " Allowed options " ) ;
po : : variables_map vm ;
opts . add_options ( )
( " help,h " , " display help and exit " )
( " version,v " , " display version information and exit " )
( " testmap " , po : : value < std : : string > ( ) , " " )
( " testsave " , po : : value < std : : string > ( ) , " " )
2025-01-09 23:24:31 +01:00
( " logLocation " , po : : value < std : : string > ( ) , " new location for log files " )
2024-08-14 18:36:42 +03:00
( " spectate,s " , " enable spectator interface for AI-only games " )
( " spectate-ignore-hero " , " wont follow heroes on adventure map " )
( " spectate-hero-speed " , po : : value < int > ( ) , " hero movement speed on adventure map " )
( " spectate-battle-speed " , po : : value < int > ( ) , " battle animation speed for spectator " )
( " spectate-skip-battle " , " skip battles in spectator view " )
( " spectate-skip-battle-result " , " skip battle result window " )
( " onlyAI " , " allow one to run without human player, all players will be default AI " )
( " headless " , " runs without GUI, implies --onlyAI " )
( " ai " , po : : value < std : : vector < std : : string > > ( ) , " AI to be used for the player, can be specified several times for the consecutive players " )
( " oneGoodAI " , " puts one default AI and the rest will be EmptyAI " )
( " autoSkip " , " automatically skip turns in GUI " )
( " disable-video " , " disable video player " )
( " nointro,i " , " skips intro movies " )
( " donotstartserver,d " , " do not attempt to start server and just connect to it instead server " )
( " serverport " , po : : value < si64 > ( ) , " override port specified in config file " )
( " savefrequency " , po : : value < si64 > ( ) , " limit auto save creation to each N days " ) ;
if ( argc > 1 )
{
try
{
po : : store ( po : : parse_command_line ( argc , argv , opts , po_style : : unix_style | po_style : : case_insensitive ) , vm ) ;
}
catch ( boost : : program_options : : error & e )
{
std : : cerr < < " Failure during parsing command-line options: \n " < < e . what ( ) < < std : : endl ;
}
}
po : : notify ( vm ) ;
if ( vm . count ( " help " ) )
{
prog_help ( opts ) ;
# ifdef VCMI_IOS
exit ( 0 ) ;
# else
return 0 ;
# endif
}
if ( vm . count ( " version " ) )
{
prog_version ( ) ;
# ifdef VCMI_IOS
exit ( 0 ) ;
# else
return 0 ;
# endif
}
// Init old logging system and new (temporary) logging system
CStopWatch total ;
CStopWatch pomtime ;
std : : cout . flags ( std : : ios : : unitbuf ) ;
2025-02-27 23:10:11 +00:00
setThreadNameLoggingOnly ( " MainGUI " ) ;
boost : : filesystem : : path logPath = VCMIDirs : : get ( ) . userLogsPath ( ) / " VCMI_Client_log.txt " ;
if ( vm . count ( " logLocation " ) )
logPath = vm [ " logLocation " ] . as < std : : string > ( ) + " /VCMI_Client_log.txt " ;
2024-08-14 18:36:42 +03:00
# ifndef VCMI_IOS
auto callbackFunction = [ ] ( std : : string buffer , bool calledFromIngameConsole )
{
ClientCommandManager commandController ;
commandController . processCommand ( buffer , calledFromIngameConsole ) ;
} ;
2025-02-27 23:10:11 +00:00
CConsoleHandler console ( callbackFunction ) ;
console . start ( ) ;
2025-02-28 11:37:56 +00:00
CBasicLogConfigurator logConfigurator ( logPath , & console ) ;
2025-02-27 23:10:11 +00:00
# else
2025-02-28 11:37:56 +00:00
CBasicLogConfigurator logConfigurator ( logPath , nullptr ) ;
2024-08-14 18:36:42 +03:00
# endif
2025-02-28 11:37:56 +00:00
logConfigurator . configureDefault ( ) ;
2024-08-14 18:36:42 +03:00
logGlobal - > info ( " Starting client of '%s' " , GameConstants : : VCMI_VERSION ) ;
logGlobal - > info ( " Creating console and configuring logger: %d ms " , pomtime . getDiff ( ) ) ;
logGlobal - > info ( " The log file will be saved to %s " , logPath ) ;
// Init filesystem and settings
try
{
2025-03-03 17:20:34 +00:00
LIBRARY = new GameLibrary ;
LIBRARY - > initializeFilesystem ( false ) ;
2024-08-14 18:36:42 +03:00
}
catch ( const DataLoadingException & e )
{
handleFatalError ( e . what ( ) , true ) ;
}
Settings session = settings . write [ " session " ] ;
2025-03-12 15:57:06 +00:00
auto setSettingBool = [ & ] ( const std : : string & key , const std : : string & arg ) {
2024-08-14 18:36:42 +03:00
Settings s = settings . write ( vstd : : split ( key , " / " ) ) ;
if ( vm . count ( arg ) )
s - > Bool ( ) = true ;
else if ( s - > isNull ( ) )
s - > Bool ( ) = false ;
} ;
2025-03-12 15:57:06 +00:00
auto setSettingInteger = [ & ] ( const std : : string & key , const std : : string & arg , si64 defaultValue ) {
2024-08-14 18:36:42 +03:00
Settings s = settings . write ( vstd : : split ( key , " / " ) ) ;
if ( vm . count ( arg ) )
s - > Integer ( ) = vm [ arg ] . as < si64 > ( ) ;
else if ( s - > isNull ( ) )
s - > Integer ( ) = defaultValue ;
} ;
setSettingBool ( " session/onlyai " , " onlyAI " ) ;
2025-02-10 22:52:48 +00:00
setSettingBool ( " session/disableVideo " , " disable-video " ) ;
2024-08-14 18:36:42 +03:00
if ( vm . count ( " headless " ) )
{
session [ " headless " ] . Bool ( ) = true ;
session [ " onlyai " ] . Bool ( ) = true ;
}
else if ( vm . count ( " spectate " ) )
{
session [ " spectate " ] . Bool ( ) = true ;
session [ " spectate-ignore-hero " ] . Bool ( ) = vm . count ( " spectate-ignore-hero " ) ;
session [ " spectate-skip-battle " ] . Bool ( ) = vm . count ( " spectate-skip-battle " ) ;
session [ " spectate-skip-battle-result " ] . Bool ( ) = vm . count ( " spectate-skip-battle-result " ) ;
if ( vm . count ( " spectate-hero-speed " ) )
session [ " spectate-hero-speed " ] . Integer ( ) = vm [ " spectate-hero-speed " ] . as < int > ( ) ;
if ( vm . count ( " spectate-battle-speed " ) )
session [ " spectate-battle-speed " ] . Float ( ) = vm [ " spectate-battle-speed " ] . as < int > ( ) ;
}
// Server settings
setSettingBool ( " session/donotstartserver " , " donotstartserver " ) ;
// Init special testing settings
setSettingInteger ( " session/serverport " , " serverport " , 0 ) ;
setSettingInteger ( " general/saveFrequency " , " savefrequency " , 1 ) ;
// Initialize logging based on settings
2025-02-28 11:37:56 +00:00
logConfigurator . configure ( ) ;
2024-08-14 18:36:42 +03:00
logGlobal - > debug ( " settings = %s " , settings . toJsonNode ( ) . toString ( ) ) ;
// Some basic data validation to produce better error messages in cases of incorrect install
2025-03-12 15:57:06 +00:00
auto testFile = [ ] ( const std : : string & filename , const std : : string & message )
2024-08-14 18:36:42 +03:00
{
if ( ! CResourceHandler : : get ( ) - > existsResource ( ResourcePath ( filename ) ) )
handleFatalError ( message , false ) ;
} ;
testFile ( " DATA/HELP.TXT " , " VCMI requires Heroes III: Shadow of Death or Heroes III: Complete data files to run! " ) ;
testFile ( " DATA/TENTCOLR.TXT " , " Heroes III: Restoration of Erathia (including HD Edition) data files are not supported! " ) ;
2025-04-06 18:41:20 +03:00
testFile ( " MODS/VCMI/MOD.JSON " , " VCMI installation is corrupted! \n Built-in mod was not found! " ) ;
testFile ( " DATA/NOTOSERIF-MEDIUM.TTF " , " VCMI installation is corrupted! \n Built-in font was not found! \n Manually deleting ' " + VCMIDirs : : get ( ) . userDataPath ( ) . string ( ) + " /Mods/VCMI' directory (if it exists) \n or clearing app data and reimporting Heroes III files may fix this problem. " ) ;
testFile ( " DATA/PLAYERS.PAL " , " Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! \n Please reinstall them. " ) ;
testFile ( " SPRITES/DEFAULT.DEF " , " Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! \n Please reinstall them. " ) ;
2024-08-14 18:36:42 +03:00
if ( ! settings [ " session " ] [ " headless " ] . Bool ( ) )
2025-03-12 13:32:22 +00:00
ENGINE = std : : make_unique < GameEngine > ( ) ;
2024-08-14 18:36:42 +03:00
2025-02-11 15:23:33 +00:00
GAME = std : : make_unique < GameInstance > ( ) ;
2025-03-12 13:32:22 +00:00
if ( ENGINE )
ENGINE - > setEngineUser ( GAME . get ( ) ) ;
2024-08-14 18:36:42 +03:00
# ifndef VCMI_NO_THREADED_LOAD
//we can properly play intro only in the main thread, so we have to move loading to the separate thread
2025-03-01 22:34:33 +00:00
std : : thread loading ( [ ] ( )
2024-08-14 18:36:42 +03:00
{
setThreadName ( " initialize " ) ;
init ( ) ;
} ) ;
# else
init ( ) ;
# endif
# ifndef VCMI_NO_THREADED_LOAD
# ifdef VCMI_ANDROID // android loads the data quite slowly so we display native progressbar to prevent having only black screen for few seconds
{
CAndroidVMHelper vmHelper ;
vmHelper . callStaticVoidMethod ( CAndroidVMHelper : : NATIVE_METHODS_DEFAULT_CLASS , " showProgress " ) ;
# endif // ANDROID
loading . join ( ) ;
# ifdef VCMI_ANDROID
vmHelper . callStaticVoidMethod ( CAndroidVMHelper : : NATIVE_METHODS_DEFAULT_CLASS , " hideProgress " ) ;
}
# endif // ANDROID
# endif // THREADED
if ( criticalInitializationError . has_value ( ) )
{
handleFatalError ( criticalInitializationError . value ( ) , false ) ;
}
2025-03-12 13:32:22 +00:00
if ( ENGINE )
2024-08-14 18:36:42 +03:00
{
pomtime . getDiff ( ) ;
graphics = new Graphics ( ) ; // should be before curh
2025-02-14 16:23:37 +00:00
ENGINE - > renderHandler ( ) . onLibraryLoadingFinished ( LIBRARY ) ;
2024-08-14 18:36:42 +03:00
CMessage : : init ( ) ;
logGlobal - > info ( " Message handler: %d ms " , pomtime . getDiff ( ) ) ;
2025-02-11 15:23:33 +00:00
ENGINE - > cursor ( ) . init ( ) ;
2025-02-10 22:08:50 +00:00
ENGINE - > cursor ( ) . show ( ) ;
2024-08-14 18:36:42 +03:00
}
logGlobal - > info ( " Initialization of VCMI (together): %d ms " , total . getDiff ( ) ) ;
session [ " autoSkip " ] . Bool ( ) = vm . count ( " autoSkip " ) ;
session [ " oneGoodAI " ] . Bool ( ) = vm . count ( " oneGoodAI " ) ;
session [ " aiSolo " ] . Bool ( ) = false ;
if ( vm . count ( " testmap " ) )
{
session [ " testmap " ] . String ( ) = vm [ " testmap " ] . as < std : : string > ( ) ;
session [ " onlyai " ] . Bool ( ) = true ;
2025-03-01 22:34:33 +00:00
GAME - > server ( ) . debugStartTest ( session [ " testmap " ] . String ( ) , false ) ;
2024-08-14 18:36:42 +03:00
}
else if ( vm . count ( " testsave " ) )
{
session [ " testsave " ] . String ( ) = vm [ " testsave " ] . as < std : : string > ( ) ;
session [ " onlyai " ] . Bool ( ) = true ;
2025-03-01 22:34:33 +00:00
GAME - > server ( ) . debugStartTest ( session [ " testsave " ] . String ( ) , true ) ;
2024-08-14 18:36:42 +03:00
}
2025-02-27 22:42:29 +00:00
else if ( ! settings [ " session " ] [ " headless " ] . Bool ( ) )
2024-08-14 18:36:42 +03:00
{
2025-02-27 22:42:29 +00:00
GAME - > mainmenu ( ) - > makeActiveInterface ( ) ;
2024-09-18 22:10:25 +02:00
2025-02-27 22:42:29 +00:00
bool playIntroVideo = ! vm . count ( " battle " ) & & ! vm . count ( " nointro " ) & & settings [ " video " ] [ " showIntro " ] . Bool ( ) ;
2024-09-18 22:10:25 +02:00
if ( playIntroVideo )
2025-02-27 22:42:29 +00:00
GAME - > mainmenu ( ) - > playIntroVideos ( ) ;
2024-09-18 22:10:25 +02:00
else
2025-02-27 22:42:29 +00:00
GAME - > mainmenu ( ) - > playMusic ( ) ;
2024-08-14 18:36:42 +03:00
}
2025-03-03 20:30:15 +00:00
# ifndef VCMI_UNIX
// on Linux, name of main thread is also name of our process. Which we don't want to change
setThreadName ( " MainGUI " ) ;
# endif
2024-08-14 18:36:42 +03:00
2025-03-04 15:25:04 +00:00
try
2024-08-14 18:36:42 +03:00
{
2025-03-12 13:32:22 +00:00
if ( ENGINE )
2025-03-04 15:25:04 +00:00
{
checkForModLoadingFailure ( ) ;
ENGINE - > mainLoop ( ) ;
}
else
{
while ( ! headlessQuit )
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 200 ) ) ;
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 500 ) ) ;
}
2024-08-14 18:36:42 +03:00
}
2025-03-04 15:25:04 +00:00
catch ( const GameShutdownException & )
2024-08-14 18:36:42 +03:00
{
2025-03-11 21:50:52 +00:00
// no-op - just break out of main loop
2025-03-12 13:32:22 +00:00
logGlobal - > info ( " Main loop termination requested " ) ;
2024-08-14 18:36:42 +03:00
}
2025-03-08 15:29:14 +00:00
2025-02-11 15:23:33 +00:00
GAME - > server ( ) . endNetwork ( ) ;
2024-08-27 03:59:09 +03:00
if ( ! settings [ " session " ] [ " headless " ] . Bool ( ) )
{
2025-02-11 15:23:33 +00:00
if ( GAME - > server ( ) . client )
GAME - > server ( ) . endGameplay ( ) ;
2024-08-27 03:59:09 +03:00
2025-03-12 13:32:22 +00:00
if ( ENGINE )
ENGINE - > windows ( ) . clear ( ) ;
2024-08-27 03:59:09 +03:00
}
2025-02-11 15:23:33 +00:00
GAME . reset ( ) ;
2024-08-27 03:59:09 +03:00
if ( ! settings [ " session " ] [ " headless " ] . Bool ( ) )
{
CMessage : : dispose ( ) ;
2025-04-27 19:58:21 +03:00
delete graphics ;
graphics = nullptr ;
2024-08-27 03:59:09 +03:00
}
2025-03-12 14:18:44 +00:00
// must be executed before reset - since unique_ptr resets pointer to null before calling destructor
ENGINE - > async ( ) . wait ( ) ;
2025-03-11 21:50:52 +00:00
ENGINE . reset ( ) ;
2024-08-27 03:59:09 +03:00
2025-04-27 19:58:21 +03:00
delete LIBRARY ;
LIBRARY = nullptr ;
2025-03-11 21:50:52 +00:00
logConfigurator . deconfigure ( ) ;
2025-02-11 15:23:33 +00:00
2024-08-27 03:59:09 +03:00
std : : cout < < " Ending... \n " ;
2025-03-11 21:50:52 +00:00
return 0 ;
2024-08-27 03:59:09 +03:00
}
/// Notify user about encountered fatal error and terminate the game
/// TODO: decide on better location for this method
void handleFatalError ( const std : : string & message , bool terminate )
{
logGlobal - > error ( " FATAL ERROR ENCOUNTERED, VCMI WILL NOW TERMINATE " ) ;
logGlobal - > error ( " Reason: %s " , message ) ;
std : : string messageToShow = " Fatal error! " + message ;
SDL_ShowSimpleMessageBox ( SDL_MESSAGEBOX_ERROR , " Fatal error! " , messageToShow . c_str ( ) , nullptr ) ;
if ( terminate )
throw std : : runtime_error ( message ) ;
else
2025-03-03 21:45:58 +00:00
: : exit ( 1 ) ;
2024-08-27 03:59:09 +03:00
}