Browse Source

Merge vcmi/beta into vcmi/develop

Ivan Savenko 2 years ago
parent
commit
57ee9a9bf3

+ 0 - 3
CI/ios/post_pack.sh

@@ -1,3 +0,0 @@
-#!/usr/bin/env bash
-
-"$1/ios/zip2ipa.sh" "$2"

+ 1 - 0
CMakeLists.txt

@@ -729,6 +729,7 @@ elseif(APPLE_MACOS AND NOT ENABLE_MONOLITHIC_INSTALL)
 	add_subdirectory(osx)
 elseif(APPLE_IOS)
 	set(CPACK_GENERATOR ZIP)
+	set(CPACK_ARCHIVE_FILE_EXTENSION ipa)
 	set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF)
 	set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_CURRENT_BINARY_DIR};${CMAKE_PROJECT_NAME};app;/")
 else()

+ 11 - 1
client/CMT.cpp

@@ -376,7 +376,8 @@ int main(int argc, char * argv[])
 		SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
 		#endif // VCMI_ANDROID
 
-		GH.mainFPSmng->init(); //(!)init here AFTER SDL_Init() while using SDL for FPS management
+		//(!)init here AFTER SDL_Init() while using SDL for FPS management
+		GH.init();
 
 		SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
 
@@ -432,11 +433,20 @@ int main(int argc, char * argv[])
 		CCS->musich->setVolume((ui32)settings["general"]["music"].Float());
 		logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
 	}
+
 #ifdef VCMI_MAC
 	// Ctrl+click should be treated as a right click on Mac OS X
 	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
 #endif
 
+#ifdef SDL_HINT_MOUSE_TOUCH_EVENTS
+	if(GH.isPointerRelativeMode)
+	{
+		SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
+		SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
+	}
+#endif
+
 #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(init);

+ 1 - 2
client/CMakeLists.txt

@@ -263,8 +263,7 @@ elseif(APPLE_IOS)
 		set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
 
 		# workaround to prevent CMAKE_SKIP_PRECOMPILE_HEADERS being added as compile flag
-		# add max version condition when https://gitlab.kitware.com/cmake/cmake/-/merge_requests/7562 is merged
-		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0")
+		if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.22.0" AND CMAKE_VERSION VERSION_LESS "3.25.0")
 			set_source_files_properties(${XCODE_RESOURCE_PATH} PROPERTIES LANGUAGE CXX)
 		endif()
 	endforeach()

+ 4 - 4
client/Client.cpp

@@ -775,14 +775,14 @@ void CClient::removeGUI()
 }
 
 #ifdef VCMI_ANDROID
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received clientSetupJNI");
 
 	CAndroidVMHelper::cacheVM(env);
 }
 
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received server closed signal");
 	if (CSH) {
@@ -790,13 +790,13 @@ extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerCl
 	}
 }
 
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jclass cls)
 {
 	logNetwork->info("Received server ready signal");
 	androidTestServerReadyFlag.store(true);
 }
 
-extern "C" JNIEXPORT bool JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jobject cls)
+extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls)
 {
 	logGlobal->info("Received emergency save game request");
 	if(!LOCPLINT || !LOCPLINT->cb)

+ 148 - 26
client/gui/CGuiHandler.cpp

@@ -79,6 +79,13 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
 	processList(CIntObject::TEXTINPUT,activityFlag,&textInterested,cb);
 }
 
+void CGuiHandler::init()
+{
+	mainFPSmng->init();
+	isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
+	pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
+}
+
 void CGuiHandler::handleElementActivate(CIntObject * elem, ui16 activityFlag)
 {
 	processLists(activityFlag,[&](std::list<CIntObject*> * lst){
@@ -207,7 +214,79 @@ void CGuiHandler::handleEvents()
 	}
 }
 
-void CGuiHandler::handleCurrentEvent(const SDL_Event & current )
+void CGuiHandler::convertTouchToMouse(SDL_Event * current)
+{
+	int rLogicalWidth, rLogicalHeight;
+
+	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
+
+	int adjustedMouseY = (int)(current->tfinger.y * rLogicalHeight);
+	int adjustedMouseX = (int)(current->tfinger.x * rLogicalWidth);
+
+	current->button.x = adjustedMouseX;
+	current->motion.x = adjustedMouseX;
+	current->button.y = adjustedMouseY;
+	current->motion.y = adjustedMouseY;
+}
+
+void CGuiHandler::fakeMoveCursor(float dx, float dy)
+{
+	int x, y, w, h;
+
+	SDL_Event event;
+	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	sme.state = SDL_GetMouseState(&x, &y);
+	SDL_GetWindowSize(mainWindow, &w, &h);
+
+	sme.x = CCS->curh->position().x + (int)(GH.pointerSpeedMultiplier * w * dx);
+	sme.y = CCS->curh->position().y + (int)(GH.pointerSpeedMultiplier * h * dy);
+
+	vstd::abetween(sme.x, 0, w);
+	vstd::abetween(sme.y, 0, h);
+
+	event.motion = sme;
+	SDL_PushEvent(&event);
+}
+
+void CGuiHandler::fakeMouseMove()
+{
+	fakeMoveCursor(0, 0);
+}
+
+void CGuiHandler::fakeMouseButtonEventRelativeMode(bool down, bool right)
+{
+	SDL_Event event;
+	SDL_MouseButtonEvent sme = {SDL_MOUSEBUTTONDOWN, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+	if(!down)
+	{
+		sme.type = SDL_MOUSEBUTTONUP;
+	}
+
+	sme.button = right ? SDL_BUTTON_RIGHT : SDL_BUTTON_LEFT;
+
+	sme.x = CCS->curh->position().x;
+	sme.y = CCS->curh->position().y;
+
+	float xScale, yScale;
+	int w, h, rLogicalWidth, rLogicalHeight;
+
+	SDL_GetWindowSize(mainWindow, &w, &h);
+	SDL_RenderGetLogicalSize(mainRenderer, &rLogicalWidth, &rLogicalHeight);
+	SDL_RenderGetScale(mainRenderer, &xScale, &yScale);
+
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+	CSDL_Ext::warpMouse(
+		(int)(sme.x * xScale) + (w - rLogicalWidth * xScale) / 2,
+		(int)(sme.y * yScale + (h - rLogicalHeight * yScale) / 2));
+	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
+
+	event.button = sme;
+	SDL_PushEvent(&event);
+}
+
+void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 {
 	if(current.type == SDL_KEYDOWN || current.type == SDL_KEYUP)
 	{
@@ -344,21 +423,77 @@ void CGuiHandler::handleCurrentEvent(const SDL_Event & current )
 			it->textEdited(current.edit);
 		}
 	}
-	//todo: muiltitouch
 	else if(current.type == SDL_MOUSEBUTTONUP)
 	{
-		switch(current.button.button)
+		if(!multifinger)
 		{
-		case SDL_BUTTON_LEFT:
-			handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
-			break;
-		case SDL_BUTTON_RIGHT:
+			switch(current.button.button)
+			{
+			case SDL_BUTTON_LEFT:
+				handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
+				break;
+			case SDL_BUTTON_RIGHT:
+				handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
+				break;
+			case SDL_BUTTON_MIDDLE:
+				handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
+				break;
+			}
+		}
+	}
+	else if(current.type == SDL_FINGERMOTION)
+	{
+		if(isPointerRelativeMode)
+		{
+			fakeMoveCursor(current.tfinger.dx, current.tfinger.dy);
+		}
+	}
+	else if(current.type == SDL_FINGERDOWN)
+	{
+		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
+
+		multifinger = fingerCount > 1;
+
+		if(isPointerRelativeMode)
+		{
+			if(current.tfinger.x > 0.5)
+			{
+				bool isRightClick = current.tfinger.y < 0.5;
+
+				fakeMouseButtonEventRelativeMode(true, isRightClick);
+			}
+		}
+#ifndef VCMI_IOS
+		else if(fingerCount == 2)
+		{
+			convertTouchToMouse(&current);
+			handleMouseMotion(current);
+			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
+		}
+#endif //VCMI_IOS
+	}
+	else if(current.type == SDL_FINGERUP)
+	{
+		auto fingerCount = SDL_GetNumTouchFingers(current.tfinger.touchId);
+
+		if(isPointerRelativeMode)
+		{
+			if(current.tfinger.x > 0.5)
+			{
+				bool isRightClick = current.tfinger.y < 0.5;
+
+				fakeMouseButtonEventRelativeMode(false, isRightClick);
+			}
+		}
+#ifndef VCMI_IOS
+		else if(multifinger)
+		{
+			convertTouchToMouse(&current);
+			handleMouseMotion(current);
 			handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
-			break;
-		case SDL_BUTTON_MIDDLE:
-			handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
-			break;
+			multifinger = fingerCount != 0;
 		}
+#endif //VCMI_IOS
 	}
 } //event end
 
@@ -431,20 +566,6 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
 	}
 }
 
-void CGuiHandler::fakeMouseMove()
-{
-	SDL_Event event;
-	SDL_MouseMotionEvent sme = {SDL_MOUSEMOTION, 0, 0, 0, 0, 0, 0, 0, 0};
-	int x, y;
-
-	sme.state = SDL_GetMouseState(&x, &y);
-	sme.x = x;
-	sme.y = y;
-
-	event.motion = sme;
-	SDL_PushEvent(&event);
-}
-
 void CGuiHandler::renderFrame()
 {
 
@@ -486,7 +607,8 @@ void CGuiHandler::renderFrame()
 
 
 CGuiHandler::CGuiHandler()
-	: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false)
+	: lastClick(-500, -500),lastClickTime(0), defActionsDef(0), captureChildren(false),
+    multifinger(false)
 {
 	continueEventHandling = true;
 	curInt = nullptr;

+ 8 - 1
client/gui/CGuiHandler.h

@@ -92,9 +92,12 @@ private:
 
 	void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
 	void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
-	void handleCurrentEvent(const SDL_Event & current);
+	void handleCurrentEvent(SDL_Event &current);
 	void handleMouseMotion(const SDL_Event & current);
 	void handleMoveInterested( const SDL_MouseMotionEvent & motion );
+	void convertTouchToMouse(SDL_Event * current);
+	void fakeMoveCursor(float dx, float dy);
+	void fakeMouseButtonEventRelativeMode(bool down, bool right);
 
 public:
 	void handleElementActivate(CIntObject * elem, ui16 activityFlag);
@@ -110,6 +113,9 @@ public:
 
 	Point lastClick;
 	unsigned lastClickTime;
+	bool multifinger;
+	bool isPointerRelativeMode;
+	float pointerSpeedMultiplier;
 
 	ui8 defActionsDef; //default auto actions
 	bool captureChildren; //all newly created objects will get their parents from stack and will be added to parents children list
@@ -118,6 +124,7 @@ public:
 	CGuiHandler();
 	~CGuiHandler();
 
+	void init();
 	void renderFrame();
 
 	void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering

+ 16 - 20
client/gui/SDL_Extensions.cpp

@@ -889,22 +889,10 @@ SDL_Color CSDL_Ext::makeColor(ui8 r, ui8 g, ui8 b, ui8 a)
 
 void CSDL_Ext::startTextInput(const Rect & whereInput)
 {
-	const SDL_Rect where = CSDL_Ext::toSDL(whereInput);
-
-	auto impl = [](SDL_Rect where)
-	{
-		if (SDL_IsTextInputActive() == SDL_FALSE)
-		{
-			SDL_StartTextInput();
-		}
-		SDL_SetTextInputRect(&where);
-	};
-
 #ifdef VCMI_APPLE
 	dispatch_async(dispatch_get_main_queue(), ^{
 #endif
 
-#ifdef VCMI_IOS
 	// TODO ios: looks like SDL bug actually, try fixing there
 	auto renderer = SDL_GetRenderer(mainWindow);
 	float scaleX, scaleY;
@@ -912,17 +900,25 @@ void CSDL_Ext::startTextInput(const Rect & whereInput)
 	SDL_RenderGetScale(renderer, &scaleX, &scaleY);
 	SDL_RenderGetViewport(renderer, &viewport);
 
+#ifdef VCMI_IOS
 	const auto nativeScale = iOS_utils::screenScale();
-	auto rectInScreenCoordinates = where;
-	rectInScreenCoordinates.x = (viewport.x + rectInScreenCoordinates.x) * scaleX / nativeScale;
-	rectInScreenCoordinates.y = (viewport.y + rectInScreenCoordinates.y) * scaleY / nativeScale;
-	rectInScreenCoordinates.w = rectInScreenCoordinates.w * scaleX / nativeScale;
-	rectInScreenCoordinates.h = rectInScreenCoordinates.h * scaleY / nativeScale;
-	impl(rectInScreenCoordinates);
-#else
-	impl(where);
+	scaleX /= nativeScale;
+	scaleY /= nativeScale;
 #endif
 
+	SDL_Rect rectInScreenCoordinates;
+	rectInScreenCoordinates.x = (viewport.x + whereInput.x) * scaleX;
+	rectInScreenCoordinates.y = (viewport.y + whereInput.y) * scaleY;
+	rectInScreenCoordinates.w = whereInput.w * scaleX;
+	rectInScreenCoordinates.h = whereInput.h * scaleY;
+
+	SDL_SetTextInputRect(&rectInScreenCoordinates);
+
+	if (SDL_IsTextInputActive() == SDL_FALSE)
+	{
+		SDL_StartTextInput();
+	}
+
 #ifdef VCMI_APPLE
 	});
 #endif

+ 3 - 1
client/ios/GameChatKeyboardHandler.h

@@ -10,11 +10,13 @@
 
 #import <UIKit/UIKit.h>
 
+#include <SDL_events.h>
+
 NS_ASSUME_NONNULL_BEGIN
 
 @interface GameChatKeyboardHandler : NSObject
 
-- (void)triggerInput;
++ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode;
 
 @end
 

+ 15 - 15
client/ios/GameChatKeyboardHandler.m

@@ -10,49 +10,49 @@
 
 #import "GameChatKeyboardHandler.h"
 
-#include <SDL_events.h>
-
 static int watchReturnKey(void * userdata, SDL_Event * event);
 
-static void sendKeyEvent(SDL_KeyCode keyCode)
+
+@interface GameChatKeyboardHandler ()
+@property (nonatomic) BOOL wasChatMessageSent;
+@end
+
+@implementation GameChatKeyboardHandler
+
++ (void)sendKeyEventWithKeyCode:(SDL_KeyCode)keyCode
 {
 	SDL_Event keyEvent;
 	keyEvent.key = (SDL_KeyboardEvent){
 		.type = SDL_KEYDOWN,
+		.state = SDL_PRESSED,
 		.keysym.sym = keyCode,
 	};
 	SDL_PushEvent(&keyEvent);
 }
 
+- (instancetype)init {
+	self = [super init];
 
-@interface GameChatKeyboardHandler ()
-@property (nonatomic) BOOL wasChatMessageSent;
-@end
-
-@implementation GameChatKeyboardHandler
-
-- (void)triggerInput {
 	__auto_type notificationCenter = NSNotificationCenter.defaultCenter;
 	[notificationCenter addObserver:self selector:@selector(textDidBeginEditing:) name:UITextFieldTextDidBeginEditingNotification object:nil];
 	[notificationCenter addObserver:self selector:@selector(textDidEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil];
 
-	self.wasChatMessageSent = NO;
-	sendKeyEvent(SDLK_TAB);
+	return self;
 }
 
 #pragma mark - Notifications
 
 - (void)textDidBeginEditing:(NSNotification *)n {
+	self.wasChatMessageSent = NO;
+
 	// watch for pressing Return to ignore sending Escape key after keyboard is closed
 	SDL_AddEventWatch(watchReturnKey, (__bridge void *)self);
 }
 
 - (void)textDidEndEditing:(NSNotification *)n {
-	[NSNotificationCenter.defaultCenter removeObserver:self];
-
 	// discard chat message
 	if(!self.wasChatMessageSent)
-		sendKeyEvent(SDLK_ESCAPE);
+		[[self class] sendKeyEventWithKeyCode:SDLK_ESCAPE];
 }
 
 @end

+ 1 - 1
client/ios/startSDL.mm

@@ -95,7 +95,7 @@
 - (void)handlePinch:(UIGestureRecognizer *)gesture {
     if(gesture.state != UIGestureRecognizerStateBegan || CSH->state != EClientState::GAMEPLAY)
         return;
-	[self.gameChatHandler triggerInput];
+	[GameChatKeyboardHandler sendKeyEventWithKeyCode:SDLK_SPACE];
 }
 
 #pragma mark - UIGestureRecognizerDelegate

+ 1 - 1
client/windows/CAdvmapInterface.cpp

@@ -177,7 +177,7 @@ void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
 void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
 {
 #if defined(VCMI_ANDROID) || defined(VCMI_IOS)
-	if(sEvent.state == 0) // any "button" is enough on mobile
+	if(sEvent.state == 0 || GH.multifinger) // any "button" is enough on mobile
 #else
 	if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
 #endif

+ 1 - 1
client/windows/QuickRecruitmentWindow.cpp

@@ -101,7 +101,7 @@ void QuickRecruitmentWindow::maxAllCards(std::vector<std::shared_ptr<CreaturePur
 
 void QuickRecruitmentWindow::purchaseUnits()
 {
-	for(auto selected : cards)
+	for(auto selected : boost::adaptors::reverse(cards))
 	{
 		if(selected->slider->getValue())
 		{

+ 23 - 1
config/schemas/settings.json

@@ -17,7 +17,21 @@
 			"type" : "object",
 			"default": {},
 			"additionalProperties" : false,
-			"required" : [ "playerName", "showfps", "music", "sound", "encoding", "language", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ],
+			"required" : [
+				"playerName",
+				"showfps",
+				"music",
+				"sound",
+				"encoding",
+				"language",
+				"swipe",
+				"saveRandomMaps",
+				"saveFrequency",
+				"notifications",
+				"extraDump",
+				"userRelativePointer",
+				"relativePointerSpeedMultiplier"
+			],
 			"properties" : {
 				"playerName" : {
 					"type":"string",
@@ -75,6 +89,14 @@
 				"extraDump" : {
 					"type" : "boolean",
 					"default" : false
+				},
+				"userRelativePointer" : {
+					"type" : "boolean",
+					"default" : false
+				},
+				"relativePointerSpeedMultiplier" : {
+					"type" : "number",
+					"default" : 1
 				}
 			}
 		},

+ 0 - 9
ios/zip2ipa.sh

@@ -1,9 +0,0 @@
-#!/usr/bin/env bash
-
-generatedZip="$1"
-if [[ -z "$generatedZip" ]]; then
-	echo 'generated zip not provided as param'
-	exit 1
-fi
-
-mv "$generatedZip" "$(basename "$generatedZip" .zip).ipa"

+ 1 - 1
lib/CAndroidVMHelper.cpp

@@ -96,7 +96,7 @@ jclass CAndroidVMHelper::findClass(const std::string & name, bool classloaded)
 	return get()->FindClass(name.c_str());
 }
 
-extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jobject * cls)
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_initClassloader(JNIEnv * baseEnv, jclass cls)
 {
 	CAndroidVMHelper::cacheVM(baseEnv);
 	CAndroidVMHelper envHelper;

+ 3 - 3
lib/HeroBonus.cpp

@@ -1583,7 +1583,7 @@ bool NBonus::hasOfType(const CBonusSystemNode *obj, Bonus::BonusType type, int s
 	return false;
 }
 
-std::string Bonus::Description() const
+std::string Bonus::Description(boost::optional<si32> customValue) const
 {
 	std::ostringstream str;
 
@@ -1622,8 +1622,8 @@ std::string Bonus::Description() const
 		str << description;
 	}
 
-	if(val != 0)
-		str << " " << std::showpos << val;
+	if(auto value = customValue.value_or(val))
+		str << " " << std::showpos << value;
 
 	return str.str();
 }

+ 1 - 1
lib/HeroBonus.h

@@ -510,7 +510,7 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 		return (high << 16) + low;
 	}
 
-	std::string Description() const;
+	std::string Description(boost::optional<si32> customValue = {}) const;
 	JsonNode toJsonNode() const;
 	std::string nameForBonus() const; // generate suitable name for bonus - e.g. for storing in json struct
 

+ 12 - 10
lib/mapObjects/CGTownInstance.cpp

@@ -554,24 +554,26 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 		if(hasBuilt(BuildingID::HORDE_2))
 			ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth));
 
-	int dwellingBonus = 0;
-	if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
+	//statue-of-legion-like bonus: % to base+castle
+	TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH_PERCENT));
+	for(const auto & b : *bonuses2)
 	{
-		dwellingBonus = getDwellingBonus(creatures[level].second, p->dwellings);
+		const auto growth = b->val * (base + castleBonus) / 100;
+		ret.entries.push_back(GrowthInfo::Entry(growth, b->Description(growth)));
 	}
 
-	if(dwellingBonus)
-		ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
-
 	//other *-of-legion-like bonuses (%d to growth cumulative with grail)
 	TConstBonusListPtr bonuses = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH).And(Selector::subtype()(level)));
 	for(const auto & b : *bonuses)
 		ret.entries.push_back(GrowthInfo::Entry(b->val, b->Description()));
 
-	//statue-of-legion-like bonus: % to base+castle
-	TConstBonusListPtr bonuses2 = getBonuses(Selector::type()(Bonus::CREATURE_GROWTH_PERCENT));
-	for(const auto & b : *bonuses2)
-		ret.entries.push_back(GrowthInfo::Entry(b->val * (base + castleBonus) / 100, b->Description()));
+	int dwellingBonus = 0;
+	if(const PlayerState *p = cb->getPlayerState(tempOwner, false))
+	{
+		dwellingBonus = getDwellingBonus(creatures[level].second, p->dwellings);
+	}
+	if(dwellingBonus)
+		ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d
 
 	if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth
 		ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2));

+ 2 - 2
lib/mapping/MapFormatJson.cpp

@@ -1326,10 +1326,10 @@ std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile)
 	out << tile.terType->shortIdentifier << (int)tile.terView << flipCodes[tile.extTileFlags % 4];
 
 	if(tile.roadType->getId() != Road::NO_ROAD)
-		out << tile.roadType << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
+		out << tile.roadType->shortIdentifier << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4];
 
 	if(tile.riverType->getId() != River::NO_RIVER)
-		out << tile.riverType << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
+		out << tile.riverType->shortIdentifier << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4];
 
 	return out.str();
 }

+ 11 - 0
server/CVCMIServer.cpp

@@ -28,6 +28,8 @@
 #include "../lib/mapping/CMap.h"
 #include "../lib/rmg/CMapGenOptions.h"
 #ifdef VCMI_ANDROID
+#include <jni.h>
+#include <android/log.h>
 #include "lib/CAndroidVMHelper.h"
 #elif !defined(VCMI_IOS)
 #include "../lib/Interprocess.h"
@@ -1122,12 +1124,21 @@ int main(int argc, char * argv[])
 }
 
 #ifdef VCMI_ANDROID
+
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_createServer(JNIEnv * env, jclass cls)
+{
+	__android_log_write(ANDROID_LOG_INFO, "VCMI", "Got jni call to init server");
+	CAndroidVMHelper::cacheVM(env);
+	CVCMIServer::create();
+}
+
 void CVCMIServer::create()
 {
 	const char * foo = "android-server";
 	std::vector<const void *> argv = {foo};
 	main(argv.size(), reinterpret_cast<char **>(const_cast<void **>(&*argv.begin())));
 }
+
 #elif defined(SINGLE_PROCESS_APP)
 void CVCMIServer::create(boost::condition_variable * cond, const std::vector<std::string> & args)
 {