Sfoglia il codice sorgente

Android support (#299)

* AI libs registering shenanigans on android;
* Fixed resolution aspect + mouse event scaling;
* Proper server init/deinit (through android IPC);
Enabled threaded init in CMT;
* Prevented a deadlock in logger on some devices;
* Fixed frozen intro frame after interrupting the video;
Added android progressbar displaying during initial data loading;
* Hacky fix for choppy animations during heroes movement (should look better now, but it's definitely not a good solution);
* Changes/fixes for new android launcher building process;
* Fixed app hang after getting SDL_QUIT when activity was destroyed;
* Functioanal, configurable advmap swiping support;
* VCMI changes cleanup;
Added few missing VCMI_ANDROID guards on swipe mechanics;
* Removed unneeded sleep in server startup code for android;
* Removed android ioapi hack (fixed in newest ndk);
* Removed unused android's library loading logic;
* Added android's swipe option to settings schema;
* Moved NO_STD_TOSTRING to be defined in global.h instead of build files;
Fay 8 anni fa
parent
commit
b5daa24982

+ 3 - 0
AI/BattleAI/CMakeLists.txt

@@ -15,6 +15,9 @@ set(battleAI_SRCS
 		ThreatMap.cpp
 )
 
+if (ANDROID) # android compiles ai libs into main lib directly, so we skip this library and just reuse sources list
+	return()
+endif()
 add_library(BattleAI SHARED ${battleAI_SRCS})
 target_link_libraries(BattleAI vcmi)
 

+ 0 - 6
AI/BattleAI/main.cpp

@@ -15,12 +15,6 @@
 #define strcpy_s(a, b, c) strncpy(a, c, b)
 #endif
 
-#ifdef VCMI_ANDROID
-#define GetGlobalAiVersion BattleAI_GetGlobalAiVersion
-#define GetAiName BattleAI_GetAiName
-#define GetNewBattleAI BattleAI_GetNewBattleAI
-#endif
-
 static const char *g_cszAiName = "Battle AI";
 
 extern "C" DLL_EXPORT int GetGlobalAiVersion()

+ 9 - 3
AI/FuzzyLite/fuzzylite/CMakeLists.txt

@@ -57,9 +57,15 @@ if (APPLE)
     add_definitions(-DFL_APPLE)
 endif()
 
-set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY bin)
-set(CMAKE_LIBRARY_OUTPUT_DIRECTORY bin)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)
+if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
+	set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY bin)
+endif()
+if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
+	set(CMAKE_LIBRARY_OUTPUT_DIRECTORY bin)
+endif()
+if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
+	set(CMAKE_RUNTIME_OUTPUT_DIRECTORY bin)
+endif()
 
 if(NOT MSVC)
     set(CMAKE_CXX_FLAGS "-pedantic -Werror -Wall -Wextra ${CMAKE_CXX_FLAGS}")

+ 1 - 7
AI/StupidAI/main.cpp

@@ -7,12 +7,6 @@
 #define strcpy_s(a, b, c) strncpy(a, c, b)
 #endif
 
-#ifdef VCMI_ANDROID
-#define GetGlobalAiVersion StupidAI_GetGlobalAiVersion
-#define GetAiName StupidAI_GetAiName
-#define GetNewBattleAI StupidAI_GetNewBattleAI
-#endif
-
 static const char *g_cszAiName = "Stupid AI 0.1";
 
 extern "C" DLL_EXPORT int GetGlobalAiVersion()
@@ -28,4 +22,4 @@ extern "C" DLL_EXPORT void GetAiName(char* name)
 extern "C" DLL_EXPORT void GetNewBattleAI(std::shared_ptr<CBattleGameInterface> &out)
 {
 	out = std::make_shared<CStupidAI>();
-}
+}

+ 7 - 3
AI/VCAI/CMakeLists.txt

@@ -17,16 +17,20 @@ set(VCAI_SRCS
     Fuzzy.cpp
 )
 
+if (ANDROID) # android compiles ai libs into main lib directly, so we skip this library and just reuse sources list
+	return()
+endif()
+
 add_library(VCAI SHARED ${VCAI_SRCS})
 if (FL_FOUND)
-    target_link_libraries(VCAI ${FL_LIBRARIES} vcmi)
+	target_link_libraries(VCAI ${FL_LIBRARIES} vcmi)
 else()
-    target_link_libraries(VCAI fl-static vcmi)
+	target_link_libraries(VCAI fl-static vcmi)
 endif()
 
 set_target_properties(VCAI PROPERTIES ${PCH_PROPERTIES})
 cotire(VCAI)
 
 if (NOT APPLE) # Already inside vcmiclient bundle
-    install(TARGETS VCAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
+	install(TARGETS VCAI RUNTIME DESTINATION ${AI_LIB_DIR} LIBRARY DESTINATION ${AI_LIB_DIR})
 endif()

+ 1 - 7
AI/VCAI/main.cpp

@@ -5,12 +5,6 @@
 #define strcpy_s(a, b, c) strncpy(a, c, b)
 #endif
 
-#ifdef VCMI_ANDROID
-#define GetGlobalAiVersion VCAI_GetGlobalAiVersion
-#define GetAiName VCAI_GetAiName
-#define GetNewAI VCAI_GetNewAI
-#endif
-
 static const char *g_cszAiName = "VCAI";
 
 extern "C" DLL_EXPORT int GetGlobalAiVersion()
@@ -26,4 +20,4 @@ extern "C" DLL_EXPORT void GetAiName(char* name)
 extern "C" DLL_EXPORT void GetNewAI(std::shared_ptr<CGlobalAI> &out)
 {
 	out = std::make_shared<VCAI>();
-}
+}

+ 17 - 0
Global.h

@@ -96,6 +96,10 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
 #  define _NO_W32_PSEUDO_MODIFIERS  // Exclude more macros for compiling with MinGW on Linux.
 #endif
 
+#ifdef VCMI_ANDROID
+#  define NO_STD_TOSTRING // android runtime (gnustl) currently doesn't support std::to_string, so we provide our impl in this case
+#endif // VCMI_ANDROID
+
 /* ---------------------------------------------------------------------------- */
 /* A macro to force inlining some of our functions */
 /* ---------------------------------------------------------------------------- */
@@ -698,3 +702,16 @@ namespace vstd
 }
 using vstd::operator-=;
 using vstd::make_unique;
+
+#ifdef NO_STD_TOSTRING
+namespace std
+{
+	template <typename T>
+	inline std::string to_string(const T& value)
+	{
+		std::ostringstream ss;
+		ss << value;
+		return ss.str();
+	}
+}
+#endif // NO_STD_TOSTRING

+ 35 - 11
client/CMT.cpp

@@ -49,6 +49,9 @@
 #ifdef VCMI_WINDOWS
 #include "SDL_syswm.h"
 #endif
+#ifdef VCMI_ANDROID
+#include "lib/CAndroidVMHelper.h"
+#endif
 #include "../lib/UnlockGuard.h"
 #include "CMT.h"
 
@@ -203,7 +206,7 @@ void OSX_checkForUpdates();
 
 #if defined(VCMI_WINDOWS) && !defined (__GNUC__)
 int wmain(int argc, wchar_t* argv[])
-#elif defined(VCMI_APPLE)
+#elif defined(VCMI_APPLE) || defined(VCMI_ANDROID)
 int SDL_main(int argc, char *argv[])
 #else
 int main(int argc, char** argv)
@@ -436,11 +439,6 @@ int main(int argc, char** argv)
 
 	logGlobal->infoStream()<<"\tInitializing video: "<<pomtime.getDiff();
 
-#if defined(VCMI_ANDROID)
-	//on Android threaded init is broken
-	#define VCMI_NO_THREADED_LOAD
-#endif // defined
-
 	//initializing audio
 	CCS->soundh = new CSoundHandler;
 	CCS->soundh->init();
@@ -466,13 +464,22 @@ int main(int argc, char** argv)
 	{
 		if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
 			playIntro();
-		SDL_FillRect(screen,nullptr,0);
+		SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
+		SDL_RenderClear(mainRenderer);
 	}
-
-	CSDL_Ext::update(screen);
+	SDL_RenderPresent(mainRenderer);
 #ifndef VCMI_NO_THREADED_LOAD
-	loading.join();
-#endif
+	#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
 	logGlobal->infoStream()<<"Initialization of VCMI (together): "<<total.getDiff();
 
 	if(!vm.count("battle"))
@@ -1027,6 +1034,9 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 
 	cleanupRenderer();
 
+#ifdef VCMI_ANDROID
+	mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN);
+#else
 	if(fullscreen)
 	{
 		//in full-screen mode always use desktop resolution
@@ -1037,6 +1047,7 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 	{
 		mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
 	}
+#endif
 
 	if(nullptr == mainWindow)
 	{
@@ -1058,7 +1069,10 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIn
 
 	SDL_RenderSetLogicalSize(mainRenderer, w, h);
 
+#ifndef VCMI_ANDROID
+    // on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
 	SDL_RenderSetViewport(mainRenderer, nullptr);
+#endif
 
 
 
@@ -1149,9 +1163,19 @@ static void handleEvent(SDL_Event & ev)
 {
 	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
 	{
+#ifdef VCMI_ANDROID
+		handleQuit(false);
+#else
 		handleQuit();
+#endif
 		return;
 	}
+#ifdef VCMI_ANDROID
+	else if (ev.type == SDL_KEYDOWN && ev.key.keysym.scancode == SDL_SCANCODE_AC_BACK)
+	{
+		handleQuit(true);
+	}
+#endif
 	else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
 	{
 		Settings full = settings.write["video"]["fullscreen"];

+ 4 - 1
client/CMakeLists.txt

@@ -65,6 +65,10 @@ set(client_HEADERS
                 gui/SDL_Compat.h
 )
 
+if(ANDROID) # android needs client/server to be libraries, not executables, so we can't reuse the build part of this script
+	return()
+endif()
+
 if(APPLE)
 	# OS X specific includes
 	include_directories(${SPARKLE_INCLUDE_DIR})
@@ -133,4 +137,3 @@ cotire(vcmiclient)
 
 
 install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
-

+ 8 - 0
client/CPlayerInterface.cpp

@@ -353,7 +353,11 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
 	for (int i=1; i<32; i+=2*speed)
 	{
 		movementPxStep(details, i, hp, hero);
+#ifndef VCMI_ANDROID
+		// currently android doesn't seem to be able to handle all these full redraws here, so let's disable it so at least it looks less choppy;
+		// most likely this is connected with the way that this manual animation+framerate handling is solved
 		adventureInt->updateScreen = true;
+#endif
 		adventureInt->show(screen);
 		{
 			//evil returns here ...
@@ -1647,7 +1651,11 @@ void CPlayerInterface::update()
 	GH.updateTime();
 	GH.handleEvents();
 
+#ifdef VCMI_ANDROID
+	if (adventureInt && !adventureInt->isActive() && (adventureInt->swipeTargetPosition.x >= 0 || adventureInt->swipeTargetPosition.y >= 0))
+#else // !VCMI_ANDROID
 	if (adventureInt && !adventureInt->isActive() && adventureInt->scrollingDir) //player forces map scrolling though interface is disabled
+#endif // !VCMI_ANDROID
 		GH.totalRedraw();
 	else
 		GH.simpleRedraw();

+ 44 - 2
client/Client.cpp

@@ -43,6 +43,8 @@
 extern std::string NAME;
 #ifndef VCMI_ANDROID
 namespace intpr = boost::interprocess;
+#else
+#include "lib/CAndroidVMHelper.h"
 #endif
 
 /*
@@ -55,6 +57,10 @@ namespace intpr = boost::interprocess;
  *
  */
 
+#ifdef VCMI_ANDROID
+std::atomic_bool androidTestServerReadyFlag;
+#endif
+
 template <typename T> class CApplyOnCL;
 
 class CBaseForCLApply
@@ -913,8 +919,7 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
 {
 	if(ps.name.size())
 	{
-		const boost::filesystem::path aiPath =
-			VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(ps.name);
+		const boost::filesystem::path aiPath = VCMIDirs::get().fullLibraryPath("AI", ps.name);
 		if (boost::filesystem::exists(aiPath))
 			return ps.name;
 	}
@@ -940,7 +945,12 @@ void CServerHandler::startServer()
 
 	th.update();
 
+#ifdef VCMI_ANDROID
+	CAndroidVMHelper envHelper;
+	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
+#else
 	serverThread = new boost::thread(&CServerHandler::callServer, this); //runs server executable;
+#endif
 	if(verbose)
 		logNetwork->infoStream() << "Setting up thread calling server: " << th.getDiff();
 }
@@ -954,12 +964,22 @@ void CServerHandler::waitForServer()
 		startServer();
 
 	th.update();
+
 #ifndef VCMI_ANDROID
 	intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
 	while(!shared->sr->ready)
 	{
 		shared->sr->cond.wait(slock);
 	}
+#else
+	logNetwork->infoStream() << "waiting for server";
+	while (!androidTestServerReadyFlag.load())
+	{
+		logNetwork->infoStream() << "still waiting...";
+		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
+	}
+	logNetwork->infoStream() << "waiting for server finished...";
+	androidTestServerReadyFlag = false;
 #endif
 	if(verbose)
 		logNetwork->infoStream() << "Waiting for server: " << th.getDiff();
@@ -1017,6 +1037,7 @@ CServerHandler::~CServerHandler()
 
 void CServerHandler::callServer()
 {
+#ifndef VCMI_ANDROID
 	setThreadName("CServerHandler::callServer");
 	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
 	const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > \"" + logName + '\"';
@@ -1032,6 +1053,7 @@ void CServerHandler::callServer()
 		logNetwork->errorStream() << "Check " << logName << " for more info";
 		exit(1);// exit in case of error. Othervice without working server VCMI will hang
 	}
+#endif
 }
 
 CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
@@ -1062,3 +1084,23 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const
 	}
 	return ret;
 }
+
+#ifdef VCMI_ANDROID
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jobject cls)
+{
+	logNetwork->infoStream() << "Received server ready signal";
+	androidTestServerReadyFlag.store(true);
+}
+
+extern "C" JNIEXPORT bool JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jobject cls)
+{
+	logGlobal->infoStream() << "Received emergency save game request";
+	if(!LOCPLINT || !LOCPLINT->cb)
+	{
+		return false;
+	}
+
+	LOCPLINT->cb->save("Saves/_Android_Autosave");
+	return true;
+}
+#endif

+ 147 - 44
client/windows/CAdvmapInterface.cpp

@@ -116,13 +116,36 @@ void CTerrainRect::deactivate()
 
 void CTerrainRect::clickLeft(tribool down, bool previousState)
 {
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
+	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
 		return;
-	if ((down==false) || indeterminate(down))
+	if(indeterminate(down))
 		return;
 
+#ifdef VCMI_ANDROID
+	if(adventureInt->swipeEnabled)
+	{
+		if(down == true)
+		{
+			swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
+			swipeInitialMapPos = int3(adventureInt->position);
+			return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
+		}
+		else if(isSwiping) // only accept this touch if it wasn't a swipe
+		{
+			isSwiping = false;
+			return;
+		}
+	}
+	else
+	{
+#endif
+		if(down == false)
+			return;
+#ifdef VCMI_ANDROID
+	}
+#endif
 	int3 mp = whichTileIsIt();
-	if (mp.x<0 || mp.y<0 || mp.x >= LOCPLINT->cb->getMapSize().x || mp.y >= LOCPLINT->cb->getMapSize().y)
+	if(mp.x < 0 || mp.y < 0 || mp.x >= LOCPLINT->cb->getMapSize().x || mp.y >= LOCPLINT->cb->getMapSize().y)
 		return;
 
 	adventureInt->tileLClicked(mp);
@@ -130,17 +153,59 @@ void CTerrainRect::clickLeft(tribool down, bool previousState)
 
 void CTerrainRect::clickRight(tribool down, bool previousState)
 {
-	if (adventureInt->mode == EAdvMapMode::WORLD_VIEW)
+#ifdef VCMI_ANDROID
+	if(adventureInt->swipeEnabled && isSwiping)
+		return;
+#endif
+	if(adventureInt->mode == EAdvMapMode::WORLD_VIEW)
 		return;
 	int3 mp = whichTileIsIt();
 
-	if (CGI->mh->map->isInTheMap(mp) && down)
+	if(CGI->mh->map->isInTheMap(mp) && down)
 		adventureInt->tileRClicked(mp);
 }
 
 void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 {
-	int3 tHovered = whichTileIsIt(sEvent.x,sEvent.y);
+	handleHover(sEvent);
+
+#ifdef VCMI_ANDROID
+	if(!adventureInt->swipeEnabled || sEvent.state == 0)
+		return;
+
+	handleSwipeMove(sEvent);
+#endif // !VCMI_ANDROID
+}
+
+#ifdef VCMI_ANDROID
+
+void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
+{
+	if(!isSwiping)
+	{
+		// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
+		if(abs(sEvent.x - swipeInitialRealPos.x) > SwipeTouchSlop ||
+		   abs(sEvent.y - swipeInitialRealPos.y) > SwipeTouchSlop)
+		{
+			isSwiping = true;
+		}
+	}
+
+	if(isSwiping)
+	{
+		adventureInt->swipeTargetPosition.x =
+			swipeInitialMapPos.x + static_cast<si32>(swipeInitialRealPos.x - sEvent.x) / 32;
+		adventureInt->swipeTargetPosition.y =
+			swipeInitialMapPos.y + static_cast<si32>(swipeInitialRealPos.y - sEvent.y) / 32;
+		adventureInt->swipeMovementRequested = true;
+	}
+}
+
+#endif // VCMI_ANDROID
+
+void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
+{
+	int3 tHovered = whichTileIsIt(sEvent.x, sEvent.y);
 	int3 pom = adventureInt->verifyPos(tHovered);
 
 	if(tHovered != pom) //tile outside the map
@@ -150,12 +215,12 @@ void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 	}
 
 	if (pom != curHoveredTile)
+	{
 		curHoveredTile = pom;
-	else
-		return;
-
-	adventureInt->tileHovered(pom);
+		adventureInt->tileHovered(pom);
+	}
 }
+
 void CTerrainRect::hover(bool on)
 {
 	if (!on)
@@ -470,6 +535,10 @@ CAdvMapInt::CAdvMapInt():
   spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr),
   updateScreen(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
 	activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false)
+#ifdef VCMI_ANDROID
+	, swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
+	swipeTargetPosition(int3(-1, -1, -1))
+#endif
 {
   adventureInt = this;
 	pos.x = pos.y = 0;
@@ -938,42 +1007,19 @@ void CAdvMapInt::show(SDL_Surface * to)
 	}
 	++heroAnim;
 
-	int scrollSpeed = settings["adventure"]["scrollSpeed"].Float();
-	//if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
-	if((animValHitCount % (4/scrollSpeed)) == 0
-		&&  (
-			(GH.topInt() == this)
-			|| isCtrlKeyDown()
-		)
-	)
+#ifdef VCMI_ANDROID
+	if(swipeEnabled)
 	{
-		if( (scrollingDir & LEFT)   &&  (position.x>-CGI->mh->frameW) )
-			position.x--;
-
-		if( (scrollingDir & RIGHT)  &&  (position.x   <   CGI->mh->map->width - CGI->mh->tilesW + CGI->mh->frameW) )
-			position.x++;
-
-		if( (scrollingDir & UP)  &&  (position.y>-CGI->mh->frameH) )
-			position.y--;
-
-		if( (scrollingDir & DOWN)  &&  (position.y  <  CGI->mh->map->height - CGI->mh->tilesH + CGI->mh->frameH) )
-			position.y++;
-
-		if(scrollingDir)
-		{
-			setScrollingCursor(scrollingDir);
-			scrollingState = true;
-			updateScreen = true;
-			minimap.redraw();
-			if (mode == EAdvMapMode::WORLD_VIEW)
-				terrain.redraw();
-		}
-		else if(scrollingState)
-		{
-			CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-			scrollingState = false;
-		}
+		handleSwipeUpdate();
+	}
+	else
+	{
+#endif // !VCMI_ANDROID
+		handleMapScrollingUpdate();
+#ifdef VCMI_ANDROID
 	}
+#endif
+
 	for(int i = 0; i < 4; i++)
 		gems[i]->setFrame(LOCPLINT->playerID.getNum());
 	if(updateScreen)
@@ -1002,6 +1048,59 @@ void CAdvMapInt::show(SDL_Surface * to)
 	statusbar.showAll(to);
 }
 
+void CAdvMapInt::handleMapScrollingUpdate()
+{
+	int scrollSpeed = settings["adventure"]["scrollSpeed"].Float();
+	//if advmap needs updating AND (no dialog is shown OR ctrl is pressed)
+	if((animValHitCount % (4 / scrollSpeed)) == 0
+	   && ((GH.topInt() == this) || isCtrlKeyDown()))
+	{
+		if((scrollingDir & LEFT) && (position.x > -CGI->mh->frameW))
+			position.x--;
+
+		if((scrollingDir & RIGHT) && (position.x < CGI->mh->map->width - CGI->mh->tilesW + CGI->mh->frameW))
+			position.x++;
+
+		if((scrollingDir & UP) && (position.y > -CGI->mh->frameH))
+			position.y--;
+
+		if((scrollingDir & DOWN) && (position.y < CGI->mh->map->height - CGI->mh->tilesH + CGI->mh->frameH))
+			position.y++;
+
+		if(scrollingDir)
+		{
+			setScrollingCursor(scrollingDir);
+			scrollingState = true;
+			updateScreen = true;
+			minimap.redraw();
+			if(mode == EAdvMapMode::WORLD_VIEW)
+				terrain.redraw();
+		}
+		else if(scrollingState)
+		{
+			CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+			scrollingState = false;
+		}
+	}
+}
+
+#ifdef VCMI_ANDROID
+
+void CAdvMapInt::handleSwipeUpdate()
+{
+	if(swipeMovementRequested)
+	{
+		position.x = swipeTargetPosition.x;
+		position.y = swipeTargetPosition.y;
+		CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
+		updateScreen = true;
+		minimap.redraw();
+		swipeMovementRequested = false;
+	}
+}
+
+#endif
+
 void CAdvMapInt::selectionChanged()
 {
 	const CGTownInstance *to = LOCPLINT->towns[townList.getSelectedIndex()];
@@ -1315,6 +1414,10 @@ void CAdvMapInt::select(const CArmedInstance *sel, bool centerView /*= true*/)
 
 void CAdvMapInt::mouseMoved( const SDL_MouseMotionEvent & sEvent )
 {
+#ifdef VCMI_ANDROID
+	if(swipeEnabled)
+		return;
+#endif
 	// adventure map scrolling with mouse
 	// currently disabled in world view mode (as it is in OH3), but should work correctly if mode check is removed
 	if(!isCtrlKeyDown() &&  isActive() && mode == EAdvMapMode::NORMAL)

+ 22 - 0
client/windows/CAdvmapInterface.h

@@ -56,6 +56,16 @@ class CTerrainRect
 	SDL_Surface * fadeSurface;
 	EMapAnimRedrawStatus lastRedrawStatus;
 	CFadeAnimation * fadeAnim;
+
+	int3 swipeInitialMapPos;
+	int3 swipeInitialRealPos;
+	bool isSwiping;
+	static constexpr float SwipeTouchSlop = 16.0f;
+
+	void handleHover(const SDL_MouseMotionEvent &sEvent);
+#ifdef VCMI_ANDROID
+	void handleSwipeMove(const SDL_MouseMotionEvent &sEvent);
+#endif // VCMI_ANDROID
 public:
 	int tilesw, tilesh; //width and height of terrain to blit in tiles
 	int3 curHoveredTile;
@@ -80,6 +90,7 @@ public:
 	/// animates view by caching current surface and crossfading it with normal screen
 	void fadeFromCurrentView();
 	bool needsAnimUpdate();
+
 };
 
 /// Resources bar which shows information about how many gold, crystals,... you have
@@ -121,6 +132,11 @@ public:
 	enum{LEFT=1, RIGHT=2, UP=4, DOWN=8};
 	ui8 scrollingDir; //uses enum: LEFT RIGHT, UP, DOWN
 	bool scrollingState;
+#ifdef VCMI_ANDROID
+	bool swipeEnabled;
+	bool swipeMovementRequested;
+	int3 swipeTargetPosition;
+#endif // !VCMI_ANDROID
 
 	enum{NA, INGAME, WAITING} state;
 
@@ -242,6 +258,12 @@ public:
 
 	/// changes current adventure map mode; used to switch between default view and world view; scale is ignored if EAdvMapMode == NORMAL
 	void changeMode(EAdvMapMode newMode, float newScale = 0.36f);
+
+	void handleMapScrollingUpdate();
+#ifdef VCMI_ANDROID
+	void handleSwipeUpdate();
+#endif
+
 };
 
 extern CAdvMapInt *adventureInt;

+ 6 - 2
config/schemas/settings.json

@@ -17,7 +17,7 @@
 			"type" : "object",
 			"default": {},
 			"additionalProperties" : false,
-			"required" : [ "playerName", "showfps", "music", "sound", "encoding" ],
+			"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe" ],
 			"properties" : {
 				"playerName" : {
 					"type":"string",
@@ -38,7 +38,11 @@
 				"encoding" : {
 					"type" : "string",
 					"default" : "CP1252"
-				}
+				},
+				"swipe" : {
+					"type" : "boolean",
+					"default" : false
+				},
 			}
 		},
 		"video" : {

+ 4 - 0
include/vstd/CLoggerBase.h

@@ -38,7 +38,11 @@ namespace ELogLevel
 			case ERROR:
 				return "error";
 			default:
+#ifdef NO_STD_TOSTRING
+				return "invalid";
+#else
 				return std::string("invalid (") + std::to_string(level) + ")";
+#endif
 		}
 	}
 }

+ 105 - 0
lib/CAndroidVMHelper.cpp

@@ -0,0 +1,105 @@
+/*
+ * CAndroidVMHelper.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
+ *
+ */
+#include "CAndroidVMHelper.h"
+
+static JavaVM * vmCache = nullptr;
+
+/// cached java classloader so that we can find our classes from other threads
+static jobject vcmiClassLoader;
+static jmethodID vcmiFindClassMethod;
+
+void CAndroidVMHelper::cacheVM(JNIEnv * env)
+{
+	env->GetJavaVM(&vmCache);
+}
+
+void CAndroidVMHelper::cacheVM(JavaVM * vm)
+{
+	vmCache = vm;
+}
+
+CAndroidVMHelper::CAndroidVMHelper()
+{
+	auto res = vmCache->GetEnv((void **) &envPtr, JNI_VERSION_1_1);
+	if(res == JNI_EDETACHED)
+	{
+		auto attachRes = vmCache->AttachCurrentThread(&envPtr, nullptr);
+		if(attachRes == JNI_OK)
+		{
+			detachInDestructor = true; // only detach if we actually attached env
+		}
+	}
+	else
+	{
+		detachInDestructor = false;
+	}
+}
+
+CAndroidVMHelper::~CAndroidVMHelper()
+{
+	if(envPtr && detachInDestructor)
+	{
+		vmCache->DetachCurrentThread();
+		envPtr = nullptr;
+	}
+}
+
+JNIEnv * CAndroidVMHelper::get()
+{
+	return envPtr;
+}
+
+jclass CAndroidVMHelper::findClassloadedClass(const std::string & name)
+{
+	auto env = get();
+	return static_cast<jclass>(env->CallObjectMethod(vcmiClassLoader, vcmiFindClassMethod,
+		env->NewStringUTF(name.c_str())));;
+}
+
+void CAndroidVMHelper::callStaticVoidMethod(const std::string & cls, const std::string & method,
+											bool classloaded /*=false*/)
+{
+	auto env = get();
+	auto javaHelper = findClass(cls, classloaded);
+	auto methodId = env->GetStaticMethodID(javaHelper, method.c_str(), "()V");
+	env->CallStaticVoidMethod(javaHelper, methodId);
+}
+
+std::string CAndroidVMHelper::callStaticStringMethod(const std::string & cls, const std::string & method,
+													 bool classloaded /*=false*/)
+{
+	auto env = get();
+	auto javaHelper = findClass(cls, classloaded);
+	auto methodId = env->GetStaticMethodID(javaHelper, method.c_str(), "()Ljava/lang/String;");
+	jstring jres = static_cast<jstring>(env->CallStaticObjectMethod(javaHelper, methodId));
+	return std::string(env->GetStringUTFChars(jres, nullptr));
+}
+
+jclass CAndroidVMHelper::findClass(const std::string & name, bool classloaded)
+{
+	if(classloaded)
+	{
+		return findClassloadedClass(name);
+	}
+	return get()->FindClass(name.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jobject * cls)
+{
+	CAndroidVMHelper::cacheVM(baseEnv);
+	CAndroidVMHelper envHelper;
+	auto env = envHelper.get();
+	auto anyVCMIClass = env->FindClass(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS);
+	jclass classClass = env->GetObjectClass(anyVCMIClass);
+	auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
+	auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader", "()Ljava/lang/ClassLoader;");
+	vcmiClassLoader = (jclass) env->NewGlobalRef(env->CallObjectMethod(anyVCMIClass, getClassLoaderMethod));
+	vcmiFindClassMethod = env->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+}

+ 41 - 0
lib/CAndroidVMHelper.h

@@ -0,0 +1,41 @@
+/*
+ * CAndroidVMHelper.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 <jni.h>
+#include <string>
+
+/// helper class that allows access to java vm to communicate with java code from native
+class CAndroidVMHelper
+{
+	JNIEnv * envPtr;
+	bool detachInDestructor;
+
+	jclass findClass(const std::string & name, bool classloaded);
+
+public:
+	CAndroidVMHelper();
+
+	~CAndroidVMHelper();
+
+	JNIEnv * get();
+
+	jclass findClassloadedClass(const std::string & name);
+
+	void callStaticVoidMethod(const std::string & cls, const std::string & method, bool classloaded = false);
+
+	std::string callStaticStringMethod(const std::string & cls, const std::string & method, bool classloaded = false);
+
+	static void cacheVM(JNIEnv * env);
+
+	static void cacheVM(JavaVM * vm);
+
+	static constexpr const char * NATIVE_METHODS_DEFAULT_CLASS = "eu/vcmi/vcmi/NativeMethods";
+};

+ 53 - 53
lib/CGameInterface.cpp

@@ -5,13 +5,21 @@
 #include "VCMIDirs.h"
 
 #ifdef VCMI_WINDOWS
-	#include <windows.h> //for .dll libs
-#else
-	#include <dlfcn.h>
+#include <windows.h> //for .dll libs
+#elif !defined VCMI_ANDROID
+#include <dlfcn.h>
 #endif
+
 #include "serializer/BinaryDeserializer.h"
 #include "serializer/BinarySerializer.h"
 
+#ifdef VCMI_ANDROID
+
+#include "AI/VCAI/VCAI.h"
+#include "AI/BattleAI/BattleAI.h"
+
+#endif
+
 /*
  * CGameInterface.cpp, part of VCMI engine
  *
@@ -22,50 +30,22 @@
  *
  */
 
-#ifdef VCMI_ANDROID
-// we can't use shared libraries on Android so here's a hack
-extern "C" DLL_EXPORT void VCAI_GetAiName(char* name);
-extern "C" DLL_EXPORT void VCAI_GetNewAI(std::shared_ptr<CGlobalAI> &out);
-
-extern "C" DLL_EXPORT void StupidAI_GetAiName(char* name);
-extern "C" DLL_EXPORT void StupidAI_GetNewBattleAI(std::shared_ptr<CGlobalAI> &out);
-
-extern "C" DLL_EXPORT void BattleAI_GetAiName(char* name);
-extern "C" DLL_EXPORT void BattleAI_GetNewBattleAI(std::shared_ptr<CBattleGameInterface> &out);
-#endif
-
 template<typename rett>
-std::shared_ptr<rett> createAny(const boost::filesystem::path& libpath, const std::string& methodName)
+std::shared_ptr<rett> createAny(const boost::filesystem::path & libpath, const std::string & methodName)
 {
-	typedef void(*TGetAIFun)(std::shared_ptr<rett>&);
-	typedef void(*TGetNameFun)(char*);
+#ifdef VCMI_ANDROID
+	// android currently doesn't support loading libs dynamically, so the access to the known libraries
+	// is possible only via specializations of this template
+	throw std::runtime_error("Could not resolve ai library " + libpath.generic_string());
+#else
+	typedef void(* TGetAIFun)(std::shared_ptr<rett> &);
+	typedef void(* TGetNameFun)(char *);
 
 	char temp[150];
 
 	TGetAIFun getAI = nullptr;
 	TGetNameFun getName = nullptr;
 
-#ifdef VCMI_ANDROID
-	// this is awful but it seems using shared libraries on some devices is even worse
-	const std::string filename = libpath.filename().string();
-	if (filename == "libVCAI.so")
-	{
-		getName = (TGetNameFun)VCAI_GetAiName;
-		getAI = (TGetAIFun)VCAI_GetNewAI;
-	}
-	else if (filename == "libStupidAI.so")
-	{
-		getName = (TGetNameFun)StupidAI_GetAiName;
-		getAI = (TGetAIFun)StupidAI_GetNewBattleAI;
-	}
-	else if (filename == "libBattleAI.so")
-	{
-		getName = (TGetNameFun)BattleAI_GetAiName;
-		getAI = (TGetAIFun)BattleAI_GetNewBattleAI;
-	}
-	else
-		throw std::runtime_error("Don't know what to do with " + libpath.string() + " and method " + methodName);
-#else // !VCMI_ANDROID
 #ifdef VCMI_WINDOWS
 	HMODULE dll = LoadLibraryW(libpath.c_str());
 	if (dll)
@@ -83,6 +63,7 @@ std::shared_ptr<rett> createAny(const boost::filesystem::path& libpath, const st
 	else
 		logGlobal->errorStream() << "Error: " << dlerror();
 #endif // VCMI_WINDOWS
+
 	if (!dll)
 	{
 		logGlobal->errorStream() << "Cannot open dynamic library ("<<libpath<<"). Throwing...";
@@ -98,7 +79,6 @@ std::shared_ptr<rett> createAny(const boost::filesystem::path& libpath, const st
 #endif
 		throw std::runtime_error("Cannot find method " + methodName);
 	}
-#endif // VCMI_ANDROID
 
 	getName(temp);
 	logGlobal->infoStream() << "Loaded " << temp;
@@ -109,14 +89,31 @@ std::shared_ptr<rett> createAny(const boost::filesystem::path& libpath, const st
 		logGlobal->error("Cannot get AI!");
 
 	return ret;
+#endif //!VCMI_ANDROID
 }
 
+#ifdef VCMI_ANDROID
+
+template<>
+std::shared_ptr<CGlobalAI> createAny(const boost::filesystem::path & libpath, const std::string & methodName)
+{
+	return std::make_shared<VCAI>();
+}
+
+template<>
+std::shared_ptr<CBattleGameInterface> createAny(const boost::filesystem::path & libpath, const std::string & methodName)
+{
+	return std::make_shared<CBattleAI>();
+}
+
+#endif
+
 template<typename rett>
-std::shared_ptr<rett> createAnyAI(std::string dllname, const std::string& methodName)
+std::shared_ptr<rett> createAnyAI(std::string dllname, const std::string & methodName)
 {
 	logGlobal->infoStream() << "Opening " << dllname;
-	const boost::filesystem::path filePath =
-		VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(dllname);
+
+	const boost::filesystem::path filePath = VCMIDirs::get().fullLibraryPath("AI", dllname);
 	auto ret = createAny<rett>(filePath, methodName);
 	ret->dllName = std::move(dllname);
 	return ret;
@@ -127,7 +124,7 @@ std::shared_ptr<CGlobalAI> CDynLibHandler::getNewAI(std::string dllname)
 	return createAnyAI<CGlobalAI>(dllname, "GetNewAI");
 }
 
-std::shared_ptr<CBattleGameInterface> CDynLibHandler::getNewBattleAI(std::string dllname )
+std::shared_ptr<CBattleGameInterface> CDynLibHandler::getNewBattleAI(std::string dllname)
 {
 	return createAnyAI<CBattleGameInterface>(dllname, "GetNewBattleAI");
 }
@@ -137,9 +134,10 @@ std::shared_ptr<CScriptingModule> CDynLibHandler::getNewScriptingModule(std::str
 	return createAny<CScriptingModule>(dllname, "GetNewModule");
 }
 
-BattleAction CGlobalAI::activeStack( const CStack * stack )
+BattleAction CGlobalAI::activeStack(const CStack * stack)
 {
-	BattleAction ba; ba.actionType = Battle::DEFEND;
+	BattleAction ba;
+	ba.actionType = Battle::DEFEND;
 	ba.stackNumber = stack->ID;
 	return ba;
 }
@@ -159,7 +157,8 @@ void CAdventureAI::battleCatapultAttacked(const CatapultAttack & ca)
 	battleAI->battleCatapultAttacked(ca);
 }
 
-void CAdventureAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side)
+void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile,
+							   const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side)
 {
 	assert(!battleAI);
 	assert(cbc);
@@ -173,7 +172,7 @@ void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> &
 	battleAI->battleStacksAttacked(bsa);
 }
 
-void CAdventureAI::actionStarted(const BattleAction &action)
+void CAdventureAI::actionStarted(const BattleAction & action)
 {
 	battleAI->actionStarted(action);
 }
@@ -183,7 +182,7 @@ void CAdventureAI::battleNewRoundFirst(int round)
 	battleAI->battleNewRoundFirst(round);
 }
 
-void CAdventureAI::actionFinished(const BattleAction &action)
+void CAdventureAI::actionFinished(const BattleAction & action)
 {
 	battleAI->actionFinished(action);
 }
@@ -213,23 +212,24 @@ void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex>
 	battleAI->battleStackMoved(stack, dest, distance);
 }
 
-void CAdventureAI::battleAttack(const BattleAttack *ba)
+void CAdventureAI::battleAttack(const BattleAttack * ba)
 {
 	battleAI->battleAttack(ba);
 }
 
-void CAdventureAI::battleSpellCast(const BattleSpellCast *sc)
+void CAdventureAI::battleSpellCast(const BattleSpellCast * sc)
 {
 	battleAI->battleSpellCast(sc);
 }
 
-void CAdventureAI::battleEnd(const BattleResult *br)
+void CAdventureAI::battleEnd(const BattleResult * br)
 {
 	battleAI->battleEnd(br);
 	battleAI.reset();
 }
 
-void CAdventureAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain, bool tentHeal, si32 lifeDrainFrom)
+void CAdventureAI::battleStacksHealedRes(const std::vector<std::pair<ui32, ui32> > & healedStacks, bool lifeDrain,
+										 bool tentHeal, si32 lifeDrainFrom)
 {
 	battleAI->battleStacksHealedRes(healedStacks, lifeDrain, tentHeal, lifeDrainFrom);
 }

+ 6 - 0
lib/CMakeLists.txt

@@ -127,6 +127,8 @@ set(lib_SRCS
 		registerTypes/TypesMapObjects3.cpp
 		registerTypes/TypesPregamePacks.cpp
 		registerTypes/TypesServerPacks.cpp
+		
+		${VCMILIB_ADDITIONAL_SOURCES}
 )
 
 set(lib_HEADERS
@@ -180,6 +182,10 @@ if(WIN32)
 	set_target_properties(vcmi PROPERTIES OUTPUT_NAME VCMI_lib)
 endif()
 
+if (ANDROID)
+	return()
+endif()
+
 set_target_properties(vcmi PROPERTIES ${PCH_PROPERTIES})
 cotire(vcmi)
 

+ 49 - 8
lib/VCMIDirs.cpp

@@ -15,6 +15,11 @@ namespace bfs = boost::filesystem;
 
 bfs::path IVCMIDirs::userSavePath() const { return userDataPath() / "Saves"; }
 
+bfs::path IVCMIDirs::fullLibraryPath(const std::string &desiredFolder, const std::string &baseLibName) const
+{
+	return libraryPath() / desiredFolder / libraryName(baseLibName);
+}
+
 void IVCMIDirs::init()
 {
 	// TODO: Log errors
@@ -24,6 +29,11 @@ void IVCMIDirs::init()
 	bfs::create_directories(userSavePath());
 }
 
+#ifdef VCMI_ANDROID
+#include "CAndroidVMHelper.h"
+
+#endif
+
 #ifdef VCMI_WINDOWS
 
 #ifdef __MINGW32__
@@ -532,26 +542,55 @@ bfs::path VCMIDirsXDG::libraryPath() const { return M_LIB_DIR; }
 bfs::path VCMIDirsXDG::binaryPath() const { return M_BIN_DIR; }
 
 std::string VCMIDirsXDG::libraryName(const std::string& basename) const { return "lib" + basename + ".so"; }
+
 #ifdef VCMI_ANDROID
+
 class VCMIDirsAndroid : public VCMIDirsXDG
 {
+	std::string basePath;
+	std::string internalPath;
+	std::string nativePath;
 public:
-	boost::filesystem::path userDataPath() const override;
-	boost::filesystem::path userCachePath() const override;
-	boost::filesystem::path userConfigPath() const override;
+	bfs::path fullLibraryPath(const std::string & desiredFolder, const std::string & baseLibName) const override;
+	bfs::path libraryPath() const override;
+	bfs::path userDataPath() const override;
+	bfs::path userCachePath() const override;
+	bfs::path userConfigPath() const override;
 
-	std::vector<boost::filesystem::path> dataPaths() const override;
+	std::vector<bfs::path> dataPaths() const override;
+
+	void init() override;
 };
 
-// on Android HOME will be set to something like /sdcard/data/Android/is.xyz.vcmi/files/
-bfs::path VCMIDirsAndroid::userDataPath() const { return getenv("HOME"); }
+bfs::path VCMIDirsAndroid::libraryPath() const { return nativePath; }
+bfs::path VCMIDirsAndroid::userDataPath() const { return basePath; }
 bfs::path VCMIDirsAndroid::userCachePath() const { return userDataPath() / "cache"; }
 bfs::path VCMIDirsAndroid::userConfigPath() const { return userDataPath() / "config"; }
 
+bfs::path VCMIDirsAndroid::fullLibraryPath(const std::string & desiredFolder, const std::string & baseLibName) const
+{
+	// ignore passed folder (all libraries in android are dumped into a single folder)
+	return libraryPath() / libraryName(baseLibName);
+}
+
 std::vector<bfs::path> VCMIDirsAndroid::dataPaths() const
 {
-	return std::vector<bfs::path>(1, userDataPath());
+	std::vector<bfs::path> paths(2);
+	paths.push_back(internalPath);
+	paths.push_back(userDataPath());
+	return paths;
+}
+
+void VCMIDirsAndroid::init()
+{
+	// asks java code to retrieve needed paths from environment
+	CAndroidVMHelper envHelper;
+	basePath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "dataRoot");
+	internalPath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "internalDataRoot");
+	nativePath = envHelper.callStaticStringMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "nativePath");
+	IVCMIDirs::init();
 }
+
 #endif // VCMI_ANDROID
 #endif // VCMI_APPLE, VCMI_XDG
 #endif // VCMI_WINDOWS, VCMI_UNIX
@@ -569,7 +608,8 @@ namespace VCMIDirs
 			static VCMIDirsXDG singleton;
 		#elif defined(VCMI_APPLE)
 			static VCMIDirsOSX singleton;
-		#endif
+        #endif
+
 		static bool initialized = false;
 		if (!initialized)
 		{
@@ -584,3 +624,4 @@ namespace VCMIDirs
 		return singleton;
 	}
 }
+

+ 35 - 30
lib/VCMIDirs.h

@@ -11,48 +11,53 @@
 
 class DLL_LINKAGE IVCMIDirs
 {
-	public:
-		// Path to user-specific data directory
-		virtual boost::filesystem::path userDataPath() const = 0;
+public:
+	// Path to user-specific data directory
+	virtual boost::filesystem::path userDataPath() const = 0;
 
-		// Path to "cache" directory, can be used for any non-essential files
-		virtual boost::filesystem::path userCachePath() const = 0;
+	// Path to "cache" directory, can be used for any non-essential files
+	virtual boost::filesystem::path userCachePath() const = 0;
 
-		// Path to writeable directory with user configs
-		virtual boost::filesystem::path userConfigPath() const = 0;
+	// Path to writeable directory with user configs
+	virtual boost::filesystem::path userConfigPath() const = 0;
 
-		// Path to saved games
-		virtual boost::filesystem::path userSavePath() const;
+	// Path to saved games
+	virtual boost::filesystem::path userSavePath() const;
 
-		// Paths to global system-wide data directories. First items have higher priority
-		virtual std::vector<boost::filesystem::path> dataPaths() const = 0;
+	// Paths to global system-wide data directories. First items have higher priority
+	virtual std::vector<boost::filesystem::path> dataPaths() const = 0;
 
-		// Full path to client executable, including server name (e.g. /usr/bin/vcmiclient)
-		virtual boost::filesystem::path clientPath() const = 0;
+	// Full path to client executable, including server name (e.g. /usr/bin/vcmiclient)
+	virtual boost::filesystem::path clientPath() const = 0;
 
-		// Full path to server executable, including server name (e.g. /usr/bin/vcmiserver)
-		virtual boost::filesystem::path serverPath() const = 0;
+	// Full path to server executable, including server name (e.g. /usr/bin/vcmiserver)
+	virtual boost::filesystem::path serverPath() const = 0;
 
-		// Path where vcmi libraries can be found (in AI and Scripting subdirectories)
-		virtual boost::filesystem::path libraryPath() const = 0;
+	// Path where vcmi libraries can be found (in AI and Scripting subdirectories)
+	virtual boost::filesystem::path libraryPath() const = 0;
 
-		// Path where vcmi binaries can be found
-		virtual boost::filesystem::path binaryPath() const = 0;
+	// absolute path to passed library (needed due to android libs being placed in single dir, not respecting original lib dirs;
+	// by default just concats libraryPath, given folder and libraryName
+	virtual boost::filesystem::path fullLibraryPath(const std::string & desiredFolder,
+													const std::string & baseLibName) const;
 
-		// Returns system-specific name for dynamic libraries ( StupidAI => "libStupidAI.so" or "StupidAI.dll")
-		virtual std::string libraryName(const std::string& basename) const = 0;
-		// virtual std::string libraryName(const char* basename) const = 0; ?
-		// virtual std::string libraryName(std::string&& basename) const = 0;?
+	// Path where vcmi binaries can be found
+	virtual boost::filesystem::path binaryPath() const = 0;
 
-		virtual std::string genHelpString() const = 0;
-		
-		// Creates not existed, but required directories.
-		// Updates directories what change name/path between versions.
-		// Function called automatically.
-		virtual void init();
+	// Returns system-specific name for dynamic libraries ( StupidAI => "libStupidAI.so" or "StupidAI.dll")
+	virtual std::string libraryName(const std::string & basename) const = 0;
+	// virtual std::string libraryName(const char* basename) const = 0; ?
+	// virtual std::string libraryName(std::string&& basename) const = 0;?
+
+	virtual std::string genHelpString() const = 0;
+
+	// Creates not existed, but required directories.
+	// Updates directories what change name/path between versions.
+	// Function called automatically.
+	virtual void init();
 };
 
 namespace VCMIDirs
 {
-	extern DLL_LINKAGE const IVCMIDirs& get();
+	extern DLL_LINKAGE const IVCMIDirs & get();
 }

+ 3 - 2
lib/logging/CLogger.cpp

@@ -345,8 +345,8 @@ void CLogConsoleTarget::write(const LogRecord & record)
 	std::string message = formatter.format(record);
 
 #ifdef VCMI_ANDROID
-	__android_log_write(ELogLevel::toAndroid(record.level), "VCMI", message.c_str());
-#endif
+    __android_log_write(ELogLevel::toAndroid(record.level), ("VCMI-" + record.domain.getName()).c_str(), message.c_str());
+#else
 
 	const bool printToStdErr = record.level >= ELogLevel::WARN;
 	if(console)
@@ -364,6 +364,7 @@ void CLogConsoleTarget::write(const LogRecord & record)
 		else
 			std::cout << message << std::endl;
 	}
+#endif
 }
 
 bool CLogConsoleTarget::isColoredOutputEnabled() const { return coloredOutputEnabled; }

+ 4 - 1
server/CMakeLists.txt

@@ -12,6 +12,10 @@ set(server_SRCS
 		NetPacksServer.cpp
 )
 
+if(ANDROID) # android needs client/server to be libraries, not executables, so we can't reuse the build part of this script
+	return()
+endif()
+
 add_executable(vcmiserver ${server_SRCS})
 
 target_link_libraries(vcmiserver vcmi ${Boost_LIBRARIES} ${SYSTEM_LIBS})
@@ -27,4 +31,3 @@ cotire(vcmiserver)
 if (NOT APPLE) # Already inside vcmiclient bundle
 	install(TARGETS vcmiserver DESTINATION ${BIN_DIR})
 endif()
-

+ 34 - 9
server/CVCMIServer.cpp

@@ -19,7 +19,9 @@
 #include "../lib/StartInfo.h"
 #include "../lib/mapping/CMap.h"
 #include "../lib/rmg/CMapGenOptions.h"
-#ifndef VCMI_ANDROID
+#ifdef VCMI_ANDROID
+#include "lib/CAndroidVMHelper.h"
+#else
 #include "../lib/Interprocess.h"
 #endif
 #include "../lib/VCMI_Lib.h"
@@ -417,18 +419,24 @@ void CVCMIServer::start()
 #ifndef VCMI_ANDROID
 	sr->setToTrueAndNotify();
 	delete mr;
+#else
+	{ // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm
+		CAndroidVMHelper envHelper;
+		envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
+		logNetwork->info("Sending server ready message to client");
+	}
 #endif
 
 	acc.join();
 	if (error)
 	{
-		logNetwork->warnStream()<<"Got connection but there is an error " << error;
+		logNetwork->warnStream() << "Got connection but there is an error " << error;
 		return;
 	}
 	logNetwork->info("We've accepted someone... ");
-	firstConnection = new CConnection(s,NAME);
+	firstConnection = new CConnection(s, NAME);
 	logNetwork->info("Got connection!");
-	while(!end2)
+	while (!end2)
 	{
 		ui8 mode;
 		*firstConnection >> mode;
@@ -490,6 +498,8 @@ void CVCMIServer::loadGame()
 	gh.run(true);
 }
 
+
+
 static void handleCommandOptions(int argc, char *argv[])
 {
 	namespace po = boost::program_options;
@@ -567,15 +577,16 @@ int main(int argc, char** argv)
 	// to log stacktrace
 	#if defined(__GNUC__) && !defined (__MINGW32__) && !defined(VCMI_ANDROID)
 	signal(SIGSEGV, handleLinuxSignal);
-	#endif
+    #endif
 
 	console = new CConsoleHandler;
 	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
 	logConfig.configureDefault();
 	logGlobal->info(NAME);
 
+
 	handleCommandOptions(argc, argv);
-	if(cmdLineOptions.count("port"))
+	if (cmdLineOptions.count("port"))
 		port = cmdLineOptions["port"].as<int>();
 	logNetwork->info("Port %d will be used.", port);
 
@@ -592,18 +603,18 @@ int main(int argc, char** argv)
 
 		try
 		{
-			while(!end2)
+			while (!end2)
 			{
 				server.start();
 			}
 			io_service.run();
 		}
-		catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
+		catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
 		{
 			logNetwork->error(e.what());
 			end2 = true;
 		}
-		catch(...)
+		catch (...)
 		{
 			handleException();
 		}
@@ -615,9 +626,23 @@ int main(int argc, char** argv)
 		//and return non-zero status so client can detect error
 		throw;
 	}
+#ifdef VCMI_ANDROID
+	CAndroidVMHelper envHelper;
+	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
+#endif
 	delete VLC;
 	VLC = nullptr;
 	CResourceHandler::clear();
 
   return 0;
 }
+
+#ifdef VCMI_ANDROID
+
+void CVCMIServer::create()
+{
+	const char * foo[1] = {"android-server"};
+	main(1, const_cast<char **>(foo));
+}
+
+#endif

+ 5 - 0
server/CVCMIServer.h

@@ -57,8 +57,13 @@ public:
 	void newGame();
 	void loadGame();
 	void newPregame();
+
+#ifdef VCMI_ANDROID
+    static void create();
+#endif
 };
 
+struct StartInfo;
 class CPregameServer
 {
 public: