浏览代码

Split vcmiclient in two

Similar to vcmiserver (app) and vcmiservercommon (lib), now
there is vcmiclient (app) and vcmiclientcommon (lib).
Simeon Manolov 1 年之前
父节点
当前提交
f2cddaa21b

+ 1 - 0
CMakeLists.txt

@@ -677,6 +677,7 @@ endif()
 
 
 if (ENABLE_CLIENT)
 if (ENABLE_CLIENT)
 	add_subdirectory(client)
 	add_subdirectory(client)
+	add_subdirectory(clientapp)
 endif()
 endif()
 
 
 if(ENABLE_SERVER)
 if(ENABLE_SERVER)

+ 4 - 386
client/CMT.cpp

@@ -8,42 +8,29 @@
  *
  *
  */
  */
 
 
-// CMT.cpp : Defines the entry point for the console application.
 #include "StdInc.h"
 #include "StdInc.h"
 #include "CMT.h"
 #include "CMT.h"
 
 
 #include "CGameInfo.h"
 #include "CGameInfo.h"
 #include "mainmenu/CMainMenu.h"
 #include "mainmenu/CMainMenu.h"
-#include "media/CEmptyVideoPlayer.h"
 #include "media/CMusicHandler.h"
 #include "media/CMusicHandler.h"
 #include "media/CSoundHandler.h"
 #include "media/CSoundHandler.h"
 #include "media/CVideoHandler.h"
 #include "media/CVideoHandler.h"
 #include "gui/CursorHandler.h"
 #include "gui/CursorHandler.h"
-#include "eventsSDL/InputHandler.h"
 #include "CPlayerInterface.h"
 #include "CPlayerInterface.h"
 #include "gui/CGuiHandler.h"
 #include "gui/CGuiHandler.h"
 #include "gui/WindowHandler.h"
 #include "gui/WindowHandler.h"
 #include "CServerHandler.h"
 #include "CServerHandler.h"
-#include "ClientCommandManager.h"
 #include "windows/CMessage.h"
 #include "windows/CMessage.h"
 #include "windows/InfoWindows.h"
 #include "windows/InfoWindows.h"
 #include "render/IScreenHandler.h"
 #include "render/IScreenHandler.h"
-#include "render/IRenderHandler.h"
 #include "render/Graphics.h"
 #include "render/Graphics.h"
 
 
-#include "../lib/CConfigHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
 #include "../lib/texts/CGeneralTextHandler.h"
-#include "../lib/CThreadHelper.h"
-#include "../lib/ExceptionsCommon.h"
-#include "../lib/VCMIDirs.h"
 #include "../lib/VCMI_Lib.h"
 #include "../lib/VCMI_Lib.h"
-#include "../lib/filesystem/Filesystem.h"
 
 
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 
 
-#include <boost/program_options.hpp>
-#include <vstd/StringUtils.h>
-
 #include <SDL_main.h>
 #include <SDL_main.h>
 #include <SDL.h>
 #include <SDL.h>
 
 
@@ -55,378 +42,9 @@
 #if __MINGW32__
 #if __MINGW32__
 #undef main
 #undef main
 #endif
 #endif
-
-namespace po = boost::program_options;
-namespace po_style = boost::program_options::command_line_style;
-
-static std::atomic<bool> headlessQuit = false;
-static std::optional<std::string> criticalInitializationError;
-
-#ifndef VCMI_IOS
-void processCommand(const std::string &message);
-#endif
-void playIntro();
-[[noreturn]] static void quitApplication();
-static void mainLoop();
-
-static CBasicLogConfigurator *logConfig;
-
-void init()
-{
-	CStopWatch tmh;
-	try
-	{
-		loadDLLClasses();
-		CGI->setFromLib();
-	}
-	catch (const DataLoadingException & e)
-	{
-		criticalInitializationError = e.what();
-		return;
-	}
-
-	logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff());
-
-	// Debug code to load all maps on start
-	//ClientCommandManager commandController;
-	//commandController.processCommand("convert txt", false);
-}
-
-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>(), "")
-		("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);
-#ifndef VCMI_IOS
-	console = new CConsoleHandler();
-
-	auto callbackFunction = [](std::string buffer, bool calledFromIngameConsole)
-	{
-		ClientCommandManager commandController;
-		commandController.processCommand(buffer, calledFromIngameConsole);
-	};
-
-	*console->cb = callbackFunction;
-	console->start();
-#endif
-
-	setThreadNameLoggingOnly("MainGUI");
-	const boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
-	logConfig = new CBasicLogConfigurator(logPath, console);
-	logConfig->configureDefault();
-	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
-	{
-		preinitDLL(::console, false);
-	}
-	catch (const DataLoadingException & e)
-	{
-		handleFatalError(e.what(), true);
-	}
-
-	Settings session = settings.write["session"];
-	auto setSettingBool = [&](std::string key, std::string arg) {
-		Settings s = settings.write(vstd::split(key, "/"));
-		if(vm.count(arg))
-			s->Bool() = true;
-		else if(s->isNull())
-			s->Bool() = false;
-	};
-	auto setSettingInteger = [&](std::string key, std::string arg, si64 defaultValue) {
-		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");
-	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
-	logConfig->configure();
-	logGlobal->debug("settings = %s", settings.toJsonNode().toString());
-
-	// Some basic data validation to produce better error messages in cases of incorrect install
-	auto testFile = [](std::string filename, std::string message)
-	{
-		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!");
-	testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
-	testFile("DATA/PLAYERS.PAL", "Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! Please reinstall them.");
-	testFile("SPRITES/DEFAULT.DEF", "Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! Please reinstall them.");
-
-	srand ( (unsigned int)time(nullptr) );
-
-	if(!settings["session"]["headless"].Bool())
-		GH.init();
-
-	CCS = new CClientState();
-	CGI = new CGameInfo(); //contains all global information about game (texts, lodHandlers, map handler etc.)
-	CSH = new CServerHandler();
-	
-	// Initialize video
-#ifdef DISABLE_VIDEO
-	CCS->videoh = new CEmptyVideoPlayer();
-#else
-	if (!settings["session"]["headless"].Bool() && !vm.count("disable-video"))
-		CCS->videoh = new CVideoPlayer();
-	else
-		CCS->videoh = new CEmptyVideoPlayer();
-#endif
-
-	logGlobal->info("\tInitializing video: %d ms", pomtime.getDiff());
-
-	if(!settings["session"]["headless"].Bool())
-	{
-		//initializing audio
-		CCS->soundh = new CSoundHandler();
-		CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
-		CCS->musich = new CMusicHandler();
-		CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
-		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
-	}
-
-#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
-	boost::thread loading([]()
-	{
-		setThreadName("initialize");
-		init();
-	});
-#else
-	init();
-#endif
-
-	if(!settings["session"]["headless"].Bool())
-	{
-		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
-			playIntro();
-		GH.screenHandler().clearScreen();
-	}
-
-#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);
-	}
-
-	if(!settings["session"]["headless"].Bool())
-	{
-		pomtime.getDiff();
-		graphics = new Graphics(); // should be before curh
-		GH.renderHandler().onLibraryLoadingFinished(CGI);
-
-		CCS->curh = new CursorHandler();
-		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
-
-		CMessage::init();
-		logGlobal->info("Message handler: %d ms", pomtime.getDiff());
-
-		CCS->curh->show();
-	}
-
-	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;
-		boost::thread(&CServerHandler::debugStartTest, CSH, session["testmap"].String(), false);
-	}
-	else if(vm.count("testsave"))
-	{
-		session["testsave"].String() = vm["testsave"].as<std::string>();
-		session["onlyai"].Bool() = true;
-		boost::thread(&CServerHandler::debugStartTest, CSH, session["testsave"].String(), true);
-	}
-	else
-	{
-		auto mmenu = CMainMenu::create();
-		GH.curInt = mmenu.get();
-	}
-	
-	std::vector<std::string> names;
-
-	if(!settings["session"]["headless"].Bool())
-	{
-		mainLoop();
-	}
-	else
-	{
-		while(!headlessQuit)
-			boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
-
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
-
-		quitApplication();
-	}
-
-	return 0;
-}
-
-//plays intro, ends when intro is over or button has been pressed (handles events)
-void playIntro()
-{
-	if(!CCS->videoh->playIntroVideo(VideoPath::builtin("3DOLOGO.SMK")))
-		return;
-
-	if (!CCS->videoh->playIntroVideo(VideoPath::builtin("NWCLOGO.SMK")))
-		return;
-
-	CCS->videoh->playIntroVideo(VideoPath::builtin("H3INTRO.SMK"));
-}
-
-static void mainLoop()
-{
-#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
-
-	while(1) //main SDL events loop
-	{
-		GH.input().fetchEvents();
-		GH.renderFrame();
-	}
-}
+extern std::atomic<bool> headlessQuit;
+extern std::optional<std::string> criticalInitializationError;
+extern CBasicLogConfigurator *logConfig;
 
 
 [[noreturn]] static void quitApplicationImmediately(int error_code)
 [[noreturn]] static void quitApplicationImmediately(int error_code)
 {
 {
@@ -442,7 +60,7 @@ static void mainLoop()
 #endif
 #endif
 }
 }
 
 
-[[noreturn]] static void quitApplication()
+[[noreturn]] void quitApplication()
 {
 {
 	CSH->endNetwork();
 	CSH->endNetwork();
 
 

+ 1 - 0
client/CMT.h

@@ -25,3 +25,4 @@ void handleQuit(bool ask = true);
 /// Notify user about encountered fatal error and terminate the game
 /// Notify user about encountered fatal error and terminate the game
 /// TODO: decide on better location for this method
 /// TODO: decide on better location for this method
 [[noreturn]] void handleFatalError(const std::string & message, bool terminate);
 [[noreturn]] void handleFatalError(const std::string & message, bool terminate);
+[[noreturn]] void quitApplication();

+ 30 - 96
client/CMakeLists.txt

@@ -1,4 +1,4 @@
-set(client_SRCS
+set(vcmiclientcommon_SRCS
 	StdInc.cpp
 	StdInc.cpp
 	../CCallback.cpp
 	../CCallback.cpp
 
 
@@ -186,7 +186,7 @@ set(client_SRCS
 	ServerRunner.cpp
 	ServerRunner.cpp
 )
 )
 
 
-set(client_HEADERS
+set(vcmiclientcommon_HEADERS
 	StdInc.h
 	StdInc.h
 
 
 	adventureMap/AdventureMapInterface.h
 	adventureMap/AdventureMapInterface.h
@@ -396,14 +396,14 @@ set(client_HEADERS
 )
 )
 
 
 if(APPLE_IOS)
 if(APPLE_IOS)
-	set(client_SRCS ${client_SRCS}
+	set(vcmiclientcommon_SRCS ${vcmiclientcommon_SRCS}
 		CFocusableHelper.cpp
 		CFocusableHelper.cpp
 		ios/GameChatKeyboardHandler.m
 		ios/GameChatKeyboardHandler.m
 		ios/main.m
 		ios/main.m
 		ios/startSDL.mm
 		ios/startSDL.mm
 		ios/utils.mm
 		ios/utils.mm
 	)
 	)
-	set(client_HEADERS ${client_HEADERS}
+	set(vcmiclientcommon_HEADERS ${vcmiclientcommon_HEADERS}
 		CFocusableHelper.h
 		CFocusableHelper.h
 		ios/GameChatKeyboardHandler.h
 		ios/GameChatKeyboardHandler.h
 		ios/startSDL.h
 		ios/startSDL.h
@@ -411,61 +411,51 @@ if(APPLE_IOS)
 	)
 	)
 endif()
 endif()
 
 
-assign_source_group(${client_SRCS} ${client_HEADERS} VCMI_client.rc)
+assign_source_group(${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS} VCMI_vcmiclientcommon.rc)
 
 
 if(ANDROID)
 if(ANDROID)
-	add_library(vcmiclient SHARED ${client_SRCS} ${client_HEADERS})
-	set_target_properties(vcmiclient PROPERTIES
+	add_library(vcmiclientcommon SHARED ${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
+	set_target_properties(vcmiclientcommon PROPERTIES
 		OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt
 		OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt
 	)
 	)
 else()
 else()
-	add_executable(vcmiclient ${client_SRCS} ${client_HEADERS})
+	add_library(vcmiclientcommon STATIC ${vcmiclientcommon_SRCS} ${vcmiclientcommon_HEADERS})
 endif()
 endif()
 
 
 if(NOT ENABLE_STATIC_LIBS)
 if(NOT ENABLE_STATIC_LIBS)
-	add_dependencies(vcmiclient
+	add_dependencies(vcmiclientcommon
 		BattleAI
 		BattleAI
 		EmptyAI
 		EmptyAI
 		StupidAI
 		StupidAI
 		VCAI
 		VCAI
 	)
 	)
 	if(ENABLE_NULLKILLER_AI)
 	if(ENABLE_NULLKILLER_AI)
-		add_dependencies(vcmiclient Nullkiller)
+		add_dependencies(vcmiclientcommon Nullkiller)
 	endif()
 	endif()
 endif()
 endif()
 if(APPLE_IOS)
 if(APPLE_IOS)
 	if(ENABLE_ERM)
 	if(ENABLE_ERM)
-		add_dependencies(vcmiclient vcmiERM)
+		add_dependencies(vcmiclientcommon vcmiERM)
 	endif()
 	endif()
 	if(ENABLE_LUA)
 	if(ENABLE_LUA)
-		add_dependencies(vcmiclient vcmiLua)
+		add_dependencies(vcmiclientcommon vcmiLua)
 	endif()
 	endif()
 endif()
 endif()
 
 
 if(WIN32)
 if(WIN32)
-	target_sources(vcmiclient PRIVATE "VCMI_client.rc")
-	set_target_properties(vcmiclient
+	target_sources(vcmiclientcommon PRIVATE "VCMI_clientcommon.rc")
+	set_target_properties(vcmiclientcommon
 		PROPERTIES
 		PROPERTIES
-			OUTPUT_NAME "VCMI_client"
-			PROJECT_LABEL "VCMI_client"
+			OUTPUT_NAME "VCMI_vcmiclientcommon"
+			PROJECT_LABEL "VCMI_vcmiclientcommon"
 	)
 	)
-	set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclient)
+	set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclientcommon)
 	if(NOT ENABLE_DEBUG_CONSOLE)
 	if(NOT ENABLE_DEBUG_CONSOLE)
-		set_target_properties(vcmiclient PROPERTIES WIN32_EXECUTABLE)
-		target_link_libraries(vcmiclient SDL2::SDL2main)
-	endif()
-	target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
-
-# TODO: very hacky, find proper solution to copy AI dlls into bin dir
-	if(MSVC)
-		add_custom_command(TARGET vcmiclient POST_BUILD
-			WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
-			COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
-			COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
-		)
+		target_link_libraries(vcmiclientcommon SDL2::SDL2main)
 	endif()
 	endif()
+	target_compile_definitions(vcmiclientcommon PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
 elseif(APPLE_IOS)
 elseif(APPLE_IOS)
-	target_link_libraries(vcmiclient PRIVATE
+	target_link_libraries(vcmiclientcommon PRIVATE
 		iOS_utils
 		iOS_utils
 
 
 		# FFmpeg
 		# FFmpeg
@@ -478,16 +468,9 @@ elseif(APPLE_IOS)
 		"-framework VideoToolbox"
 		"-framework VideoToolbox"
 	)
 	)
 
 
-	set_target_properties(vcmiclient PROPERTIES
-		MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
-		XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
-		XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "$(CODE_SIGNING_ALLOWED_FOR_APPS)"
-		XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
-	)
-
 	foreach(XCODE_RESOURCE LaunchScreen.storyboard Images.xcassets Settings.bundle vcmi_logo.png)
 	foreach(XCODE_RESOURCE LaunchScreen.storyboard Images.xcassets Settings.bundle vcmi_logo.png)
 		set(XCODE_RESOURCE_PATH ios/${XCODE_RESOURCE})
 		set(XCODE_RESOURCE_PATH ios/${XCODE_RESOURCE})
-		target_sources(vcmiclient PRIVATE ${XCODE_RESOURCE_PATH})
+		target_sources(vcmiclientcommon PRIVATE ${XCODE_RESOURCE_PATH})
 		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 
 
 		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
 		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
@@ -495,83 +478,34 @@ elseif(APPLE_IOS)
 			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
 			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
 		endif()
 		endif()
 	endforeach()
 	endforeach()
-
-	set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_client_main")
 endif()
 endif()
 
 
-target_link_libraries(vcmiclient PRIVATE vcmiservercommon)
+target_link_libraries(vcmiclientcommon PRIVATE vcmiservercommon)
 if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER)
 if(ENABLE_SINGLE_APP_BUILD AND ENABLE_LAUNCHER)
-	target_link_libraries(vcmiclient PRIVATE vcmilauncher)
+	target_link_libraries(vcmiclientcommon PRIVATE vcmilauncher)
 endif()
 endif()
 
 
-target_link_libraries(vcmiclient PRIVATE
+target_link_libraries(vcmiclientcommon PUBLIC
 		vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF
 		vcmi SDL2::SDL2 SDL2::Image SDL2::Mixer SDL2::TTF
 )
 )
 
 
 if(ffmpeg_LIBRARIES)
 if(ffmpeg_LIBRARIES)
-	target_link_libraries(vcmiclient PRIVATE
+	target_link_libraries(vcmiclientcommon PRIVATE
 		${ffmpeg_LIBRARIES}
 		${ffmpeg_LIBRARIES}
 	)
 	)
 else()
 else()
-	target_compile_definitions(vcmiclient PRIVATE DISABLE_VIDEO)
+	target_compile_definitions(vcmiclientcommon PRIVATE DISABLE_VIDEO)
 endif()
 endif()
 
 
-target_include_directories(vcmiclient PUBLIC
+target_include_directories(vcmiclientcommon PUBLIC
 	${CMAKE_CURRENT_SOURCE_DIR}
 	${CMAKE_CURRENT_SOURCE_DIR}
 )
 )
 
 
 if (ffmpeg_INCLUDE_DIRS)
 if (ffmpeg_INCLUDE_DIRS)
-	target_include_directories(vcmiclient PRIVATE
+	target_include_directories(vcmiclientcommon PRIVATE
 		${ffmpeg_INCLUDE_DIRS}
 		${ffmpeg_INCLUDE_DIRS}
 	)
 	)
 endif()
 endif()
 
 
-vcmi_set_output_dir(vcmiclient "")
-enable_pch(vcmiclient)
-
-if(APPLE_IOS)
-	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
-	add_custom_command(TARGET vcmiclient POST_BUILD
-		COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
-		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
-		COMMAND ios/rpath_remove_symlinks.sh
-		COMMAND ios/codesign.sh
-		WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
-	)
-	install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack
-elseif(ANDROID)
-	find_program(androidDeployQt androiddeployqt
-		PATHS "${qtBinDir}"
-	)
-	vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
-
-	add_custom_target(android_deploy ALL
-		COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
-		COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
-		COMMAND_EXPAND_LISTS
-		VERBATIM
-		COMMENT "Create android package"
-	)
-	add_dependencies(android_deploy vcmiclient)
-else()
-	install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
-endif()
-
-#install icons and desktop file on Linux
-if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
-	#FIXME: move to client makefile?
-	foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048)
-		install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png"
-			DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps"
-			RENAME vcmiclient.png
-		)
-	endforeach()
-
-	install(FILES icons/vcmiclient.svg
-		DESTINATION share/icons/hicolor/scalable/apps
-		RENAME vcmiclient.svg
-	)
-	install(FILES icons/vcmiclient.desktop
-		DESTINATION share/applications
-	)
-endif()
+vcmi_set_output_dir(vcmiclientcommon "")
+enable_pch(vcmiclientcommon)

+ 0 - 1
client/CPlayerInterface.cpp

@@ -13,7 +13,6 @@
 #include <vcmi/Artifact.h>
 #include <vcmi/Artifact.h>
 
 
 #include "CGameInfo.h"
 #include "CGameInfo.h"
-#include "CMT.h"
 #include "CServerHandler.h"
 #include "CServerHandler.h"
 #include "HeroMovementController.h"
 #include "HeroMovementController.h"
 #include "PlayerLocalState.h"
 #include "PlayerLocalState.h"

+ 0 - 1
client/ClientCommandManager.cpp

@@ -38,7 +38,6 @@
 #include "../lib/CHeroHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/logging/VisualLogger.h"
 #include "../lib/logging/VisualLogger.h"
-#include "CMT.h"
 #include "../lib/serializer/Connection.h"
 #include "../lib/serializer/Connection.h"
 
 
 #ifdef SCRIPTING_ENABLED
 #ifdef SCRIPTING_ENABLED

+ 0 - 1
client/eventsSDL/InputSourceText.cpp

@@ -11,7 +11,6 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "InputSourceText.h"
 #include "InputSourceText.h"
 
 
-#include "../CMT.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/EventDispatcher.h"
 #include "../gui/EventDispatcher.h"
 #include "../render/IScreenHandler.h"
 #include "../render/IScreenHandler.h"

+ 0 - 1
client/eventsSDL/InputSourceTouch.cpp

@@ -14,7 +14,6 @@
 #include "InputHandler.h"
 #include "InputHandler.h"
 
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CConfigHandler.h"
-#include "../CMT.h"
 #include "../CGameInfo.h"
 #include "../CGameInfo.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"

+ 100 - 0
clientapp/CMakeLists.txt

@@ -0,0 +1,100 @@
+set(clientapp_SRCS
+    StdInc.cpp
+    EntryPoint.cpp
+)
+
+set(clientapp_HEADERS
+    StdInc.h
+)
+
+assign_source_group(${clientapp_SRCS} ${clientapp_HEADERS})
+add_executable(vcmiclient ${clientapp_SRCS} ${clientapp_HEADERS})
+
+target_link_libraries(vcmiclient PRIVATE vcmiclientcommon)
+
+target_include_directories(vcmiclient
+  PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+if(WIN32)
+  target_sources(vcmiclient PRIVATE "VCMI_client.rc")
+  set_target_properties(vcmiclient
+    PROPERTIES
+      OUTPUT_NAME "VCMI_client"
+      PROJECT_LABEL "VCMI_client"
+  )
+  set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vcmiclient)
+  if(NOT ENABLE_DEBUG_CONSOLE)
+    set_target_properties(vcmiclient PROPERTIES WIN32_EXECUTABLE)
+    target_link_libraries(vcmiclient SDL2::SDL2main)
+  endif()
+  target_compile_definitions(vcmiclient PRIVATE WINDOWS_IGNORE_PACKING_MISMATCH)
+
+# TODO: very hacky, find proper solution to copy AI dlls into bin dir
+  if(MSVC)
+    add_custom_command(TARGET vcmiclient POST_BUILD
+      WORKING_DIRECTORY "$<TARGET_FILE_DIR:vcmiclient>"
+      COMMAND ${CMAKE_COMMAND} -E copy AI/fuzzylite.dll fuzzylite.dll
+      COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
+    )
+  endif()
+elseif(APPLE_IOS)
+  set_target_properties(vcmiclient PROPERTIES
+    MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_LIST_DIR}/ios/Info.plist"
+    XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
+    XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "$(CODE_SIGNING_ALLOWED_FOR_APPS)"
+    XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon
+  )
+
+  set(CMAKE_EXE_LINKER_FLAGS "-Wl,-e,_vcmiclient_main")
+endif()
+
+vcmi_set_output_dir(vcmiclient "")
+enable_pch(vcmiclient)
+
+if(APPLE_IOS)
+  vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}")
+  add_custom_command(TARGET vcmiclient POST_BUILD
+    COMMAND ios/set_build_version.sh "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
+    COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --component "${CMAKE_INSTALL_DEFAULT_COMPONENT_NAME}" --config "$<CONFIG>" --prefix "$<TARGET_BUNDLE_CONTENT_DIR:vcmiclient>"
+    COMMAND ios/rpath_remove_symlinks.sh
+    COMMAND ios/codesign.sh
+    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+  )
+  install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack
+elseif(ANDROID)
+  find_program(androidDeployQt androiddeployqt
+    PATHS "${qtBinDir}"
+  )
+  vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}")
+
+  add_custom_target(android_deploy ALL
+    COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$<CONFIG>" --prefix "${androidQtBuildDir}"
+    COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
+    COMMAND_EXPAND_LISTS
+    VERBATIM
+    COMMENT "Create android package"
+  )
+  add_dependencies(android_deploy vcmiclient)
+else()
+  install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
+endif()
+
+#install icons and desktop file on Linux
+if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
+  #FIXME: move to client makefile?
+  foreach(iconSize 16 22 32 48 64 128 256 512 1024 2048)
+    install(FILES "icons/vcmiclient.${iconSize}x${iconSize}.png"
+      DESTINATION "share/icons/hicolor/${iconSize}x${iconSize}/apps"
+      RENAME vcmiclient.png
+    )
+  endforeach()
+
+  install(FILES icons/vcmiclient.svg
+    DESTINATION share/icons/hicolor/scalable/apps
+    RENAME vcmiclient.svg
+  )
+  install(FILES icons/vcmiclient.desktop
+    DESTINATION share/applications
+  )
+endif()

+ 424 - 0
clientapp/EntryPoint.cpp

@@ -0,0 +1,424 @@
+/*
+ * 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.
+#include "Global.h"
+#include "StdInc.h"
+
+#include "client/CGameInfo.h"
+#include "client/mainmenu/CMainMenu.h"
+#include "media/CEmptyVideoPlayer.h"
+#include "media/CMusicHandler.h"
+#include "media/CSoundHandler.h"
+#include "media/CVideoHandler.h"
+#include "client/gui/CursorHandler.h"
+#include "client/eventsSDL/InputHandler.h"
+#include "client/gui/CGuiHandler.h"
+#include "client/CServerHandler.h"
+#include "client/ClientCommandManager.h"
+#include "client/windows/CMessage.h"
+#include "client/render/IRenderHandler.h"
+#include "client/render/IScreenHandler.h"
+#include "client/render/Graphics.h"
+#include "client/CMT.h"
+
+#include "../lib/CThreadHelper.h"
+#include "../lib/ExceptionsCommon.h"
+#include "../lib/VCMIDirs.h"
+#include "../lib/VCMI_Lib.h"
+#include "../lib/filesystem/Filesystem.h"
+
+#include "../lib/logging/CBasicLogConfigurator.h"
+
+#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;
+
+std::atomic<bool> headlessQuit = false;
+std::optional<std::string> criticalInitializationError;
+
+#ifndef VCMI_IOS
+void processCommand(const std::string &message);
+#endif
+void playIntro();
+static void mainLoop();
+
+CBasicLogConfigurator *logConfig;
+
+void init()
+{
+	CStopWatch tmh;
+	try
+	{
+		loadDLLClasses();
+		CGI->setFromLib();
+	}
+	catch (const DataLoadingException & e)
+	{
+		criticalInitializationError = e.what();
+		return;
+	}
+
+	logGlobal->info("Initializing VCMI_Lib: %d ms", tmh.getDiff());
+
+	// Debug code to load all maps on start
+	//ClientCommandManager commandController;
+	//commandController.processCommand("convert txt", false);
+}
+
+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>(), "")
+		("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);
+#ifndef VCMI_IOS
+	console = new CConsoleHandler();
+
+	auto callbackFunction = [](std::string buffer, bool calledFromIngameConsole)
+	{
+		ClientCommandManager commandController;
+		commandController.processCommand(buffer, calledFromIngameConsole);
+	};
+
+	*console->cb = callbackFunction;
+	console->start();
+#endif
+
+	setThreadNameLoggingOnly("MainGUI");
+	const boost::filesystem::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
+	logConfig = new CBasicLogConfigurator(logPath, console);
+	logConfig->configureDefault();
+	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
+	{
+		preinitDLL(::console, false);
+	}
+	catch (const DataLoadingException & e)
+	{
+		handleFatalError(e.what(), true);
+	}
+
+	Settings session = settings.write["session"];
+	auto setSettingBool = [&](std::string key, std::string arg) {
+		Settings s = settings.write(vstd::split(key, "/"));
+		if(vm.count(arg))
+			s->Bool() = true;
+		else if(s->isNull())
+			s->Bool() = false;
+	};
+	auto setSettingInteger = [&](std::string key, std::string arg, si64 defaultValue) {
+		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");
+	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
+	logConfig->configure();
+	logGlobal->debug("settings = %s", settings.toJsonNode().toString());
+
+	// Some basic data validation to produce better error messages in cases of incorrect install
+	auto testFile = [](std::string filename, std::string message)
+	{
+		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!");
+	testFile("MODS/VCMI/MOD.JSON", "VCMI installation is corrupted! Built-in mod was not found!");
+	testFile("DATA/PLAYERS.PAL", "Heroes III data files (Data/H3Bitmap.lod) are incomplete or corruped! Please reinstall them.");
+	testFile("SPRITES/DEFAULT.DEF", "Heroes III data files (Data/H3Sprite.lod) are incomplete or corruped! Please reinstall them.");
+
+	srand ( (unsigned int)time(nullptr) );
+
+	if(!settings["session"]["headless"].Bool())
+		GH.init();
+
+	CCS = new CClientState();
+	CGI = new CGameInfo(); //contains all global information about game (texts, lodHandlers, map handler etc.)
+	CSH = new CServerHandler();
+	
+	// Initialize video
+#ifdef DISABLE_VIDEO
+	CCS->videoh = new CEmptyVideoPlayer();
+#else
+	if (!settings["session"]["headless"].Bool() && !vm.count("disable-video"))
+		CCS->videoh = new CVideoPlayer();
+	else
+		CCS->videoh = new CEmptyVideoPlayer();
+#endif
+
+	logGlobal->info("\tInitializing video: %d ms", pomtime.getDiff());
+
+	if(!settings["session"]["headless"].Bool())
+	{
+		//initializing audio
+		CCS->soundh = new CSoundHandler();
+		CCS->soundh->setVolume((ui32)settings["general"]["sound"].Float());
+		CCS->musich = new CMusicHandler();
+		CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
+		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
+	}
+
+#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
+	boost::thread loading([]()
+	{
+		setThreadName("initialize");
+		init();
+	});
+#else
+	init();
+#endif
+
+	if(!settings["session"]["headless"].Bool())
+	{
+		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
+			playIntro();
+		GH.screenHandler().clearScreen();
+	}
+
+#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);
+	}
+
+	if(!settings["session"]["headless"].Bool())
+	{
+		pomtime.getDiff();
+		graphics = new Graphics(); // should be before curh
+		GH.renderHandler().onLibraryLoadingFinished(CGI);
+
+		CCS->curh = new CursorHandler();
+		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
+
+		CMessage::init();
+		logGlobal->info("Message handler: %d ms", pomtime.getDiff());
+
+		CCS->curh->show();
+	}
+
+	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;
+		boost::thread(&CServerHandler::debugStartTest, CSH, session["testmap"].String(), false);
+	}
+	else if(vm.count("testsave"))
+	{
+		session["testsave"].String() = vm["testsave"].as<std::string>();
+		session["onlyai"].Bool() = true;
+		boost::thread(&CServerHandler::debugStartTest, CSH, session["testsave"].String(), true);
+	}
+	else
+	{
+		auto mmenu = CMainMenu::create();
+		GH.curInt = mmenu.get();
+	}
+	
+	std::vector<std::string> names;
+
+	if(!settings["session"]["headless"].Bool())
+	{
+		mainLoop();
+	}
+	else
+	{
+		while(!headlessQuit)
+			boost::this_thread::sleep_for(boost::chrono::milliseconds(200));
+
+		boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
+
+		quitApplication();
+	}
+
+	return 0;
+}
+
+//plays intro, ends when intro is over or button has been pressed (handles events)
+void playIntro()
+{
+	if(!CCS->videoh->playIntroVideo(VideoPath::builtin("3DOLOGO.SMK")))
+		return;
+
+	if (!CCS->videoh->playIntroVideo(VideoPath::builtin("NWCLOGO.SMK")))
+		return;
+
+	CCS->videoh->playIntroVideo(VideoPath::builtin("H3INTRO.SMK"));
+}
+
+static void mainLoop()
+{
+#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
+
+	while(1) //main SDL events loop
+	{
+		GH.input().fetchEvents();
+		GH.renderFrame();
+	}
+}

+ 11 - 0
clientapp/StdInc.cpp

@@ -0,0 +1,11 @@
+/*
+ * StdInc.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
+ *
+ */
+// Creates the precompiled header
+#include "StdInc.h"

+ 14 - 0
clientapp/StdInc.h

@@ -0,0 +1,14 @@
+/*
+ * StdInc.h, 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
+ *
+ */
+#pragma once
+
+#include "../Global.h"
+
+VCMI_LIB_USING_NAMESPACE