Prechádzať zdrojové kódy

Merge branch 'develop' into creature-numeric-quantities

Dydzio 2 rokov pred
rodič
commit
685d63603d
100 zmenil súbory, kde vykonal 1531 pridanie a 1219 odobranie
  1. 7 5
      AI/BattleAI/AttackPossibility.cpp
  2. 9 10
      AI/BattleAI/BattleAI.cpp
  3. 4 2
      AI/BattleAI/BattleExchangeVariant.cpp
  4. 4 6
      AI/BattleAI/PotentialTargets.cpp
  5. 2 2
      AI/BattleAI/StackWithBonuses.cpp
  6. 1 1
      AI/BattleAI/StackWithBonuses.h
  7. 11 11
      AI/Nullkiller/AIGateway.cpp
  8. 1 1
      AI/Nullkiller/AIUtility.cpp
  9. 2 2
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  10. 2 2
      AI/Nullkiller/Analyzers/HeroManager.cpp
  11. 0 1
      AI/Nullkiller/Analyzers/ObjectClusterizer.cpp
  12. 8 8
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  13. 1 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  14. 2 2
      AI/Nullkiller/Goals/AbstractGoal.cpp
  15. 6 6
      AI/Nullkiller/Goals/AdventureSpellCast.cpp
  16. 2 2
      AI/Nullkiller/Goals/BuildThis.cpp
  17. 1 1
      AI/Nullkiller/Goals/BuyArmy.cpp
  18. 4 4
      AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp
  19. 6 6
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  20. 3 3
      AI/Nullkiller/Goals/RecruitHero.cpp
  21. 2 2
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  22. 1 1
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp
  23. 1 1
      AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp
  24. 2 2
      AI/Nullkiller/Pathfinding/Actors.cpp
  25. 2 1
      AI/StupidAI/StupidAI.cpp
  26. 1 1
      AI/VCAI/AIUtility.cpp
  27. 2 2
      AI/VCAI/Goals/AbstractGoal.cpp
  28. 7 7
      AI/VCAI/Goals/AdventureSpellCast.cpp
  29. 1 1
      AI/VCAI/Goals/BuyArmy.cpp
  30. 2 2
      AI/VCAI/Goals/CompleteQuest.cpp
  31. 2 2
      AI/VCAI/Goals/Explore.cpp
  32. 1 1
      AI/VCAI/Goals/GatherArmy.cpp
  33. 1 1
      AI/VCAI/Goals/GatherTroops.cpp
  34. 1 1
      AI/VCAI/Goals/VisitHero.cpp
  35. 1 1
      AI/VCAI/Goals/VisitObj.cpp
  36. 1 1
      AI/VCAI/Goals/VisitTile.cpp
  37. 1 1
      AI/VCAI/Pathfinding/AIPathfinder.cpp
  38. 17 17
      AI/VCAI/VCAI.cpp
  39. 12 0
      CI/conan/base/apple
  40. 5 0
      CI/conan/base/ios
  41. 4 0
      CI/conan/base/macos
  42. 2 11
      CI/conan/ios-arm64
  43. 4 10
      CI/conan/ios-armv7
  44. 2 10
      CI/conan/macos-arm
  45. 2 10
      CI/conan/macos-intel
  46. 2 2
      CI/ios/before_install.sh
  47. 2 2
      CI/mac/before_install.sh
  48. 7 7
      CI/msvc/before_install.sh
  49. 18 6
      CMakeLists.txt
  50. 14 0
      Mods/vcmi/mod.json
  51. 1 2
      client/CBitmapHandler.cpp
  52. 2 2
      client/CGameInfo.h
  53. 48 461
      client/CMT.cpp
  54. 5 2
      client/CMT.h
  55. 6 5
      client/CMakeLists.txt
  56. 1 3
      client/CMessage.cpp
  57. 0 1
      client/CMessage.h
  58. 20 35
      client/CMusicHandler.cpp
  59. 0 3
      client/CMusicHandler.h
  60. 35 18
      client/CPlayerInterface.cpp
  61. 7 6
      client/CVideoHandler.cpp
  62. 5 6
      client/CVideoHandler.h
  63. 22 3
      client/Client.cpp
  64. 1 0
      client/Client.h
  65. 494 0
      client/ClientCommandManager.cpp
  66. 31 0
      client/ClientCommandManager.h
  67. 1 1
      client/Graphics.cpp
  68. 0 1
      client/Graphics.h
  69. 1 1
      client/NetPacksLobbyClient.cpp
  70. 13 10
      client/battle/BattleActionsController.cpp
  71. 1 1
      client/battle/BattleAnimationClasses.cpp
  72. 1 2
      client/battle/BattleAnimationClasses.h
  73. 1 1
      client/battle/BattleEffectsController.h
  74. 5 5
      client/battle/BattleFieldController.cpp
  75. 2 3
      client/battle/BattleFieldController.h
  76. 4 3
      client/battle/BattleInterface.cpp
  77. 8 8
      client/battle/BattleInterfaceClasses.cpp
  78. 1 1
      client/battle/BattleObstacleController.h
  79. 2 3
      client/battle/BattleProjectileController.cpp
  80. 1 2
      client/battle/BattleProjectileController.h
  81. 21 14
      client/battle/BattleSiegeController.cpp
  82. 2 2
      client/battle/BattleSiegeController.h
  83. 6 6
      client/battle/BattleStacksController.cpp
  84. 1 1
      client/battle/BattleStacksController.h
  85. 5 5
      client/battle/BattleWindow.cpp
  86. 1 0
      client/battle/CreatureAnimation.cpp
  87. 12 18
      client/gui/CAnimation.cpp
  88. 6 4
      client/gui/CAnimation.h
  89. 0 317
      client/gui/CCursorHandler.cpp
  90. 10 9
      client/gui/CGuiHandler.cpp
  91. 5 3
      client/gui/CGuiHandler.h
  92. 3 11
      client/gui/CIntObject.cpp
  93. 4 4
      client/gui/CIntObject.h
  94. 6 12
      client/gui/Canvas.cpp
  95. 2 5
      client/gui/Canvas.h
  96. 1 1
      client/gui/ColorFilter.cpp
  97. 439 0
      client/gui/CursorHandler.cpp
  98. 85 31
      client/gui/CursorHandler.h
  99. 6 7
      client/gui/Fonts.cpp
  100. 1 1
      client/gui/Fonts.h

+ 7 - 5
AI/BattleAI/AttackPossibility.cpp

@@ -49,7 +49,8 @@ int64_t AttackPossibility::calculateDamageReduce(
 
 	vstd::amin(damageDealt, defender->getAvailableHealth());
 
-	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(BattleAttackInfo(defender, attacker, defender->canShoot()));
+	// FIXME: provide distance info for Jousting bonus
+	auto enemyDamageBeforeAttack = cb.battleEstimateDamage(defender, attacker, 0);
 	auto enemiesKilled = damageDealt / defender->MaxHealth() + (damageDealt % defender->MaxHealth() >= defender->getFirstHPleft() ? 1 : 0);
 	auto enemyDamage = averageDmg(enemyDamageBeforeAttack);
 	auto damagePerEnemy = enemyDamage / (double)defender->getCount();
@@ -74,10 +75,11 @@ int64_t AttackPossibility::evaluateBlockedShootersDmg(const BattleAttackInfo & a
 		if(!state.battleCanShoot(st))
 			continue;
 
-		BattleAttackInfo rangeAttackInfo(st, attacker, true);
+		// FIXME: provide distance info for Jousting bonus
+		BattleAttackInfo rangeAttackInfo(st, attacker, 0, true);
 		rangeAttackInfo.defenderPos = hex;
 
-		BattleAttackInfo meleeAttackInfo(st, attacker, false);
+		BattleAttackInfo meleeAttackInfo(st, attacker, 0, false);
 		meleeAttackInfo.defenderPos = hex;
 
 		auto rangeDmg = state.battleEstimateDamage(rangeAttackInfo);
@@ -211,8 +213,8 @@ AttackPossibility AttackPossibility::evaluate(const BattleAttackInfo & attackInf
 	bestAp.shootersBlockedDmg = evaluateBlockedShootersDmg(attackInfo, hex, state);
 
 	logAi->debug("BattleAI best AP: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
-		attackInfo.attacker->unitType()->identifier,
-		attackInfo.defender->unitType()->identifier,
+		attackInfo.attacker->unitType()->getJsonKey(),
+		attackInfo.defender->unitType()->getJsonKey(),
 		(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 		bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
 

+ 9 - 10
AI/BattleAI/BattleAI.cpp

@@ -46,14 +46,14 @@ std::vector<BattleHex> CBattleAI::getBrokenWallMoatHexes() const
 {
 	std::vector<BattleHex> result;
 
-	for(int wallPart = EWallPart::BOTTOM_WALL; wallPart < EWallPart::UPPER_WALL; wallPart++)
+	for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL })
 	{
 		auto state = cb->battleGetWallState(wallPart);
 
 		if(state != EWallState::DESTROYED)
 			continue;
 
-		auto wallHex = cb->wallPartToBattleHex((EWallPart::EWallPart)wallPart);
+		auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart);
 		auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT);
 
 		result.push_back(moatHex);
@@ -207,10 +207,10 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 				}
 
 				logAi->debug("BattleAI: %s -> %s x %d, %s, from %d curpos %d dist %d speed %d: +%lld -%lld = %lld",
-					bestAttack.attackerState->unitType()->identifier,
-					bestAttack.affectedUnits[0]->unitType()->identifier,
+					bestAttack.attackerState->unitType()->getJsonKey(),
+					bestAttack.affectedUnits[0]->unitType()->getJsonKey(),
 					(int)bestAttack.affectedUnits[0]->getCount(), action, (int)bestAttack.from, (int)bestAttack.attack.attacker->getPosition().hex,
-					bestAttack.attack.chargedFields, bestAttack.attack.attacker->Speed(0, true),
+					bestAttack.attack.chargeDistance, bestAttack.attack.attacker->Speed(0, true),
 					bestAttack.defenderDamageReduce, bestAttack.attackerDamageReduce, bestAttack.attackValue()
 				);
 			}
@@ -364,7 +364,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 	}
 	else
 	{
-		EWallPart::EWallPart wallParts[] = {
+		EWallPart wallParts[] = {
 			EWallPart::KEEP,
 			EWallPart::BOTTOM_TOWER,
 			EWallPart::UPPER_TOWER,
@@ -378,10 +378,9 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 		{
 			auto wallState = cb->battleGetWallState(wallPart);
 
-			if(wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
+			if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
 			{
 				targetHex = cb->wallPartToBattleHex(wallPart);
-
 				break;
 			}
 		}
@@ -688,7 +687,7 @@ void CBattleAI::attemptCastingSpell()
 
 	if(castToPerform.value > 0)
 	{
-		LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->name % castToPerform.value);
+		LOGFL("Best spell is %s (value %d). Will cast.", castToPerform.spell->getNameTranslated() % castToPerform.value);
 		BattleAction spellcast;
 		spellcast.actionType = EActionType::HERO_SPELL;
 		spellcast.actionSubtype = castToPerform.spell->id;
@@ -699,7 +698,7 @@ void CBattleAI::attemptCastingSpell()
 	}
 	else
 	{
-		LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->name % castToPerform.value);
+		LOGFL("Best spell is %s. But it is actually useless (value %d).", castToPerform.spell->getNameTranslated() % castToPerform.value);
 	}
 }
 

+ 4 - 2
AI/BattleAI/BattleExchangeVariant.cpp

@@ -69,7 +69,8 @@ int64_t BattleExchangeVariant::trackAttack(
 	const bool counterAttacksBlocked = attacker->hasBonus(selectorBlocksRetaliation, cachingStringBlocksRetaliation);
 
 	TDmgRange retaliation;
-	BattleAttackInfo bai(attacker.get(), defender.get(), shooting);
+	// FIXME: provide distance info for Jousting bonus
+	BattleAttackInfo bai(attacker.get(), defender.get(), 0, shooting);
 
 	if(shooting)
 	{
@@ -234,7 +235,8 @@ MoveTarget BattleExchangeEvaluator::findMoveTowardsUnreachable(const battle::Uni
 
 		for(auto hex : hexes)
 		{
-			auto bai = BattleAttackInfo(activeStack, closestStack, cb->battleCanShoot(activeStack));
+			// FIXME: provide distance info for Jousting bonus
+			auto bai = BattleAttackInfo(activeStack, closestStack, 0, cb->battleCanShoot(activeStack));
 			auto attack = AttackPossibility::evaluate(bai, hex, hb);
 
 			attack.shootersBlockedDmg = 0; // we do not want to count on it, it is not for sure

+ 4 - 6
AI/BattleAI/PotentialTargets.cpp

@@ -46,10 +46,8 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 
 		auto GenerateAttackInfo = [&](bool shooting, BattleHex hex) -> AttackPossibility
 		{
-			auto bai = BattleAttackInfo(attackerInfo, defender, shooting);
-
-			if(hex.isValid() && !shooting)
-				bai.chargedFields = reachability.distances[hex];
+			int distance = hex.isValid() ? reachability.distances[hex] : 0;
+			auto bai = BattleAttackInfo(attackerInfo, defender, distance, shooting);
 
 			return AttackPossibility::evaluate(bai, hex, state);
 		};
@@ -92,8 +90,8 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 		auto & bestAp = possibleAttacks[0];
 
 		logGlobal->info("Battle AI best: %s -> %s at %d from %d, affects %d units: d:%lld a:%lld c:%lld s:%lld",
-			bestAp.attack.attacker->unitType()->identifier,
-			state.battleGetUnitByPos(bestAp.dest)->unitType()->identifier,
+			bestAp.attack.attacker->unitType()->getJsonKey(),
+			state.battleGetUnitByPos(bestAp.dest)->unitType()->getJsonKey(),
 			(int)bestAp.dest, (int)bestAp.from, (int)bestAp.affectedUnits.size(),
 			bestAp.defenderDamageReduce, bestAp.attackerDamageReduce, bestAp.collateralDamageReduce, bestAp.shootersBlockedDmg);
 	}

+ 2 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -205,7 +205,7 @@ std::string StackWithBonuses::getDescription() const
 	oss << unitOwner().getStr();
 	oss << " battle stack [" << unitId() << "]: " << getCount() << " of ";
 	if(type)
-		oss << type->namePl;
+		oss << type->getJsonKey();
 	else
 		oss << "[UNDEFINED TYPE]";
 
@@ -403,7 +403,7 @@ void HypotheticBattle::removeUnitBonus(uint32_t id, const std::vector<Bonus> & b
 	bonusTreeVersion++;
 }
 
-void HypotheticBattle::setWallState(int partOfWall, si8 state)
+void HypotheticBattle::setWallState(EWallPart partOfWall, EWallState state)
 {
 	//TODO:HypotheticBattle::setWallState
 }

+ 1 - 1
AI/BattleAI/StackWithBonuses.h

@@ -130,7 +130,7 @@ public:
 	void updateUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
 	void removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus) override;
 
-	void setWallState(int partOfWall, si8 state) override;
+	void setWallState(EWallPart partOfWall, EWallState state) override;
 
 	void addObstacle(const ObstacleChanges & changes) override;
 	void updateObstacle(const ObstacleChanges& changes) override;

+ 11 - 11
AI/Nullkiller/AIGateway.cpp

@@ -283,7 +283,7 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
 	auto firstHero = cb->getHero(hero1);
 	auto secondHero = cb->getHero(hero2);
 
-	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner));
+	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
 	requestActionASAP([=]()
 	{
@@ -544,7 +544,7 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimaryS
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 
-	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level));
+	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
 	HeroPtr hPtr = hero;
 
 	requestActionASAP([=]()
@@ -737,7 +737,7 @@ bool AIGateway::makePossibleUpgrades(const CArmedInstance * obj)
 			{
 				myCb->upgradeCreature(obj, SlotID(i), ui.newID[0]);
 				upgraded = true;
-				logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->namePl, ui.newID[0].toCreature()->namePl);
+				logAi->debug("Upgraded %d %s to %s", s->count, ui.oldID.toCreature()->getNamePluralTranslated(), ui.newID[0].toCreature()->getNamePluralTranslated());
 			}
 		}
 	}
@@ -787,7 +787,7 @@ void AIGateway::makeTurn()
 		for (auto h : cb->getHeroesInfo())
 		{
 			if (h->movement)
-				logAi->warn("Hero %s has %d MP left", h->name, h->movement);
+				logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
 		}
 #if NKAI_TRACE_LEVEL == 0
 	}
@@ -808,7 +808,7 @@ void AIGateway::makeTurn()
 
 void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 {
-	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
+	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
 	switch(obj->ID)
 	{
 	case Obj::CREATURE_GENERATOR1:
@@ -1070,7 +1070,7 @@ void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * arm
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
-	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
+	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
@@ -1172,7 +1172,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		}
 	};
 
-	logAi->debug("Moving hero %s to tile %s", h->name, dst.toString());
+	logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString());
 	int3 startHpos = h->visitablePos();
 	bool ret = false;
 	if(startHpos == dst)
@@ -1192,7 +1192,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		cb->getPathsInfo(h.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
-			logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
+			logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
 			return true;
 		}
 		int i = (int)path.nodes.size() - 1;
@@ -1344,15 +1344,15 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 			throw cannotFulfillGoalException("Invalid path found!");
 		}
 
-		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret);
+		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret);
 	}
 	return ret;
 }
 
 void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
 {
-	auto name = t->town->buildings.at(building)->Name();
-	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->name, t->pos.toString());
+	auto name = t->town->buildings.at(building)->getNameTranslated();
+	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
 	cb->buildBuilding(t, building); //just do this;
 }
 

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 
 	h = H;
-	name = h->name;
+	name = h->getNameTranslated();
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }

+ 2 - 2
AI/Nullkiller/Analyzers/BuildAnalyzer.cpp

@@ -130,7 +130,7 @@ void BuildAnalyzer::update()
 
 	for(const CGTownInstance* town : towns)
 	{
-		logAi->trace("Checking town %s", town->name);
+		logAi->trace("Checking town %s", town->getNameTranslated());
 
 		developmentInfos.push_back(TownDevelopmentInfo(town));
 		TownDevelopmentInfo & developmentInfo = developmentInfos.back();
@@ -351,7 +351,7 @@ BuildingInfo::BuildingInfo(
 	dailyIncome = building->produce;
 	exists = town->hasBuilt(id);
 	prerequisitesCount = 1;
-	name = building->Name();
+	name = building->getNameTranslated();
 
 	if(creature)
 	{

+ 2 - 2
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -70,7 +70,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
-	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->ID.getNum());
+	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex());
 	auto secondarySkillBonus = Selector::type()(Bonus::SECONDARY_SKILL_PREMY);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
 	float specialityScore = 0.0f;
@@ -172,7 +172,7 @@ void HeroManager::update()
 
 	for(auto hero : myHeroes)
 	{
-		logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
+		logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
 	}
 }
 

+ 0 - 1
AI/Nullkiller/Analyzers/ObjectClusterizer.cpp

@@ -202,7 +202,6 @@ void ObjectClusterizer::clusterize()
 		Obj::WHIRLPOOL,
 		Obj::BUOY,
 		Obj::SIGN,
-		Obj::SIGN,
 		Obj::GARRISON,
 		Obj::MONSTER,
 		Obj::GARRISON2,

+ 8 - 8
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -50,14 +50,14 @@ Goals::TGoalVec DefenceBehavior::decompose() const
 
 void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInstance * town) const
 {
-	logAi->trace("Evaluating defence for %s", town->name);
+	logAi->trace("Evaluating defence for %s", town->getNameTranslated());
 
 	auto treatNode = ai->nullkiller->dangerHitMap->getObjectTreat(town);
 	auto treats = { treatNode.fastestDanger, treatNode.maximumDanger };
 
 	if(!treatNode.fastestDanger.hero)
 	{
-		logAi->trace("No treat found for town %s", town->name);
+		logAi->trace("No treat found for town %s", town->getNameTranslated());
 
 		return;
 	}
@@ -78,8 +78,8 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		logAi->trace(
 			"Hero %s in garrison of town %s is suposed to defend the town",
-			town->garrisonHero->name,
-			town->name);
+			town->garrisonHero->getNameTranslated(),
+			town->getNameTranslated());
 
 		return;
 	}
@@ -88,7 +88,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 	if(reinforcement)
 	{
-		logAi->trace("Town %s can buy defence army %lld", town->name, reinforcement);
+		logAi->trace("Town %s can buy defence army %lld", town->getNameTranslated(), reinforcement);
 		tasks.push_back(Goals::sptr(Goals::BuyArmy(town, reinforcement).setpriority(0.5f)));
 	}
 
@@ -98,10 +98,10 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 	{
 		logAi->trace(
 			"Town %s has treat %lld in %s turns, hero: %s",
-			town->name,
+			town->getNameTranslated(),
 			treat.danger,
 			std::to_string(treat.turn),
-			treat.hero->name);
+			treat.hero->getNameTranslated());
 
 		bool treatIsUnderControl = false;
 
@@ -187,7 +187,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		if(paths.empty())
 		{
-			logAi->trace("No ways to defend town %s", town->name);
+			logAi->trace("No ways to defend town %s", town->getNameTranslated());
 
 			continue;
 		}

+ 1 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -191,7 +191,7 @@ int getDwellingArmyCost(const CGObjectInstance * target)
 
 uint64_t evaluateArtifactArmyValue(CArtifactInstance * art)
 {
-	if(art->artType->id == ArtifactID::SPELL_SCROLL)
+	if(art->artType->getId() == ArtifactID::SPELL_SCROLL)
 		return 1500;
 
 	auto statsValue =

+ 2 - 2
AI/Nullkiller/Goals/AbstractGoal.cpp

@@ -60,7 +60,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize
 		desc = "GATHER TROOPS";
 		break;
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + VLC->arth->objects[aid]->getName();
+		desc = "GET ARTIFACT OF TYPE " + VLC->arth->objects[aid]->getNameTranslated();
 		break;
 	case DIG_AT_TILE:
 		desc = "DIG AT TILE " + tile.toString();
@@ -69,7 +69,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize
 		return boost::lexical_cast<std::string>(goalType);
 	}
 	if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
-		desc += " (" + hero->name + ")";
+		desc += " (" + hero->getNameTranslated() + ")";
 	return desc;
 }
 

+ 6 - 6
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -33,19 +33,19 @@ void AdventureSpellCast::accept(AIGateway * ai)
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->name, hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->getNameTranslated());
 
 	if(!spell->isAdventure())
-		throw cannotFulfillGoalException(spell->name + " is not an adventure spell.");
+		throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell.");
 
 	if(!hero->canCastThisSpell(spell))
-		throw cannotFulfillGoalException("Hero can not cast " + spell->name);
+		throw cannotFulfillGoalException("Hero can not cast " + spell->getNameTranslated());
 
 	if(hero->mana < hero->getSpellCost(spell))
-		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->name);
+		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
+		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 
 	if(town && spellID == SpellID::TOWN_PORTAL)
 	{
@@ -70,7 +70,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
 
 std::string AdventureSpellCast::toString() const
 {
-	return "AdventureSpellCast " + spellID.toSpell()->name;
+	return "AdventureSpellCast " + spellID.toSpell()->getNameTranslated();
 }
 
 }

+ 2 - 2
AI/Nullkiller/Goals/BuildThis.cpp

@@ -46,7 +46,7 @@ bool BuildThis::operator==(const BuildThis & other) const
 
 std::string BuildThis::toString() const
 {
-	return "Build " + buildingInfo.name + " in " + town->name;
+	return "Build " + buildingInfo.name + " in " + town->getNameTranslated();
 }
 
 void BuildThis::accept(AIGateway * ai)
@@ -58,7 +58,7 @@ void BuildThis::accept(AIGateway * ai)
 		if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED)
 		{
 			logAi->debug("Player %d will build %s in town of %s at %s",
-				ai->playerID, town->town->buildings.at(b)->Name(), town->name, town->pos.toString());
+				ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->pos.toString());
 			cb->buildBuilding(town, b);
 
 			return;

+ 1 - 1
AI/Nullkiller/Goals/BuyArmy.cpp

@@ -29,7 +29,7 @@ bool BuyArmy::operator==(const BuyArmy & other) const
 
 std::string BuyArmy::toString() const
 {
-	return "Buy army at " + town->name;
+	return "Buy army at " + town->getNameTranslated();
 }
 
 void BuyArmy::accept(AIGateway * ai)

+ 4 - 4
AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp

@@ -33,7 +33,7 @@ ExchangeSwapTownHeroes::ExchangeSwapTownHeroes(
 
 std::string ExchangeSwapTownHeroes::toString() const
 {
-	return "Exchange and swap heroes of " + town->name;
+	return "Exchange and swap heroes of " + town->getNameTranslated();
 }
 
 bool ExchangeSwapTownHeroes::operator==(const ExchangeSwapTownHeroes & other) const
@@ -54,13 +54,13 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 
 		if(currentGarrisonHero.get() != town->visitingHero.get())
 		{
-			logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->name);
+			logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->getNameTranslated());
 			return;
 		}
 
 		ai->buildArmyIn(town);
 		ai->nullkiller->unlockHero(currentGarrisonHero.get());
-		logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->name, town->name);
+		logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->getNameTranslated());
 
 		return;
 	}
@@ -91,7 +91,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 		ai->makePossibleUpgrades(town->visitingHero);
 	}
 
-	logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name);
+	logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->getNameTranslated());
 }
 
 }

+ 6 - 6
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -75,7 +75,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			continue;
 		}
 
-		logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->name, node.coord.toString());
+		logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node.coord.toString());
 
 		try
 		{
@@ -111,7 +111,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					{
 						logAi->error(
 							"Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this",
-							hero->name,
+							hero->getNameTranslated(),
 							node.coord.toString());
 
 						return;
@@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 							if(isOk && path.nodes.back().turns > 0)
 							{
-								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->name, hero->movement, node.coord.toString());
+								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movement, node.coord.toString());
 
 								ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
 								return;
@@ -162,7 +162,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			{
 				logAi->error(
 					"Enable to complete chain. Expected hero %s to arive to %s but he is at %s", 
-					hero->name, 
+					hero->getNameTranslated(),
 					node.coord.toString(),
 					hero->visitablePos().toString());
 
@@ -187,14 +187,14 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 std::string ExecuteHeroChain::toString() const
 {
-	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name;
+	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated();
 }
 
 bool ExecuteHeroChain::moveHeroToTile(const CGHeroInstance * hero, const int3 & tile)
 {
 	if(tile == hero->visitablePos() && cb->getVisitableObjs(hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->name, tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->getNameTranslated(), tile.toString());
 
 		return true;
 	}

+ 3 - 3
AI/Nullkiller/Goals/RecruitHero.cpp

@@ -26,7 +26,7 @@ using namespace Goals;
 
 std::string RecruitHero::toString() const
 {
-	return "Recruit hero at " + town->name;
+	return "Recruit hero at " + town->getNameTranslated();
 }
 
 void RecruitHero::accept(AIGateway * ai)
@@ -40,7 +40,7 @@ void RecruitHero::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("No town to recruit hero!");
 	}
 
-	logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString());
+	logAi->debug("Trying to recruit a hero in %s at %s", t->getNameTranslated(), t->visitablePos().toString());
 
 	auto heroes = cb->getAvailableHeroes(t);
 
@@ -78,4 +78,4 @@ void RecruitHero::accept(AIGateway * ai)
 		ai->moveHeroToTile(t->visitablePos(), t->visitingHero.get());
 }
 
-}
+}

+ 2 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -1437,10 +1437,10 @@ std::string AIPath::toString() const
 {
 	std::stringstream str;
 
-	str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
+	str << targetHero->getNameTranslated() << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
 
 	for(auto node : nodes)
-		str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
+		str << node.targetHero->getNameTranslated() << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
 
 	return str.str();
 }

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp

@@ -27,7 +27,7 @@ namespace AIPathfinding
 		if(!hero->visitedTown)
 		{
 			throw cannotFulfillGoalException(
-				hero->name + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures.");
+				hero->getNameTranslated() + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures.");
 		}
 
 		ai->recruitCreatures(hero->visitedTown, hero);

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/TownPortalAction.cpp

@@ -34,7 +34,7 @@ void TownPortalAction::execute(const CGHeroInstance * hero) const
 
 std::string TownPortalAction::toString() const
 {
-	return "Town Portal to " + target->name;
+	return "Town Portal to " + target->getNameTranslated();
 }
 /*
 bool TownPortalAction::canAct(const CGHeroInstance * hero, const AIPathNode * source) const

+ 2 - 2
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -80,7 +80,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
 
 std::string ChainActor::toString() const
 {
-	return hero->name;
+	return hero->getNameTranslated();
 }
 
 ObjectActor::ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn)
@@ -463,5 +463,5 @@ TownGarrisonActor::TownGarrisonActor(const CGTownInstance * town, uint64_t chain
 
 std::string TownGarrisonActor::toString() const
 {
-	return town->name;
+	return town->getNameTranslated();
 }

+ 2 - 1
AI/StupidAI/StupidAI.cpp

@@ -55,7 +55,8 @@ public:
 	{}
 	void calcDmg(const CStack * ourStack)
 	{
-		TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, &retal);
+		// FIXME: provide distance info for Jousting bonus
+		TDmgRange retal, dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
 		adi = static_cast<int>((dmg.first + dmg.second) / 2);
 		adr = static_cast<int>((retal.first + retal.second) / 2);
 	}

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 
 	h = H;
-	name = h->name;
+	name = h->getNameTranslated();
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }

+ 2 - 2
AI/VCAI/Goals/AbstractGoal.cpp

@@ -91,7 +91,7 @@ std::string AbstractGoal::name() const //TODO: virtualize
 	}
 	break;
 	case GET_ART_TYPE:
-		desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getName();
+		desc = "GET ARTIFACT OF TYPE " + VLC->artifacts()->getByIndex(aid)->getNameTranslated();
 		break;
 	case VISIT_TILE:
 		desc = "VISIT TILE " + tile.toString();
@@ -106,7 +106,7 @@ std::string AbstractGoal::name() const //TODO: virtualize
 		return boost::lexical_cast<std::string>(goalType);
 	}
 	if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
-		desc += " (" + hero->name + ")";
+		desc += " (" + hero->getNameTranslated() + ")";
 	return desc;
 }
 

+ 7 - 7
AI/VCAI/Goals/AdventureSpellCast.cpp

@@ -33,19 +33,19 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->getNameTranslated());
 
 	if(!spell->isAdventure())
-		throw cannotFulfillGoalException(spell->getName() + " is not an adventure spell.");
+		throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell.");
 
 	if(!hero->canCastThisSpell(spell))
-		throw cannotFulfillGoalException("Hero can not cast " + spell->getName());
+		throw cannotFulfillGoalException("Hero can not cast " + spell->getNameTranslated());
 
 	if(hero->mana < hero->getSpellCost(spell))
-		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getName());
+		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
+		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 
 	return iAmElementar();
 }
@@ -75,10 +75,10 @@ void AdventureSpellCast::accept(VCAI * ai)
 
 std::string AdventureSpellCast::name() const
 {
-	return "AdventureSpellCast " + getSpell()->getName();
+	return "AdventureSpellCast " + getSpell()->getNameTranslated();
 }
 
 std::string AdventureSpellCast::completeMessage() const
 {
-	return "Spell cast successfully " + getSpell()->getName();
+	return "Spell cast successfully " + getSpell()->getNameTranslated();
 }

+ 1 - 1
AI/VCAI/Goals/BuyArmy.cpp

@@ -41,5 +41,5 @@ TSubgoal BuyArmy::whatToDoToAchieve()
 
 std::string BuyArmy::completeMessage() const
 {
-	return boost::format("Bought army of value %d in town of %s") % value, town->name;
+	return boost::format("Bought army of value %d in town of %s") % value, town->getNameTranslated();
 }

+ 2 - 2
AI/VCAI/Goals/CompleteQuest.cpp

@@ -92,7 +92,7 @@ TSubgoal CompleteQuest::whatToDoToAchieve()
 		result->name(),
 		result->tile.toString(),
 		result->objid,
-		result->hero.validAndSet() ? result->hero->name : "not specified");
+		result->hero.validAndSet() ? result->hero->getNameTranslated() : "not specified");
 
 	return result;
 }
@@ -273,4 +273,4 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 	}
 
 	return solutions;
-}
+}

+ 2 - 2
AI/VCAI/Goals/Explore.cpp

@@ -240,7 +240,7 @@ bool Explore::operator==(const Explore & other) const
 
 std::string Explore::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " completed exploration";
+	return "Hero " + hero.get()->getNameTranslated() + " completed exploration";
 }
 
 TSubgoal Explore::whatToDoToAchieve()
@@ -339,7 +339,7 @@ TGoalVec Explore::getAllPossibleSubgoals()
 	{
 		for(auto h : heroes)
 		{
-			logAi->trace("Exploration searching for a new point for hero %s", h->name);
+			logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated());
 
 			TSubgoal goal = explorationNewPoint(h);
 

+ 1 - 1
AI/VCAI/Goals/GatherArmy.cpp

@@ -33,7 +33,7 @@ bool GatherArmy::operator==(const GatherArmy & other) const
 
 std::string GatherArmy::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast<std::string>(value);
+	return "Hero " + hero.get()->getNameTranslated() + " gathered army of value " + boost::lexical_cast<std::string>(value);
 }
 
 TSubgoal GatherArmy::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Goals/GatherTroops.cpp

@@ -56,7 +56,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 	{
 		if(getCreaturesCount(hero) >= this->value)
 		{
-			logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name);
+			logAi->trace("Completing GATHER_TROOPS by hero %s", hero->getNameTranslated());
 
 			throw goalFulfilledException(sptr(*this));
 		}

+ 1 - 1
AI/VCAI/Goals/VisitHero.cpp

@@ -33,7 +33,7 @@ bool VisitHero::operator==(const VisitHero & other) const
 
 std::string VisitHero::completeMessage() const
 {
-	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid);
+	return "hero " + hero.get()->getNameTranslated() + " visited hero " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal VisitHero::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Goals/VisitObj.cpp

@@ -33,7 +33,7 @@ bool VisitObj::operator==(const VisitObj & other) const
 
 std::string VisitObj::completeMessage() const
 {
-	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
+	return "hero " + hero.get()->getNameTranslated() + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
 }
 
 TGoalVec VisitObj::getAllPossibleSubgoals()

+ 1 - 1
AI/VCAI/Goals/VisitTile.cpp

@@ -33,7 +33,7 @@ bool VisitTile::operator==(const VisitTile & other) const
 
 std::string VisitTile::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " visited tile " + tile.toString();
+	return "Hero " + hero.get()->getNameTranslated() + " visited tile " + tile.toString();
 }
 
 TSubgoal VisitTile::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -55,7 +55,7 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 
 	auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
 	{
-		logAi->debug("Recalculate paths for %s", hero->name);
+		logAi->debug("Recalculate paths for %s", hero->getNameTranslated());
 		
 		cb->calculatePaths(config);
 	};

+ 17 - 17
AI/VCAI/VCAI.cpp

@@ -303,7 +303,7 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
 	auto firstHero = cb->getHero(hero1);
 	auto secondHero = cb->getHero(hero2);
 
-	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner));
+	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
 	requestActionASAP([=]()
 	{
@@ -617,7 +617,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill
 {
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
-	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level));
+	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
 	requestActionASAP([=](){ answerQuery(queryID, 0); });
 }
 
@@ -814,7 +814,7 @@ void VCAI::makeTurn()
 		for (auto h : cb->getHeroesInfo())
 		{
 			if (h->movement)
-				logAi->warn("Hero %s has %d MP left", h->name, h->movement);
+				logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
 		}
 	}
 	catch (boost::thread_interrupted & e)
@@ -1034,7 +1034,7 @@ void VCAI::mainLoop()
 
 void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 {
-	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
+	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
 	switch(obj->ID)
 	{
 	case Obj::CREATURE_GENERATOR1:
@@ -1427,7 +1427,7 @@ void VCAI::wander(HeroPtr h)
 			{
 				//TODO pick the truly best
 				const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
-				logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->name, t->name, t->visitablePos().toString());
+				logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->getNameTranslated(), t->visitablePos().toString());
 				int3 pos1 = h->pos;
 				striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
 				//if out hero is stuck, we may need to request another hero to clear the way we see
@@ -1581,7 +1581,7 @@ void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, i
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
-	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
+	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
@@ -1667,7 +1667,7 @@ void VCAI::validateVisitableObjs()
 	});
 	for(auto & p : reservedHeroesMap)
 	{
-		errorMsg = " shouldn't be on list for hero " + p.first->name + "!";
+		errorMsg = " shouldn't be on list for hero " + p.first->getNameTranslated() + "!";
 		vstd::erase_if(p.second, shouldBeErased);
 	}
 
@@ -1808,7 +1808,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		}
 	};
 
-	logAi->debug("Moving hero %s to tile %s", h->name, dst.toString());
+	logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString());
 	int3 startHpos = h->visitablePos();
 	bool ret = false;
 	if(startHpos == dst)
@@ -1828,7 +1828,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		cb->getPathsInfo(h.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
-			logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
+			logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
 			throw goalFulfilledException(sptr(Goals::VisitTile(dst).sethero(h)));
 		}
 		int i = (int)path.nodes.size() - 1;
@@ -1990,15 +1990,15 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			throw cannotFulfillGoalException("Invalid path found!");
 		}
 		evaluateGoal(h); //new hero position means new game situation
-		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret);
+		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret);
 	}
 	return ret;
 }
 
 void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
 {
-	auto name = t->town->buildings.at(building)->Name();
-	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->name, t->pos.toString());
+	auto name = t->town->buildings.at(building)->getNameTranslated();
+	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->pos.toString());
 	cb->buildBuilding(t, building); //just do this;
 }
 
@@ -2027,7 +2027,7 @@ void VCAI::tryRealize(Goals::VisitTile & g)
 		throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!");
 	if(g.tile == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString());
 		throw goalFulfilledException(sptr(g));
 	}
 	if(ai->moveHeroToTile(g.tile, g.hero.get()))
@@ -2043,7 +2043,7 @@ void VCAI::tryRealize(Goals::VisitObj & g)
 		throw cannotFulfillGoalException("Cannot visit object: hero is out of MPs!");
 	if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString());
 		throw goalFulfilledException(sptr(g));
 	}
 	if(ai->moveHeroToTile(position, g.hero.get()))
@@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g)
 		if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
 		{
 			logAi->debug("Player %d will build %s in town of %s at %s",
-				playerID, t->town->buildings.at(b)->Name(), t->name, t->pos.toString());
+				playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->pos.toString());
 			cb->buildBuilding(t, b);
 			throw goalFulfilledException(sptr(g));
 		}
@@ -2404,7 +2404,7 @@ void VCAI::performTypicalActions()
 		if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
 			continue;
 
-		logAi->debug("Hero %s started wandering, MP=%d", h->name.c_str(), h->movement);
+		logAi->debug("Hero %s started wandering, MP=%d", h->getNameTranslated(), h->movement);
 		makePossibleUpgrades(*h);
 		pickBestArtifacts(*h);
 		try
@@ -2439,7 +2439,7 @@ void VCAI::checkHeroArmy(HeroPtr h)
 
 void VCAI::recruitHero(const CGTownInstance * t, bool throwing)
 {
-	logAi->debug("Trying to recruit a hero in %s at %s", t->name, t->visitablePos().toString());
+	logAi->debug("Trying to recruit a hero in %s at %s", t->getNameTranslated(), t->visitablePos().toString());
 
 	auto heroes = cb->getAvailableHeroes(t);
 	if(heroes.size())

+ 12 - 0
CI/conan/base/apple

@@ -0,0 +1,12 @@
+[settings]
+compiler=apple-clang
+compiler.version=14
+compiler.libcxx=libc++
+build_type=Release
+
+# required for Boost.Locale in versions >= 1.81
+compiler.cppstd=11
+
+[conf]
+tools.apple:enable_bitcode = False
+tools.cmake.cmaketoolchain:generator = Ninja

+ 5 - 0
CI/conan/base/ios

@@ -0,0 +1,5 @@
+include(apple)
+
+[settings]
+os=iOS
+os.sdk=iphoneos

+ 4 - 0
CI/conan/base/macos

@@ -0,0 +1,4 @@
+include(apple)
+
+[settings]
+os=Macos

+ 2 - 11
CI/conan/ios-arm64

@@ -1,14 +1,5 @@
+include(base/ios)
+
 [settings]
-os=iOS
 os.version=12.0
-os.sdk=iphoneos
 arch=armv8
-compiler=apple-clang
-compiler.version=13
-compiler.libcxx=libc++
-build_type=Release
-[options]
-[build_requires]
-[env]
-[conf]
-tools.cmake.cmaketoolchain:generator = Ninja

+ 4 - 10
CI/conan/ios-armv7

@@ -1,14 +1,8 @@
+include(base/ios)
+
 [settings]
-os=iOS
 os.version=10.0
-os.sdk=iphoneos
 arch=armv7
-compiler=apple-clang
+
+# Xcode 13.x is the last version that can build for armv7
 compiler.version=13
-compiler.libcxx=libc++
-build_type=Release
-[options]
-[build_requires]
-[env]
-[conf]
-tools.cmake.cmaketoolchain:generator = Ninja

+ 2 - 10
CI/conan/macos-arm

@@ -1,13 +1,5 @@
+include(base/macos)
+
 [settings]
-os=Macos
 os.version=11.0
 arch=armv8
-compiler=apple-clang
-compiler.version=13
-compiler.libcxx=libc++
-build_type=Release
-[options]
-[build_requires]
-[env]
-[conf]
-tools.cmake.cmaketoolchain:generator = Ninja

+ 2 - 10
CI/conan/macos-intel

@@ -1,13 +1,5 @@
+include(base/macos)
+
 [settings]
-os=Macos
 os.version=10.13
 arch=x86_64
-compiler=apple-clang
-compiler.version=13
-compiler.libcxx=libc++
-build_type=Release
-[options]
-[build_requires]
-[env]
-[conf]
-tools.cmake.cmaketoolchain:generator = Ninja

+ 2 - 2
CI/ios/before_install.sh

@@ -1,7 +1,7 @@
 #!/usr/bin/env bash
 
-echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
+echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
 
 mkdir ~/.conan ; cd ~/.conan
-curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.1/ios-arm64.xz' \
+curl -L 'https://github.com/vcmi/vcmi-ios-deps/releases/download/1.2/ios-arm64.txz' \
 	| tar -xf -

+ 2 - 2
CI/mac/before_install.sh

@@ -1,9 +1,9 @@
 #!/usr/bin/env bash
 
-echo DEVELOPER_DIR=/Applications/Xcode_13.4.1.app >> $GITHUB_ENV
+echo DEVELOPER_DIR=/Applications/Xcode_14.2.app >> $GITHUB_ENV
 
 brew install ninja
 
 mkdir ~/.conan ; cd ~/.conan
-curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.1/$DEPS_FILENAME.txz" \
+curl -L "https://github.com/vcmi/vcmi-deps-macos/releases/download/1.2/$DEPS_FILENAME.txz" \
 	| tar -xf -

+ 7 - 7
CI/msvc/before_install.sh

@@ -1,10 +1,10 @@
-curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z" \
-	"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.5/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
-7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v140.7z"
+curl -LfsS -o "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z" \
+	"https://github.com/vcmi/vcmi-deps-windows/releases/download/v1.6/vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
+7z x "vcpkg-export-${VCMI_BUILD_PLATFORM}-windows-v143.7z"
 
-rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
-mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
-cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
+#rm -r -f vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug
+#mkdir -p vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
+#cp vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/bin/* vcpkg/installed/${VCMI_BUILD_PLATFORM}-windows/debug/bin
 
 DUMPBIN_DIR=$(vswhere -latest -find **/dumpbin.exe | head -n 1)
-dirname "$DUMPBIN_DIR" > $GITHUB_PATH
+dirname "$DUMPBIN_DIR" > $GITHUB_PATH

+ 18 - 6
CMakeLists.txt

@@ -90,6 +90,11 @@ if(APPLE_IOS AND COPY_CONFIG_ON_BUILD)
 	set(COPY_CONFIG_ON_BUILD OFF)
 endif()
 
+# No QT Linguist on MXE
+if((MINGW) AND (${CMAKE_CROSSCOMPILING}))
+	set(ENABLE_TRANSLATIONS OFF)
+endif()
+
 ############################################
 #        Miscellaneous options             #
 ############################################
@@ -181,6 +186,14 @@ set(CMAKE_XCODE_ATTRIBUTE_MARKETING_VERSION ${APP_SHORT_VERSION})
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO)
 set(CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH[variant=Debug] YES)
 
+if(ENABLE_LAUNCHER)
+	add_definitions(-DENABLE_LAUNCHER)
+endif()
+
+if(ENABLE_EDITOR)
+	add_definitions(-DENABLE_EDITOR)
+endif()
+
 if(ENABLE_SINGLE_APP_BUILD)
 	add_definitions(-DSINGLE_PROCESS_APP=1)
 endif()
@@ -232,7 +245,7 @@ if(MINGW OR MSVC)
 		#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4800") # 4800: implicit conversion from 'xxx' to bool. Possible information loss
 
 		if(ENABLE_STRICT_COMPILATION)
-			set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wx") # Treats all compiler warnings as errors
+			set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /WX") # Treats all compiler warnings as errors
 		endif()
 
 		if(ENABLE_MULTI_PROCESS_BUILDS)
@@ -360,12 +373,8 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
 	find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
 	find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
 
-	find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools)
-	find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools)
-	if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR)
-		set(ENABLE_TRANSLATIONS OFF)
-	endif()
 	if(ENABLE_TRANSLATIONS)
+		find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS LinguistTools)
 		add_definitions(-DENABLE_QT_TRANSLATIONS)
 	endif()
 endif()
@@ -563,6 +572,9 @@ if(WIN32)
 				FILES ${integration_loc}
 				DESTINATION ${BIN_DIR}/platforms
 			)
+			install(
+				FILES "$<TARGET_FILE:Qt${QT_VERSION_MAJOR}::QWindowsVistaStylePlugin>" 
+				DESTINATION ${BIN_DIR}/styles) 
 		endif()
 	endif()
 

+ 14 - 0
Mods/vcmi/mod.json

@@ -1,6 +1,20 @@
 {
 	"name" : "VCMI essential files",
 	"description" : "Essential files required for VCMI to run correctly",
+	
+	"german" : {
+		"name" : "VCMI - grundlegende Dateien",
+		"description" : "Grundlegende Dateien, die für die korrekte Ausführung von VCMI erforderlich sind",
+		"author" : "VCMI-Team",
+		"modType" : "Grafik",
+	},
+	
+	"ukrainian" : {
+		"name" : "VCMI - ключові файли",
+		"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
+		"author" : "Команда VCMI",
+		"modType" : "Графіка",
+	},
 
 	"version" : "1.1",
 	"author" : "VCMI Team",

+ 1 - 2
client/CBitmapHandler.cpp

@@ -10,8 +10,7 @@
 #include "StdInc.h"
 
 #include "../lib/filesystem/Filesystem.h"
-#include "SDL.h"
-#include "SDL_image.h"
+#include <SDL_image.h>
 #include "CBitmapHandler.h"
 #include "gui/SDL_Extensions.h"
 #include "../lib/vcmi_endian.h"

+ 2 - 2
client/CGameInfo.h

@@ -38,7 +38,7 @@ VCMI_LIB_NAMESPACE_END
 class CMapHandler;
 class CSoundHandler;
 class CMusicHandler;
-class CCursorHandler;
+class CursorHandler;
 class IMainVideoPlayer;
 class CServerHandler;
 
@@ -49,7 +49,7 @@ public:
 	CSoundHandler * soundh;
 	CMusicHandler * musich;
 	CConsoleHandler * consoleh;
-	CCursorHandler * curh;
+	CursorHandler * curh;
 	IMainVideoPlayer * videoh;
 };
 extern CClientState * CCS;

+ 48 - 461
client/CMT.cpp

@@ -13,8 +13,6 @@
 
 #include <boost/program_options.hpp>
 
-#include <vcmi/scripting/Service.h>
-
 #include "gui/SDL_Extensions.h"
 #include "CGameInfo.h"
 #include "mapHandler.h"
@@ -25,7 +23,7 @@
 #include "lobby/CSelectionBase.h"
 #include "windows/CCastleInterface.h"
 #include "../lib/CConsoleHandler.h"
-#include "gui/CCursorHandler.h"
+#include "gui/CursorHandler.h"
 #include "../lib/CGameState.h"
 #include "../CCallback.h"
 #include "CPlayerInterface.h"
@@ -33,37 +31,32 @@
 #include "../lib/CBuildingHandler.h"
 #include "CVideoHandler.h"
 #include "../lib/CHeroHandler.h"
-#include "../lib/CCreatureHandler.h"
 #include "../lib/spells/CSpellHandler.h"
 #include "CMusicHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "Graphics.h"
 #include "Client.h"
-#include "../lib/CConfigHandler.h"
 #include "../lib/serializer/BinaryDeserializer.h"
 #include "../lib/serializer/BinarySerializer.h"
-#include "../lib/VCMI_Lib.h"
 #include "../lib/VCMIDirs.h"
 #include "../lib/NetPacks.h"
 #include "CMessage.h"
 #include "../lib/CModHandler.h"
-#include "../lib/ScriptHandler.h"
 #include "../lib/CTownHandler.h"
-#include "../lib/CArtHandler.h"
-#include "../lib/GameConstants.h"
 #include "gui/CGuiHandler.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
-#include "../lib/StringConstants.h"
 #include "../lib/CPlayerState.h"
 #include "gui/CAnimation.h"
 #include "../lib/serializer/Connection.h"
 #include "CServerHandler.h"
 #include "gui/NotificationHandler.h"
+#include "ClientCommandManager.h"
 
 #include <boost/asio.hpp>
 
 #include "mainmenu/CPrologEpilogVideo.h"
 #include <vstd/StringUtils.h>
+#include <SDL.h>
 
 #ifdef VCMI_WINDOWS
 #include "SDL_syswm.h"
@@ -71,7 +64,7 @@
 #ifdef VCMI_ANDROID
 #include "lib/CAndroidVMHelper.h"
 #endif
-#include "../lib/UnlockGuard.h"
+
 #include "CMT.h"
 
 #if __MINGW32__
@@ -208,6 +201,8 @@ int main(int argc, char * argv[])
 		("lobby-host", "if this client hosts session")
 		("lobby-uuid", po::value<std::string>(), "uuid to the server")
 		("lobby-connections", po::value<ui16>(), "connections of server")
+		("lobby-username", po::value<std::string>(), "player name")
+		("lobby-gamemode", po::value<ui16>(), "use 0 for new game and 1 for load game")
 		("uuid", po::value<std::string>(), "uuid for the client");
 
 	if(argc > 1)
@@ -247,7 +242,14 @@ int main(int argc, char * argv[])
 	std::cout.flags(std::ios::unitbuf);
 #ifndef VCMI_IOS
 	console = new CConsoleHandler();
-	*console->cb = processCommand;
+
+	auto callbackFunction = [](std::string buffer, bool calledFromIngameConsole)
+	{
+		ClientCommandManager commandController;
+		commandController.processCommand(buffer, calledFromIngameConsole);
+	};
+
+	*console->cb = callbackFunction;
 	console->start();
 #endif
 
@@ -470,7 +472,7 @@ int main(int argc, char * argv[])
 		pomtime.getDiff();
 		graphics = new Graphics(); // should be before curh
 
-		CCS->curh = new CCursorHandler();
+		CCS->curh = new CursorHandler();
 		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
 		pomtime.getDiff();
 
@@ -489,13 +491,41 @@ int main(int argc, char * argv[])
 	session["autoSkip"].Bool()  = vm.count("autoSkip");
 	session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
 	session["aiSolo"].Bool() = false;
+	std::shared_ptr<CMainMenu> mmenu;
 	
+	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
+	{
+		mmenu = CMainMenu::create();
+		GH.curInt = mmenu.get();
+	}
+	
+	std::vector<std::string> names;
 	session["lobby"].Bool() = false;
 	if(vm.count("lobby"))
 	{
 		session["lobby"].Bool() = true;
 		session["host"].Bool() = false;
 		session["address"].String() = vm["lobby-address"].as<std::string>();
+		if(vm.count("lobby-username"))
+			session["username"].String() = vm["lobby-username"].as<std::string>();
+		else
+			session["username"].String() = settings["launcher"]["lobbyUsername"].String();
+		if(vm.count("lobby-gamemode"))
+			session["gamemode"].Integer() = vm["lobby-gamemode"].as<ui16>();
+		else
+			session["gamemode"].Integer() = 0;
 		CSH->uuid = vm["uuid"].as<std::string>();
 		session["port"].Integer() = vm["lobby-port"].as<ui16>();
 		logGlobal->info("Remote lobby mode at %s:%d, uuid is %s", session["address"].String(), session["port"].Integer(), CSH->uuid);
@@ -510,23 +540,11 @@ int main(int argc, char * argv[])
 		//we should not reconnect to previous game in online mode
 		Settings saveSession = settings.write["server"]["reconnect"];
 		saveSession->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
-	{
-		GH.curInt = CMainMenu::create().get();
+		
+		//start lobby immediately
+		names.push_back(session["username"].String());
+		ESelectionScreen sscreen = session["gamemode"].Integer() == 0 ? ESelectionScreen::newGame : ESelectionScreen::loadGame;
+		mmenu->openLobby(sscreen, session["host"].Bool(), &names, ELoadMode::MULTI);
 	}
 	
 	// Restore remote session - start game immediately
@@ -548,437 +566,6 @@ int main(int argc, char * argv[])
 	return 0;
 }
 
-void printInfoAboutIntObject(const CIntObject *obj, int level)
-{
-	std::stringstream sbuffer;
-	sbuffer << std::string(level, '\t');
-
-	sbuffer << typeid(*obj).name() << " *** ";
-	if (obj->active)
-	{
-#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
-		PRINT(LCLICK, 'L');
-		PRINT(RCLICK, 'R');
-		PRINT(HOVER, 'H');
-		PRINT(MOVE, 'M');
-		PRINT(KEYBOARD, 'K');
-		PRINT(TIME, 'T');
-		PRINT(GENERAL, 'A');
-		PRINT(WHEEL, 'W');
-		PRINT(DOUBLECLICK, 'D');
-#undef  PRINT
-	}
-	else
-		sbuffer << "inactive";
-	sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
-	sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
-	logGlobal->info(sbuffer.str());
-
-	for(const CIntObject *child : obj->children)
-		printInfoAboutIntObject(child, level+1);
-}
-
-void removeGUI()
-{
-	// CClient::endGame
-	GH.curInt = nullptr;
-	if(GH.topInt())
-		GH.topInt()->deactivate();
-	adventureInt = nullptr;
-	GH.listInt.clear();
-	GH.objsToBlit.clear();
-	GH.statusbar = nullptr;
-	logGlobal->info("Removed GUI.");
-
-	LOCPLINT = nullptr;
-}
-
-#ifndef VCMI_IOS
-void processCommand(const std::string &message)
-{
-	std::istringstream readed;
-	readed.str(message);
-	std::string cn; //command name
-	readed >> cn;
-
-// Check mantis issue 2292 for details
-//	if(LOCPLINT && LOCPLINT->cingconsole)
-//		LOCPLINT->cingconsole->print(message);
-
-	if(message==std::string("die, fool"))
-	{
-		exit(EXIT_SUCCESS);
-	}
-	else if(cn==std::string("activate"))
-	{
-		int what;
-		readed >> what;
-		switch (what)
-		{
-		case 0:
-			GH.topInt()->activate();
-			break;
-		case 1:
-			adventureInt->activate();
-			break;
-		case 2:
-			LOCPLINT->castleInt->activate();
-			break;
-		}
-	}
-	else if(cn=="redraw")
-	{
-		GH.totalRedraw();
-	}
-	else if(cn=="screen")
-	{
-		std::cout << "Screenbuf points to ";
-
-		if(screenBuf == screen)
-			logGlobal->error("screen");
-		else if(screenBuf == screen2)
-			logGlobal->error("screen2");
-		else
-			logGlobal->error("?!?");
-
-		SDL_SaveBMP(screen, "Screen_c.bmp");
-		SDL_SaveBMP(screen2, "Screen2_c.bmp");
-	}
-	else if(cn=="save")
-	{
-		if(!CSH->client)
-		{
-			std::cout << "Game in not active";
-			return;
-		}
-		std::string fname;
-		readed >> fname;
-		CSH->client->save(fname);
-	}
-//	else if(cn=="load")
-//	{
-//		// TODO: this code should end the running game and manage to call startGame instead
-//		std::string fname;
-//		readed >> fname;
-//		CSH->client->loadGame(fname);
-//	}
-	else if(message=="convert txt")
-	{
-		VLC->generaltexth->dumpAllTexts();
-	}
-	else if(message=="get config")
-	{
-		std::cout << "Command accepted.\t";
-
-		const bfs::path outPath =
-			VCMIDirs::get().userExtractedPath() / "configuration";
-
-		bfs::create_directories(outPath);
-
-		const std::vector<std::string> contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"};
-
-		for(auto contentName : contentNames)
-		{
-			auto & content = (*VLC->modh->content)[contentName];
-
-			auto contentOutPath = outPath / contentName;
-			bfs::create_directories(contentOutPath);
-
-			for(auto & iter : content.modData)
-			{
-				const JsonNode & modData = iter.second.modData;
-
-				for(auto & nameAndObject : modData.Struct())
-				{
-					const JsonNode & object = nameAndObject.second;
-
-					std::string name = CModHandler::normalizeIdentifier(object.meta, CModHandler::scopeBuiltin(), nameAndObject.first);
-
-					boost::algorithm::replace_all(name,":","_");
-
-					const bfs::path filePath = contentOutPath / (name + ".json");
-					bfs::ofstream file(filePath);
-					file << object.toJson();
-				}
-			}
-		}
-
-		std::cout << "\rExtracting done :)\n";
-		std::cout << " Extracted files can be found in " << outPath << " directory\n";
-	}
-#if SCRIPTING_ENABLED
-	else if(message=="get scripts")
-	{
-		std::cout << "Command accepted.\t";
-
-		const bfs::path outPath =
-			VCMIDirs::get().userExtractedPath() / "scripts";
-
-		bfs::create_directories(outPath);
-
-		for(auto & kv : VLC->scriptHandler->objects)
-		{
-			std::string name = kv.first;
-			boost::algorithm::replace_all(name,":","_");
-
-			const scripting::ScriptImpl * script = kv.second.get();
-			bfs::path filePath = outPath / (name + ".lua");
-			bfs::ofstream file(filePath);
-			file << script->getSource();
-		}
-		std::cout << "\rExtracting done :)\n";
-		std::cout << " Extracted files can be found in " << outPath << " directory\n";
-	}
-#endif
-	else if(message=="get txt")
-	{
-		std::cout << "Command accepted.\t";
-
-		const bfs::path outPath =
-			VCMIDirs::get().userExtractedPath();
-
-		auto list = CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
-		{
-			return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
-		});
-
-		for (auto & filename : list)
-		{
-			const bfs::path filePath = outPath / (filename.getName() + ".TXT");
-
-			bfs::create_directories(filePath.parent_path());
-
-			bfs::ofstream file(filePath);
-			auto text = CResourceHandler::get()->load(filename)->readAll();
-
-			file.write((char*)text.first.get(), text.second);
-		}
-
-		std::cout << "\rExtracting done :)\n";
-		std::cout << " Extracted files can be found in " << outPath << " directory\n";
-	}
-	else if(cn=="crash")
-	{
-		int *ptr = nullptr;
-		*ptr = 666;
-		//disaster!
-	}
-	else if(cn == "mp" && adventureInt)
-	{
-		if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
-			std::cout << h->movement << "; max: " << h->maxMovePoints(true) << "/" << h->maxMovePoints(false) << std::endl;
-	}
-	else if(cn == "bonuses")
-	{
-		bool jsonFormat = (message == "bonuses json");
-		auto format = [jsonFormat](const BonusList & b) -> std::string
-		{
-			if(jsonFormat)
-				return b.toJsonNode().toJson(true);
-			std::ostringstream ss;
-			ss << b;
-			return ss.str();
-		};
-		std::cout << "Bonuses of " << adventureInt->selection->getObjectName() << std::endl
-			<< format(adventureInt->selection->getBonusList()) << std::endl;
-
-		std::cout << "\nInherited bonuses:\n";
-		TCNodes parents;
-		adventureInt->selection->getParents(parents);
-		for(const CBonusSystemNode *parent : parents)
-		{
-			std::cout << "\nBonuses from " << typeid(*parent).name() << std::endl << format(*parent->getAllBonuses(Selector::all, Selector::all)) << std::endl;
-		}
-	}
-	else if(cn == "not dialog")
-	{
-		LOCPLINT->showingDialog->setn(false);
-	}
-	else if(cn == "gui")
-	{
-		for(auto & child : GH.listInt)
-		{
-			const auto childPtr = child.get();
-			if(const CIntObject * obj = dynamic_cast<const CIntObject *>(childPtr))
-				printInfoAboutIntObject(obj, 0);
-			else
-				std::cout << typeid(childPtr).name() << std::endl;
-		}
-	}
-	else if(cn=="tell")
-	{
-		std::string what;
-		int id1, id2;
-		readed >> what >> id1 >> id2;
-		if(what == "hs")
-		{
-			for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo())
-				if(h->type->ID.getNum() == id1)
-					if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2)))
-						std::cout << a->nodeName();
-		}
-	}
-	else if (cn == "set")
-	{
-		std::string what, value;
-		readed >> what;
-
-		Settings conf = settings.write["session"][what];
-
-		readed >> value;
-
-		if (value == "on")
-		{
-			conf->Bool() = true;
-			logGlobal->info("Option %s enabled!", what);
-		}
-		else if (value == "off")
-		{
-			conf->Bool() = false;
-			logGlobal->info("Option %s disabled!", what);
-		}
-	}
-	else if(cn == "unlock")
-	{
-		std::string mxname;
-		readed >> mxname;
-		if(mxname == "pim" && LOCPLINT)
-			LOCPLINT->pim->unlock();
-	}
-	else if(cn == "def2bmp")
-	{
-		std::string URI;
-		readed >> URI;
-		std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
-		anim->preload();
-		anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
-	}
-	else if(cn == "extract")
-	{
-		std::string URI;
-		readed >> URI;
-
-		if (CResourceHandler::get()->existsResource(ResourceID(URI)))
-		{
-			const bfs::path outPath = VCMIDirs::get().userExtractedPath() / URI;
-
-			auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
-
-			bfs::create_directories(outPath.parent_path());
-			bfs::ofstream outFile(outPath, bfs::ofstream::binary);
-			outFile.write((char*)data.first.get(), data.second);
-		}
-		else
-			logGlobal->error("File not found!");
-	}
-	else if(cn == "setBattleAI")
-	{
-		std::string fname;
-		readed >> fname;
-		std::cout << "Will try loading that AI to see if it is correct name...\n";
-		try
-		{
-			if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game
-			{
-				Settings neutralAI = settings.write["server"]["neutralAI"];
-				neutralAI->String() = fname;
-				std::cout << "Setting changed, from now the battle ai will be " << fname << "!\n";
-			}
-		}
-		catch(std::exception &e)
-		{
-			logGlobal->warn("Failed opening %s: %s", fname, e.what());
-			logGlobal->warn("Setting not changes, AI not found or invalid!");
-		}
-	}
-
-	auto giveTurn = [&](PlayerColor player)
-	{
-		YourTurn yt;
-		yt.player = player;
-		yt.daysWithoutCastle = CSH->client->getPlayerState(player)->daysWithoutCastle;
-		yt.applyCl(CSH->client);
-	};
-
-	Settings session = settings.write["session"];
-	if(cn == "autoskip")
-	{
-		session["autoSkip"].Bool() = !session["autoSkip"].Bool();
-	}
-	else if(cn == "gosolo")
-	{
-		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!CSH->client)
-		{
-			std::cout << "Game in not active";
-			return;
-		}
-		PlayerColor color;
-		if(session["aiSolo"].Bool())
-		{
-			for(auto & elem : CSH->client->gameState()->players)
-			{
-				if(elem.second.human)
-					CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
-			}
-		}
-		else
-		{
-			color = LOCPLINT->playerID;
-			removeGUI();
-			for(auto & elem : CSH->client->gameState()->players)
-			{
-				if(elem.second.human)
-				{
-					auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
-					logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive);
-					CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
-				}
-			}
-			GH.totalRedraw();
-			giveTurn(color);
-		}
-		session["aiSolo"].Bool() = !session["aiSolo"].Bool();
-	}
-	else if(cn == "controlai")
-	{
-		std::string colorName;
-		readed >> colorName;
-		boost::to_lower(colorName);
-
-		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		if(!CSH->client)
-		{
-			std::cout << "Game in not active";
-			return;
-		}
-		PlayerColor color;
-		if(LOCPLINT)
-			color = LOCPLINT->playerID;
-		for(auto & elem : CSH->client->gameState()->players)
-		{
-			if(elem.second.human || (colorName.length() &&
-				elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
-			{
-				continue;
-			}
-
-			removeGUI();
-			CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
-		}
-		GH.totalRedraw();
-		if(color != PlayerColor::NEUTRAL)
-			giveTurn(color);
-	}
-	// Check mantis issue 2292 for details
-/* 	else if(client && client->serv && client->serv->connected && LOCPLINT) //send to server
-	{
-		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		LOCPLINT->cb->sendMessage(message);
-	}*/
-}
-#endif
-
 //plays intro, ends when intro is over or button has been pressed (handles events)
 void playIntro()
 {

+ 5 - 2
client/CMT.h

@@ -8,7 +8,11 @@
  *
  */
 #pragma once
-#include <SDL_render.h>
+
+struct SDL_Texture;
+struct SDL_Window;
+struct SDL_Renderer;
+struct SDL_Surface;
 
 extern SDL_Texture * screenTexture;
 
@@ -19,5 +23,4 @@ extern SDL_Surface *screen;      // main screen surface
 extern SDL_Surface *screen2;     // and hlp surface (used to store not-active interfaces layer)
 extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
 
-void removeGUI();
 void handleQuit(bool ask = true);

+ 6 - 5
client/CMakeLists.txt

@@ -18,12 +18,11 @@ set(client_SRCS
 
 		gui/CAnimation.cpp
 		gui/Canvas.cpp
-		gui/CCursorHandler.cpp
+		gui/CursorHandler.cpp
 		gui/CGuiHandler.cpp
 		gui/CIntObject.cpp
 		gui/ColorFilter.cpp
 		gui/Fonts.cpp
-		gui/Geometries.cpp
 		gui/SDL_Extensions.cpp
 		gui/NotificationHandler.cpp
 		gui/InterfaceObjectConfigurable.cpp
@@ -82,6 +81,7 @@ set(client_SRCS
 		NetPacksClient.cpp
 		NetPacksLobbyClient.cpp
 		SDLRWwrapper.cpp
+		ClientCommandManager.cpp
 )
 
 set(client_HEADERS
@@ -104,12 +104,12 @@ set(client_HEADERS
 
 		gui/CAnimation.h
 		gui/Canvas.h
-		gui/CCursorHandler.h
+		gui/CursorHandler.h
 		gui/CGuiHandler.h
 		gui/ColorFilter.h
 		gui/CIntObject.h
 		gui/Fonts.h
-		gui/Geometries.h
+		gui/TextAlignment.h
 		gui/SDL_Compat.h
 		gui/SDL_Extensions.h
 		gui/SDL_Pixels.h
@@ -168,6 +168,7 @@ set(client_HEADERS
 		mapHandler.h
 		resource.h
 		SDLRWwrapper.h
+		ClientCommandManager.h
 )
 
 if(APPLE_IOS)
@@ -232,7 +233,7 @@ if(WIN32)
 		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/tbb.dll tbb.dll
+			COMMAND ${CMAKE_COMMAND} -E copy AI/tbb12.dll tbb12.dll
 		)
 	endif()
 elseif(APPLE_IOS)

+ 1 - 3
client/CMessage.cpp

@@ -102,9 +102,7 @@ SDL_Surface * CMessage::drawDialogBox(int w, int h, PlayerColor playerColor)
 	{
 		for (int j=0; j<h; j+=background->h)
 		{
-			Rect srcR(0,0,background->w, background->h);
-			Rect dstR(i,j,w,h);
-			CSDL_Ext::blitSurface(background, &srcR, ret, &dstR);
+			CSDL_Ext::blitSurface(background, ret, Point(i,j));
 		}
 	}
 	drawBorder(playerColor, ret, w, h);

+ 0 - 1
client/CMessage.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "Graphics.h"
-#include "gui/Geometries.h"
 
 struct SDL_Surface;
 class CInfoWindow;

+ 20 - 35
client/CMusicHandler.cpp

@@ -9,7 +9,7 @@
  */
 #include "StdInc.h"
 #include <SDL_mixer.h>
-#include <SDL.h>
+#include <SDL_timer.h>
 
 #include "CMusicHandler.h"
 #include "CGameInfo.h"
@@ -20,7 +20,7 @@
 #include "../lib/StringConstants.h"
 #include "../lib/CRandomGenerator.h"
 #include "../lib/VCMIDirs.h"
-#include "../lib/Terrain.h"
+#include "../lib/TerrainHandler.h"
 
 #define VCMI_SOUND_NAME(x)
 #define VCMI_SOUND_FILE(y) #y,
@@ -89,36 +89,6 @@ CSoundHandler::CSoundHandler():
 		soundBase::battle02, soundBase::battle03, soundBase::battle04,
 		soundBase::battle05, soundBase::battle06, soundBase::battle07
 	};
-	
-	//predefine terrain set
-	//TODO: support custom sounds for new terrains and load from json
-	horseSounds =
-	{
-		{Terrain::DIRT, soundBase::horseDirt},
-		{Terrain::SAND, soundBase::horseSand},
-		{Terrain::GRASS, soundBase::horseGrass},
-		{Terrain::SNOW, soundBase::horseSnow},
-		{Terrain::SWAMP, soundBase::horseSwamp},
-		{Terrain::ROUGH, soundBase::horseRough},
-		{Terrain::SUBTERRANEAN, soundBase::horseSubterranean},
-		{Terrain::LAVA, soundBase::horseLava},
-		{Terrain::WATER, soundBase::horseWater},
-		{Terrain::ROCK, soundBase::horseRock}
-	};
-}
-
-void CSoundHandler::loadHorseSounds()
-{
-	const auto & terrains = CGI->terrainTypeHandler->terrains();
-	for(const auto & terrain : terrains)
-	{
-		//since all sounds are hardcoded, let's keep it
-		if(vstd::contains(horseSounds, terrain.id))
-			continue;
-
-		//Use already existing horse sound
-		horseSounds[terrain.id] = horseSounds.at(terrains[terrain.id].horseSoundId);
-	}
 }
 
 void CSoundHandler::init()
@@ -368,9 +338,9 @@ CMusicHandler::CMusicHandler():
 
 void CMusicHandler::loadTerrainMusicThemes()
 {
-	for (const auto & terrain : CGI->terrainTypeHandler->terrains())
+	for (const auto & terrain : CGI->terrainTypeHandler->objects)
 	{
-		addEntryToSet("terrain_" + terrain.name, "Music/" + terrain.musicFilename);
+		addEntryToSet("terrain_" + terrain->getJsonKey(), "Music/" + terrain->musicFilename);
 	}
 }
 
@@ -399,6 +369,7 @@ void CMusicHandler::release()
 		boost::mutex::scoped_lock guard(mutex);
 
 		Mix_HookMusicFinished(nullptr);
+		current->stop();
 
 		current.reset();
 		next.reset();
@@ -542,6 +513,20 @@ MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string mu
 }
 MusicEntry::~MusicEntry()
 {
+	if (playing)
+	{
+		assert(0);
+		logGlobal->error("Attempt to delete music while playing!");
+		Mix_HaltMusic();
+	}
+
+	if (loop == 0 && Mix_FadingMusic() != MIX_NO_FADING)
+	{
+		assert(0);
+		logGlobal->error("Attempt to delete music while fading out!");
+		Mix_HaltMusic();
+	}
+
 	logGlobal->trace("Del-ing music file %s", currentName);
 	if (music)
 		Mix_FreeMusic(music);
@@ -619,7 +604,7 @@ bool MusicEntry::play()
 
 bool MusicEntry::stop(int fade_ms)
 {
-	if (playing)
+	if (Mix_PlayingMusic())
 	{
 		playing = false;
 		loop = 0;

+ 0 - 3
client/CMusicHandler.h

@@ -11,7 +11,6 @@
 
 #include "../lib/CConfigHandler.h"
 #include "../lib/CSoundBase.h"
-#include "../lib/Terrain.h"
 
 struct _Mix_Music;
 struct SDL_RWops;
@@ -61,7 +60,6 @@ public:
 	CSoundHandler();
 
 	void init() override;
-	void loadHorseSounds();
 	void release() override;
 
 	void setVolume(ui32 percent) override;
@@ -84,7 +82,6 @@ public:
 	// Sets
 	std::vector<soundBase::soundID> pickupSounds;
 	std::vector<soundBase::soundID> battleIntroSounds;
-	std::map<TerrainId, soundBase::soundID> horseSounds;
 };
 
 // Helper //now it looks somewhat useless

+ 35 - 18
client/CPlayerInterface.cpp

@@ -19,7 +19,7 @@
 #include "battle/BattleWindow.h"
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
-#include "gui/CCursorHandler.h"
+#include "gui/CursorHandler.h"
 #include "windows/CKingdomInterface.h"
 #include "CGameInfo.h"
 #include "windows/CHeroWindow.h"
@@ -61,7 +61,9 @@
 #include "windows/InfoWindows.h"
 #include "../lib/UnlockGuard.h"
 #include "../lib/CPathfinder.h"
-#include <SDL.h>
+#include "../lib/RoadHandler.h"
+#include "../lib/TerrainHandler.h"
+#include <SDL_timer.h>
 #include "CServerHandler.h"
 // FIXME: only needed for CGameState::mutex
 #include "../lib/CGameState.h"
@@ -156,7 +158,6 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
 	cb = CB;
 	env = ENV;
 
-	CCS->soundh->loadHorseSounds();
 	CCS->musich->loadTerrainMusicThemes();
 
 	initializeHeroTownList();
@@ -260,7 +261,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 	{
 		updateAmbientSounds();
 		//We may need to change music - select new track, music handler will change it if needed
-		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->name, true, false);
+		CCS->musich->playMusicFromSet("terrain", LOCPLINT->cb->getTile(hero->visitablePos())->terType->getJsonKey(), true, false);
 
 		if(details.result == TryMoveHero::TELEPORTATION)
 		{
@@ -409,7 +410,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->name % playerID);
+	LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->getNameTranslated() % playerID);
 
 	const CArmedInstance *newSelection = nullptr;
 	if (makingTurn)
@@ -436,7 +437,7 @@ void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 		adventureInt->select(newSelection, true);
 	else if (adventureInt->selection == hero)
 		adventureInt->selection = nullptr;
-	
+
 	if (vstd::contains(paths, hero))
 		paths.erase(hero);
 }
@@ -1267,7 +1268,7 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 			if (p.second.nodes.size())
 				pathsMap[p.first] = p.second.endPos();
 			else
-				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->name);
+				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
 		}
 		h & pathsMap;
 	}
@@ -1362,14 +1363,14 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
  */
 void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes)
 {
-	std::string text = artifact->getDescription();
+	std::string text = artifact->getDescriptionTranslated();
 	text += "\n\n";
 	std::vector<std::shared_ptr<CComponent>> scs;
 
 	if(assembledArtifact)
 	{
 		// You possess all of the components to...
-		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getName());
+		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
 
 		// Picture of assembled artifact at bottom.
 		auto sc = std::make_shared<CComponent>(CComponent::artifact, assembledArtifact->getIndex(), 0);
@@ -1623,7 +1624,7 @@ int CPlayerInterface::getLastIndex( std::string namePrefix)
 	else
 	for (directory_iterator dir(gamesDir); dir != enddir; ++dir)
 	{
-		if (is_regular(dir->status()))
+		if (is_regular_file(dir->status()))
 		{
 			std::string name = dir->path().filename().string();
 			if (starts_with(name, namePrefix) && ends_with(name, ".vcgm1"))
@@ -2372,8 +2373,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		for (auto & elem : path.nodes)
 			elem.coord = h->convertFromVisitablePos(elem.coord);
 
-		TerrainId currentTerrain = Terrain::BORDER; // not init yet
+		TerrainId currentTerrain = ETerrainId::NONE;
 		TerrainId newTerrain;
+		bool wasOnRoad = true;
 		int sh = -1;
 
 		auto canStop = [&](CGPathNode * node) -> bool
@@ -2389,13 +2391,18 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 
 		for (i=(int)path.nodes.size()-1; i>0 && (stillMoveHero.data == CONTINUE_MOVE || !canStop(&path.nodes[i])); i--)
 		{
-			int3 currentCoord = path.nodes[i].coord;
+			int3 prevCoord = path.nodes[i].coord;
 			int3 nextCoord = path.nodes[i-1].coord;
 
-			auto currentObject = getObj(currentCoord, currentCoord == h->pos);
+			auto prevRoad = cb->getTile(h->convertToVisitablePos(prevCoord))->roadType;
+			auto nextRoad = cb->getTile(h->convertToVisitablePos(nextCoord))->roadType;
+
+			bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
+
+			auto prevObject = getObj(prevCoord, prevCoord == h->pos);
 			auto nextObjectTop = getObj(nextCoord, false);
 			auto nextObject = getObj(nextCoord, true);
-			auto destTeleportObj = getDestTeleportObj(currentObject, nextObjectTop, nextObject);
+			auto destTeleportObj = getDestTeleportObj(prevObject, nextObjectTop, nextObject);
 			if (isTeleportAction(path.nodes[i-1].action) && destTeleportObj != nullptr)
 			{
 				CCS->soundh->stopSound(sh);
@@ -2410,7 +2417,10 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 				}
 				if(i != path.nodes.size() - 1)
 				{
-					sh = CCS->soundh->playSound(CCS->soundh->horseSounds[currentTerrain], -1);
+					if (movingOnRoad)
+						sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSound, -1);
+					else
+						sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(currentTerrain)->horseSoundPenalty, -1);
 				}
 				continue;
 			}
@@ -2428,12 +2438,16 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 				sh = CCS->soundh->playSound(soundBase::horseFlying, -1);
 #endif
 			{
-				newTerrain = cb->getTile(h->convertToVisitablePos(currentCoord))->terType->id;
-				if(newTerrain != currentTerrain)
+				newTerrain = cb->getTile(h->convertToVisitablePos(prevCoord))->terType->getId();
+				if(newTerrain != currentTerrain || wasOnRoad != movingOnRoad)
 				{
 					CCS->soundh->stopSound(sh);
-					sh = CCS->soundh->playSound(CCS->soundh->horseSounds[newTerrain], -1);
+					if (movingOnRoad)
+						sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSound, -1);
+					else
+						sh = CCS->soundh->playSound(VLC->terrainTypeHandler->getById(newTerrain)->horseSoundPenalty, -1);
 					currentTerrain = newTerrain;
+					wasOnRoad = movingOnRoad;
 				}
 			}
 
@@ -2473,6 +2487,9 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		// (i == 0) means hero went through all the path
 		adventureInt->updateMoveHero(h, (i != 0));
 		adventureInt->updateNextHero(h);
+
+		// ugly workaround to force instant update of adventure map
+		adventureInt->animValHitCount = 8;
 	}
 
 	setMovementStatus(false);

+ 7 - 6
client/CVideoHandler.cpp

@@ -8,7 +8,6 @@
  *
  */
 #include "StdInc.h"
-#include <SDL.h>
 #include "CVideoHandler.h"
 
 #include "gui/CGuiHandler.h"
@@ -67,8 +66,8 @@ CVideoPlayer::CVideoPlayer()
 	context = nullptr;
 	texture = nullptr;
 	dest = nullptr;
-	destRect = genRect(0,0,0,0);
-	pos = genRect(0,0,0,0);
+	destRect = CSDL_Ext::genRect(0,0,0,0);
+	pos = CSDL_Ext::genRect(0,0,0,0);
 	refreshWait = 0;
 	refreshCount = 0;
 	doLoop = false;
@@ -339,10 +338,10 @@ void CVideoPlayer::show( int x, int y, SDL_Surface *dst, bool update )
 
 	pos.x = x;
 	pos.y = y;
-	CSDL_Ext::blitSurface(dest, &destRect, dst, &pos);
+	CSDL_Ext::blitSurface(dest, destRect, dst, pos.topLeft());
 
 	if (update)
-		SDL_UpdateRect(dst, pos.x, pos.y, pos.w, pos.h);
+		CSDL_Ext::updateRect(dst, pos);
 }
 
 void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update )
@@ -442,7 +441,9 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 		if(stopOnKey && keyDown())
 			return false;
 
-		SDL_RenderCopy(mainRenderer, texture, nullptr, &pos);
+		SDL_Rect rect = CSDL_Ext::toSDL(pos);
+
+		SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
 		SDL_RenderPresent(mainRenderer);
 
 		// Wait 3 frames

+ 5 - 6
client/CVideoHandler.h

@@ -9,8 +9,10 @@
  */
 #pragma once
 
-struct SDL_Surface;
+#include "../lib/Rect.h"
 
+struct SDL_Surface;
+struct SDL_Texture;
 
 class IVideoPlayer
 {
@@ -54,9 +56,6 @@ public:
 
 #include "../lib/filesystem/CInputStream.h"
 
-#include <SDL.h>
-#include <SDL_video.h>
-
 extern "C" {
 #include <libavformat/avformat.h>
 #include <libavcodec/avcodec.h>
@@ -106,8 +105,8 @@ class CVideoPlayer : public IMainVideoPlayer
 
 	SDL_Texture *texture;
 	SDL_Surface *dest;
-	SDL_Rect destRect;			// valid when dest is used
-	SDL_Rect pos;				// destination on screen
+	Rect destRect;			// valid when dest is used
+	Rect pos;				// destination on screen
 
 	int refreshWait; // Wait several refresh before updating the image
 	int refreshCount;

+ 22 - 3
client/Client.cpp

@@ -10,8 +10,6 @@
 #include "StdInc.h"
 #include "Client.h"
 
-#include <SDL.h>
-
 #include "CMusicHandler.h"
 #include "../lib/mapping/CCampaignHandler.h"
 #include "../CCallback.h"
@@ -46,9 +44,9 @@
 #include "../lib/CThreadHelper.h"
 #include "../lib/registerTypes/RegisterTypes.h"
 #include "gui/CGuiHandler.h"
-#include "CMT.h"
 #include "CServerHandler.h"
 #include "../lib/ScriptHandler.h"
+#include "windows/CAdvmapInterface.h"
 #include <vcmi/events/EventBus.h>
 
 #ifdef VCMI_ANDROID
@@ -761,8 +759,29 @@ void CClient::reinitScripting()
 #endif
 }
 
+void CClient::removeGUI()
+{
+	// CClient::endGame
+	GH.curInt = nullptr;
+	if(GH.topInt())
+		GH.topInt()->deactivate();
+	adventureInt.reset();
+	GH.listInt.clear();
+	GH.objsToBlit.clear();
+	GH.statusbar.reset();
+	logGlobal->info("Removed GUI.");
+
+	LOCPLINT = nullptr;
+}
 
 #ifdef VCMI_ANDROID
+extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_clientSetupJNI(JNIEnv * env, jobject cls)
+{
+	logNetwork->info("Received clientSetupJNI");
+
+	CAndroidVMHelper::cacheVM(env);
+}
+
 extern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerClosed(JNIEnv * env, jobject cls)
 {
 	logNetwork->info("Received server closed signal");

+ 1 - 0
client/Client.h

@@ -240,6 +240,7 @@ public:
 
 	void showInfoDialog(InfoWindow * iw) override {};
 	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
+	void removeGUI();
 
 #if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;

+ 494 - 0
client/ClientCommandManager.cpp

@@ -0,0 +1,494 @@
+/*
+ * ClientCommandManager.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 "StdInc.h"
+
+#include "ClientCommandManager.h"
+
+#include "Client.h"
+#include "CPlayerInterface.h"
+#include "CServerHandler.h"
+#include "gui/CGuiHandler.h"
+#include "../lib/NetPacks.h"
+#include "../lib/CConfigHandler.h"
+#include "../lib/CGameState.h"
+#include "../lib/CPlayerState.h"
+#include "../lib/StringConstants.h"
+#include "gui/CAnimation.h"
+#include "windows/CAdvmapInterface.h"
+#include "windows/CCastleInterface.h"
+#include "../CCallback.h"
+#include "../lib/CGeneralTextHandler.h"
+#include "../lib/CHeroHandler.h"
+#include "../lib/CModHandler.h"
+#include "../lib/VCMIDirs.h"
+
+#ifdef SCRIPTING_ENABLED
+#include "../lib/ScriptHandler.h"
+#endif
+
+void ClientCommandManager::handleGoSolo()
+{
+	Settings session = settings.write["session"];
+
+	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+	if(!CSH->client)
+	{
+		printCommandMessage("Game is not in playing state");
+		return;
+	}
+	PlayerColor color;
+	if(session["aiSolo"].Bool())
+	{
+		for(auto & elem : CSH->client->gameState()->players)
+		{
+			if(elem.second.human)
+				CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+		}
+	}
+	else
+	{
+		color = LOCPLINT->playerID;
+		CSH->client->removeGUI();
+		for(auto & elem : CSH->client->gameState()->players)
+		{
+			if(elem.second.human)
+			{
+				auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
+				printCommandMessage("Player " + elem.first.getStr() + " will be lead by " + AiToGive, ELogLevel::INFO);
+				CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
+			}
+		}
+		GH.totalRedraw();
+		giveTurn(color);
+	}
+	session["aiSolo"].Bool() = !session["aiSolo"].Bool();
+}
+
+void ClientCommandManager::handleControlAi(const std::string &colorName)
+{
+	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+	if(!CSH->client)
+	{
+		printCommandMessage("Game is not in playing state");
+		return;
+	}
+	PlayerColor color;
+	if(LOCPLINT)
+		color = LOCPLINT->playerID;
+	for(auto & elem : CSH->client->gameState()->players)
+	{
+		if(elem.second.human || (colorName.length() &&
+								 elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
+		{
+			continue;
+		}
+
+		CSH->client->removeGUI();
+		CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+	}
+	GH.totalRedraw();
+	if(color != PlayerColor::NEUTRAL)
+		giveTurn(color);
+}
+
+void ClientCommandManager::processCommand(const std::string &message, bool calledFromIngameConsole)
+{
+	std::istringstream singleWordBuffer;
+	singleWordBuffer.str(message);
+	std::string commandName;
+	singleWordBuffer >> commandName;
+	currentCallFromIngameConsole = calledFromIngameConsole;
+
+	if(message==std::string("die, fool"))
+	{
+		exit(EXIT_SUCCESS);
+	}
+	else if(commandName == std::string("activate"))
+	{
+		int what;
+		singleWordBuffer >> what;
+		switch (what)
+		{
+			case 0:
+				GH.topInt()->activate();
+				break;
+			case 1:
+				adventureInt->activate();
+				break;
+			case 2:
+				LOCPLINT->castleInt->activate();
+				break;
+			default:
+				printCommandMessage("Wrong argument specified!", ELogLevel::ERROR);
+		}
+	}
+	else if(commandName == "redraw")
+	{
+		GH.totalRedraw();
+	}
+	else if(commandName == "screen")
+	{
+		printCommandMessage("Screenbuf points to ");
+
+		if(screenBuf == screen)
+			printCommandMessage("screen", ELogLevel::ERROR);
+		else if(screenBuf == screen2)
+			printCommandMessage("screen2", ELogLevel::ERROR);
+		else
+			printCommandMessage("?!?", ELogLevel::ERROR);
+
+		SDL_SaveBMP(screen, "Screen_c.bmp");
+		SDL_SaveBMP(screen2, "Screen2_c.bmp");
+	}
+	else if(commandName == "save")
+	{
+		if(!CSH->client)
+		{
+			printCommandMessage("Game is not in playing state");
+			return;
+		}
+		std::string fname;
+		singleWordBuffer >> fname;
+		CSH->client->save(fname);
+	}
+//	else if(commandName=="load")
+//	{
+//		// TODO: this code should end the running game and manage to call startGame instead
+//		std::string fname;
+//		singleWordBuffer >> fname;
+//		CSH->client->loadGame(fname);
+//	}
+	else if(message=="convert txt")
+	{
+		VLC->generaltexth->dumpAllTexts();
+	}
+	else if(message=="get config")
+	{
+		printCommandMessage("Command accepted.\t");
+
+		const boost::filesystem::path outPath =
+				VCMIDirs::get().userExtractedPath() / "configuration";
+
+		boost::filesystem::create_directories(outPath);
+
+		const std::vector<std::string> contentNames = {"heroClasses", "artifacts", "creatures", "factions", "objects", "heroes", "spells", "skills"};
+
+		for(auto contentName : contentNames)
+		{
+			auto & content = (*VLC->modh->content)[contentName];
+
+			auto contentOutPath = outPath / contentName;
+			boost::filesystem::create_directories(contentOutPath);
+
+			for(auto & iter : content.modData)
+			{
+				const JsonNode & modData = iter.second.modData;
+
+				for(auto & nameAndObject : modData.Struct())
+				{
+					const JsonNode & object = nameAndObject.second;
+
+					std::string name = CModHandler::makeFullIdentifier(object.meta, contentName, nameAndObject.first);
+
+					boost::algorithm::replace_all(name,":","_");
+
+					const boost::filesystem::path filePath = contentOutPath / (name + ".json");
+					boost::filesystem::ofstream file(filePath);
+					file << object.toJson();
+				}
+			}
+		}
+
+		printCommandMessage("\rExtracting done :)\n");
+		printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
+	}
+#if SCRIPTING_ENABLED
+		else if(message=="get scripts")
+	{
+		printCommandMessage("Command accepted.\t");
+
+		const boost::filesystem::path outPath =
+			VCMIDirs::get().userExtractedPath() / "scripts";
+
+		boost::filesystem::create_directories(outPath);
+
+		for(auto & kv : VLC->scriptHandler->objects)
+		{
+			std::string name = kv.first;
+			boost::algorithm::replace_all(name,":","_");
+
+			const scripting::ScriptImpl * script = kv.second.get();
+			boost::filesystem::path filePath = outPath / (name + ".lua");
+			boost::filesystem::ofstream file(filePath);
+			file << script->getSource();
+		}
+		printCommandMessage("\rExtracting done :)\n");
+		printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
+	}
+#endif
+	else if(message=="get txt")
+	{
+		printCommandMessage("Command accepted.\t");
+
+		const boost::filesystem::path outPath =
+				VCMIDirs::get().userExtractedPath();
+
+		auto list =
+				CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
+				{
+					return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
+				});
+
+		for (auto & filename : list)
+		{
+			const boost::filesystem::path filePath = outPath / (filename.getName() + ".TXT");
+
+			boost::filesystem::create_directories(filePath.parent_path());
+
+			boost::filesystem::ofstream file(filePath);
+			auto text = CResourceHandler::get()->load(filename)->readAll();
+
+			file.write((char*)text.first.get(), text.second);
+		}
+
+		printCommandMessage("\rExtracting done :)\n");
+		printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
+	}
+	else if(commandName == "crash")
+	{
+		int *ptr = nullptr;
+		*ptr = 666;
+		//disaster!
+	}
+	else if(commandName == "mp" && adventureInt)
+	{
+		if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
+			printCommandMessage(std::to_string(h->movement) + "; max: " + std::to_string(h->maxMovePoints(true)) + "/" + std::to_string(h->maxMovePoints(false)) + "\n");
+	}
+	else if(commandName == "bonuses")
+	{
+		bool jsonFormat = (message == "bonuses json");
+		auto format = [jsonFormat](const BonusList & b) -> std::string
+		{
+			if(jsonFormat)
+				return b.toJsonNode().toJson(true);
+			std::ostringstream ss;
+			ss << b;
+			return ss.str();
+		};
+		printCommandMessage("Bonuses of " + adventureInt->selection->getObjectName() + "\n");
+		printCommandMessage(format(adventureInt->selection->getBonusList()) + "\n");
+
+		printCommandMessage("\nInherited bonuses:\n");
+		TCNodes parents;
+		adventureInt->selection->getParents(parents);
+		for(const CBonusSystemNode *parent : parents)
+		{
+			printCommandMessage(std::string("\nBonuses from ") + typeid(*parent).name() + "\n" + format(*parent->getAllBonuses(Selector::all, Selector::all)) + "\n");
+		}
+	}
+	else if(commandName == "not dialog")
+	{
+		LOCPLINT->showingDialog->setn(false);
+	}
+	else if(commandName == "gui")
+	{
+		for(auto & child : GH.listInt)
+		{
+			const auto childPtr = child.get();
+			if(const CIntObject * obj = dynamic_cast<const CIntObject *>(childPtr))
+				printInfoAboutInterfaceObject(obj, 0);
+			else
+				printCommandMessage(std::string(typeid(childPtr).name()) + "\n");
+		}
+	}
+	else if(commandName == "tell")
+	{
+		std::string what;
+		int id1, id2;
+		singleWordBuffer >> what >> id1 >> id2;
+		if(what == "hs")
+		{
+			for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo())
+				if(h->type->getIndex() == id1)
+					if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2)))
+						printCommandMessage(a->nodeName());
+		}
+	}
+	else if (commandName == "set")
+	{
+		std::string what, value;
+		singleWordBuffer >> what;
+
+		Settings config = settings.write["session"][what];
+
+		singleWordBuffer >> value;
+
+		if (value == "on")
+		{
+			config->Bool() = true;
+			printCommandMessage("Option " + what + " enabled!", ELogLevel::INFO);
+		}
+		else if (value == "off")
+		{
+			config->Bool() = false;
+			printCommandMessage("Option " + what + " disabled!", ELogLevel::INFO);
+		}
+	}
+	else if(commandName == "unlock")
+	{
+		std::string mxname;
+		singleWordBuffer >> mxname;
+		if(mxname == "pim" && LOCPLINT)
+			LOCPLINT->pim->unlock();
+	}
+	else if(commandName == "def2bmp")
+	{
+		std::string URI;
+		singleWordBuffer >> URI;
+		std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
+		anim->preload();
+		anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
+	}
+	else if(commandName == "extract")
+	{
+		std::string URI;
+		singleWordBuffer >> URI;
+
+		if (CResourceHandler::get()->existsResource(ResourceID(URI)))
+		{
+			const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI;
+
+			auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
+
+			boost::filesystem::create_directories(outPath.parent_path());
+			boost::filesystem::ofstream outFile(outPath, boost::filesystem::ofstream::binary);
+			outFile.write((char*)data.first.get(), data.second);
+		}
+		else
+			printCommandMessage("File not found!", ELogLevel::ERROR);
+	}
+	else if(commandName == "setBattleAI")
+	{
+		std::string fname;
+		singleWordBuffer >> fname;
+		printCommandMessage("Will try loading that AI to see if it is correct name...\n");
+		try
+		{
+			if(auto ai = CDynLibHandler::getNewBattleAI(fname)) //test that given AI is indeed available... heavy but it is easy to make a typo and break the game
+			{
+				Settings neutralAI = settings.write["server"]["neutralAI"];
+				neutralAI->String() = fname;
+				printCommandMessage("Setting changed, from now the battle ai will be " + fname + "!\n");
+			}
+		}
+		catch(std::exception &e)
+		{
+			printCommandMessage("Failed opening " + fname + ": " + e.what(), ELogLevel::WARN);
+			printCommandMessage("Setting not changed, AI not found or invalid!", ELogLevel::WARN);
+		}
+	}
+	else if(commandName == "autoskip")
+	{
+		Settings session = settings.write["session"];
+		session["autoSkip"].Bool() = !session["autoSkip"].Bool();
+	}
+	else if(commandName == "gosolo")
+	{
+		ClientCommandManager::handleGoSolo();
+	}
+	else if(commandName == "controlai")
+	{
+		std::string colorName;
+		singleWordBuffer >> colorName;
+		boost::to_lower(colorName);
+
+		ClientCommandManager::handleControlAi(colorName);
+	}
+	else
+	{
+		printCommandMessage("Command not found :(", ELogLevel::ERROR);
+	}
+}
+
+void ClientCommandManager::giveTurn(const PlayerColor &colorIdentifier)
+{
+	YourTurn yt;
+	yt.player = colorIdentifier;
+	yt.daysWithoutCastle = CSH->client->getPlayerState(colorIdentifier)->daysWithoutCastle;
+	yt.applyCl(CSH->client);
+}
+
+void ClientCommandManager::printInfoAboutInterfaceObject(const CIntObject *obj, int level)
+{
+	std::stringstream sbuffer;
+	sbuffer << std::string(level, '\t');
+
+	sbuffer << typeid(*obj).name() << " *** ";
+	if (obj->active)
+	{
+#define PRINT(check, text) if (obj->active & CIntObject::check) sbuffer << text
+		PRINT(LCLICK, 'L');
+		PRINT(RCLICK, 'R');
+		PRINT(HOVER, 'H');
+		PRINT(MOVE, 'M');
+		PRINT(KEYBOARD, 'K');
+		PRINT(TIME, 'T');
+		PRINT(GENERAL, 'A');
+		PRINT(WHEEL, 'W');
+		PRINT(DOUBLECLICK, 'D');
+#undef  PRINT
+	}
+	else
+		sbuffer << "inactive";
+	sbuffer << " at " << obj->pos.x <<"x"<< obj->pos.y;
+	sbuffer << " (" << obj->pos.w <<"x"<< obj->pos.h << ")";
+	printCommandMessage(sbuffer.str(), ELogLevel::INFO);
+
+	for(const CIntObject *child : obj->children)
+		printInfoAboutInterfaceObject(child, level+1);
+}
+
+void ClientCommandManager::printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType)
+{
+	switch(messageType)
+	{
+		case ELogLevel::NOT_SET:
+			std::cout << commandMessage;
+			break;
+		case ELogLevel::TRACE:
+			logGlobal->trace(commandMessage);
+			break;
+		case ELogLevel::DEBUG:
+			logGlobal->debug(commandMessage);
+			break;
+		case ELogLevel::INFO:
+			logGlobal->info(commandMessage);
+			break;
+		case ELogLevel::WARN:
+			logGlobal->warn(commandMessage);
+			break;
+		case ELogLevel::ERROR:
+			logGlobal->error(commandMessage);
+			break;
+		default:
+			std::cout << commandMessage;
+			break;
+	}
+
+	if(currentCallFromIngameConsole)
+	{
+		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+		if(LOCPLINT && LOCPLINT->cingconsole)
+		{
+			LOCPLINT->cingconsole->print(commandMessage);
+		}
+	}
+}

+ 31 - 0
client/ClientCommandManager.h

@@ -0,0 +1,31 @@
+/*
+ * ClientCommandManager.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
+
+VCMI_LIB_NAMESPACE_BEGIN
+class PlayerColor;
+VCMI_LIB_NAMESPACE_END
+class CIntObject;
+
+class ClientCommandManager //take mantis #2292 issue about account if thinking about handling cheats from command-line
+{
+	bool currentCallFromIngameConsole;
+
+	void giveTurn(const PlayerColor &color);
+	void printInfoAboutInterfaceObject(const CIntObject *obj, int level);
+	void printCommandMessage(const std::string &commandMessage, ELogLevel::ELogLevel messageType = ELogLevel::NOT_SET);
+	void handleGoSolo();
+	void handleControlAi(const std::string &colorName);
+
+public:
+	ClientCommandManager() = default;
+	void processCommand(const std::string &message, bool calledFromIngameConsole);
+};

+ 1 - 1
client/Graphics.cpp

@@ -286,7 +286,7 @@ void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player)
 //FIXME: not all player colored images have player palette at last 32 indexes
 //NOTE: following code is much more correct but still not perfect (bugged with status bar)
 
-		SDL_SetColors(sur, palette, 224, 32);
+		CSDL_Ext::setColors(sur, palette, 224, 32);
 
 
 #if 0

+ 0 - 1
client/Graphics.h

@@ -11,7 +11,6 @@
 
 #include "gui/Fonts.h"
 #include "../lib/GameConstants.h"
-#include "gui/Geometries.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 

+ 1 - 1
client/NetPacksLobbyClient.cpp

@@ -63,7 +63,7 @@ void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHa
 
 void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
 {
-	if(lobby)
+	if(lobby && lobby->card)
 	{
 		lobby->card->chat->addNewMessage(playerName + ": " + message);
 		lobby->card->setChat(true);

+ 13 - 10
client/battle/BattleActionsController.cpp

@@ -19,7 +19,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CIntObject.h"
 #include "../windows/CCreatureWindow.h"
@@ -460,28 +460,31 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 						}
 					};
 
-					TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
+					BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
+					TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere, attackFromHex);
 					std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
 					newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[36]) % shere->getName() % estDmgText).str(); //Attack %s (%s damage)
 				}
 				break;
 			case PossiblePlayerBattleAction::SHOOT:
 			{
-				if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber))
+				auto const * shooter = owner.stacksController->getActiveStack();
+
+				if (owner.curInt->cb->battleHasShootingPenalty(shooter, myNumber))
 					cursorFrame = Cursor::Combat::SHOOT_PENALTY;
 				else
 					cursorFrame = Cursor::Combat::SHOOT;
 
 				realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
-				TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
+				TDmgRange damage = owner.curInt->cb->battleEstimateDamage(shooter, shere, shooter->getPosition());
 				std::string estDmgText = formatDmgRange(std::make_pair((ui32)damage.first, (ui32)damage.second)); //calculating estimated dmg
 				//printing - Shoot %s (%d shots left, %s damage)
-				newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % owner.stacksController->getActiveStack()->shots.available() % estDmgText).str();
+				newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[296]) % shere->getName() % shooter->shots.available() % estDmgText).str();
 			}
 				break;
 			case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
 				currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->name % shere->getName()); //Cast %s on %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[27]) % currentSpell->getNameTranslated() % shere->getName()); //Cast %s on %s
 				switch (currentSpell->id)
 				{
 					case SpellID::SACRIFICE:
@@ -494,7 +497,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::ANY_LOCATION:
 				currentSpell = CGI->spellh->objects[creatureCasting ? owner.stacksController->activeStackSpellToCast() : spellToCast->actionSubtype]; //necessary if creature has random Genie spell at same time
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: //we assume that teleport / sacrifice will never be available as random spell
@@ -519,7 +522,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				isCastingPossible = true;
 				break;
 			case PossiblePlayerBattleAction::HEAL:
@@ -558,7 +561,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				break;
 			case PossiblePlayerBattleAction::FREE_LOCATION:
 				cursorFrame = Cursor::Combat::BLOCKED;
-				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
+				newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->getNameTranslated()); //No room to place %s here
 				break;
 			default:
 				if (myNumber == -1)
@@ -579,7 +582,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			default:
 				spellcastingCursor = true;
 				if (newConsoleMsg.empty() && currentSpell)
-					newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
+					newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->getNameTranslated()); //Cast %s
 				break;
 		}
 

+ 1 - 1
client/battle/BattleAnimationClasses.cpp

@@ -22,7 +22,7 @@
 #include "../CGameInfo.h"
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 
 #include "../../CCallback.h"

+ 1 - 2
client/battle/BattleAnimationClasses.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "../../lib/battle/BattleHex.h"
-#include "../gui/Geometries.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -18,6 +17,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CStack;
 class CCreature;
 class CSpell;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -28,7 +28,6 @@ class CAnimation;
 class BattleInterface;
 class CreatureAnimation;
 struct StackAttackedInfo;
-struct Point;
 
 /// Base class of battle animations
 class BattleAnimation

+ 1 - 1
client/battle/BattleEffectsController.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../../lib/battle/BattleHex.h"
-#include "../gui/Geometries.h"
+#include "../../lib/Point.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN

+ 5 - 5
client/battle/BattleFieldController.cpp

@@ -26,7 +26,7 @@
 #include "../gui/CAnimation.h"
 #include "../gui/Canvas.h"
 #include "../gui/CGuiHandler.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/BattleFieldHandler.h"
@@ -211,11 +211,11 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
 	for(BattleHex hex : set)
 		result.insert(hex);
 
-	// display the movement shadow of the stack at b (i.e. stack under mouse)
-	const CStack * const shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, false);
-	if(shere && shere != owner.stacksController->getActiveStack() && shere->alive())
+	// display the movement shadow of stack under mouse
+	const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	if(hoveredStack && hoveredStack != owner.stacksController->getActiveStack())
 	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
+		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, nullptr);
 		for(BattleHex hex : v)
 			result.insert(hex);
 	}

+ 2 - 3
client/battle/BattleFieldController.h

@@ -15,12 +15,11 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class CStack;
+class Rect;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-struct Rect;
-struct Point;
-
 class ClickableHex;
 class BattleHero;
 class Canvas;

+ 4 - 3
client/battle/BattleInterface.cpp

@@ -28,7 +28,7 @@
 #include "../CMusicHandler.h"
 #include "../CPlayerInterface.h"
 #include "../gui/Canvas.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../windows/CAdvmapInterface.h"
 
@@ -41,6 +41,7 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/UnlockGuard.h"
+#include "../../lib/TerrainHandler.h"
 
 CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
 
@@ -136,8 +137,8 @@ BattleInterface::~BattleInterface()
 	if (adventureInt && adventureInt->selection)
 	{
 		//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
-		const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
-		CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
+		const auto * terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
+		CCS->musich->playMusicFromSet("terrain", terrain->getJsonKey(), true, false);
 	}
 
 	// may happen if user decided to close game while in battle

+ 8 - 8
client/battle/BattleInterfaceClasses.cpp

@@ -26,7 +26,7 @@
 #include "../Graphics.h"
 #include "../gui/CAnimation.h"
 #include "../gui/Canvas.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../widgets/AdventureMapClasses.h"
 #include "../widgets/Buttons.h"
@@ -160,7 +160,7 @@ void BattleConsole::setEnteringMode(bool on)
 	if (on)
 	{
 		assert(enteringText == false);
-		CSDL_Ext::startTextInput(&pos);
+		CSDL_Ext::startTextInput(pos);
 	}
 	else
 	{
@@ -308,7 +308,7 @@ void BattleHero::clickRight(tribool down, bool previousState)
 		return;
 
 	Point windowPosition;
-	windowPosition.x = (!defender) ? owner.fieldController->pos.topLeft().x + 1 : owner.fieldController->pos.topRight().x - 79;
+	windowPosition.x = (!defender) ? owner.fieldController->pos.left() + 1 : owner.fieldController->pos.right() - 79;
 	windowPosition.y = owner.fieldController->pos.y + 135;
 
 	InfoAboutHero targetHero;
@@ -494,7 +494,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	pos = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
+	pos = CSDL_Ext::genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
 	background = std::make_shared<CPicture>("CPRESULT");
 	background->colorize(owner.playerID);
 
@@ -551,7 +551,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 			if(best != stacks.end()) //should be always but to be safe...
 			{
 				icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->type->getIconIndex(), 0, xs[i], 38));
-				sideNames[i] = (*best)->type->getPluralName();
+				sideNames[i] = (*best)->type->getNamePluralTranslated();
 			}
 		}
 	}
@@ -613,7 +613,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 		if (ourHero)
 		{
 			str += CGI->generaltexth->allTexts[305];
-			boost::algorithm::replace_first(str, "%s", ourHero->name);
+			boost::algorithm::replace_first(str, "%s", ourHero->getNameTranslated());
 			boost::algorithm::replace_first(str, "%d", boost::lexical_cast<std::string>(br.exp[weAreAttacker ? 0 : 1]));
 		}
 
@@ -804,7 +804,7 @@ void StackQueue::update()
 
 int32_t StackQueue::getSiegeShooterIconID()
 {
-	return owner.siegeController->getSiegedTown()->town->faction->index;
+	return owner.siegeController->getSiegedTown()->town->faction->getIndex();
 }
 
 StackQueue::StackBox::StackBox(StackQueue * owner):
@@ -850,7 +850,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn)
 		if (unit->unitType()->idNumber == CreatureID::ARROW_TOWERS)
 			icon->setFrame(owner->getSiegeShooterIconID(), 1);
 
-		amount->setText(makeNumberShort(unit->getCount()));
+		amount->setText(CSDL_Ext::makeNumberShort(unit->getCount()));
 
 		if(stateIcon)
 		{

+ 1 - 1
client/battle/BattleObstacleController.h

@@ -13,6 +13,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 struct BattleHex;
 struct CObstacleInstance;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -21,7 +22,6 @@ class Canvas;
 class CAnimation;
 class BattleInterface;
 class BattleRenderer;
-struct Point;
 
 /// Controls all currently active projectiles on the battlefield
 /// (with exception of moat, which is apparently handled by siege controller)

+ 2 - 3
client/battle/BattleProjectileController.cpp

@@ -15,7 +15,6 @@
 #include "BattleStacksController.h"
 #include "CreatureAnimation.h"
 
-#include "../gui/Geometries.h"
 #include "../gui/CAnimation.h"
 #include "../gui/Canvas.h"
 #include "../gui/CGuiHandler.h"
@@ -158,7 +157,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
 
 	if(creature->animation.missleFrameAngles.empty())
 	{
-		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->nameSing);
+		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
 		creature = CGI->creh->objects[CreatureID::ARCHER];
 	}
 
@@ -313,7 +312,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 	std::shared_ptr<ProjectileBase> projectile;
 	if (stackUsesRayProjectile(shooter) && stackUsesMissileProjectile(shooter))
 	{
-		logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.nameSing);
+		logAnim->error("Mod error: Creature '%s' has both missile and ray projectiles configured. Mod should be fixed. Using ray projectile configuration...", shooterInfo.getNameSingularTranslated());
 	}
 
 	if (stackUsesRayProjectile(shooter))

+ 1 - 2
client/battle/BattleProjectileController.h

@@ -10,7 +10,7 @@
 #pragma once
 
 #include "../../lib/CCreatureHandler.h"
-#include "../gui/Geometries.h"
+#include "../../lib/Point.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -19,7 +19,6 @@ class CSpell;
 
 VCMI_LIB_NAMESPACE_END
 
-struct Point;
 class CAnimation;
 class Canvas;
 class BattleInterface;

+ 21 - 14
client/battle/BattleSiegeController.cpp

@@ -28,22 +28,29 @@
 #include "../../lib/CStack.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
-std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const
+std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
 {
 	auto getImageIndex = [&]() -> int
 	{
+		bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER);
+
 		switch (state)
 		{
-		case EWallState::INTACT :
+		case EWallState::REINFORCED :
 			return 1;
+		case EWallState::INTACT :
+			if (town->hasBuilt(BuildingID::CASTLE))
+				return 2; // reinforced walls were damaged
+			else
+				return 1;
 		case EWallState::DAMAGED :
 			// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2
-			if(what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
+			if (isTower)
 				return 1;
 			else
 				return 2;
 		case EWallState::DESTROYED :
-			if (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER)
+			if (isTower)
 				return 2;
 			else
 				return 3;
@@ -58,7 +65,7 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua
 	{
 	case EWallVisual::BACKGROUND_WALL:
 		{
-			switch(town->town->faction->index)
+			switch(town->town->faction->getIndex())
 			{
 			case ETownType::RAMPART:
 			case ETownType::NECROPOLIS:
@@ -128,11 +135,11 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what)
 
 	switch (what)
 	{
-	case EWallVisual::MOAT:              return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER;
-	case EWallVisual::MOAT_BANK:         return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS;
-	case EWallVisual::KEEP_BATTLEMENT:   return town->hasBuilt(BuildingID::CITADEL) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::KEEP)) != EWallState::DESTROYED;
-	case EWallVisual::UPPER_BATTLEMENT:  return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER)) != EWallState::DESTROYED;
-	case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && EWallState::EWallState(owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER)) != EWallState::DESTROYED;
+	case EWallVisual::MOAT:              return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->getIndex() != ETownType::TOWER;
+	case EWallVisual::MOAT_BANK:         return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->getIndex() != ETownType::TOWER && town->town->faction->getIndex() != ETownType::NECROPOLIS;
+	case EWallVisual::KEEP_BATTLEMENT:   return town->hasBuilt(BuildingID::CITADEL) && owner.curInt->cb->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
+	case EWallVisual::UPPER_BATTLEMENT:  return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
+	case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
 	default:                             return true;
 	}
 }
@@ -177,7 +184,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
 			continue;
 
-		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::INTACT));
+		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
 	}
 }
 
@@ -321,7 +328,7 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
 	if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart))
 		return false;
 
-	auto state = owner.curInt->cb->battleGetWallState(static_cast<int>(wallPart));
+	auto state = owner.curInt->cb->battleGetWallState(wallPart);
 	return state != EWallState::DESTROYED && state != EWallState::NONE;
 }
 
@@ -354,12 +361,12 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 
 	for (auto attackInfo : ca.attackedParts)
 	{
-		int wallId = attackInfo.attackedPart + EWallVisual::DESTRUCTIBLE_FIRST;
+		int wallId = static_cast<int>(attackInfo.attackedPart) + EWallVisual::DESTRUCTIBLE_FIRST;
 		//gate state changing handled separately
 		if (wallId == EWallVisual::GATE)
 			continue;
 
-		auto wallState = EWallState::EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));
+		auto wallState = EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));
 
 		wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
 	}

+ 2 - 2
client/battle/BattleSiegeController.h

@@ -18,10 +18,10 @@ struct CatapultAttack;
 class CCreature;
 class CStack;
 class CGTownInstance;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-struct Point;
 class Canvas;
 class BattleInterface;
 class BattleRenderer;
@@ -76,7 +76,7 @@ class BattleSiegeController
 	std::array<std::shared_ptr<IImage>, EWallVisual::WALL_LAST + 1> wallPieceImages;
 
 	/// return URI for image for a wall piece
-	std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState::EWallState state) const;
+	std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const;
 
 	/// returns BattleHex to which chosen wall piece is bound
 	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;

+ 6 - 6
client/battle/BattleStacksController.cpp

@@ -28,6 +28,7 @@
 #include "../gui/CAnimation.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/Canvas.h"
+#include "../gui/SDL_Extensions.h"
 #include "../../lib/spells/ISpellMechanics.h"
 
 #include "../../CCallback.h"
@@ -83,10 +84,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP");
 	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
 
-	static const auto shifterNormal   = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 );
-	static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 );
-	static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 );
-	static const auto shifterNeutral  = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 );
+	static const auto shifterNormal   = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f );
+	static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f );
+	static const auto shifterNegative = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 0.2f, 0.2f );
+	static const auto shifterNeutral  = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 1.0f, 1.0f, 0.2f );
 
 	amountNormal->adjustPalette(shifterNormal);
 	amountPositive->adjustPalette(shifterPositive);
@@ -317,7 +318,7 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
 	//blitting amount
 	Point textPos = stackAnimation[stack->ID]->pos.topLeft() + amountBG->dimensions()/2 + Point(xAdd, yAdd);
 
-	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, makeNumberShort(stack->getCount()));
+	canvas.drawText(textPos, EFonts::FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, CSDL_Ext::makeNumberShort(stack->getCount()));
 }
 
 void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
@@ -543,7 +544,6 @@ void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleH
 bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, const CStack * defender)
 {
 	bool mustReverse = owner.curInt->cb->isToReverse(
-				attacker->getPosition(),
 				attacker,
 				defender);
 

+ 1 - 1
client/battle/BattleStacksController.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 
-#include "../gui/Geometries.h"
 #include "../gui/ColorFilter.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -19,6 +18,7 @@ class BattleAction;
 class CStack;
 class CSpell;
 class SpellID;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 

+ 5 - 5
client/battle/BattleWindow.cpp

@@ -21,7 +21,7 @@
 #include "../CPlayerInterface.h"
 #include "../CMusicHandler.h"
 #include "../gui/Canvas.h"
-#include "../gui/CCursorHandler.h"
+#include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CAnimation.h"
 #include "../windows/CSpellWindow.h"
@@ -274,10 +274,10 @@ void BattleWindow::bFleef()
 		//calculating fleeing hero's name
 		if (owner.attackingHeroInstance)
 			if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
-				heroName = owner.attackingHeroInstance->name;
+				heroName = owner.attackingHeroInstance->getNameTranslated();
 		if (owner.defendingHeroInstance)
 			if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
-				heroName = owner.defendingHeroInstance->name;
+				heroName = owner.defendingHeroInstance->getNameTranslated();
 		//calculating text
 		auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present.  %s can not retreat!
 
@@ -416,11 +416,11 @@ void BattleWindow::bSpellf()
 			const auto artID = ArtifactID(blockingBonus->sid);
 			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
 			//TODO check who *really* is source of bonus
-			std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name;
+			std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name;
 
 			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
 			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
-										% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
+										% heroName % CGI->artifacts()->getByIndex(artID)->getNameTranslated()));
 		}
 	}
 }

+ 1 - 0
client/battle/CreatureAnimation.cpp

@@ -15,6 +15,7 @@
 
 #include "../gui/Canvas.h"
 #include "../gui/ColorFilter.h"
+#include "../gui/SDL_Extensions.h"
 
 static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
 static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };

+ 12 - 18
client/gui/CAnimation.cpp

@@ -94,8 +94,8 @@ public:
 	// Keep the original palette, in order to do color switching operation
 	void savePalette();
 
-	void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr, ui8 alpha=255) const override;
-	void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha=255) const override;
+	void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
+	void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const override;
 	std::shared_ptr<IImage> scaleFast(float scale) const override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
 	void playerColored(PlayerColor player) override;
@@ -642,17 +642,16 @@ SDLImage::SDLImage(std::string filename)
 	}
 }
 
-void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src, ui8 alpha) const
+void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
 {
 	if(!surf)
 		return;
 
 	Rect destRect(posX, posY, surf->w, surf->h);
-
 	draw(where, &destRect, src);
 }
 
-void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* src, ui8 alpha) const
+void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) const
 {
 	if (!surf)
 		return;
@@ -669,28 +668,23 @@ void SDLImage::draw(SDL_Surface* where, const SDL_Rect* dest, const SDL_Rect* sr
 		if(src->y < margins.y)
 			destShift.y += margins.y - src->y;
 
-		sourceRect = Rect(*src) & Rect(margins.x, margins.y, surf->w, surf->h);
+		sourceRect = Rect(*src).intersect(Rect(margins.x, margins.y, surf->w, surf->h));
 
 		sourceRect -= margins;
 	}
 	else
 		destShift = margins;
 
-	Rect destRect(destShift.x, destShift.y, surf->w, surf->h);
-
 	if(dest)
-	{
-		destRect.x += dest->x;
-		destRect.y += dest->y;
-	}
+		destShift += dest->topLeft();
 
 	if(surf->format->BitsPerPixel == 8)
 	{
-		CSDL_Ext::blit8bppAlphaTo24bpp(surf, &sourceRect, where, &destRect);
+		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift);
 	}
 	else
 	{
-		SDL_UpperBlit(surf, &sourceRect, where, &destRect);
+		CSDL_Ext::blitSurface(surf, sourceRect, where, destShift);
 	}
 }
 
@@ -788,7 +782,7 @@ void SDLImage::shiftPalette(int from, int howMany)
 		{
 			palette[(i+1)%howMany] = surf->format->palette->colors[from + i];
 		}
-		SDL_SetColors(surf, palette, from, howMany);
+		CSDL_Ext::setColors(surf, palette, from, howMany);
 	}
 }
 
@@ -828,7 +822,7 @@ void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
 {
 	if(surf->format->palette)
 	{
-		SDL_SetColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
+		CSDL_Ext::setColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
 	}
 }
 
@@ -1274,7 +1268,7 @@ void CFadeAnimation::init(EMode mode, SDL_Surface * sourceSurface, bool freeSurf
 	shouldFreeSurface = freeSurfaceAtEnd;
 }
 
-void CFadeAnimation::draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect)
+void CFadeAnimation::draw(SDL_Surface * targetSurface, const Point &targetPoint)
 {
 	if (!fading || !fadingSurface || fadingMode == EMode::NONE)
 	{
@@ -1283,6 +1277,6 @@ void CFadeAnimation::draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRe
 	}
 
 	CSDL_Ext::setAlpha(fadingSurface, (int)(fadingCounter * 255));
-	SDL_BlitSurface(fadingSurface, const_cast<SDL_Rect *>(sourceRect), targetSurface, destRect); //FIXME
+	CSDL_Ext::blitSurface(fadingSurface, targetSurface, targetPoint); //FIXME
 	CSDL_Ext::setAlpha(fadingSurface, 255);
 }

+ 6 - 4
client/gui/CAnimation.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "../../lib/vcmi_endian.h"
-#include "Geometries.h"
 #include "../../lib/GameConstants.h"
 
 #ifdef IN
@@ -24,10 +23,13 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class JsonNode;
+class Rect;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
 struct SDL_Surface;
+struct SDL_Color;
 class CDefFile;
 class ColorFilter;
 
@@ -40,8 +42,8 @@ public:
 	using SpecialPalette = std::array<SDL_Color, 7>;
 
 	//draws image on surface "where" at position
-	virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0;
-	virtual void draw(SDL_Surface * where, const SDL_Rect * dest, const SDL_Rect * src, ui8 alpha = 255) const = 0;
+	virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
+	virtual void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const = 0;
 
 	virtual std::shared_ptr<IImage> scaleFast(float scale) const = 0;
 
@@ -177,6 +179,6 @@ public:
 	~CFadeAnimation();
 	void init(EMode mode, SDL_Surface * sourceSurface, bool freeSurfaceAtEnd = false, float animDelta = DEFAULT_DELTA);
 	void update();
-	void draw(SDL_Surface * targetSurface, const SDL_Rect * sourceRect, SDL_Rect * destRect);
+	void draw(SDL_Surface * targetSurface, const Point & targetPoint);
 	bool isFading() const { return fading; }
 };

+ 0 - 317
client/gui/CCursorHandler.cpp

@@ -1,317 +0,0 @@
-/*
- * CCursorHandler.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 "StdInc.h"
-#include "CCursorHandler.h"
-
-#include <SDL.h>
-
-#include "SDL_Extensions.h"
-#include "CGuiHandler.h"
-#include "../widgets/Images.h"
-
-#include "../CMT.h"
-
-void CCursorHandler::clearBuffer()
-{
-	Uint32 fillColor = SDL_MapRGBA(buffer->format, 0, 0, 0, 0);
-	CSDL_Ext::fillRect(buffer, nullptr, fillColor);
-}
-
-void CCursorHandler::updateBuffer(CIntObject * payload)
-{
-	payload->moveTo(Point(0,0));
-	payload->showAll(buffer);
-
-	needUpdate = true;
-}
-
-void CCursorHandler::replaceBuffer(CIntObject * payload)
-{
-	clearBuffer();
-	updateBuffer(payload);
-}
-
-CCursorHandler::CCursorHandler()
-	: needUpdate(true)
-	, buffer(nullptr)
-	, cursorLayer(nullptr)
-	, frameTime(0.f)
-	, showing(false)
-{
-	cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40);
-	SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND);
-
-	xpos = ypos = 0;
-	type = Cursor::Type::DEFAULT;
-	dndObject = nullptr;
-
-	cursors =
-	{
-		std::make_unique<CAnimImage>("CRADVNTR", 0),
-		std::make_unique<CAnimImage>("CRCOMBAT", 0),
-		std::make_unique<CAnimImage>("CRDEFLT",  0),
-		std::make_unique<CAnimImage>("CRSPELL",  0)
-	};
-
-	currentCursor = cursors.at(static_cast<size_t>(Cursor::Type::DEFAULT)).get();
-
-	buffer = CSDL_Ext::newSurface(40,40);
-
-	SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE);
-	SDL_ShowCursor(SDL_DISABLE);
-
-	set(Cursor::Map::POINTER);
-}
-
-Point CCursorHandler::position() const
-{
-	return Point(xpos, ypos);
-}
-
-void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
-{
-	assert(dndObject == nullptr);
-
-	if(type != this->type)
-	{
-		this->type = type;
-		this->frame = index;
-		currentCursor = cursors.at(static_cast<size_t>(type)).get();
-		currentCursor->setFrame(index);
-	}
-	else if(index != this->frame)
-	{
-		this->frame = index;
-		currentCursor->setFrame(index);
-	}
-
-	replaceBuffer(currentCursor);
-}
-
-void CCursorHandler::set(Cursor::Default index)
-{
-	changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
-}
-
-void CCursorHandler::set(Cursor::Map index)
-{
-	changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
-}
-
-void CCursorHandler::set(Cursor::Combat index)
-{
-	changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
-}
-
-void CCursorHandler::set(Cursor::Spellcast index)
-{
-	//Note: this is animated cursor, ignore specified frame and only change type
-	changeGraphic(Cursor::Type::SPELLBOOK, frame);
-}
-
-void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
-{
-	dndObject = std::move(object);
-	if(dndObject)
-		replaceBuffer(dndObject.get());
-	else
-		replaceBuffer(currentCursor);
-}
-
-void CCursorHandler::cursorMove(const int & x, const int & y)
-{
-	xpos = x;
-	ypos = y;
-}
-
-void CCursorHandler::shiftPos( int &x, int &y )
-{
-	if(( type == Cursor::Type::COMBAT && frame != static_cast<size_t>(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK)
-	{
-		x-=16;
-		y-=16;
-
-		// Properly align the melee attack cursors.
-		if (type == Cursor::Type::COMBAT)
-		{
-			switch (static_cast<Cursor::Combat>(frame))
-			{
-			case Cursor::Combat::HIT_NORTHEAST:
-				x -= 6;
-				y += 16;
-				break;
-			case Cursor::Combat::HIT_EAST:
-				x -= 16;
-				y += 10;
-				break;
-			case Cursor::Combat::HIT_SOUTHEAST:
-				x -= 6;
-				y -= 6;
-				break;
-			case Cursor::Combat::HIT_SOUTHWEST:
-				x += 16;
-				y -= 6;
-				break;
-			case Cursor::Combat::HIT_WEST:
-				x += 16;
-				y += 11;
-				break;
-			case Cursor::Combat::HIT_NORTHWEST:
-				x += 16;
-				y += 16;
-				break;
-			case Cursor::Combat::HIT_NORTH:
-				x += 9;
-				y += 16;
-				break;
-			case Cursor::Combat::HIT_SOUTH:
-				x += 9;
-				y -= 15;
-				break;
-			}
-		}
-	}
-	else if(type == Cursor::Type::ADVENTURE)
-	{
-		if (frame == 0)
-		{
-			//no-op
-		}
-		else if(frame == 2)
-		{
-			x -= 12;
-			y -= 10;
-		}
-		else if(frame == 3)
-		{
-			x -= 12;
-			y -= 12;
-		}
-		else if(frame < 27)
-		{
-			int hlpNum = (frame - 4)%6;
-			if(hlpNum == 0)
-			{
-				x -= 15;
-				y -= 13;
-			}
-			else if(hlpNum == 1)
-			{
-				x -= 13;
-				y -= 13;
-			}
-			else if(hlpNum == 2)
-			{
-				x -= 20;
-				y -= 20;
-			}
-			else if(hlpNum == 3)
-			{
-				x -= 13;
-				y -= 16;
-			}
-			else if(hlpNum == 4)
-			{
-				x -= 8;
-				y -= 9;
-			}
-			else if(hlpNum == 5)
-			{
-				x -= 14;
-				y -= 16;
-			}
-		}
-		else if(frame == 41)
-		{
-			x -= 14;
-			y -= 16;
-		}
-		else if(frame < 31 || frame == 42)
-		{
-			x -= 20;
-			y -= 20;
-		}
-	}
-}
-
-void CCursorHandler::centerCursor()
-{
-	this->xpos = static_cast<int>((screen->w / 2.) - (currentCursor->pos.w / 2.));
-	this->ypos = static_cast<int>((screen->h / 2.) - (currentCursor->pos.h / 2.));
-	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
-	SDL_WarpMouse(this->xpos, this->ypos);
-	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
-}
-
-void CCursorHandler::render()
-{
-	if(!showing)
-		return;
-
-	if (type == Cursor::Type::SPELLBOOK)
-	{
-		static const float frameDisplayDuration = 0.1f;
-
-		frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
-		size_t newFrame = frame;
-
-		while (frameTime > frameDisplayDuration)
-		{
-			frameTime -= frameDisplayDuration;
-			newFrame++;
-		}
-
-		auto & animation = cursors.at(static_cast<size_t>(type));
-
-		while (newFrame > animation->size())
-			newFrame -= animation->size();
-
-		changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
-	}
-
-	//the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads
-	updateTexture();
-
-	int x = xpos;
-	int y = ypos;
-	shiftPos(x, y);
-
-	if(dndObject)
-	{
-		x -= dndObject->pos.w/2;
-		y -= dndObject->pos.h/2;
-	}
-
-	SDL_Rect destRect;
-	destRect.x = x;
-	destRect.y = y;
-	destRect.w = 40;
-	destRect.h = 40;
-
-	SDL_RenderCopy(mainRenderer, cursorLayer, nullptr, &destRect);
-}
-
-void CCursorHandler::updateTexture()
-{
-	if(needUpdate)
-	{
-		SDL_UpdateTexture(cursorLayer, nullptr, buffer->pixels, buffer->pitch);
-		needUpdate = false;
-	}
-}
-
-CCursorHandler::~CCursorHandler()
-{
-	if(buffer)
-		SDL_FreeSurface(buffer);
-
-	if(cursorLayer)
-		SDL_DestroyTexture(cursorLayer);
-}

+ 10 - 9
client/gui/CGuiHandler.cpp

@@ -11,10 +11,11 @@
 #include "CGuiHandler.h"
 #include "../lib/CondSh.h"
 
-#include <SDL.h>
+#include <SDL_timer.h>
 
 #include "CIntObject.h"
-#include "CCursorHandler.h"
+#include "CursorHandler.h"
+#include "SDL_Extensions.h"
 
 #include "../CGameInfo.h"
 #include "../../lib/CThreadHelper.h"
@@ -167,7 +168,7 @@ void CGuiHandler::totalRedraw()
 #endif
 	for(auto & elem : objsToBlit)
 		elem->showAll(screen2);
-	blitAt(screen2,0,0,screen);
+	CSDL_Ext::blitAt(screen2,0,0,screen);
 }
 
 void CGuiHandler::updateTime()
@@ -289,7 +290,7 @@ void CGuiHandler::handleCurrentEvent()
 				for(auto i = hlp.begin(); i != hlp.end() && continueEventHandling; i++)
 				{
 					if(!vstd::contains(doubleClickInterested, *i)) continue;
-					if(isItIn(&(*i)->pos, current->motion.x, current->motion.y))
+					if((*i)->pos.isInside(current->motion.x, current->motion.y))
 					{
 						(*i)->onDoubleClick();
 					}
@@ -321,7 +322,7 @@ void CGuiHandler::handleCurrentEvent()
 			// SDL doesn't have the proper values for mouse positions on SDL_MOUSEWHEEL, refetch them
 			int x = 0, y = 0;
 			SDL_GetMouseState(&x, &y);
-			(*i)->wheelScrolled(current->wheel.y < 0, isItIn(&(*i)->pos, x, y));
+			(*i)->wheelScrolled(current->wheel.y < 0, (*i)->pos.isInside(x, y));
 		}
 	}
 	else if(current->type == SDL_TEXTINPUT)
@@ -367,7 +368,7 @@ void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntOb
 		auto prev = (*i)->mouseState(btn);
 		if(!isPressed)
 			(*i)->updateMouseState(btn, isPressed);
-		if(isItIn(&(*i)->pos, current->motion.x, current->motion.y))
+		if((*i)->pos.isInside(current->motion.x, current->motion.y))
 		{
 			if(isPressed)
 				(*i)->updateMouseState(btn, isPressed);
@@ -384,7 +385,7 @@ void CGuiHandler::handleMouseMotion()
 	std::vector<CIntObject*> hlp;
 	for(auto & elem : hoverable)
 	{
-		if(isItIn(&(elem)->pos, current->motion.x, current->motion.y))
+		if(elem->pos.isInside(current->motion.x, current->motion.y))
 		{
 			if (!(elem)->hovered)
 				hlp.push_back((elem));
@@ -408,7 +409,7 @@ void CGuiHandler::simpleRedraw()
 {
 	//update only top interface and draw background
 	if(objsToBlit.size() > 1)
-		blitAt(screen2,0,0,screen); //blit background
+		CSDL_Ext::blitAt(screen2,0,0,screen); //blit background
 	if(!objsToBlit.empty())
 		objsToBlit.back()->show(screen); //blit active interface/window
 }
@@ -419,7 +420,7 @@ void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)
 	std::list<CIntObject*> miCopy = motioninterested;
 	for(auto & elem : miCopy)
 	{
-		if(elem->strongInterest || isItInOrLowerBounds(&elem->pos, motion.x, motion.y)) //checking lower bounds fixes bug #2476
+		if(elem->strongInterest || Rect::createAround(elem->pos, 1).isInside( motion.x, motion.y)) //checking bounds including border fixes bug #2476
 		{
 			(elem)->mouseMoved(motion);
 		}

+ 5 - 3
client/gui/CGuiHandler.h

@@ -9,9 +9,9 @@
  */
 #pragma once
 
-//#include "../../lib/CStopWatch.h"
-#include "Geometries.h"
-#include "SDL_Extensions.h"
+#include "../../lib/Point.h"
+
+#include <SDL_events.h>
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -19,6 +19,8 @@ template <typename T> struct CondSh;
 
 VCMI_LIB_NAMESPACE_END
 
+union SDL_Event;
+
 class CFramerateManager;
 class IStatusBar;
 class CIntObject;

+ 3 - 11
client/gui/CIntObject.cpp

@@ -14,6 +14,8 @@
 #include "SDL_Extensions.h"
 #include "../CMessage.h"
 
+#include <SDL_pixels.h>
+
 IShowActivatable::IShowActivatable()
 {
 	type = 0;
@@ -176,7 +178,7 @@ void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFon
 
 void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst )
 {
-	blitAt(src, pos.x + x, pos.y + y, dst);
+	CSDL_Ext::blitAt(src, pos.x + x, pos.y + y, dst);
 }
 
 void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst)
@@ -219,16 +221,6 @@ void CIntObject::enable()
 	recActions = 255;
 }
 
-bool CIntObject::isItInLoc( const SDL_Rect &rect, int x, int y )
-{
-	return isItIn(&rect, x - pos.x, y - pos.y);
-}
-
-bool CIntObject::isItInLoc( const SDL_Rect &rect, const Point &p )
-{
-	return isItIn(&rect, p.x - pos.x, p.y - pos.y);
-}
-
 void CIntObject::fitToScreen(int borderWidth, bool propagate)
 {
 	Point newPos = pos.topLeft();

+ 4 - 4
client/gui/CIntObject.h

@@ -9,8 +9,7 @@
  */
 #pragma once
 
-#include <SDL_events.h>
-#include "Geometries.h"
+#include "../../lib/Rect.h"
 #include "../Graphics.h"
 
 struct SDL_Surface;
@@ -18,6 +17,9 @@ class CGuiHandler;
 class CPicture;
 
 struct SDL_KeyboardEvent;
+struct SDL_TextInputEvent;
+struct SDL_TextEditingEvent;
+struct SDL_MouseMotionEvent;
 
 using boost::logic::tribool;
 
@@ -165,8 +167,6 @@ public:
 	//request complete redraw of this object
 	void redraw() override;
 
-	bool isItInLoc(const SDL_Rect &rect, int x, int y);
-	bool isItInLoc(const SDL_Rect &rect, const Point &p);
 	const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
 	const Rect & center(const Point &p, bool propagate = true);  //moves object so that point p will be in its center
 	const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position

+ 6 - 12
client/gui/Canvas.cpp

@@ -11,11 +11,12 @@
 #include "Canvas.h"
 
 #include "SDL_Extensions.h"
-#include "Geometries.h"
 #include "CAnimation.h"
 
 #include "../Graphics.h"
 
+#include <SDL_surface.h>
+
 Canvas::Canvas(SDL_Surface * surface):
 	surface(surface),
 	renderOffset(0,0)
@@ -34,10 +35,10 @@ Canvas::Canvas(Canvas & other, const Rect & newClipRect):
 	Canvas(other)
 {
 	clipRect.emplace();
-	SDL_GetClipRect(surface, clipRect.get_ptr());
+	CSDL_Ext::getClipRect(surface, clipRect.get());
 
 	Rect currClipRect = newClipRect + renderOffset;
-	SDL_SetClipRect(surface, &currClipRect);
+	CSDL_Ext::setClipRect(surface, currClipRect);
 
 	renderOffset += newClipRect.topLeft();
 }
@@ -51,7 +52,7 @@ Canvas::Canvas(const Point & size):
 Canvas::~Canvas()
 {
 	if (clipRect)
-		SDL_SetClipRect(surface, clipRect.get_ptr());
+		CSDL_Ext::setClipRect(surface, clipRect.get());
 
 	SDL_FreeSurface(surface);
 }
@@ -70,16 +71,9 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect &
 		image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
 }
 
-void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha)
-{
-	assert(image);
-	if (image)
-		image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha);
-}
-
 void Canvas::draw(Canvas & image, const Point & pos)
 {
-	blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);
+	CSDL_Ext::blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);
 }
 
 void Canvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest)

+ 2 - 5
client/gui/Canvas.h

@@ -9,7 +9,8 @@
  */
 #pragma once
 
-#include "Geometries.h"
+#include "TextAlignment.h"
+#include "../../lib/Rect.h"
 
 struct SDL_Color;
 struct SDL_Surface;
@@ -51,10 +52,6 @@ public:
 	/// renders section of image bounded by sourceRect at specified position
 	void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
 
-	/// renders section of image bounded by sourceRect at specified position at specific transparency value
-	void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha);
-
-
 	/// renders another canvas onto this canvas
 	void draw(Canvas & image, const Point & pos);
 

+ 1 - 1
client/gui/ColorFilter.cpp

@@ -10,7 +10,7 @@
 #include "StdInc.h"
 #include "ColorFilter.h"
 
-#include <SDL2/SDL_pixels.h>
+#include <SDL_pixels.h>
 
 #include "../../lib/JsonNode.h"
 

+ 439 - 0
client/gui/CursorHandler.cpp

@@ -0,0 +1,439 @@
+/*
+ * CCursorHandler.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 "StdInc.h"
+#include "CursorHandler.h"
+
+#include "SDL_Extensions.h"
+#include "CGuiHandler.h"
+#include "CAnimation.h"
+#include "../../lib/CConfigHandler.h"
+
+std::unique_ptr<ICursor> CursorHandler::createCursor()
+{
+	if (settings["video"]["cursor"].String() == "auto")
+	{
+#if defined(VCMI_ANDROID) || defined(VCMI_IOS)
+		return std::make_unique<CursorSoftware>();
+#else
+		return std::make_unique<CursorHardware>();
+#endif
+	}
+
+	if (settings["video"]["cursor"].String() == "hardware")
+		return std::make_unique<CursorHardware>();
+
+	assert(settings["video"]["cursor"].String() == "software");
+	return std::make_unique<CursorSoftware>();
+}
+
+CursorHandler::CursorHandler()
+	: cursor(createCursor())
+	, frameTime(0.f)
+	, showing(false)
+	, pos(0,0)
+{
+
+	type = Cursor::Type::DEFAULT;
+	dndObject = nullptr;
+
+	cursors =
+	{
+		std::make_unique<CAnimation>("CRADVNTR"),
+		std::make_unique<CAnimation>("CRCOMBAT"),
+		std::make_unique<CAnimation>("CRDEFLT"),
+		std::make_unique<CAnimation>("CRSPELL")
+	};
+
+	for (auto & cursor : cursors)
+		cursor->preload();
+
+	set(Cursor::Map::POINTER);
+}
+
+Point CursorHandler::position() const
+{
+	return pos;
+}
+
+void CursorHandler::changeGraphic(Cursor::Type type, size_t index)
+{
+	assert(dndObject == nullptr);
+
+	if (type == this->type && index == this->frame)
+		return;
+
+	this->type = type;
+	this->frame = index;
+
+	cursor->setImage(getCurrentImage(), getPivotOffset());
+}
+
+void CursorHandler::set(Cursor::Default index)
+{
+	changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
+}
+
+void CursorHandler::set(Cursor::Map index)
+{
+	changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
+}
+
+void CursorHandler::set(Cursor::Combat index)
+{
+	changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
+}
+
+void CursorHandler::set(Cursor::Spellcast index)
+{
+	//Note: this is animated cursor, ignore specified frame and only change type
+	changeGraphic(Cursor::Type::SPELLBOOK, frame);
+}
+
+void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
+{
+	dndObject = image;
+	cursor->setImage(getCurrentImage(), getPivotOffset());
+}
+
+void CursorHandler::dragAndDropCursor (std::string path, size_t index)
+{
+	CAnimation anim(path);
+	anim.load(index);
+	dragAndDropCursor(anim.getImage(index));
+}
+
+void CursorHandler::cursorMove(const int & x, const int & y)
+{
+	pos.x = x;
+	pos.y = y;
+
+	cursor->setCursorPosition(pos);
+}
+
+Point CursorHandler::getPivotOffsetDefault(size_t index)
+{
+	return {0, 0};
+}
+
+Point CursorHandler::getPivotOffsetMap(size_t index)
+{
+	static const std::array<Point, 43> offsets = {{
+		{  0,  0}, // POINTER          =  0,
+		{  0,  0}, // HOURGLASS        =  1,
+		{ 12, 10}, // HERO             =  2,
+		{ 12, 12}, // TOWN             =  3,
+
+		{ 15, 13}, // T1_MOVE          =  4,
+		{ 13, 13}, // T1_ATTACK        =  5,
+		{ 16, 32}, // T1_SAIL          =  6,
+		{ 13, 20}, // T1_DISEMBARK     =  7,
+		{  8,  9}, // T1_EXCHANGE      =  8,
+		{ 14, 16}, // T1_VISIT         =  9,
+
+		{ 15, 13}, // T2_MOVE          = 10,
+		{ 13, 13}, // T2_ATTACK        = 11,
+		{ 16, 32}, // T2_SAIL          = 12,
+		{ 13, 20}, // T2_DISEMBARK     = 13,
+		{  8,  9}, // T2_EXCHANGE      = 14,
+		{ 14, 16}, // T2_VISIT         = 15,
+
+		{ 15, 13}, // T3_MOVE          = 16,
+		{ 13, 13}, // T3_ATTACK        = 17,
+		{ 16, 32}, // T3_SAIL          = 18,
+		{ 13, 20}, // T3_DISEMBARK     = 19,
+		{  8,  9}, // T3_EXCHANGE      = 20,
+		{ 14, 16}, // T3_VISIT         = 21,
+
+		{ 15, 13}, // T4_MOVE          = 22,
+		{ 13, 13}, // T4_ATTACK        = 23,
+		{ 16, 32}, // T4_SAIL          = 24,
+		{ 13, 20}, // T4_DISEMBARK     = 25,
+		{  8,  9}, // T4_EXCHANGE      = 26,
+		{ 14, 16}, // T4_VISIT         = 27,
+
+		{ 16, 32}, // T1_SAIL_VISIT    = 28,
+		{ 16, 32}, // T2_SAIL_VISIT    = 29,
+		{ 16, 32}, // T3_SAIL_VISIT    = 30,
+		{ 16, 32}, // T4_SAIL_VISIT    = 31,
+
+		{  6,  1}, // SCROLL_NORTH     = 32,
+		{ 16,  2}, // SCROLL_NORTHEAST = 33,
+		{ 21,  6}, // SCROLL_EAST      = 34,
+		{ 16, 16}, // SCROLL_SOUTHEAST = 35,
+		{  6, 21}, // SCROLL_SOUTH     = 36,
+		{  1, 16}, // SCROLL_SOUTHWEST = 37,
+		{  1,  5}, // SCROLL_WEST      = 38,
+		{  2,  1}, // SCROLL_NORTHWEST = 39,
+
+		{  0,  0}, // POINTER_COPY     = 40,
+		{ 14, 16}, // TELEPORT         = 41,
+		{ 20, 20}, // SCUTTLE_BOAT     = 42
+	}};
+
+	assert(offsets.size() == size_t(Cursor::Map::COUNT)); //Invalid number of pivot offsets for cursor
+	assert(index < offsets.size());
+	return offsets[index];
+}
+
+Point CursorHandler::getPivotOffsetCombat(size_t index)
+{
+	static const std::array<Point, 20> offsets = {{
+		{ 12, 12 }, // BLOCKED        = 0,
+		{ 10, 14 }, // MOVE           = 1,
+		{ 14, 14 }, // FLY            = 2,
+		{ 12, 12 }, // SHOOT          = 3,
+		{ 12, 12 }, // HERO           = 4,
+		{  8, 12 }, // QUERY          = 5,
+		{  0,  0 }, // POINTER        = 6,
+		{ 21,  0 }, // HIT_NORTHEAST  = 7,
+		{ 31,  5 }, // HIT_EAST       = 8,
+		{ 21, 21 }, // HIT_SOUTHEAST  = 9,
+		{  0, 21 }, // HIT_SOUTHWEST  = 10,
+		{  0,  5 }, // HIT_WEST       = 11,
+		{  0,  0 }, // HIT_NORTHWEST  = 12,
+		{  6,  0 }, // HIT_NORTH      = 13,
+		{  6, 31 }, // HIT_SOUTH      = 14,
+		{ 14,  0 }, // SHOOT_PENALTY  = 15,
+		{ 12, 12 }, // SHOOT_CATAPULT = 16,
+		{ 12, 12 }, // HEAL           = 17,
+		{ 12, 12 }, // SACRIFICE      = 18,
+		{ 14, 20 }, // TELEPORT       = 19
+	}};
+
+	assert(offsets.size() == size_t(Cursor::Combat::COUNT)); //Invalid number of pivot offsets for cursor
+	assert(index < offsets.size());
+	return offsets[index];
+}
+
+Point CursorHandler::getPivotOffsetSpellcast()
+{
+	return { 18, 28};
+}
+
+Point CursorHandler::getPivotOffset()
+{
+	if (dndObject)
+		return dndObject->dimensions() / 2;
+
+	switch (type) {
+	case Cursor::Type::ADVENTURE: return getPivotOffsetMap(frame);
+	case Cursor::Type::COMBAT:    return getPivotOffsetCombat(frame);
+	case Cursor::Type::DEFAULT:   return getPivotOffsetDefault(frame);
+	case Cursor::Type::SPELLBOOK: return getPivotOffsetSpellcast();
+	};
+
+	assert(0);
+	return {0, 0};
+}
+
+std::shared_ptr<IImage> CursorHandler::getCurrentImage()
+{
+	if (dndObject)
+		return dndObject;
+
+	return cursors[static_cast<size_t>(type)]->getImage(frame);
+}
+
+void CursorHandler::centerCursor()
+{
+	Point screenSize {screen->w, screen->h};
+	pos = screenSize / 2 - getPivotOffset();
+
+	SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
+	CSDL_Ext::warpMouse(pos.x, pos.y);
+	SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE);
+
+	cursor->setCursorPosition(pos);
+}
+
+void CursorHandler::updateSpellcastCursor()
+{
+	static const float frameDisplayDuration = 0.1f;
+
+	frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
+	size_t newFrame = frame;
+
+	while (frameTime >= frameDisplayDuration)
+	{
+		frameTime -= frameDisplayDuration;
+		newFrame++;
+	}
+
+	auto & animation = cursors.at(static_cast<size_t>(type));
+
+	while (newFrame >= animation->size())
+		newFrame -= animation->size();
+
+	changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
+}
+
+void CursorHandler::render()
+{
+	if(!showing)
+		return;
+
+	if (type == Cursor::Type::SPELLBOOK)
+		updateSpellcastCursor();
+
+	cursor->render();
+}
+
+void CursorHandler::hide()
+{
+	if (!showing)
+		return;
+
+	showing = false;
+	cursor->setVisible(false);
+}
+
+void CursorHandler::show()
+{
+	if (showing)
+		return;
+
+	showing = true;
+	cursor->setVisible(true);
+}
+
+void CursorSoftware::render()
+{
+	//texture must be updated in the main (renderer) thread, but changes to cursor type may come from other threads
+	if (needUpdate)
+		updateTexture();
+
+	Point renderPos = pos - pivot;
+
+	SDL_Rect destRect;
+	destRect.x = renderPos.x;
+	destRect.y = renderPos.y;
+	destRect.w = 40;
+	destRect.h = 40;
+
+	SDL_RenderCopy(mainRenderer, cursorTexture, nullptr, &destRect);
+}
+
+void CursorSoftware::createTexture(const Point & dimensions)
+{
+	if(cursorTexture)
+		SDL_DestroyTexture(cursorTexture);
+
+	if (cursorSurface)
+		SDL_FreeSurface(cursorSurface);
+
+	cursorSurface = CSDL_Ext::newSurface(dimensions.x, dimensions.y);
+	cursorTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
+
+	SDL_SetSurfaceBlendMode(cursorSurface, SDL_BLENDMODE_NONE);
+	SDL_SetTextureBlendMode(cursorTexture, SDL_BLENDMODE_BLEND);
+}
+
+void CursorSoftware::updateTexture()
+{
+	Point dimensions(-1, -1);
+
+	if (!cursorSurface ||  Point(cursorSurface->w, cursorSurface->h) != cursorImage->dimensions())
+		createTexture(cursorImage->dimensions());
+
+	CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
+
+	cursorImage->draw(cursorSurface);
+	SDL_UpdateTexture(cursorTexture, NULL, cursorSurface->pixels, cursorSurface->pitch);
+	needUpdate = false;
+}
+
+void CursorSoftware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
+{
+	assert(image != nullptr);
+	cursorImage = image;
+	pivot = pivotOffset;
+	needUpdate = true;
+}
+
+void CursorSoftware::setCursorPosition( const Point & newPos )
+{
+	pos = newPos;
+}
+
+void CursorSoftware::setVisible(bool on)
+{
+	visible = on;
+}
+
+CursorSoftware::CursorSoftware():
+	cursorTexture(nullptr),
+	cursorSurface(nullptr),
+	needUpdate(false),
+	visible(false),
+	pivot(0,0)
+{
+	SDL_ShowCursor(SDL_DISABLE);
+}
+
+CursorSoftware::~CursorSoftware()
+{
+	if(cursorTexture)
+		SDL_DestroyTexture(cursorTexture);
+
+	if (cursorSurface)
+		SDL_FreeSurface(cursorSurface);
+}
+
+CursorHardware::CursorHardware():
+	cursor(nullptr)
+{
+	SDL_ShowCursor(SDL_DISABLE);
+}
+
+CursorHardware::~CursorHardware()
+{
+	if(cursor)
+		SDL_FreeCursor(cursor);
+}
+
+void CursorHardware::setVisible(bool on)
+{
+	if (on)
+		SDL_ShowCursor(SDL_ENABLE);
+	else
+		SDL_ShowCursor(SDL_DISABLE);
+}
+
+void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivotOffset)
+{
+	auto cursorSurface = CSDL_Ext::newSurface(image->dimensions().x, image->dimensions().y);
+
+	CSDL_Ext::fillSurface(cursorSurface, Colors::TRANSPARENCY);
+
+	image->draw(cursorSurface);
+
+	auto oldCursor = cursor;
+	cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);
+
+	if (!cursor)
+		logGlobal->error("Failed to set cursor! SDL says %s", SDL_GetError());
+
+	SDL_FreeSurface(cursorSurface);
+	SDL_SetCursor(cursor);
+
+	if (oldCursor)
+		SDL_FreeCursor(oldCursor);
+}
+
+void CursorHardware::setCursorPosition( const Point & newPos )
+{
+	//no-op
+}
+
+void CursorHardware::render()
+{
+	//no-op
+}

+ 85 - 31
client/gui/CCursorHandler.h → client/gui/CursorHandler.h

@@ -8,11 +8,14 @@
  *
  */
 #pragma once
-class CIntObject;
-class CAnimImage;
+
+class CAnimation;
+class IImage;
 struct SDL_Surface;
 struct SDL_Texture;
-struct Point;
+struct SDL_Cursor;
+
+#include "../../lib/Point.h"
 
 namespace Cursor
 {
@@ -51,7 +54,9 @@ namespace Cursor
 		SHOOT_CATAPULT = 16,
 		HEAL           = 17,
 		SACRIFICE      = 18,
-		TELEPORT       = 19
+		TELEPORT       = 19,
+
+		COUNT
 	};
 
 	enum class Map {
@@ -97,7 +102,9 @@ namespace Cursor
 		SCROLL_NORTHWEST = 39,
 		//POINTER_COPY       = 40, // probably unused
 		TELEPORT         = 41,
-		SCUTTLE_BOAT     = 42
+		SCUTTLE_BOAT     = 42,
+
+		COUNT
 	};
 
 	enum class Spellcast {
@@ -105,49 +112,96 @@ namespace Cursor
 	};
 }
 
-/// handles mouse cursor
-class CCursorHandler final
+class ICursor
 {
-	bool needUpdate;
-	SDL_Texture * cursorLayer;
+public:
+	virtual ~ICursor() = default;
 
-	SDL_Surface * buffer;
-	CAnimImage * currentCursor;
+	virtual void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) = 0;
+	virtual void setCursorPosition( const Point & newPos ) = 0;
+	virtual void render() = 0;
+	virtual void setVisible( bool on) = 0;
+};
 
-	std::unique_ptr<CAnimImage> dndObject; //if set, overrides currentCursor
+class CursorHardware : public ICursor
+{
+	std::shared_ptr<IImage> cursorImage;
 
-	std::array<std::unique_ptr<CAnimImage>, 4> cursors;
+	SDL_Cursor * cursor;
 
-	bool showing;
+public:
+	CursorHardware();
+	~CursorHardware();
+
+	void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
+	void setCursorPosition( const Point & newPos ) override;
+	void render() override;
+	void setVisible( bool on) override;
+};
+
+class CursorSoftware : public ICursor
+{
+	std::shared_ptr<IImage> cursorImage;
 
-	void clearBuffer();
-	void updateBuffer(CIntObject * payload);
-	void replaceBuffer(CIntObject * payload);
-	void shiftPos( int &x, int &y );
+	SDL_Texture * cursorTexture;
+	SDL_Surface * cursorSurface;
 
+	Point pos;
+	Point pivot;
+	bool needUpdate;
+	bool visible;
+
+	void createTexture(const Point & dimensions);
 	void updateTexture();
+public:
+	CursorSoftware();
+	~CursorSoftware();
+
+	void setImage(std::shared_ptr<IImage> image, const Point & pivotOffset) override;
+	void setCursorPosition( const Point & newPos ) override;
+	void render() override;
+	void setVisible( bool on) override;
+};
+
+/// handles mouse cursor
+class CursorHandler final
+{
+	std::shared_ptr<IImage> dndObject; //if set, overrides currentCursor
+
+	std::array<std::unique_ptr<CAnimation>, 4> cursors;
+
+	bool showing;
 
 	/// Current cursor
 	Cursor::Type type;
 	size_t frame;
 	float frameTime;
+	Point pos;
 
 	void changeGraphic(Cursor::Type type, size_t index);
 
-	/// position of cursor
-	int xpos, ypos;
+	Point getPivotOffsetDefault(size_t index);
+	Point getPivotOffsetMap(size_t index);
+	Point getPivotOffsetCombat(size_t index);
+	Point getPivotOffsetSpellcast();
+	Point getPivotOffset();
+
+	void updateSpellcastCursor();
 
+	std::shared_ptr<IImage> getCurrentImage();
+
+	std::unique_ptr<ICursor> cursor;
+
+	static std::unique_ptr<ICursor> createCursor();
 public:
-	CCursorHandler();
-	~CCursorHandler();
+	CursorHandler();
+	~CursorHandler();
+
+	/// Replaces the cursor with a custom image.
+	/// @param image Image to replace cursor with or nullptr to use the normal cursor.
+	void dragAndDropCursor(std::shared_ptr<IImage> image);
 
-	/**
-	 * Replaces the cursor with a custom image.
-	 *
-	 * @param image Image to replace cursor with or nullptr to use the normal
-	 * cursor. CursorHandler takes ownership of object
-	 */
-	void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
+	void dragAndDropCursor(std::string path, size_t index);
 
 	/// Returns current position of the cursor
 	Point position() const;
@@ -172,8 +226,8 @@ public:
 
 	void render();
 
-	void hide() { showing=false; };
-	void show() { showing=true; };
+	void hide();
+	void show();
 
 	/// change cursor's positions to (x, y)
 	void cursorMove(const int & x, const int & y);

+ 6 - 7
client/gui/Fonts.cpp

@@ -130,11 +130,11 @@ size_t CBitmapFont::getGlyphWidth(const char * data) const
 void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & character, const SDL_Color & color, int &posX, int &posY) const
 {
 	Rect clipRect;
-	SDL_GetClipRect(surface, &clipRect);
+	CSDL_Ext::getClipRect(surface, clipRect);
 
 	posX += character.leftOffset;
 
-	TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
+	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
 
 	Uint8 bpp = surface->format->BytesPerPixel;
 
@@ -270,7 +270,7 @@ void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data,
 	if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
 	{
 		SDL_Color black = { 0, 0, 0, SDL_ALPHA_OPAQUE};
-		renderText(surface, data, black, Point(pos.x + 1, pos.y + 1));
+		renderText(surface, data, black, pos + Point(1,1));
 	}
 
 	if (!data.empty())
@@ -283,8 +283,7 @@ void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data,
 
 		assert(rendered);
 
-		Rect rect(pos.x, pos.y, rendered->w, rendered->h);
-		SDL_BlitSurface(rendered, nullptr, surface, &rect);
+		CSDL_Ext::blitSurface(rendered, surface, pos);
 		SDL_FreeSurface(rendered);
 	}
 }
@@ -308,9 +307,9 @@ void CBitmapHanFont::renderCharacter(SDL_Surface * surface, int characterIndex,
 {
 	//TODO: somewhat duplicated with CBitmapFont::renderCharacter();
 	Rect clipRect;
-	SDL_GetClipRect(surface, &clipRect);
+	CSDL_Ext::getClipRect(surface, clipRect);
 
-	TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
+	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
 	Uint8 bpp = surface->format->BytesPerPixel;
 
 	// start of line, may differ from 0 due to end of surface or clipped surface

+ 1 - 1
client/gui/Fonts.h

@@ -12,10 +12,10 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 class JsonNode;
+class Point;
 
 VCMI_LIB_NAMESPACE_END
 
-struct Point;
 struct SDL_Surface;
 struct SDL_Color;
 

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov