瀏覽代碼

Merge remote-tracking branch 'upstream/develop' into launcher-size-support

nordsoft 2 年之前
父節點
當前提交
8c94d082c8
共有 100 個文件被更改,包括 1073 次插入1354 次删除
  1. 28 28
      AI/BattleAI/BattleAI.cpp
  2. 6 6
      AI/BattleAI/BattleAI.h
  3. 26 26
      AI/BattleAI/BattleEvaluator.cpp
  4. 6 3
      AI/BattleAI/BattleEvaluator.h
  5. 1 1
      AI/BattleAI/BattleExchangeVariant.h
  6. 27 3
      AI/BattleAI/StackWithBonuses.cpp
  7. 7 2
      AI/BattleAI/StackWithBonuses.h
  8. 6 6
      AI/EmptyAI/CEmptyAI.cpp
  9. 3 3
      AI/EmptyAI/CEmptyAI.h
  10. 16 15
      AI/Nullkiller/AIGateway.cpp
  11. 4 4
      AI/Nullkiller/AIGateway.h
  12. 1 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  13. 3 3
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  14. 37 34
      AI/StupidAI/StupidAI.cpp
  15. 18 16
      AI/StupidAI/StupidAI.h
  16. 14 14
      AI/VCAI/VCAI.cpp
  17. 4 4
      AI/VCAI/VCAI.h
  18. 45 32
      CCallback.cpp
  19. 28 19
      CCallback.h
  20. 二進制
      Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png
  21. 二進制
      android/vcmi-app/src/main/res/mipmap-hdpi/ic_launcher.png
  22. 二進制
      android/vcmi-app/src/main/res/mipmap-mdpi/ic_launcher.png
  23. 二進制
      android/vcmi-app/src/main/res/mipmap-xhdpi/ic_launcher.png
  24. 二進制
      android/vcmi-app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  25. 二進制
      android/vcmi-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  26. 4 4
      client/CMT.cpp
  27. 4 0
      client/CMakeLists.txt
  28. 39 35
      client/CMusicHandler.cpp
  29. 15 19
      client/CMusicHandler.h
  30. 52 53
      client/CPlayerInterface.cpp
  31. 23 23
      client/CPlayerInterface.h
  32. 1 1
      client/CServerHandler.cpp
  33. 12 9
      client/CVideoHandler.cpp
  34. 8 7
      client/CVideoHandler.h
  35. 27 37
      client/Client.cpp
  36. 6 7
      client/Client.h
  37. 8 7
      client/ClientCommandManager.cpp
  38. 40 41
      client/NetPacksClient.cpp
  39. 19 0
      client/adventureMap/AdventureMapInterface.cpp
  40. 3 0
      client/adventureMap/AdventureMapInterface.h
  41. 20 24
      client/adventureMap/AdventureMapWidget.cpp
  42. 5 5
      client/adventureMap/AdventureMapWidget.h
  43. 6 6
      client/adventureMap/AdventureOptions.cpp
  44. 1 1
      client/adventureMap/CInGameConsole.cpp
  45. 19 19
      client/adventureMap/CInfoBar.cpp
  46. 2 1
      client/adventureMap/CInfoBar.h
  47. 10 10
      client/adventureMap/CList.cpp
  48. 1 1
      client/adventureMap/CMinimap.cpp
  49. 2 2
      client/adventureMap/CResDataBar.cpp
  50. 3 2
      client/adventureMap/CResDataBar.h
  51. 5 5
      client/adventureMap/MapAudioPlayer.cpp
  52. 2 1
      client/adventureMap/MapAudioPlayer.h
  53. 3 3
      client/adventureMap/TurnTimerWidget.cpp
  54. 19 19
      client/battle/BattleActionsController.cpp
  55. 21 20
      client/battle/BattleAnimationClasses.cpp
  56. 9 8
      client/battle/BattleAnimationClasses.h
  57. 13 13
      client/battle/BattleEffectsController.cpp
  58. 2 1
      client/battle/BattleEffectsController.h
  59. 22 21
      client/battle/BattleFieldController.cpp
  60. 39 28
      client/battle/BattleInterface.cpp
  61. 10 3
      client/battle/BattleInterface.h
  62. 37 36
      client/battle/BattleInterfaceClasses.cpp
  63. 13 12
      client/battle/BattleObstacleController.cpp
  64. 3 1
      client/battle/BattleObstacleController.h
  65. 5 4
      client/battle/BattleProjectileController.cpp
  66. 3 2
      client/battle/BattleProjectileController.h
  67. 38 36
      client/battle/BattleSiegeController.cpp
  68. 3 2
      client/battle/BattleSiegeController.h
  69. 19 20
      client/battle/BattleStacksController.cpp
  70. 23 22
      client/battle/BattleWindow.cpp
  71. 5 3
      client/battle/CreatureAnimation.cpp
  72. 2 2
      client/battle/CreatureAnimation.h
  73. 7 0
      client/gui/CGuiHandler.cpp
  74. 3 1
      client/gui/CGuiHandler.h
  75. 9 8
      client/gui/CursorHandler.cpp
  76. 3 2
      client/gui/CursorHandler.h
  77. 11 11
      client/gui/InterfaceObjectConfigurable.cpp
  78. 二進制
      client/icons/vcmiclient.1024x1024.png
  79. 二進制
      client/icons/vcmiclient.128x128.png
  80. 二進制
      client/icons/vcmiclient.16x16.png
  81. 二進制
      client/icons/vcmiclient.2048x2048.png
  82. 二進制
      client/icons/vcmiclient.22x22.png
  83. 二進制
      client/icons/vcmiclient.256x256.png
  84. 二進制
      client/icons/vcmiclient.32x32.png
  85. 二進制
      client/icons/vcmiclient.48x48.png
  86. 二進制
      client/icons/vcmiclient.512x512.png
  87. 二進制
      client/icons/vcmiclient.64x64.png
  88. 134 298
      client/icons/vcmiclient.svg
  89. 0 238
      client/icons/vcmiclient.xpm
  90. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  91. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  92. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  93. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  94. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  95. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  96. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  97. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  98. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  99. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]
  100. 二進制
      client/ios/Images.xcassets/AppIcon.appiconset/[email protected]

+ 28 - 28
AI/BattleAI/BattleAI.cpp

@@ -52,7 +52,7 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	setCbc(CB);
 	env = ENV;
 	cb = CB;
-	playerID = *CB->getPlayerID(); //TODO should be sth in callback
+	playerID = *CB->getPlayerID();
 	wasWaitingForRealize = CB->waitTillRealize;
 	wasUnlockingGs = CB->unlockGsWhenWaiting;
 	CB->waitTillRealize = false;
@@ -66,9 +66,9 @@ void CBattleAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	autobattlePreferences = autocombatPreferences;
 }
 
-BattleAction CBattleAI::useHealingTent(const CStack *stack)
+BattleAction CBattleAI::useHealingTent(const BattleID & battleID, const CStack *stack)
 {
-	auto healingTargets = cb->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
+	auto healingTargets = cb->getBattle(battleID)->battleGetStacks(CBattleInfoEssentials::ONLY_MINE);
 	std::map<int, const CStack*> woundHpToStack;
 	for(const auto * stack : healingTargets)
 	{
@@ -82,12 +82,12 @@ BattleAction CBattleAI::useHealingTent(const CStack *stack)
 		return BattleAction::makeHeal(stack, woundHpToStack.rbegin()->second); //last element of the woundHpToStack is the most wounded stack
 }
 
-void CBattleAI::yourTacticPhase(int distance)
+void CBattleAI::yourTacticPhase(const BattleID & battleID, int distance)
 {
-	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+	cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
 }
 
-float getStrengthRatio(std::shared_ptr<CBattleCallback> cb, int side)
+static float getStrengthRatio(std::shared_ptr<CBattleInfoCallback> cb, int side)
 {
 	auto stacks = cb->battleGetAllStacks();
 	auto our = 0, enemy = 0;
@@ -108,7 +108,7 @@ float getStrengthRatio(std::shared_ptr<CBattleCallback> cb, int side)
 	return enemy == 0 ? 1.0f : static_cast<float>(our) / enemy;
 }
 
-void CBattleAI::activeStack(const CStack * stack )
+void CBattleAI::activeStack(const BattleID & battleID, const CStack * stack )
 {
 	LOG_TRACE_PARAMS(logAi, "stack: %s", stack->nodeName());
 
@@ -128,12 +128,12 @@ void CBattleAI::activeStack(const CStack * stack )
 	{
 		if(stack->creatureId() == CreatureID::CATAPULT)
 		{
-			cb->battleMakeUnitAction(useCatapult(stack));
+			cb->battleMakeUnitAction(battleID, useCatapult(battleID, stack));
 			return;
 		}
 		if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON) && stack->hasBonusOfType(BonusType::HEALER))
 		{
-			cb->battleMakeUnitAction(useHealingTent(stack));
+			cb->battleMakeUnitAction(battleID, useHealingTent(battleID, stack));
 			return;
 		}
 
@@ -141,7 +141,7 @@ void CBattleAI::activeStack(const CStack * stack )
 		logAi->trace("Build evaluator and targets");
 #endif
 
-		BattleEvaluator evaluator(env, cb, stack, playerID, side, getStrengthRatio(cb, side));
+		BattleEvaluator evaluator(env, cb, stack, playerID, battleID, side, getStrengthRatio(cb->getBattle(battleID), side));
 
 		result = evaluator.selectStackAction(stack);
 
@@ -157,9 +157,9 @@ void CBattleAI::activeStack(const CStack * stack )
 
 		logAi->trace("Spellcast attempt completed in %lld", timeElapsed(start));
 
-		if(auto action = considerFleeingOrSurrendering())
+		if(auto action = considerFleeingOrSurrendering(battleID))
 		{
-			cb->battleMakeUnitAction(*action);
+			cb->battleMakeUnitAction(battleID, *action);
 			return;
 		}
 	}
@@ -183,17 +183,17 @@ void CBattleAI::activeStack(const CStack * stack )
 
 	logAi->trace("BattleAI decission made in %lld", timeElapsed(start));
 
-	cb->battleMakeUnitAction(result);
+	cb->battleMakeUnitAction(battleID, result);
 }
 
-BattleAction CBattleAI::useCatapult(const CStack * stack)
+BattleAction CBattleAI::useCatapult(const BattleID & battleID, const CStack * stack)
 {
 	BattleAction attack;
 	BattleHex targetHex = BattleHex::INVALID;
 
-	if(cb->battleGetGateState() == EGateState::CLOSED)
+	if(cb->getBattle(battleID)->battleGetGateState() == EGateState::CLOSED)
 	{
-		targetHex = cb->wallPartToBattleHex(EWallPart::GATE);
+		targetHex = cb->getBattle(battleID)->wallPartToBattleHex(EWallPart::GATE);
 	}
 	else
 	{
@@ -209,11 +209,11 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 
 		for(auto wallPart : wallParts)
 		{
-			auto wallState = cb->battleGetWallState(wallPart);
+			auto wallState = cb->getBattle(battleID)->battleGetWallState(wallPart);
 
 			if(wallState == EWallState::REINFORCED || wallState == EWallState::INTACT || wallState == EWallState::DAMAGED)
 			{
-				targetHex = cb->wallPartToBattleHex(wallPart);
+				targetHex = cb->getBattle(battleID)->wallPartToBattleHex(wallPart);
 				break;
 			}
 		}
@@ -234,7 +234,7 @@ BattleAction CBattleAI::useCatapult(const CStack * stack)
 	return attack;
 }
 
-void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
+void CBattleAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
 {
 	LOG_TRACE(logAi);
 	side = Side;
@@ -244,20 +244,20 @@ void CBattleAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2
 
 void CBattleAI::print(const std::string &text) const
 {
-	logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
+	logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
 }
 
-std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
+std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering(const BattleID & battleID)
 {
 	BattleStateInfoForRetreat bs;
 
-	bs.canFlee = cb->battleCanFlee();
-	bs.canSurrender = cb->battleCanSurrender(playerID);
-	bs.ourSide = cb->battleGetMySide();
-	bs.ourHero = cb->battleGetMyHero(); 
+	bs.canFlee = cb->getBattle(battleID)->battleCanFlee();
+	bs.canSurrender = cb->getBattle(battleID)->battleCanSurrender(playerID);
+	bs.ourSide = cb->getBattle(battleID)->battleGetMySide();
+	bs.ourHero = cb->getBattle(battleID)->battleGetMyHero();
 	bs.enemyHero = nullptr;
 
-	for(auto stack : cb->battleGetAllStacks(false))
+	for(auto stack : cb->getBattle(battleID)->battleGetAllStacks(false))
 	{
 		if(stack->alive())
 		{
@@ -266,7 +266,7 @@ std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 			else
 			{
 				bs.enemyStacks.push_back(stack);
-				bs.enemyHero = cb->battleGetOwnerHero(stack);
+				bs.enemyHero = cb->getBattle(battleID)->battleGetOwnerHero(stack);
 			}
 		}
 	}
@@ -278,7 +278,7 @@ std::optional<BattleAction> CBattleAI::considerFleeingOrSurrendering()
 		return std::nullopt;
 	}
 
-	auto result = cb->makeSurrenderRetreatDecision(bs);
+	auto result = cb->makeSurrenderRetreatDecision(battleID, bs);
 
 	if(!result && bs.canFlee && bs.turnsSkippedByDefense > 30)
 	{

+ 6 - 6
AI/BattleAI/BattleAI.h

@@ -71,16 +71,16 @@ public:
 	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
 	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
 
-	void activeStack(const CStack * stack) override; //called when it's turn of that stack
-	void yourTacticPhase(int distance) override;
+	void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
+	void yourTacticPhase(const BattleID & battleID, int distance) override;
 
-	std::optional<BattleAction> considerFleeingOrSurrendering();
+	std::optional<BattleAction> considerFleeingOrSurrendering(const BattleID & battleID);
 
 	void print(const std::string &text) const;
-	BattleAction useCatapult(const CStack *stack);
-	BattleAction useHealingTent(const CStack *stack);
+	BattleAction useCatapult(const BattleID & battleID, const CStack *stack);
+	BattleAction useHealingTent(const BattleID & battleID, const CStack *stack);
 
-	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
+	void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool Side, bool replayAllowed) override;
 	//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
 	//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
 	//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack

+ 26 - 26
AI/BattleAI/BattleEvaluator.cpp

@@ -53,12 +53,12 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
 
 	for(EWallPart wallPart : { EWallPart::BOTTOM_WALL, EWallPart::BELOW_GATE, EWallPart::OVER_GATE, EWallPart::UPPER_WALL })
 	{
-		auto state = cb->battleGetWallState(wallPart);
+		auto state = cb->getBattle(battleID)->battleGetWallState(wallPart);
 
 		if(state != EWallState::DESTROYED)
 			continue;
 
-		auto wallHex = cb->wallPartToBattleHex((EWallPart)wallPart);
+		auto wallHex = cb->getBattle(battleID)->wallPartToBattleHex((EWallPart)wallPart);
 		auto moatHex = wallHex.cloneInDirection(BattleHex::LEFT);
 
 		result.push_back(moatHex);
@@ -70,15 +70,15 @@ std::vector<BattleHex> BattleEvaluator::getBrokenWallMoatHexes() const
 std::optional<PossibleSpellcast> BattleEvaluator::findBestCreatureSpell(const CStack *stack)
 {
 	//TODO: faerie dragon type spell should be selected by server
-	SpellID creatureSpellToCast = cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
+	SpellID creatureSpellToCast = cb->getBattle(battleID)->battleGetRandomStackSpell(CRandomGenerator::getDefault(), stack, CBattleInfoCallback::RANDOM_AIMED);
 	if(stack->hasBonusOfType(BonusType::SPELLCASTER) && stack->canCast() && creatureSpellToCast != SpellID::NONE)
 	{
 		const CSpell * spell = creatureSpellToCast.toSpell();
 
-		if(spell->canBeCast(getCbc().get(), spells::Mode::CREATURE_ACTIVE, stack))
+		if(spell->canBeCast(cb->getBattle(battleID).get(), spells::Mode::CREATURE_ACTIVE, stack))
 		{
 			std::vector<PossibleSpellcast> possibleCasts;
-			spells::BattleCast temp(getCbc().get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
+			spells::BattleCast temp(cb->getBattle(battleID).get(), stack, spells::Mode::CREATURE_ACTIVE, spell);
 			for(auto & target : temp.findPotentialTargets())
 			{
 				PossibleSpellcast ps;
@@ -201,7 +201,7 @@ BattleAction BattleEvaluator::selectStackAction(const CStack * stack)
 	if(score <= EvaluationResult::INEFFECTIVE_SCORE
 		&& !stack->hasBonusOfType(BonusType::FLYING)
 		&& stack->unitSide() == BattleSide::ATTACKER
-		&& cb->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
+		&& cb->getBattle(battleID)->battleGetSiegeLevel() >= CGTownInstance::CITADEL)
 	{
 		auto brokenWallMoat = getBrokenWallMoatHexes();
 
@@ -228,8 +228,8 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
 
 BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes)
 {
-	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
+	auto reachability = cb->getBattle(battleID)->getReachability(stack);
+	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{
@@ -325,16 +325,16 @@ BattleAction BattleEvaluator::goTowardsNearest(const CStack * stack, std::vector
 
 bool BattleEvaluator::canCastSpell()
 {
-	auto hero = cb->battleGetMyHero();
+	auto hero = cb->getBattle(battleID)->battleGetMyHero();
 	if(!hero)
 		return false;
 
-	return cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK;
+	return cb->getBattle(battleID)->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK;
 }
 
 bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 {
-	auto hero = cb->battleGetMyHero();
+	auto hero = cb->getBattle(battleID)->battleGetMyHero();
 	if(!hero)
 		return false;
 
@@ -343,7 +343,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 	std::vector<const CSpell*> possibleSpells;
 	vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [hero, this](const CSpell *s) -> bool
 	{
-		return s->canBeCast(cb.get(), spells::Mode::HERO, hero);
+		return s->canBeCast(cb->getBattle(battleID).get(), spells::Mode::HERO, hero);
 	});
 	LOGFL("I can cast %d spells.", possibleSpells.size());
 
@@ -358,7 +358,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 	std::vector<PossibleSpellcast> possibleCasts;
 	for(auto spell : possibleSpells)
 	{
-		spells::BattleCast temp(cb.get(), hero, spells::Mode::HERO, spell);
+		spells::BattleCast temp(cb->getBattle(battleID).get(), hero, spells::Mode::HERO, spell);
 
 		if(spell->getTargetType() == spells::AimType::LOCATION)
 			continue;
@@ -390,7 +390,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 		for(auto & round : queue)
 		{
 			if(!firstRound)
-				state->nextRound(0);//todo: set actual value?
+				state->nextRound();
 			for(auto unit : round)
 			{
 				if(!vstd::contains(values, unit->unitId()))
@@ -468,7 +468,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 	ValueMap valueOfStack;
 	ValueMap healthOfStack;
 
-	TStacks all = cb->battleGetAllStacks(false);
+	TStacks all = cb->getBattle(battleID)->battleGetAllStacks(false);
 
 	size_t ourRemainingTurns = 0;
 
@@ -477,7 +477,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 		healthOfStack[unit->unitId()] = unit->getAvailableHealth();
 		valueOfStack[unit->unitId()] = 0;
 
-		if(cb->battleGetOwner(unit) == playerID && unit->canMove() && !unit->moved())
+		if(cb->getBattle(battleID)->battleGetOwner(unit) == playerID && unit->canMove() && !unit->moved())
 			ourRemainingTurns++;
 	}
 
@@ -494,12 +494,12 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 
 	std::vector<battle::Units> turnOrder;
 
-	cb->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
+	cb->getBattle(battleID)->battleGetTurnOrder(turnOrder, amount, 2); //no more than 1 turn after current, each unit at least once
 
 	{
 		bool enemyHadTurn = false;
 
-		auto state = std::make_shared<HypotheticBattle>(env.get(), cb);
+		auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
 
 		evaluateQueue(valueOfStack, turnOrder, state, 0, &enemyHadTurn);
 
@@ -531,7 +531,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 				logAi->trace("Evaluating %s", ps.spell->getNameTranslated());
 #endif
 
-				auto state = std::make_shared<HypotheticBattle>(env.get(), cb);
+				auto state = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
 
 				spells::BattleCast cast(state.get(), hero, spells::Mode::HERO, ps.spell);
 				cast.castEval(state->getServerCallback(), ps.dest);
@@ -540,7 +540,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 
 				auto needFullEval = vstd::contains_if(allUnits, [&](const battle::Unit * u) -> bool
 					{
-						auto original = cb->battleGetUnitByID(u->unitId());
+						auto original = cb->getBattle(battleID)->battleGetUnitByID(u->unitId());
 						return  !original || u->speed() != original->speed();
 					});
 
@@ -583,7 +583,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 					if(oldHealth != newHealth)
 					{
 						auto damage = std::abs(oldHealth - newHealth);
-						auto originalDefender = cb->battleGetUnitByID(unit->unitId());
+						auto originalDefender = cb->getBattle(battleID)->battleGetUnitByID(unit->unitId());
 
 						auto dpsReduce = AttackPossibility::calculateDamageReduce(
 							nullptr,
@@ -639,7 +639,7 @@ bool BattleEvaluator::attemptCastingSpell(const CStack * activeStack)
 		spellcast.setTarget(castToPerform.dest);
 		spellcast.side = side;
 		spellcast.stackNumber = (!side) ? -1 : -2;
-		cb->battleMakeSpellAction(spellcast);
+		cb->battleMakeSpellAction(battleID, spellcast);
 		activeActionMade = true;
 
 		return true;
@@ -656,8 +656,8 @@ void BattleEvaluator::evaluateCreatureSpellcast(const CStack * stack, PossibleSp
 	using ValueMap = PossibleSpellcast::ValueMap;
 
 	RNGStub rngStub;
-	HypotheticBattle state(env.get(), cb);
-	TStacks all = cb->battleGetAllStacks(false);
+	HypotheticBattle state(env.get(), cb->getBattle(battleID));
+	TStacks all = cb->getBattle(battleID)->battleGetAllStacks(false);
 
 	ValueMap healthOfStack;
 	ValueMap newHealthOfStack;
@@ -686,7 +686,7 @@ void BattleEvaluator::evaluateCreatureSpellcast(const CStack * stack, PossibleSp
 
 		auto healthDiff = newHealthOfStack[unitId] - healthOfStack[unitId];
 
-		if(localUnit->unitOwner() != getCbc()->getPlayerID())
+		if(localUnit->unitOwner() != cb->getBattle(battleID)->getPlayerID())
 			healthDiff = -healthDiff;
 
 		if(healthDiff < 0)
@@ -703,7 +703,7 @@ void BattleEvaluator::evaluateCreatureSpellcast(const CStack * stack, PossibleSp
 
 void BattleEvaluator::print(const std::string & text) const
 {
-	logAi->trace("%s Battle AI[%p]: %s", playerID.getStr(), this, text);
+	logAi->trace("%s Battle AI[%p]: %s", playerID.toString(), this, text);
 }
 
 

+ 6 - 3
AI/BattleAI/BattleEvaluator.h

@@ -32,6 +32,7 @@ class BattleEvaluator
 	bool activeActionMade = false;
 	std::optional<AttackPossibility> cachedAttack;
 	PlayerColor playerID;
+	BattleID battleID;
 	int side;
 	float cachedScore;
 	DamageCache damageCache;
@@ -52,11 +53,12 @@ public:
 		std::shared_ptr<CBattleCallback> cb,
 		const battle::Unit * activeStack,
 		PlayerColor playerID,
+		BattleID battleID,
 		int side,
 		float strengthRatio)
-		:scoreEvaluator(cb, env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio)
+		:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), strengthRatio(strengthRatio), battleID(battleID)
 	{
-		hb = std::make_shared<HypotheticBattle>(env.get(), cb);
+		hb = std::make_shared<HypotheticBattle>(env.get(), cb->getBattle(battleID));
 		damageCache.buildDamageCache(hb, side);
 
 		targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
@@ -70,9 +72,10 @@ public:
 		DamageCache & damageCache,
 		const battle::Unit * activeStack,
 		PlayerColor playerID,
+		BattleID battleID,
 		int side,
 		float strengthRatio)
-		:scoreEvaluator(cb, env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio)
+		:scoreEvaluator(cb->getBattle(battleID), env, strengthRatio), cachedAttack(), playerID(playerID), side(side), env(env), cb(cb), hb(hb), damageCache(damageCache), strengthRatio(strengthRatio), battleID(battleID)
 	{
 		targets = std::make_unique<PotentialTargets>(activeStack, damageCache, hb);
 		cachedScore = EvaluationResult::INEFFECTIVE_SCORE;

+ 1 - 1
AI/BattleAI/BattleExchangeVariant.h

@@ -133,4 +133,4 @@ public:
 
 	float getPositiveEffectMultiplier() { return 1; }
 	float getNegativeEffectMultiplier() { return negativeEffectMultiplier; }
-};
+};

+ 27 - 3
AI/BattleAI/StackWithBonuses.cpp

@@ -231,7 +231,7 @@ void StackWithBonuses::removeUnitBonus(const CSelector & selector)
 std::string StackWithBonuses::getDescription() const
 {
 	std::ostringstream oss;
-	oss << unitOwner().getStr();
+	oss << unitOwner().toString();
 	oss << " battle stack [" << unitId() << "]: " << getCount() << " of ";
 	if(type)
 		oss << type->getJsonKey();
@@ -320,12 +320,17 @@ battle::Units HypotheticBattle::getUnitsIf(battle::UnitFilter predicate) const
 	return ret;
 }
 
+BattleID HypotheticBattle::getBattleID() const
+{
+	return subject->getBattle()->getBattleID();
+}
+
 int32_t HypotheticBattle::getActiveStackID() const
 {
 	return activeUnitId;
 }
 
-void HypotheticBattle::nextRound(int32_t roundNr)
+void HypotheticBattle::nextRound()
 {
 	//TODO:HypotheticBattle::nextRound
 	for(auto unit : battleAliveUnits())
@@ -462,6 +467,24 @@ int64_t HypotheticBattle::getActualDamage(const DamageRange & damage, int32_t at
 	return (damage.min + damage.max) / 2;
 }
 
+std::vector<SpellID> HypotheticBattle::getUsedSpells(ui8 side) const
+{
+	// TODO
+	return {};
+}
+
+int3 HypotheticBattle::getLocation() const
+{
+	// TODO
+	return int3(-1, -1, -1);
+}
+
+bool HypotheticBattle::isCreatureBank() const
+{
+	// TODO
+	return false;
+}
+
 int64_t HypotheticBattle::getTreeVersion() const
 {
 	return getBonusBearer()->getTreeVersion() + bonusTreeVersion;
@@ -552,8 +575,9 @@ const Services * HypotheticBattle::HypotheticEnvironment::services() const
 	return env->services();
 }
 
-const Environment::BattleCb * HypotheticBattle::HypotheticEnvironment::battle() const
+const Environment::BattleCb * HypotheticBattle::HypotheticEnvironment::battle(const BattleID & battleID) const
 {
+	assert(battleID == owner->getBattleID());
 	return owner;
 }
 

+ 7 - 2
AI/BattleAI/StackWithBonuses.h

@@ -110,11 +110,13 @@ public:
 
 	std::shared_ptr<StackWithBonuses> getForUpdate(uint32_t id);
 
+	BattleID getBattleID() const override;
+
 	int32_t getActiveStackID() const override;
 
 	battle::Units getUnitsIf(battle::UnitFilter predicate) const override;
 
-	void nextRound(int32_t roundNr) override;
+	void nextRound() override;
 	void nextTurn(uint32_t unitId) override;
 
 	void addUnit(uint32_t id, const JsonNode & data) override;
@@ -136,6 +138,9 @@ public:
 	uint32_t nextUnitId() const override;
 
 	int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
+	std::vector<SpellID> getUsedSpells(ui8 side) const override;
+	int3 getLocation() const override;
+	bool isCreatureBank() const override;
 
 	int64_t getTreeVersion() const;
 
@@ -177,7 +182,7 @@ private:
 		HypotheticEnvironment(HypotheticBattle * owner_, const Environment * upperEnvironment);
 
 		const Services * services() const override;
-		const BattleCb * battle() const override;
+		const BattleCb * battle(const BattleID & battleID) const override;
 		const GameCb * game() const override;
 		vstd::CLoggerBase * logger() const override;
 		events::EventBus * eventBus() const override;

+ 6 - 6
AI/EmptyAI/CEmptyAI.cpp

@@ -27,7 +27,7 @@ void CEmptyAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_p
 	cb = CB;
 	env = ENV;
 	human=false;
-	playerID = *cb->getMyColor();
+	playerID = *cb->getPlayerID();
 }
 
 void CEmptyAI::yourTurn(QueryID queryID)
@@ -36,14 +36,14 @@ void CEmptyAI::yourTurn(QueryID queryID)
 	cb->endTurn();
 }
 
-void CEmptyAI::activeStack(const CStack * stack)
+void CEmptyAI::activeStack(const BattleID & battleID, const CStack * stack)
 {
-	cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+	cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
 }
 
-void CEmptyAI::yourTacticPhase(int distance)
+void CEmptyAI::yourTacticPhase(const BattleID & battleID, int distance)
 {
-	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+	cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
 }
 
 void CEmptyAI::heroGotLevel(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID)
@@ -76,7 +76,7 @@ void CEmptyAI::showMapObjectSelectDialog(QueryID askID, const Component & icon,
 	cb->selectionMade(0, askID);
 }
 
-std::optional<BattleAction> CEmptyAI::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> CEmptyAI::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
 {
 	return std::nullopt;
 }

+ 3 - 3
AI/EmptyAI/CEmptyAI.h

@@ -24,15 +24,15 @@ public:
 
 	void initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CCallback> CB) override;
 	void yourTurn(QueryID queryID) override;
-	void yourTacticPhase(int distance) override;
-	void activeStack(const CStack * stack) override;
+	void yourTacticPhase(const BattleID & battleID, int distance) override;
+	void activeStack(const BattleID & battleID, const CStack * stack) override;
 	void heroGotLevel(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, QueryID queryID) override;
 	void commanderGotLevel (const CCommanderInstance * commander, std::vector<ui32> skills, QueryID queryID) override;
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, QueryID askID, const int soundID, bool selection, bool cancel) override;
 	void showTeleportDialog(TeleportChannelID channel, TTeleportExitsList exits, bool impassable, QueryID askID) override;
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, QueryID queryID) override;
 	void showMapObjectSelectDialog(QueryID askID, const Component & icon, const MetaString & title, const MetaString & description, const std::vector<ObjectInstanceID> & objects) override;
-	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
+	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
 };
 
 #define NAME "EmptyAI 0.1"

+ 16 - 15
AI/Nullkiller/AIGateway.cpp

@@ -21,6 +21,7 @@
 #include "../../lib/serializer/BinarySerializer.h"
 #include "../../lib/serializer/BinaryDeserializer.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
+#include "../../lib/battle/BattleInfo.h"
 
 #include "AIGateway.h"
 #include "Goals/Goals.h"
@@ -192,7 +193,7 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
 {
 	LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf.toString());
 	NET_EVENT_HANDLER;
-	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.getStr(), player, player.getStr(), (victoryLossCheckResult.victory() ? "won" : "lost"));
+	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.toString(), player, player.toString(), (victoryLossCheckResult.victory() ? "won" : "lost"));
 
 	// some whitespace to flush stream
 	logAi->debug(std::string(200, ' '));
@@ -201,12 +202,12 @@ void AIGateway::gameOver(PlayerColor player, const EVictoryLossCheckResult & vic
 	{
 		if(victoryLossCheckResult.victory())
 		{
-			logAi->debug("AIGateway: Player %d (%s) won. I won! Incredible!", player, player.getStr());
+			logAi->debug("AIGateway: Player %d (%s) won. I won! Incredible!", player, player.toString());
 			logAi->debug("Turn nr %d", myCb->getDate());
 		}
 		else
 		{
-			logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.getStr());
+			logAi->debug("AIGateway: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
 		}
 
 		// some whitespace to flush stream
@@ -387,7 +388,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
 	NET_EVENT_HANDLER;
 }
 
-void AIGateway::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
 	NET_EVENT_HANDLER;
@@ -510,7 +511,7 @@ void AIGateway::showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositio
 	NET_EVENT_HANDLER;
 }
 
-std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> AIGateway::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
 {
 	LOG_TRACE(logAi);
 	NET_EVENT_HANDLER;
@@ -535,7 +536,7 @@ void AIGateway::initGameInterface(std::shared_ptr<Environment> env, std::shared_
 	cbc = CB;
 
 	NET_EVENT_HANDLER;
-	playerID = *myCb->getMyColor();
+	playerID = *myCb->getPlayerID();
 	myCb->waitTillRealize = true;
 	myCb->unlockGsWhenWaiting = true;
 
@@ -776,7 +777,7 @@ void AIGateway::makeTurn()
 	MAKING_TURN;
 
 	auto day = cb->getDate(Date::DAY);
-	logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.getStr(), day);
+	logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
 
 	boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
 	setThreadName("AIGateway::makeTurn");
@@ -1080,23 +1081,23 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 	}
 }
 
-void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
+void AIGateway::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
 {
 	NET_EVENT_HANDLER;
 	assert(!playerID.isValidPlayer() || 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->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
-	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side, replayAllowed);
+	CAdventureAI::battleStart(battleID, army1, army2, tile, hero1, hero2, side, replayAllowed);
 }
 
-void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
+void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID)
 {
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
 	status.setBattle(ENDING_BATTLE);
-	bool won = br->winner == myCb->battleGetMySide();
-	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
+	bool won = br->winner == myCb->getBattle(battleID)->battleGetMySide();
+	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 
 	if (queryID != QueryID::NONE)
@@ -1108,7 +1109,7 @@ void AIGateway::battleEnd(const BattleResult * br, QueryID queryID)
 			answerQuery(queryID, confirmAction);
 		});
 	}
-	CAdventureAI::battleEnd(br, queryID);
+	CAdventureAI::battleEnd(battleID, br, queryID);
 }
 
 void AIGateway::waitTillFree()
@@ -1421,7 +1422,7 @@ void AIGateway::tryRealize(Goals::Trade & g) //trade
 
 void AIGateway::endTurn()
 {
-	logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr());
+	logAi->info("Player %d (%s) ends turn", playerID, playerID.toString());
 	if(!status.haveTurn())
 	{
 		logAi->error("Not having turn at the end of turn???");
@@ -1441,7 +1442,7 @@ void AIGateway::endTurn()
 	}
 	while(status.haveTurn()); //for some reasons, our request may fail -> stop requesting end of turn only after we've received a confirmation that it's over
 
-	logGlobal->info("Player %d (%s) ended turn", playerID, playerID.getStr());
+	logGlobal->info("Player %d (%s) ended turn", playerID, playerID.toString());
 }
 
 void AIGateway::buildArmyIn(const CGTownInstance * t)

+ 4 - 4
AI/Nullkiller/AIGateway.h

@@ -152,7 +152,7 @@ public:
 	void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
 	void playerBonusChanged(const Bonus & bonus, bool gain) override;
 	void heroCreated(const CGHeroInstance *) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override;
 	void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
 	void requestRealized(PackageApplied * pa) override;
 	void receivedResource() override;
@@ -167,10 +167,10 @@ public:
 	void heroBonusChanged(const CGHeroInstance * hero, const Bonus & bonus, bool gain) override;
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
-	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
+	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
 
-	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
-	void battleEnd(const BattleResult * br, QueryID queryID) override;
+	void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
+	void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
 
 	void makeTurn();
 

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

@@ -68,7 +68,7 @@ PriorityEvaluator::~PriorityEvaluator()
 
 void PriorityEvaluator::initVisitTile()
 {
-	auto file = CResourceHandler::get()->load(ResourceID("config/ai/object-priorities.txt"))->readAll();
+	auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll();
 	std::string str = std::string((char *)file.first.get(), file.second);
 	engine = fl::FllImporter().fromString(str);
 	armyLossPersentageVariable = engine->getInputVariable("armyLoss");

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

@@ -1208,7 +1208,7 @@ bool AINodeStorage::hasBetterChain(
 					"Block ineficient battle move %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 					source->coord.toString(),
 					candidateNode->coord.toString(),
-					candidateNode->actor->hero->name,
+					candidateNode->actor->hero->getNameTranslated(),
 					candidateNode->actor->chainMask,
 					candidateNode->actor->armyValue,
 					node.moveRemains - candidateNode->moveRemains);
@@ -1232,7 +1232,7 @@ bool AINodeStorage::hasBetterChain(
 				"Block ineficient move because of stronger army %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 				source->coord.toString(),
 				candidateNode->coord.toString(),
-				candidateNode->actor->hero->name,
+				candidateNode->actor->hero->getNameTranslated(),
 				candidateNode->actor->chainMask,
 				candidateNode->actor->armyValue,
 				node.moveRemains - candidateNode->moveRemains);
@@ -1258,7 +1258,7 @@ bool AINodeStorage::hasBetterChain(
 					"Block ineficient move because of stronger hero %s->%s, hero: %s[%X], army %lld, mp diff: %i",
 					source->coord.toString(),
 					candidateNode->coord.toString(),
-					candidateNode->actor->hero->name,
+					candidateNode->actor->hero->getNameTranslated(),
 					candidateNode->actor->chainMask,
 					candidateNode->actor->armyValue,
 					node.moveRemains - candidateNode->moveRemains);

+ 37 - 34
AI/StupidAI/StupidAI.cpp

@@ -14,6 +14,7 @@
 #include "../../CCallback.h"
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/battle/BattleAction.h"
+#include "../../lib/battle/BattleInfo.h"
 
 static std::shared_ptr<CBattleCallback> cbc;
 
@@ -53,12 +54,12 @@ void CStupidAI::initBattleInterface(std::shared_ptr<Environment> ENV, std::share
 	initBattleInterface(ENV, CB);
 }
 
-void CStupidAI::actionFinished(const BattleAction &action)
+void CStupidAI::actionFinished(const BattleID & battleID, const BattleAction &action)
 {
 	print("actionFinished called");
 }
 
-void CStupidAI::actionStarted(const BattleAction &action)
+void CStupidAI::actionStarted(const BattleID & battleID, const BattleAction &action)
 {
 	print("actionStarted called");
 }
@@ -71,11 +72,11 @@ public:
 	std::vector<BattleHex> attackFrom; //for melee fight
 	EnemyInfo(const CStack * _s) : s(_s), adi(0), adr(0)
 	{}
-	void calcDmg(const CStack * ourStack)
+	void calcDmg(const BattleID & battleID, const CStack * ourStack)
 	{
 		// FIXME: provide distance info for Jousting bonus
 		DamageEstimation retal;
-		DamageEstimation dmg = cbc->battleEstimateDamage(ourStack, s, 0, &retal);
+		DamageEstimation dmg = cbc->getBattle(battleID)->battleEstimateDamage(ourStack, s, 0, &retal);
 		adi = static_cast<int>((dmg.damage.min + dmg.damage.max) / 2);
 		adr = static_cast<int>((retal.damage.min + retal.damage.max) / 2);
 	}
@@ -91,14 +92,14 @@ bool isMoreProfitable(const EnemyInfo &ei1, const EnemyInfo& ei2)
 	return (ei1.adi-ei1.adr) < (ei2.adi - ei2.adr);
 }
 
-static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const BattleHex &h2)
+static bool willSecondHexBlockMoreEnemyShooters(const BattleID & battleID, const BattleHex &h1, const BattleHex &h2)
 {
 	int shooters[2] = {0}; //count of shooters on hexes
 
 	for(int i = 0; i < 2; i++)
 	{
 		for (auto & neighbour : (i ? h2 : h1).neighbouringTiles())
-			if(const auto * s = cbc->battleGetUnitByPos(neighbour))
+			if(const auto * s = cbc->getBattle(battleID)->battleGetUnitByPos(neighbour))
 				if(s->isShooter())
 					shooters[i]++;
 	}
@@ -106,16 +107,16 @@ static bool willSecondHexBlockMoreEnemyShooters(const BattleHex &h1, const Battl
 	return shooters[0] < shooters[1];
 }
 
-void CStupidAI::yourTacticPhase(int distance)
+void CStupidAI::yourTacticPhase(const BattleID & battleID, int distance)
 {
-	cb->battleMakeTacticAction(BattleAction::makeEndOFTacticPhase(cb->battleGetTacticsSide()));
+	cb->battleMakeTacticAction(battleID, BattleAction::makeEndOFTacticPhase(cb->getBattle(battleID)->battleGetTacticsSide()));
 }
 
-void CStupidAI::activeStack( const CStack * stack )
+void CStupidAI::activeStack(const BattleID & battleID, const CStack * stack)
 {
 	//boost::this_thread::sleep_for(boost::chrono::seconds(2));
 	print("activeStack called for " + stack->nodeName());
-	ReachabilityInfo dists = cb->getReachability(stack);
+	ReachabilityInfo dists = cb->getBattle(battleID)->getReachability(stack);
 	std::vector<EnemyInfo> enemiesShootable, enemiesReachable, enemiesUnreachable;
 
 	if(stack->creatureId() == CreatureID::CATAPULT)
@@ -128,24 +129,24 @@ void CStupidAI::activeStack( const CStack * stack )
 		attack.side = side;
 		attack.stackNumber = stack->unitId();
 
-		cb->battleMakeUnitAction(attack);
+		cb->battleMakeUnitAction(battleID, attack);
 		return;
 	}
 	else if(stack->hasBonusOfType(BonusType::SIEGE_WEAPON))
 	{
-		cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+		cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
 		return;
 	}
 
-	for (const CStack *s : cb->battleGetStacks(CBattleCallback::ONLY_ENEMY))
+	for (const CStack *s : cb->getBattle(battleID)->battleGetStacks(CBattleInfoEssentials::ONLY_ENEMY))
 	{
-		if(cb->battleCanShoot(stack, s->getPosition()))
+		if(cb->getBattle(battleID)->battleCanShoot(stack, s->getPosition()))
 		{
 			enemiesShootable.push_back(s);
 		}
 		else
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, false);
+			std::vector<BattleHex> avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(stack, false);
 
 			for (BattleHex hex : avHexes)
 			{
@@ -168,21 +169,23 @@ void CStupidAI::activeStack( const CStack * stack )
 	}
 
 	for ( auto & enemy : enemiesReachable )
-		enemy.calcDmg( stack );
+		enemy.calcDmg(battleID, stack);
 
 	for ( auto & enemy : enemiesShootable )
-		enemy.calcDmg( stack );
+		enemy.calcDmg(battleID, stack);
 
 	if(enemiesShootable.size())
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesShootable.begin(), enemiesShootable.end(), isMoreProfitable);
-		cb->battleMakeUnitAction(BattleAction::makeShotAttack(stack, ei.s));
+		cb->battleMakeUnitAction(battleID, BattleAction::makeShotAttack(stack, ei.s));
 		return;
 	}
 	else if(enemiesReachable.size())
 	{
 		const EnemyInfo &ei= *std::max_element(enemiesReachable.begin(), enemiesReachable.end(), &isMoreProfitable);
-		cb->battleMakeUnitAction(BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), &willSecondHexBlockMoreEnemyShooters)));
+		BattleHex targetHex = *std::max_element(ei.attackFrom.begin(), ei.attackFrom.end(), [&](auto a, auto b) { return willSecondHexBlockMoreEnemyShooters(battleID, a, b);});
+
+		cb->battleMakeUnitAction(battleID, BattleAction::makeMeleeAttack(stack, ei.s->getPosition(), targetHex));
 		return;
 	}
 	else if(enemiesUnreachable.size()) //due to #955 - a buggy battle may occur when there are no enemies
@@ -194,26 +197,26 @@ void CStupidAI::activeStack( const CStack * stack )
 
 		if(dists.distToNearestNeighbour(stack, closestEnemy->s) < GameConstants::BFIELD_SIZE)
 		{
-			cb->battleMakeUnitAction(goTowards(stack, closestEnemy->s->getAttackableHexes(stack)));
+			cb->battleMakeUnitAction(battleID, goTowards(battleID, stack, closestEnemy->s->getAttackableHexes(stack)));
 			return;
 		}
 	}
 
-	cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+	cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
 	return;
 }
 
-void CStupidAI::battleAttack(const BattleAttack *ba)
+void CStupidAI::battleAttack(const BattleID & battleID, const BattleAttack *ba)
 {
 	print("battleAttack called");
 }
 
-void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
+void CStupidAI::battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged)
 {
 	print("battleStacksAttacked called");
 }
 
-void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
+void CStupidAI::battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID)
 {
 	print("battleEnd called");
 }
@@ -223,38 +226,38 @@ void CStupidAI::battleEnd(const BattleResult *br, QueryID queryID)
 // 	print("battleResultsApplied called");
 // }
 
-void CStupidAI::battleNewRoundFirst(int round)
+void CStupidAI::battleNewRoundFirst(const BattleID & battleID)
 {
 	print("battleNewRoundFirst called");
 }
 
-void CStupidAI::battleNewRound(int round)
+void CStupidAI::battleNewRound(const BattleID & battleID)
 {
 	print("battleNewRound called");
 }
 
-void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
+void CStupidAI::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
 {
 	print("battleStackMoved called");
 }
 
-void CStupidAI::battleSpellCast(const BattleSpellCast *sc)
+void CStupidAI::battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc)
 {
 	print("battleSpellCast called");
 }
 
-void CStupidAI::battleStacksEffectsSet(const SetStackEffect & sse)
+void CStupidAI::battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse)
 {
 	print("battleStacksEffectsSet called");
 }
 
-void CStupidAI::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
+void CStupidAI::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool Side, bool replayAllowed)
 {
 	print("battleStart called");
 	side = Side;
 }
 
-void CStupidAI::battleCatapultAttacked(const CatapultAttack & ca)
+void CStupidAI::battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca)
 {
 	print("battleCatapultAttacked called");
 }
@@ -264,10 +267,10 @@ void CStupidAI::print(const std::string &text) const
 	logAi->trace("CStupidAI  [%p]: %s", this, text);
 }
 
-BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> hexes) const
+BattleAction CStupidAI::goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const
 {
-	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
+	auto reachability = cb->getBattle(battleID)->getReachability(stack);
+	auto avHexes = cb->getBattle(battleID)->battleGetAvailableHexes(reachability, stack, false);
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{

+ 18 - 16
AI/StupidAI/StupidAI.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/battle/ReachabilityInfo.h"
+#include "../../lib/CGameInterface.h"
 
 class EnemyInfo;
 
@@ -30,25 +31,26 @@ public:
 
 	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB) override;
 	void initBattleInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<CBattleCallback> CB, AutocombatPreferences autocombatPreferences) override;
-	void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
-	void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
-	void activeStack(const CStack * stack) override; //called when it's turn of that stack
-	void yourTacticPhase(int distance) override;
-
-	void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
-	void battleEnd(const BattleResult *br, QueryID queryID) override;
+
+	void actionFinished(const BattleID & battleID, const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
+	void actionStarted(const BattleID & battleID, const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
+	void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
+	void yourTacticPhase(const BattleID & battleID, int distance) override;
+
+	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //called when stack is performing attack
+	void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
+	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override;
 	//void battleResultsApplied() override; //called when all effects of last battle are applied
-	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
-	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
-	void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
-	void battleSpellCast(const BattleSpellCast *sc) override;
-	void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
+	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied;
+	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
+	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
+	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override;//called when a specific effect is set to stacks
 	//void battleTriggerEffect(const BattleTriggerEffect & bte) override;
-	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
+	void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
+	void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
 
 private:
-	BattleAction goTowards(const CStack * stack, std::vector<BattleHex> hexes) const;
+	BattleAction goTowards(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> hexes) const;
 };
 

+ 14 - 14
AI/VCAI/VCAI.cpp

@@ -204,7 +204,7 @@ void VCAI::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryL
 {
 	LOG_TRACE_PARAMS(logAi, "victoryLossCheckResult '%s'", victoryLossCheckResult.messageToSelf.toString());
 	NET_EVENT_HANDLER;
-	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.getStr(), player, player.getStr(), (victoryLossCheckResult.victory() ? "won" : "lost"));
+	logAi->debug("Player %d (%s): I heard that player %d (%s) %s.", playerID, playerID.toString(), player, player.toString(), (victoryLossCheckResult.victory() ? "won" : "lost"));
 	if(player == playerID)
 	{
 		if(victoryLossCheckResult.victory())
@@ -214,7 +214,7 @@ void VCAI::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryL
 		}
 		else
 		{
-			logAi->debug("VCAI: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.getStr());
+			logAi->debug("VCAI: Player %d (%s) lost. It's me. What a disappointment! :(", player, player.toString());
 		}
 
 		finish();
@@ -475,7 +475,7 @@ void VCAI::heroCreated(const CGHeroInstance * h)
 	NET_EVENT_HANDLER;
 }
 
-void VCAI::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void VCAI::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
 	NET_EVENT_HANDLER;
@@ -600,7 +600,7 @@ void VCAI::initGameInterface(std::shared_ptr<Environment> ENV, std::shared_ptr<C
 	ah->init(CB.get());
 
 	NET_EVENT_HANDLER; //sets ah->rm->cb
-	playerID = *myCb->getMyColor();
+	playerID = *myCb->getPlayerID();
 	myCb->waitTillRealize = true;
 	myCb->unlockGsWhenWaiting = true;
 
@@ -779,7 +779,7 @@ void VCAI::makeTurn()
 	MAKING_TURN;
 
 	auto day = cb->getDate(Date::DAY);
-	logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.getStr(), day);
+	logAi->info("Player %d (%s) starting turn, day %d", playerID, playerID.toString(), day);
 
 	boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
 	setThreadName("VCAI::makeTurn");
@@ -1577,23 +1577,23 @@ void VCAI::completeGoal(Goals::TSubgoal goal)
 
 }
 
-void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
+void VCAI::battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed)
 {
 	NET_EVENT_HANDLER;
 	assert(!playerID.isValidPlayer() || 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->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
-	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side, replayAllowed);
+	CAdventureAI::battleStart(battleID, army1, army2, tile, hero1, hero2, side, replayAllowed);
 }
 
-void VCAI::battleEnd(const BattleResult * br, QueryID queryID)
+void VCAI::battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID)
 {
 	NET_EVENT_HANDLER;
 	assert(status.getBattle() == ONGOING_BATTLE);
 	status.setBattle(ENDING_BATTLE);
-	bool won = br->winner == myCb->battleGetMySide();
-	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.getStr(), (won ? "won" : "lost"), battlename);
+	bool won = br->winner == myCb->getBattle(battleID)->battleGetMySide();
+	logAi->debug("Player %d (%s): I %s the %s!", playerID, playerID.toString(), (won ? "won" : "lost"), battlename);
 	battlename.clear();
 
 	if (queryID != QueryID::NONE)
@@ -1605,7 +1605,7 @@ void VCAI::battleEnd(const BattleResult * br, QueryID queryID)
 			answerQuery(queryID, confirmAction);
 		});
 	}
-	CAdventureAI::battleEnd(br, queryID);
+	CAdventureAI::battleEnd(battleID, br, queryID);
 }
 
 void VCAI::waitTillFree()
@@ -2288,7 +2288,7 @@ HeroPtr VCAI::primaryHero() const
 
 void VCAI::endTurn()
 {
-	logAi->info("Player %d (%s) ends turn", playerID, playerID.getStr());
+	logAi->info("Player %d (%s) ends turn", playerID, playerID.toString());
 	if(!status.haveTurn())
 	{
 		logAi->error("Not having turn at the end of turn???");
@@ -2300,7 +2300,7 @@ void VCAI::endTurn()
 	}
 	while(status.haveTurn()); //for some reasons, our request may fail -> stop requesting end of turn only after we've received a confirmation that it's over
 
-	logGlobal->info("Player %d (%s) ended turn", playerID, playerID.getStr());
+	logGlobal->info("Player %d (%s) ended turn", playerID, playerID.toString());
 }
 
 void VCAI::striveToGoal(Goals::TSubgoal basicGoal)
@@ -2894,7 +2894,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	return true;
 }
 
-std::optional<BattleAction> VCAI::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> VCAI::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
 {
 	return std::nullopt;
 }

+ 4 - 4
AI/VCAI/VCAI.h

@@ -185,7 +185,7 @@ public:
 	void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
 	void playerBonusChanged(const Bonus & bonus, bool gain) override;
 	void heroCreated(const CGHeroInstance *) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override;
 	void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
 	void requestRealized(PackageApplied * pa) override;
 	void receivedResource() override;
@@ -201,9 +201,9 @@ public:
 	void showMarketWindow(const IMarket * market, const CGHeroInstance * visitor) override;
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
-	void battleStart(const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
-	void battleEnd(const BattleResult * br, QueryID queryID) override;
-	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
+	void battleStart(const BattleID & battleID, const CCreatureSet * army1, const CCreatureSet * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool side, bool replayAllowed) override;
+	void battleEnd(const BattleID & battleID, const BattleResult * br, QueryID queryID) override;
+	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
 
 	void makeTurn();
 	void mainLoop();

+ 45 - 32
CCallback.cpp

@@ -168,14 +168,10 @@ bool CCallback::swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation
  * @param assembleTo If assemble is true, this represents the artifact ID of the combination
  * artifact to assemble to. Otherwise it's not used.
  */
-bool CCallback::assembleArtifacts (const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
+void CCallback::assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)
 {
-	if (player != hero->tempOwner)
-		return false;
-
 	AssembleArtifacts aa(hero->id, artifactSlot, assemble, assembleTo);
 	sendRequest(&aa);
-	return true;
 }
 
 void CCallback::bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap)
@@ -203,16 +199,17 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID)
 	return true;
 }
 
-void CBattleCallback::battleMakeSpellAction(const BattleAction & action)
+void CBattleCallback::battleMakeSpellAction(const BattleID & battleID, const BattleAction & action)
 {
 	assert(action.actionType == EActionType::HERO_SPELL);
 	MakeAction mca(action);
+	mca.battleID = battleID;
 	sendRequest(&mca);
 }
 
 int CBattleCallback::sendRequest(const CPackForServer * request)
 {
-	int requestID = cl->sendRequest(request, *player);
+	int requestID = cl->sendRequest(request, *getPlayerID());
 	if(waitTillRealize)
 	{
 		logGlobal->trace("We'll wait till request %d is answered.\n", requestID);
@@ -226,8 +223,7 @@ int CBattleCallback::sendRequest(const CPackForServer * request)
 
 void CCallback::swapGarrisonHero( const CGTownInstance *town )
 {
-	if(town->tempOwner == *player
-	   || (town->garrisonHero && town->garrisonHero->tempOwner == *player ))
+	if(town->tempOwner == *player || (town->garrisonHero && town->garrisonHero->tempOwner == *player ))
 	{
 		GarrisonHeroSwap pack(town->id);
 		sendRequest(&pack);
@@ -236,7 +232,7 @@ void CCallback::swapGarrisonHero( const CGTownInstance *town )
 
 void CCallback::buyArtifact(const CGHeroInstance *hero, ArtifactID aid)
 {
-	if(hero->tempOwner != player) return;
+	if(hero->tempOwner != *player) return;
 
 	BuyArtifact pack(hero->id,aid);
 	sendRequest(&pack);
@@ -297,8 +293,8 @@ void CCallback::buildBoat( const IShipyard *obj )
 	sendRequest(&bb);
 }
 
-CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C):
-	CBattleCallback(Player, C)
+CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C)
+	: CBattleCallback(Player, C)
 {
 	gs = GS;
 
@@ -306,10 +302,7 @@ CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient
 	unlockGsWhenWaiting = false;
 }
 
-CCallback::~CCallback()
-{
-//trivial, but required. Don`t remove.
-}
+CCallback::~CCallback() = default;
 
 bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
 {
@@ -322,6 +315,11 @@ std::shared_ptr<const CPathsInfo> CCallback::getPathsInfo(const CGHeroInstance *
 	return cl->getPathsInfo(h);
 }
 
+std::optional<PlayerColor> CCallback::getPlayerID() const
+{
+	return CBattleCallback::getPlayerID();
+}
+
 int3 CCallback::getGuardingCreaturePosition(int3 tile)
 {
 	if (!gs->map->isInTheMap(tile))
@@ -364,36 +362,51 @@ void CCallback::unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver>
 	cl->additionalBattleInts[*player] -= battleEvents;
 }
 
-#if SCRIPTING_ENABLED
-scripting::Pool * CBattleCallback::getContextPool() const
-{
-	return cl->getGlobalContextPool();
-}
-#endif
-
-CBattleCallback::CBattleCallback(std::optional<PlayerColor> Player, CClient * C)
+CBattleCallback::CBattleCallback(std::optional<PlayerColor> player, CClient * C):
+	cl(C),
+	player(player)
 {
-	player = Player;
-	cl = C;
 }
 
-void CBattleCallback::battleMakeUnitAction(const BattleAction & action)
+void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const BattleAction & action)
 {
-	assert(!cl->gs->curB->tacticDistance);
+	assert(!cl->gs->getBattle(battleID)->tacticDistance);
 	MakeAction ma;
 	ma.ba = action;
+	ma.battleID = battleID;
 	sendRequest(&ma);
 }
 
-void CBattleCallback::battleMakeTacticAction( const BattleAction & action )
+void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action )
 {
-	assert(cl->gs->curB->tacticDistance);
+	assert(cl->gs->getBattle(battleID)->tacticDistance);
 	MakeAction ma;
 	ma.ba = action;
+	ma.battleID = battleID;
 	sendRequest(&ma);
 }
 
-std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> CBattleCallback::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
+{
+	return cl->playerint[getPlayerID().value()]->makeSurrenderRetreatDecision(battleID, battleState);
+}
+
+std::shared_ptr<CPlayerBattleCallback> CBattleCallback::getBattle(const BattleID & battleID)
+{
+	return activeBattles.at(battleID);
+}
+
+std::optional<PlayerColor> CBattleCallback::getPlayerID() const
+{
+	return player;
+}
+
+void CBattleCallback::onBattleStarted(const IBattleInfo * info)
+{
+	activeBattles[info->getBattleID()] = std::make_shared<CPlayerBattleCallback>(info, *getPlayerID());
+}
+
+void CBattleCallback::onBattleEnded(const BattleID & battleID)
 {
-	return cl->playerint[getPlayerID().value()]->makeSurrenderRetreatDecision(battleState);
+	activeBattles.erase(battleID);
 }

+ 28 - 19
CCallback.h

@@ -53,10 +53,13 @@ public:
 	bool waitTillRealize = false; //if true, request functions will return after they are realized by server
 	bool unlockGsWhenWaiting = false;//if true after sending each request, gs mutex will be unlocked so the changes can be applied; NOTICE caller must have gs mx locked prior to any call to actiob callback!
 	//battle
-	virtual void battleMakeSpellAction(const BattleAction & action) = 0;
-	virtual void battleMakeUnitAction(const BattleAction & action) = 0;
-	virtual void battleMakeTacticAction(const BattleAction & action) = 0;
-	virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) = 0;
+	virtual void battleMakeSpellAction(const BattleID & battleID, const BattleAction & action) = 0;
+	virtual void battleMakeUnitAction(const BattleID & battleID, const BattleAction & action) = 0;
+	virtual void battleMakeTacticAction(const BattleID & battleID, const BattleAction & action) = 0;
+	virtual std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) = 0;
+
+	virtual std::shared_ptr<CPlayerBattleCallback> getBattle(const BattleID & battleID) = 0;
+	virtual std::optional<PlayerColor> getPlayerID() const = 0;
 };
 
 class IGameActionCallback
@@ -86,7 +89,7 @@ public:
 	virtual int splitStack(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2, int val)=0;//split creatures from the first stack
 	//virtual bool swapArtifacts(const CGHeroInstance * hero1, ui16 pos1, const CGHeroInstance * hero2, ui16 pos2)=0; //swaps artifacts between two given heroes
 	virtual bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2)=0;
-	virtual bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
+	virtual void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo)=0;
 	virtual void eraseArtifactByClient(const ArtifactLocation & al)=0;
 	virtual bool dismissCreature(const CArmedInstance *obj, SlotID stackPos)=0;
 	virtual void endTurn()=0;
@@ -108,30 +111,34 @@ public:
 	virtual void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) = 0;
 };
 
-class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
+class CBattleCallback : public IBattleCallback
 {
+	std::map<BattleID, std::shared_ptr<CPlayerBattleCallback>> activeBattles;
+
+	std::optional<PlayerColor> player;
+
 protected:
 	int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied)
 	CClient *cl;
 
 public:
-	CBattleCallback(std::optional<PlayerColor> Player, CClient * C);
-	void battleMakeSpellAction(const BattleAction & action) override;//for casting spells by hero - DO NOT use it for moving active stack
-	void battleMakeUnitAction(const BattleAction & action) override;
-	void battleMakeTacticAction(const BattleAction & action) override; // performs tactic phase actions
-	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
-
-#if SCRIPTING_ENABLED
-	scripting::Pool * getContextPool() const override;
-#endif
+	CBattleCallback(std::optional<PlayerColor> player, CClient * C);
+	void battleMakeSpellAction(const BattleID & battleID, const BattleAction & action) override;//for casting spells by hero - DO NOT use it for moving active stack
+	void battleMakeUnitAction(const BattleID & battleID, const BattleAction & action) override;
+	void battleMakeTacticAction(const BattleID & battleID, const BattleAction & action) override; // performs tactic phase actions
+	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
+
+	std::shared_ptr<CPlayerBattleCallback> getBattle(const BattleID & battleID) override;
+	std::optional<PlayerColor> getPlayerID() const override;
+
+	void onBattleStarted(const IBattleInfo * info);
+	void onBattleEnded(const BattleID & battleID);
 
 	friend class CCallback;
 	friend class CClient;
 };
 
-class CCallback : public CPlayerSpecificInfoCallback,
-	public IGameActionCallback,
-	public CBattleCallback
+class CCallback : public CPlayerSpecificInfoCallback, public CBattleCallback, public IGameActionCallback
 {
 public:
 	CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C);
@@ -142,6 +149,8 @@ public:
 	virtual int3 getGuardingCreaturePosition(int3 tile);
 	virtual std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
 
+	std::optional<PlayerColor> getPlayerID() const override;
+
 	//Set of metrhods that allows adding more interfaces for this player that'll receive game event call-ins.
 	void registerBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
 	void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
@@ -161,7 +170,7 @@ public:
 	int bulkMergeStacks(ObjectInstanceID armyId, SlotID srcSlot) override;
 	bool dismissHero(const CGHeroInstance * hero) override;
 	bool swapArtifacts(const ArtifactLocation &l1, const ArtifactLocation &l2) override;
-	bool assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
+	void assembleArtifacts(const CGHeroInstance * hero, ArtifactPosition artifactSlot, bool assemble, ArtifactID assembleTo) override;
 	void bulkMoveArtifacts(ObjectInstanceID srcHero, ObjectInstanceID dstHero, bool swap) override;
 	void eraseArtifactByClient(const ArtifactLocation & al) override;
 	bool buildBuilding(const CGTownInstance *town, BuildingID buildingID) override;

二進制
Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png


二進制
android/vcmi-app/src/main/res/mipmap-hdpi/ic_launcher.png


二進制
android/vcmi-app/src/main/res/mipmap-mdpi/ic_launcher.png


二進制
android/vcmi-app/src/main/res/mipmap-xhdpi/ic_launcher.png


二進制
android/vcmi-app/src/main/res/mipmap-xxhdpi/ic_launcher.png


二進制
android/vcmi-app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 4 - 4
client/CMT.cpp

@@ -251,7 +251,7 @@ int main(int argc, char * argv[])
 	// Some basic data validation to produce better error messages in cases of incorrect install
 	auto testFile = [](std::string filename, std::string message)
 	{
-		if (!CResourceHandler::get()->existsResource(ResourceID(filename)))
+		if (!CResourceHandler::get()->existsResource(ResourcePath(filename)))
 			handleFatalError(message, false);
 	};
 
@@ -423,10 +423,10 @@ int main(int argc, char * argv[])
 //plays intro, ends when intro is over or button has been pressed (handles events)
 void playIntro()
 {
-	if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 0, 1, true, true))
+	if(CCS->videoh->openAndPlayVideo(VideoPath::builtin("3DOLOGO.SMK"), 0, 1, true, true))
 	{
-		if (CCS->videoh->openAndPlayVideo("NWCLOGO.SMK", 0, 1, true, true))
-			CCS->videoh->openAndPlayVideo("H3INTRO.SMK", 0, 1, true, true);
+		if (CCS->videoh->openAndPlayVideo(VideoPath::builtin("NWCLOGO.SMK"), 0, 1, true, true))
+			CCS->videoh->openAndPlayVideo(VideoPath::builtin("H3INTRO.SMK"), 0, 1, true, true);
 	}
 }
 

+ 4 - 0
client/CMakeLists.txt

@@ -84,6 +84,7 @@ set(client_SRCS
 	renderSDL/CTrueTypeFont.cpp
 	renderSDL/CursorHardware.cpp
 	renderSDL/CursorSoftware.cpp
+	renderSDL/RenderHandler.cpp
 	renderSDL/SDLImage.cpp
 	renderSDL/SDLImageLoader.cpp
 	renderSDL/SDLRWwrapper.cpp
@@ -235,6 +236,7 @@ set(client_HEADERS
 	render/IFont.h
 	render/IImage.h
 	render/IImageLoader.h
+	render/IRenderHandler.h
 	render/IScreenHandler.h
 
 	renderSDL/CBitmapFont.h
@@ -242,6 +244,7 @@ set(client_HEADERS
 	renderSDL/CTrueTypeFont.h
 	renderSDL/CursorHardware.h
 	renderSDL/CursorSoftware.h
+	renderSDL/RenderHandler.h
 	renderSDL/SDLImage.h
 	renderSDL/SDLImageLoader.h
 	renderSDL/SDLRWwrapper.h
@@ -462,6 +465,7 @@ endif()
 if(NOT WIN32 AND NOT APPLE AND NOT ANDROID)
 	#FIXME: move to client makefile?
 	install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.16x16.png"     DESTINATION share/icons/hicolor/16x16/apps RENAME vcmiclient.png)
+	install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.22x22.png"     DESTINATION share/icons/hicolor/22x22/apps RENAME vcmiclient.png)
 	install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.32x32.png"     DESTINATION share/icons/hicolor/32x32/apps RENAME vcmiclient.png)
 	install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.48x48.png"     DESTINATION share/icons/hicolor/48x48/apps RENAME vcmiclient.png)
 	install(FILES "${CMAKE_SOURCE_DIR}/client/icons/vcmiclient.64x64.png"     DESTINATION share/icons/hicolor/64x64/apps RENAME vcmiclient.png)

+ 39 - 35
client/CMusicHandler.cpp

@@ -75,7 +75,7 @@ void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
 
 CSoundHandler::CSoundHandler():
 	listener(settings.listen["general"]["sound"]),
-	ambientConfig(JsonNode(ResourceID("config/ambientSounds.json")))
+	ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
 {
 	listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
 
@@ -119,25 +119,25 @@ void CSoundHandler::release()
 }
 
 // Allocate an SDL chunk and cache it.
-Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound, bool cache)
+Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
 {
 	try
 	{
 		if (cache && soundChunks.find(sound) != soundChunks.end())
 			return soundChunks[sound].first;
 
-		auto data = CResourceHandler::get()->load(ResourceID(std::string("SOUNDS/") + sound, EResType::SOUND))->readAll();
+		auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
 		SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
 		Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1);	// will free ops
 
 		if (cache)
-			soundChunks.insert(std::pair<std::string, CachedChunk>(sound, std::make_pair (chunk, std::move (data.first))));
+			soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
 
 		return chunk;
 	}
 	catch(std::exception &e)
 	{
-		logGlobal->warn("Cannot get sound %s chunk: %s", sound, e.what());
+		logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
 		return nullptr;
 	}
 }
@@ -153,7 +153,7 @@ int CSoundHandler::ambientDistToVolume(int distance) const
 	return volume * (int)ambientConfig["volume"].Integer() / 100;
 }
 
-void CSoundHandler::ambientStopSound(std::string soundId)
+void CSoundHandler::ambientStopSound(const AudioPath & soundId)
 {
 	stopSound(ambientChannels[soundId]);
 	setChannelVolume(ambientChannels[soundId], volume);
@@ -163,13 +163,13 @@ void CSoundHandler::ambientStopSound(std::string soundId)
 int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
 {
 	assert(soundID < soundBase::sound_after_last);
-	auto sound = sounds[soundID];
-	logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound);
+	auto sound = AudioPath::builtin(sounds[soundID]);
+	logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
 
 	return playSound(sound, repeats, true);
 }
 
-int CSoundHandler::playSound(std::string sound, int repeats, bool cache)
+int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
 {
 	if (!initialized || sound.empty())
 		return -1;
@@ -182,7 +182,7 @@ int CSoundHandler::playSound(std::string sound, int repeats, bool cache)
 		channel = Mix_PlayChannel(-1, chunk, repeats);
 		if (channel == -1)
 		{
-			logGlobal->error("Unable to play sound file %s , error %s", sound, Mix_GetError());
+			logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
 			if (!cache)
 				Mix_FreeChunk(chunk);
 		}
@@ -290,14 +290,14 @@ int CSoundHandler::ambientGetRange() const
 	return static_cast<int>(ambientConfig["range"].Integer());
 }
 
-void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
+void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
 {
 	boost::mutex::scoped_lock guard(mutex);
 
-	std::vector<std::string> stoppedSounds;
+	std::vector<AudioPath> stoppedSounds;
 	for(auto & pair : ambientChannels)
 	{
-		const std::string & soundId = pair.first;
+		const auto & soundId = pair.first;
 		const int channel = pair.second;
 
 		if(!vstd::contains(soundsArg, soundId))
@@ -320,7 +320,7 @@ void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
 
 	for(auto & pair : soundsArg)
 	{
-		const std::string & soundId = pair.first;
+		const auto & soundId = pair.first;
 		const int distance = pair.second;
 
 		if(!vstd::contains(ambientChannels, soundId))
@@ -357,7 +357,7 @@ CMusicHandler::CMusicHandler():
 {
 	listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
 
-	auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourceID & id) ->  bool
+	auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) ->  bool
 	{
 		if(id.getType() != EResType::SOUND)
 			return false;
@@ -369,12 +369,12 @@ CMusicHandler::CMusicHandler():
 		return true;
 	});
 
-	for(const ResourceID & file : mp3files)
+	for(const ResourcePath & file : mp3files)
 	{
 		if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
-			addEntryToSet("battle", file.getName());
+			addEntryToSet("battle", AudioPath::fromResource(file));
 		else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
-			addEntryToSet("enemy-turn", file.getName());
+			addEntryToSet("enemy-turn", AudioPath::fromResource(file));
 	}
 
 }
@@ -383,11 +383,11 @@ void CMusicHandler::loadTerrainMusicThemes()
 {
 	for (const auto & terrain : CGI->terrainTypeHandler->objects)
 	{
-		addEntryToSet("terrain_" + terrain->getJsonKey(), "Music/" + terrain->musicFilename);
+		addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
 	}
 }
 
-void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicURI)
+void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
 {
 	musicsSet[set].push_back(musicURI);
 }
@@ -421,7 +421,7 @@ void CMusicHandler::release()
 	CAudioBase::release();
 }
 
-void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart)
+void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
 {
 	boost::mutex::scoped_lock guard(mutex);
 
@@ -451,7 +451,7 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bo
 		return;
 
 	// in this mode - play random track from set
-	queueNext(this, whichSet, "", loop, fromStart);
+	queueNext(this, whichSet, AudioPath(), loop, fromStart);
 }
 
 void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
@@ -468,7 +468,7 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
 	}
 }
 
-void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
+void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
 {
 	queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
 }
@@ -523,7 +523,7 @@ void CMusicHandler::musicFinishedCallback()
 	});
 }
 
-MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart):
+MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
 	owner(owner),
 	music(nullptr),
 	playing(false),
@@ -552,39 +552,43 @@ MusicEntry::~MusicEntry()
 		Mix_HaltMusic();
 	}
 
-	logGlobal->trace("Del-ing music file %s", currentName);
+	logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
 	if (music)
 		Mix_FreeMusic(music);
 }
 
-void MusicEntry::load(std::string musicURI)
+void MusicEntry::load(const AudioPath & musicURI)
 {
 	if (music)
 	{
-		logGlobal->trace("Del-ing music file %s", currentName);
+		logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
 		Mix_FreeMusic(music);
 		music = nullptr;
 	}
 
-	currentName = musicURI;
+	if (CResourceHandler::get()->existsResource(musicURI))
+		currentName = musicURI;
+	else
+		currentName = musicURI.addPrefix("MUSIC/");
+
 	music = nullptr;
 
-	logGlobal->trace("Loading music file %s", musicURI);
+	logGlobal->trace("Loading music file %s", currentName.getOriginalName());
 
 	try
 	{
-		auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::SOUND)));
+		auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
 		music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
 	}
 	catch(std::exception &e)
 	{
-		logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, musicURI);
+		logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
 		logGlobal->error("Exception: %s", e.what());
 	}
 
 	if(!music)
 	{
-		logGlobal->warn("Warning: Cannot open %s: %s", currentName, Mix_GetError());
+		logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
 		return;
 	}
 }
@@ -601,7 +605,7 @@ bool MusicEntry::play()
 		load(*iter);
 	}
 
-	logGlobal->trace("Playing music file %s", currentName);
+	logGlobal->trace("Playing music file %s", currentName.getOriginalName());
 
 	if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
 	{
@@ -646,7 +650,7 @@ bool MusicEntry::stop(int fade_ms)
 		assert(startTime != uint32_t(-1));
 		float playDuration = (endTime - startTime + startPosition) / 1000.f;
 		owner->trackPositions[currentName] = playDuration;
-		logGlobal->trace("Stopping music file %s at %f", currentName, playDuration);
+		logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
 
 		Mix_FadeOutMusic(fade_ms);
 		return true;
@@ -664,7 +668,7 @@ bool MusicEntry::isSet(std::string set)
 	return !setName.empty() && set == setName;
 }
 
-bool MusicEntry::isTrack(std::string track)
+bool MusicEntry::isTrack(const AudioPath & track)
 {
 	return setName.empty() && track == currentName;
 }

+ 15 - 19
client/CMusicHandler.h

@@ -35,15 +35,14 @@ public:
 class CSoundHandler: public CAudioBase
 {
 private:
-	//soundBase::soundID getSoundID(const std::string &fileName);
 	//update volume on configuration change
 	SettingsListener listener;
 	void onVolumeChange(const JsonNode &volumeNode);
 
 	using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
-	std::map<std::string, CachedChunk> soundChunks;
+	std::map<AudioPath, CachedChunk> soundChunks;
 
-	Mix_Chunk *GetSoundChunk(std::string &sound, bool cache);
+	Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
 
 	/// have entry for every currently active channel
 	/// vector will be empty if callback was not set
@@ -54,12 +53,12 @@ private:
 	boost::mutex mutexCallbacks;
 
 	int ambientDistToVolume(int distance) const;
-	void ambientStopSound(std::string soundId);
+	void ambientStopSound(const AudioPath & soundId);
 	void updateChannelVolume(int channel);
 
 	const JsonNode ambientConfig;
 
-	std::map<std::string, int> ambientChannels;
+	std::map<AudioPath, int> ambientChannels;
 	std::map<int, int> channelVolumes;
 
 	void initCallback(int channel, const std::function<void()> & function);
@@ -76,7 +75,7 @@ public:
 
 	// Sounds
 	int playSound(soundBase::soundID soundID, int repeats=0);
-	int playSound(std::string sound, int repeats=0, bool cache=false);
+	int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
 	int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
 	void stopSound(int handler);
 
@@ -84,16 +83,13 @@ public:
 	void soundFinishedCallback(int channel);
 
 	int ambientGetRange() const;
-	void ambientUpdateChannels(std::map<std::string, int> currentSounds);
+	void ambientUpdateChannels(std::map<AudioPath, int> currentSounds);
 	void ambientStopAllChannels();
 
 	// Sets
 	std::vector<soundBase::soundID> battleIntroSounds;
 };
 
-// Helper //now it looks somewhat useless
-#define battle_sound(creature,what_sound) creature->sounds.what_sound
-
 class CMusicHandler;
 
 //Class for handling one music file
@@ -109,16 +105,16 @@ class MusicEntry
 	uint32_t startPosition;
 	//if not null - set from which music will be randomly selected
 	std::string setName;
-	std::string currentName;
+	AudioPath currentName;
 
-	void load(std::string musicURI);
+	void load(const AudioPath & musicURI);
 
 public:
-	MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart);
+	MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
 	~MusicEntry();
 
 	bool isSet(std::string setName);
-	bool isTrack(std::string trackName);
+	bool isTrack(const AudioPath & trackName);
 	bool isPlaying();
 
 	bool play();
@@ -135,20 +131,20 @@ private:
 	std::unique_ptr<MusicEntry> current;
 	std::unique_ptr<MusicEntry> next;
 
-	void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart);
+	void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
 	void queueNext(std::unique_ptr<MusicEntry> queued);
 	void musicFinishedCallback();
 
 	/// map <set name> -> <list of URI's to tracks belonging to the said set>
-	std::map<std::string, std::vector<std::string>> musicsSet;
+	std::map<std::string, std::vector<AudioPath>> musicsSet;
 	/// stored position, in seconds at which music player should resume playing this track
-	std::map<std::string, float> trackPositions;
+	std::map<AudioPath, float> trackPositions;
 
 public:
 	CMusicHandler();
 
 	/// add entry with URI musicURI in set. Track will have ID musicID
-	void addEntryToSet(const std::string & set, const std::string & musicURI);
+	void addEntryToSet(const std::string & set, const AudioPath & musicURI);
 
 	void init() override;
 	void loadTerrainMusicThemes();
@@ -156,7 +152,7 @@ public:
 	void setVolume(ui32 percent) override;
 
 	/// play track by URI, if loop = true music will be looped
-	void playMusic(const std::string & musicURI, bool loop, bool fromStart);
+	void playMusic(const AudioPath & musicURI, bool loop, bool fromStart);
 	/// play random track from this set
 	void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
 	/// play random track from set (musicSet, entryID)

+ 52 - 53
client/CPlayerInterface.cpp

@@ -125,7 +125,7 @@ struct HeroObjectRetriever
 CPlayerInterface::CPlayerInterface(PlayerColor Player):
 	localState(std::make_unique<PlayerLocalState>(*this))
 {
-	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
+	logGlobal->trace("\tHuman player interface for player %s being constructed", Player.toString());
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 	GH.defActionsDef = 0;
@@ -147,7 +147,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player):
 
 CPlayerInterface::~CPlayerInterface()
 {
-	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
+	logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.toString());
 	delete showingDialog;
 	delete cingconsole;
 	if (LOCPLINT == this)
@@ -658,7 +658,7 @@ void CPlayerInterface::buildChanged(const CGTownInstance *town, BuildingID build
 	}
 }
 
-void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
+void CPlayerInterface::battleStartBefore(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2)
 {
 	// when battle starts, game will send battleStart pack *before* movement confirmation
 	// and since network thread wait for battle intro to play, movement confirmation will only happen after intro
@@ -670,7 +670,7 @@ void CPlayerInterface::battleStartBefore(const CCreatureSet *army1, const CCreat
 		waitForAllDialogs();
 }
 
-void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed)
+void CPlayerInterface::battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
@@ -685,7 +685,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 		autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool();
 
 		autofightingAI->initBattleInterface(env, cb, autocombatPreferences);
-		autofightingAI->battleStart(army1, army2, tile, hero1, hero2, side, false);
+		autofightingAI->battleStart(battleID, army1, army2, tile, hero1, hero2, side, false);
 		isAutoFightOn = true;
 		cb->registerBattleInterface(autofightingAI);
 	}
@@ -697,7 +697,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 	BATTLE_EVENT_POSSIBLE_RETURN;
 }
 
-void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units)
+void CPlayerInterface::battleUnitsChanged(const BattleID & battleID, const std::vector<UnitChanges> & units)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -708,7 +708,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 		{
 		case UnitChanges::EOperation::RESET_STATE:
 			{
-				const CStack * stack = cb->battleGetStackByID(info.id );
+				const CStack * stack = cb->getBattle(battleID)->battleGetStackByID(info.id );
 
 				if(!stack)
 				{
@@ -723,7 +723,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 			break;
 		case UnitChanges::EOperation::ADD:
 			{
-				const CStack * unit = cb->battleGetStackByID(info.id);
+				const CStack * unit = cb->getBattle(battleID)->battleGetStackByID(info.id);
 				if(!unit)
 				{
 					logGlobal->error("Invalid unit ID %d", info.id);
@@ -739,7 +739,7 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
 	}
 }
 
-void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
+void CPlayerInterface::battleObstaclesChanged(const BattleID & battleID, const std::vector<ObstacleChanges> & obstacles)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -751,7 +751,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
 	{
 		if(change.operation == BattleChanges::EOperation::ADD)
 		{
-			auto instance = cb->battleGetObstacleByID(change.id);
+			auto instance = cb->getBattle(battleID)->battleGetObstacleByID(change.id);
 			if(instance)
 				newObstacles.push_back(instance);
 			else
@@ -770,7 +770,7 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
 	battleInt->fieldController->redrawBackgroundWithHexes();
 }
 
-void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
+void CPlayerInterface::battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -778,15 +778,15 @@ void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)
 	battleInt->stackIsCatapulting(ca);
 }
 
-void CPlayerInterface::battleNewRound(int round) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
+void CPlayerInterface::battleNewRound(const BattleID & battleID) //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	battleInt->newRound(round);
+	battleInt->newRound();
 }
 
-void CPlayerInterface::actionStarted(const BattleAction &action)
+void CPlayerInterface::actionStarted(const BattleID & battleID, const BattleAction &action)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -794,7 +794,7 @@ void CPlayerInterface::actionStarted(const BattleAction &action)
 	battleInt->startAction(action);
 }
 
-void CPlayerInterface::actionFinished(const BattleAction &action)
+void CPlayerInterface::actionFinished(const BattleID & battleID, const BattleAction &action)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -802,17 +802,17 @@ void CPlayerInterface::actionFinished(const BattleAction &action)
 	battleInt->endAction(action);
 }
 
-void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn of that stack
+void CPlayerInterface::activeStack(const BattleID & battleID, const CStack * stack) //called when it's turn of that stack
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	logGlobal->trace("Awaiting command for %s", stack->nodeName());
 
-	assert(!cb->battleIsFinished());
-	if (cb->battleIsFinished())
+	assert(!cb->getBattle(battleID)->battleIsFinished());
+	if (cb->getBattle(battleID)->battleIsFinished())
 	{
 		logGlobal->error("Received CPlayerInterface::activeStack after battle is finished!");
 
-		cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+		cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
 		return ;
 	}
 
@@ -823,7 +823,7 @@ void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn
 			//FIXME: we want client rendering to proceed while AI is making actions
 			// so unlock mutex while AI is busy since this might take quite a while, especially if hero has many spells
 			auto unlockPim = vstd::makeUnlockGuard(*pim);
-			autofightingAI->activeStack(stack);
+			autofightingAI->activeStack(battleID, stack);
 			return;
 		}
 
@@ -835,7 +835,7 @@ void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn
 	if(!battleInt)
 	{
 		// probably battle is finished already
-		cb->battleMakeUnitAction(BattleAction::makeDefend(stack));
+		cb->battleMakeUnitAction(battleID, BattleAction::makeDefend(stack));
 	}
 
 	{
@@ -845,7 +845,7 @@ void CPlayerInterface::activeStack(const CStack * stack) //called when it's turn
 	}
 }
 
-void CPlayerInterface::battleEnd(const BattleResult *br, QueryID queryID)
+void CPlayerInterface::battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	if(isAutoFightOn || autofightingAI)
@@ -880,7 +880,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br, QueryID queryID)
 	battleInt->battleFinished(*br, queryID);
 }
 
-void CPlayerInterface::battleLogMessage(const std::vector<MetaString> & lines)
+void CPlayerInterface::battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -888,28 +888,28 @@ void CPlayerInterface::battleLogMessage(const std::vector<MetaString> & lines)
 	battleInt->displayBattleLog(lines);
 }
 
-void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
+void CPlayerInterface::battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 	battleInt->stackMoved(stack, dest, distance, teleport);
 }
-void CPlayerInterface::battleSpellCast( const BattleSpellCast *sc )
+void CPlayerInterface::battleSpellCast(const BattleID & battleID, const BattleSpellCast * sc)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 	battleInt->spellCast(sc);
 }
-void CPlayerInterface::battleStacksEffectsSet( const SetStackEffect & sse )
+void CPlayerInterface::battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 	battleInt->battleStacksEffectsSet(sse);
 }
-void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
+void CPlayerInterface::battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -923,7 +923,7 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
 		battleInt->windowObject->heroManaPointsChanged(manaDrainedHero);
 	}
 }
-void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
+void CPlayerInterface::battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -931,8 +931,8 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 	std::vector<StackAttackedInfo> arg;
 	for(auto & elem : bsa)
 	{
-		const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
-		const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
+		const CStack * defender = cb->getBattle(battleID)->battleGetStackByID(elem.stackAttacked, false);
+		const CStack * attacker = cb->getBattle(battleID)->battleGetStackByID(elem.attackerID, false);
 
 		assert(defender);
 
@@ -955,13 +955,13 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
 	}
 	battleInt->stacksAreAttacked(arg);
 }
-void CPlayerInterface::battleAttack(const BattleAttack * ba)
+void CPlayerInterface::battleAttack(const BattleID & battleID, const BattleAttack * ba)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
 	StackAttackInfo info;
-	info.attacker = cb->battleGetStackByID(ba->stackAttacking);
+	info.attacker = cb->getBattle(battleID)->battleGetStackByID(ba->stackAttacking);
 	info.defender = nullptr;
 	info.indirectAttack = ba->shot();
 	info.lucky = ba->lucky();
@@ -979,11 +979,11 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 		if(!elem.isSecondary())
 		{
 			assert(info.defender == nullptr);
-			info.defender = cb->battleGetStackByID(elem.stackAttacked);
+			info.defender = cb->getBattle(battleID)->battleGetStackByID(elem.stackAttacked);
 		}
 		else
 		{
-			info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked));
+			info.secondaryDefender.push_back(cb->getBattle(battleID)->battleGetStackByID(elem.stackAttacked));
 		}
 	}
 	assert(info.defender != nullptr);
@@ -992,7 +992,7 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 	battleInt->stackAttacking(info);
 }
 
-void CPlayerInterface::battleGateStateChanged(const EGateState state)
+void CPlayerInterface::battleGateStateChanged(const BattleID & battleID, const EGateState state)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
@@ -1000,7 +1000,7 @@ void CPlayerInterface::battleGateStateChanged(const EGateState state)
 	battleInt->gateStateChanged(state);
 }
 
-void CPlayerInterface::yourTacticPhase(int distance)
+void CPlayerInterface::yourTacticPhase(const BattleID & battleID, int distance)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 }
@@ -1112,11 +1112,11 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v
 		for (auto & component : components)
 			intComps.push_back(std::make_shared<CSelectableComponent>(component)); //will be deleted by CSelWindow::close
 
-		std::vector<std::pair<std::string,CFunctionList<void()> > > pom;
-		pom.push_back(std::pair<std::string,CFunctionList<void()> >("IOKAY.DEF",0));
+		std::vector<std::pair<AnimationPath,CFunctionList<void()> > > pom;
+		pom.push_back({ AnimationPath::builtin("IOKAY.DEF"),0});
 		if (cancel)
 		{
-			pom.push_back(std::pair<std::string,CFunctionList<void()> >("ICANCEL.DEF",0));
+			pom.push_back({AnimationPath::builtin("ICANCEL.DEF"),0});
 		}
 
 		int charperline = 35;
@@ -1288,7 +1288,7 @@ void CPlayerInterface::showGarrisonDialog( const CArmedInstance *up, const CGHer
  * into a combinational one on an artifact screen. Does not require the combination of
  * artifacts to be legal.
  */
-void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes)
+void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes)
 {
 	std::string text = artifact->getDescriptionTranslated();
 	text += "\n\n";
@@ -1657,7 +1657,7 @@ void CPlayerInterface::viewWorldMap()
 	adventureInt->openWorldView();
 }
 
-void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
@@ -1667,8 +1667,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 	if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
 		localState->erasePath(caster);
 
-	const spells::Spell * spell = CGI->spells()->getByIndex(spellID);
-	auto castSoundPath = spell->getCastSound();
+	auto castSoundPath = spellID.toSpell()->getCastSound();
 	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 }
@@ -1705,12 +1704,12 @@ void CPlayerInterface::tryDigging(const CGHeroInstance * h)
 		showInfoDialog(CGI->generaltexth->allTexts[msgToShow]);
 }
 
-void CPlayerInterface::battleNewRoundFirst( int round )
+void CPlayerInterface::battleNewRoundFirst(const BattleID & battleID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	battleInt->newRoundFirst(round);
+	battleInt->newRoundFirst();
 }
 
 void CPlayerInterface::stopMovement()
@@ -1992,22 +1991,22 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 			elem.coord = h->convertFromVisitablePos(elem.coord);
 
 		int soundChannel = -1;
-		std::string soundName;
+		AudioPath soundName;
 
-		auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> std::string
+		auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> AudioPath
 		{
 			if (moveType == EPathNodeAction::TELEPORT_BATTLE || moveType == EPathNodeAction::TELEPORT_BLOCKING_VISIT || moveType == EPathNodeAction::TELEPORT_NORMAL)
-				return "";
+				return {};
 
 			if (moveType == EPathNodeAction::EMBARK || moveType == EPathNodeAction::DISEMBARK)
-				return "";
+				return {};
 
 			if (moveType == EPathNodeAction::BLOCKING_VISIT)
-				return "";
+				return {};
 
 			// flying movement sound
 			if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
-				return "HORSE10.wav";
+				return AudioPath::builtin("HORSE10.wav");
 
 			auto prevTile = cb->getTile(h->convertToVisitablePos(posPrev));
 			auto nextTile = cb->getTile(h->convertToVisitablePos(posNext));
@@ -2073,7 +2072,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 
 			{
 				// Start a new sound for the hero movement or let the existing one carry on.
-				std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
+				AudioPath newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
 
 				if(newSoundName != soundName)
 				{
@@ -2126,7 +2125,7 @@ void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectP
 	adventureInt->openWorldView(objectPositions, showTerrain );
 }
 
-std::optional<BattleAction> CPlayerInterface::makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState)
+std::optional<BattleAction> CPlayerInterface::makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState)
 {
 	return std::nullopt;
 }

+ 23 - 23
client/CPlayerInterface.h

@@ -128,7 +128,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;
 	void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor) override;
 	void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override; //called when a hero casts a spell
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell
 	void tileHidden(const std::unordered_set<int3> &pos) override; //called when given tiles become hidden under fog of war
 	void tileRevealed(const std::unordered_set<int3> &pos) override; //called when fog of war disappears from given tiles
 	void newObject(const CGObjectInstance * obj) override;
@@ -152,27 +152,27 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions, bool showTerrain) override;
 
 	//for battles
-	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero
-	void actionStarted(const BattleAction& action) override;//occurs BEFORE action taken by active stack or by the hero
-	void activeStack(const CStack * stack) override; //called when it's turn of that stack
-	void battleAttack(const BattleAttack *ba) override; //stack performs attack
-	void battleEnd(const BattleResult *br, QueryID queryID) override; //end of battle
-	void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
-	void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
-	void battleLogMessage(const std::vector<MetaString> & lines) override;
-	void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
-	void battleSpellCast(const BattleSpellCast *sc) override;
-	void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
-	void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
-	void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override;
-	void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
-	void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
-	void battleUnitsChanged(const std::vector<UnitChanges> & units) override;
-	void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
-	void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
-	void battleGateStateChanged(const EGateState state) override;
-	void yourTacticPhase(int distance) override;
-	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleStateInfoForRetreat & battleState) override;
+	void actionFinished(const BattleID & battleID, const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero
+	void actionStarted(const BattleID & battleID, const BattleAction& action) override;//occurs BEFORE action taken by active stack or by the hero
+	void activeStack(const BattleID & battleID, const CStack * stack) override; //called when it's turn of that stack
+	void battleAttack(const BattleID & battleID, const BattleAttack *ba) override; //stack performs attack
+	void battleEnd(const BattleID & battleID, const BattleResult *br, QueryID queryID) override; //end of battle
+	void battleNewRoundFirst(const BattleID & battleID) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
+	void battleNewRound(const BattleID & battleID) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
+	void battleLogMessage(const BattleID & battleID, const std::vector<MetaString> & lines) override;
+	void battleStackMoved(const BattleID & battleID, const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
+	void battleSpellCast(const BattleID & battleID, const BattleSpellCast *sc) override;
+	void battleStacksEffectsSet(const BattleID & battleID, const SetStackEffect & sse) override; //called when a specific effect is set to stacks
+	void battleTriggerEffect(const BattleID & battleID, const BattleTriggerEffect & bte) override; //various one-shot effect
+	void battleStacksAttacked(const BattleID & battleID, const std::vector<BattleStackAttacked> & bsa, bool ranged) override;
+	void battleStartBefore(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
+	void battleStart(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side, bool replayAllowed) override; //called by engine when battle starts; side=0 - left, side=1 - right
+	void battleUnitsChanged(const BattleID & battleID, const std::vector<UnitChanges> & units) override;
+	void battleObstaclesChanged(const BattleID & battleID, const std::vector<ObstacleChanges> & obstacles) override;
+	void battleCatapultAttacked(const BattleID & battleID, const CatapultAttack & ca) override; //called when catapult makes an attack
+	void battleGateStateChanged(const BattleID & battleID, const EGateState state) override;
+	void yourTacticPhase(const BattleID & battleID, int distance) override;
+	std::optional<BattleAction> makeSurrenderRetreatDecision(const BattleID & battleID, const BattleStateInfoForRetreat & battleState) override;
 
 public: // public interface for use by client via LOCPLINT access
 
@@ -185,7 +185,7 @@ public: // public interface for use by client via LOCPLINT access
 	void showShipyardDialog(const IShipyard *obj) override; //obj may be town or shipyard;
 
 	void showHeroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2);
-	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<bool()> onYes);
+	void showArtifactAssemblyDialog(const Artifact * artifact, const Artifact * assembledArtifact, CFunctionList<void()> onYes);
 	void waitWhileDialog(bool unlockPim = true);
 	void waitForAllDialogs(bool unlockPim = true);
 	void openTownWindow(const CGTownInstance * town); //shows townscreen

+ 1 - 1
client/CServerHandler.cpp

@@ -748,7 +748,7 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 	if(save)
 	{
 		resetStateForLobby(StartInfo::LOAD_GAME);
-		mapInfo->saveInit(ResourceID(filename, EResType::SAVEGAME));
+		mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
 		screenType = ESelectionScreen::loadGame;
 	}
 	else

+ 12 - 9
client/CVideoHandler.cpp

@@ -70,30 +70,32 @@ CVideoPlayer::CVideoPlayer()
 	, doLoop(false)
 {}
 
-bool CVideoPlayer::open(std::string fname, bool scale)
+bool CVideoPlayer::open(const VideoPath & fname, bool scale)
 {
 	return open(fname, true, false);
 }
 
 // loop = to loop through the video
 // useOverlay = directly write to the screen.
-bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scale)
+bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverlay, bool scale)
 {
 	close();
 
-	this->fname = fname;
 	doLoop = loop;
 	frameTime = 0;
 
-	ResourceID resource(std::string("Video/") + fname, EResType::VIDEO);
+	if (CResourceHandler::get()->existsResource(videoToOpen))
+		fname = videoToOpen;
+	else
+		fname = videoToOpen.addPrefix("VIDEO/");
 
-	if (!CResourceHandler::get()->existsResource(resource))
+	if (!CResourceHandler::get()->existsResource(fname))
 	{
-		logGlobal->error("Error: video %s was not found", resource.getName());
+		logGlobal->error("Error: video %s was not found", fname.getName());
 		return false;
 	}
 
-	data = CResourceHandler::get()->load(resource);
+	data = CResourceHandler::get()->load(fname);
 
 	static const int BUFFER_SIZE = 4096;
 
@@ -382,7 +384,8 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 
 void CVideoPlayer::close()
 {
-	fname.clear();
+	fname = VideoPath();
+
 	if (sws)
 	{
 		sws_freeContext(sws);
@@ -467,7 +470,7 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 	return true;
 }
 
-bool CVideoPlayer::openAndPlayVideo(std::string name, int x, int y, bool stopOnKey, bool scale)
+bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey, bool scale)
 {
 	open(name, false, true, scale);
 	bool ret = playVideo(x, y,  stopOnKey);

+ 8 - 7
client/CVideoHandler.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../lib/Rect.h"
+#include "../lib/filesystem/ResourcePath.h"
 
 struct SDL_Surface;
 struct SDL_Texture;
@@ -17,7 +18,7 @@ struct SDL_Texture;
 class IVideoPlayer
 {
 public:
-	virtual bool open(std::string name, bool scale = false)=0; //true - succes
+	virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
 	virtual void close()=0;
 	virtual bool nextFrame()=0;
 	virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
@@ -30,10 +31,10 @@ public:
 class IMainVideoPlayer : public IVideoPlayer
 {
 public:
-	std::string fname;  //name of current video file (empty if idle)
+	VideoPath fname;  //name of current video file (empty if idle)
 
 	virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true){}
-	virtual bool openAndPlayVideo(std::string name, int x, int y, bool stopOnKey = false, bool scale = false)
+	virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false)
 	{
 		return false;
 	}
@@ -49,7 +50,7 @@ public:
 	bool nextFrame() override {return false;};
 	void close() override {};
 	bool wait() override {return false;};
-	bool open(std::string name, bool scale = false) override {return false;};
+	bool open(const VideoPath & name, bool scale = false) override {return false;};
 };
 
 #ifndef DISABLE_VIDEO
@@ -85,14 +86,14 @@ class CVideoPlayer : public IMainVideoPlayer
 	bool doLoop;				// loop through video
 
 	bool playVideo(int x, int y, bool stopOnKey);
-	bool open(std::string fname, bool loop, bool useOverlay = false, bool scale = false);
+	bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
 
 public:
 	CVideoPlayer();
 	~CVideoPlayer();
 
 	bool init();
-	bool open(std::string fname, bool scale = false) override;
+	bool open(const VideoPath & fname, bool scale = false) override;
 	void close() override;
 	bool nextFrame() override;			// display next frame
 
@@ -101,7 +102,7 @@ public:
 	void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
 
 	// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
-	bool openAndPlayVideo(std::string name, int x, int y, bool stopOnKey = false, bool scale = false) override;
+	bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override;
 
 	//TODO:
 	bool wait() override {return false;};

+ 27 - 37
client/Client.cpp

@@ -122,9 +122,9 @@ events::EventBus * CPlayerEnvironment::eventBus() const
 	return cl->eventBus();//always get actual value
 }
 
-const CPlayerEnvironment::BattleCb * CPlayerEnvironment::battle() const
+const CPlayerEnvironment::BattleCb * CPlayerEnvironment::battle(const BattleID & battleID) const
 {
-	return mainCallback.get();
+	return mainCallback->getBattle(battleID).get();
 }
 
 const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
@@ -153,9 +153,9 @@ const Services * CClient::services() const
 	return VLC; //todo: this should be CGI
 }
 
-const CClient::BattleCb * CClient::battle() const
+const CClient::BattleCb * CClient::battle(const BattleID & battleID) const
 {
-	return this;
+	return nullptr; //todo?
 }
 
 const CClient::GameCb * CClient::game() const
@@ -222,7 +222,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 	// try to deserialize client data including sleepingHeroes
 	try
 	{
-		boost::filesystem::path clientSaveName = *CResourceHandler::get()->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
+		boost::filesystem::path clientSaveName = *CResourceHandler::get()->getResourceName(ResourcePath(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
 
 		if(clientSaveName.empty())
 			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
@@ -345,7 +345,7 @@ void CClient::serialize(BinaryDeserializer & h, const int version)
 
 void CClient::save(const std::string & fname)
 {
-	if(gs->curB)
+	if(!gs->currentBattles.empty())
 	{
 		logNetwork->error("Game cannot be saved during battle!");
 		return;
@@ -408,7 +408,7 @@ void CClient::initPlayerEnvironments()
 	bool hasHumanPlayer = false;
 	for(auto & color : allPlayers)
 	{
-		logNetwork->info("Preparing environment for player %s", color.getStr());
+		logNetwork->info("Preparing environment for player %s", color.toString());
 		playerEnvironments[color] = std::make_shared<CPlayerEnvironment>(color, this, std::make_shared<CCallback>(gs, color, this));
 		
 		if(!hasHumanPlayer && gs->players[color].isHuman())
@@ -439,7 +439,7 @@ void CClient::initPlayerInterfaces()
 
 		if(!vstd::contains(playerint, color))
 		{
-			logNetwork->info("Preparing interface for player %s", color.getStr());
+			logNetwork->info("Preparing interface for player %s", color.toString());
 			if(playerInfo.second.isControlledByAI())
 			{
 				bool alliedToHuman = false;
@@ -448,12 +448,12 @@ void CClient::initPlayerInterfaces()
 						alliedToHuman = true;
 
 				auto AiToGive = aiNameForPlayer(playerInfo.second, false, alliedToHuman);
-				logNetwork->info("Player %s will be lead by %s", color.getStr(), AiToGive);
+				logNetwork->info("Player %s will be lead by %s", color.toString(), AiToGive);
 				installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
 			}
 			else
 			{
-				logNetwork->info("Player %s will be lead by human", color.getStr());
+				logNetwork->info("Player %s will be lead by human", color.toString());
 				installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
 			}
 		}
@@ -503,7 +503,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInte
 
 	playerint[color] = gameInterface;
 
-	logGlobal->trace("\tInitializing the interface for player %s", color.getStr());
+	logGlobal->trace("\tInitializing the interface for player %s", color.toString());
 	auto cb = std::make_shared<CCallback>(gs, color, this);
 	battleCallbacks[color] = cb;
 	gameInterface->initGameInterface(playerEnvironments.at(color), cb);
@@ -519,7 +519,7 @@ void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> ba
 
 	if(needCallback)
 	{
-		logGlobal->trace("\tInitializing the battle interface for player %s", color.getStr());
+		logGlobal->trace("\tInitializing the battle interface for player %s", color.toString());
 		auto cbc = std::make_shared<CBattleCallback>(color, this);
 		battleCallbacks[color] = cbc;
 		battleInterface->initBattleInterface(playerEnvironments.at(color), cbc);
@@ -565,14 +565,12 @@ int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
 
 void CClient::battleStarted(const BattleInfo * info)
 {
-	setBattle(info);
-
 	for(auto & battleCb : battleCallbacks)
 	{
 		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })
 			|| !battleCb.first.isValidPlayer())
 		{
-			battleCb.second->setBattle(info);
+			battleCb.second->onBattleStarted(info);
 		}
 	}
 
@@ -583,7 +581,7 @@ void CClient::battleStarted(const BattleInfo * info)
 	auto callBattleStart = [&](PlayerColor color, ui8 side)
 	{
 		if(vstd::contains(battleints, color))
-			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side, info->replayAllowed);
+			battleints[color]->battleStart(info->battleID, leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side, info->replayAllowed);
 	};
 	
 	callBattleStart(leftSide.color, 0);
@@ -601,11 +599,11 @@ void CClient::battleStarted(const BattleInfo * info)
 	//Remove player interfaces for auto battle (quickCombat option)
 	if(att && att->isAutoFightOn)
 	{
-		if (att->cb->battleGetTacticDist())
+		if (att->cb->getBattle(info->battleID)->battleGetTacticDist())
 		{
-			auto side = att->cb->playerToSide(att->playerID);
+			auto side = att->cb->getBattle(info->battleID)->playerToSide(att->playerID);
 			auto action = BattleAction::makeEndOFTacticPhase(*side);
-			att->cb->battleMakeTacticAction(action);
+			att->cb->battleMakeTacticAction(info->battleID, action);
 		}
 
 		att.reset();
@@ -617,15 +615,15 @@ void CClient::battleStarted(const BattleInfo * info)
 		if(att || def)
 		{
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
+			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(info->getBattleID(), leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
 		}
 		else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
 		{
 			//TODO: This certainly need improvement
 			auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
-			spectratorInt->cb->setBattle(info);
+			spectratorInt->cb->onBattleStarted(info);
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
+			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(info->getBattleID(), leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
 		}
 	}
 
@@ -634,24 +632,21 @@ void CClient::battleStarted(const BattleInfo * info)
 		auto tacticianColor = info->sides[info->tacticsSide].color;
 
 		if (vstd::contains(battleints, tacticianColor))
-			battleints[tacticianColor]->yourTacticPhase(info->tacticDistance);
+			battleints[tacticianColor]->yourTacticPhase(info->battleID, info->tacticDistance);
 	}
 }
 
-void CClient::battleFinished()
+void CClient::battleFinished(const BattleID & battleID)
 {
-	for(auto & side : gs->curB->sides)
+	for(auto & side : gs->getBattle(battleID)->sides)
 		if(battleCallbacks.count(side.color))
-			battleCallbacks[side.color]->setBattle(nullptr);
+			battleCallbacks[side.color]->onBattleEnded(battleID);
 
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
-		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
-
-	setBattle(nullptr);
-	gs->curB.dellNull();
+		battleCallbacks[PlayerColor::SPECTATOR]->onBattleEnded(battleID);
 }
 
-void CClient::startPlayerBattleAction(PlayerColor color)
+void CClient::startPlayerBattleAction(const BattleID & battleID, PlayerColor color)
 {
 	assert(vstd::contains(battleints, color));
 
@@ -661,7 +656,7 @@ void CClient::startPlayerBattleAction(PlayerColor color)
 		auto unlock = vstd::makeUnlockGuardIf(*CPlayerInterface::pim, !battleints[color]->human);
 
 		assert(vstd::contains(battleints, color));
-		battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
+		battleints[color]->activeStack(battleID, gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->activeStack, false));
 	}
 }
 
@@ -698,11 +693,6 @@ scripting::Pool * CClient::getGlobalContextPool() const
 {
 	return clientScripts.get();
 }
-
-scripting::Pool * CClient::getContextPool() const
-{
-	return clientScripts.get();
-}
 #endif
 
 void CClient::reinitScripting()

+ 6 - 7
client/Client.h

@@ -13,7 +13,6 @@
 #include <vcmi/Environment.h>
 
 #include "../lib/IGameCallback.h"
-#include "../lib/battle/CBattleInfoCallback.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -25,6 +24,7 @@ class CGameInterface;
 class BinaryDeserializer;
 class BinarySerializer;
 class BattleAction;
+class BattleInfo;
 
 template<typename T> class CApplier;
 
@@ -105,12 +105,12 @@ public:
 	const Services * services() const override;
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
-	const BattleCb * battle() const override;
+	const BattleCb * battle(const BattleID & battle) const override;
 	const GameCb * game() const override;
 };
 
 /// Class which handles client - server logic
-class CClient : public IGameCallback, public CBattleInfoCallback, public Environment
+class CClient : public IGameCallback, public Environment
 {
 public:
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
@@ -124,7 +124,7 @@ public:
 	~CClient();
 
 	const Services * services() const override;
-	const BattleCb * battle() const override;
+	const BattleCb * battle(const BattleID & battle) const override;
 	const GameCb * game() const override;
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
@@ -151,8 +151,8 @@ public:
 	int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
 
 	void battleStarted(const BattleInfo * info);
-	void battleFinished();
-	void startPlayerBattleAction(PlayerColor color);
+	void battleFinished(const BattleID & battleID);
+	void startPlayerBattleAction(const BattleID & battleID, PlayerColor color);
 
 	void invalidatePaths();
 	std::shared_ptr<const CPathsInfo> getPathsInfo(const CGHeroInstance * h);
@@ -220,7 +220,6 @@ public:
 
 #if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;
-	scripting::Pool * getContextPool() const override;
 #endif
 
 private:

+ 8 - 7
client/ClientCommandManager.cpp

@@ -17,6 +17,7 @@
 #include "CServerHandler.h"
 #include "gui/CGuiHandler.h"
 #include "gui/WindowHandler.h"
+#include "render/IRenderHandler.h"
 #include "../lib/NetPacks.h"
 #include "ClientNetPackVisitors.h"
 #include "../lib/CConfigHandler.h"
@@ -97,7 +98,7 @@ void ClientCommandManager::handleGoSoloCommand()
 			if(elem.second.human)
 			{
 				auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false, false);
-				printCommandMessage("Player " + elem.first.getStr() + " will be lead by " + AiToGive, ELogLevel::INFO);
+				printCommandMessage("Player " + elem.first.toString() + " will be lead by " + AiToGive, ELogLevel::INFO);
 				CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
 			}
 		}
@@ -182,12 +183,12 @@ void ClientCommandManager::handleNotDialogCommand()
 void ClientCommandManager::handleConvertTextCommand()
 {
 	logGlobal->info("Searching for available maps");
-	std::unordered_set<ResourceID> mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	std::unordered_set<ResourcePath> mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
 	{
 		return ident.getType() == EResType::MAP;
 	});
 
-	std::unordered_set<ResourceID> campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	std::unordered_set<ResourcePath> campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
 	{
 		return ident.getType() == EResType::CAMPAIGN;
 	});
@@ -292,7 +293,7 @@ void ClientCommandManager::handleGetTextCommand()
 			VCMIDirs::get().userExtractedPath();
 
 	auto list =
-			CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
+			CResourceHandler::get()->getFilteredFiles([](const ResourcePath & ident)
 			{
 				return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
 			});
@@ -317,7 +318,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu
 {
 	std::string URI;
 	singleWordBuffer >> URI;
-	std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
+	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI));
 	anim->preload();
 	anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
 }
@@ -327,11 +328,11 @@ void ClientCommandManager::handleExtractCommand(std::istringstream& singleWordBu
 	std::string URI;
 	singleWordBuffer >> URI;
 
-	if(CResourceHandler::get()->existsResource(ResourceID(URI)))
+	if(CResourceHandler::get()->existsResource(ResourcePath(URI)))
 	{
 		const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI;
 
-		auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
+		auto data = CResourceHandler::get()->load(ResourcePath(URI))->readAll();
 
 		boost::filesystem::create_directories(outPath.parent_path());
 		std::ofstream outFile(outPath.c_str(), std::ofstream::binary);

+ 40 - 41
client/NetPacksClient.cpp

@@ -95,18 +95,18 @@ void callAllInterfaces(CClient & cl, void (T::*ptr)(Args...), Args2 && ...args)
 
 //calls all normal interfaces and privileged ones, playerints may be updated when iterating over it, so we need a copy
 template<typename T, typename ... Args, typename ... Args2>
-void callBattleInterfaceIfPresentForBothSides(CClient & cl, void (T::*ptr)(Args...), Args2 && ...args)
+void callBattleInterfaceIfPresentForBothSides(CClient & cl, const BattleID & battleID, void (T::*ptr)(Args...), Args2 && ...args)
 {
-	assert(cl.gameState()->curB);
+	assert(cl.gameState()->getBattle(battleID));
 
-	if (!cl.gameState()->curB)
+	if (!cl.gameState()->getBattle(battleID))
 	{
 		logGlobal->error("Attempt to call battle interface without ongoing battle!");
 		return;
 	}
 
-	callOnlyThatBattleInterface(cl, cl.gameState()->curB->sides[0].color, ptr, std::forward<Args2>(args)...);
-	callOnlyThatBattleInterface(cl, cl.gameState()->curB->sides[1].color, ptr, std::forward<Args2>(args)...);
+	callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->sides[0].color, ptr, std::forward<Args2>(args)...);
+	callOnlyThatBattleInterface(cl, cl.gameState()->getBattle(battleID)->sides[1].color, ptr, std::forward<Args2>(args)...);
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt)
 	{
 		callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, ptr, std::forward<Args2>(args)...);
@@ -306,14 +306,13 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 		}
 	};
 
-	ArtifactLocation srcLoc(pack.srcArtHolder, pack.artsPack0.front().srcPos);
-	ArtifactLocation dstLoc(pack.dstArtHolder, pack.artsPack0.front().dstPos);
+	auto srcOwner = std::get<ConstTransitivePtr<CGHeroInstance>>(pack.srcArtHolder)->tempOwner;
+	auto dstOwner = std::get<ConstTransitivePtr<CGHeroInstance>>(pack.dstArtHolder)->tempOwner;
 
 	// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
-	callInterfaceIfPresent(cl, srcLoc.owningPlayer(), &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
-
-	if (srcLoc.owningPlayer() != dstLoc.owningPlayer())
-		callInterfaceIfPresent(cl, dstLoc.owningPlayer(), &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
+	callInterfaceIfPresent(cl, srcOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
+	if(srcOwner != dstOwner)
+		callInterfaceIfPresent(cl, dstOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
 
 	applyMove(pack.artsPack0);
 	if(pack.swap)
@@ -715,11 +714,11 @@ void ApplyClientNetPackVisitor::visitMapObjectSelectDialog(MapObjectSelectDialog
 void ApplyFirstClientNetPackVisitor::visitBattleStart(BattleStart & pack)
 {
 	// Cannot use the usual code because curB is not set yet
-	callOnlyThatBattleInterface(cl, pack.info->sides[0].color, &IBattleEventsReceiver::battleStartBefore, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
+	callOnlyThatBattleInterface(cl, pack.info->sides[0].color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
 		pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
-	callOnlyThatBattleInterface(cl, pack.info->sides[1].color, &IBattleEventsReceiver::battleStartBefore, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
+	callOnlyThatBattleInterface(cl, pack.info->sides[1].color, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
 		pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
-	callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, &IBattleEventsReceiver::battleStartBefore, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
+	callOnlyThatBattleInterface(cl, PlayerColor::SPECTATOR, &IBattleEventsReceiver::battleStartBefore, pack.battleID, pack.info->sides[0].armyObject, pack.info->sides[1].armyObject,
 		pack.info->tile, pack.info->sides[0].hero, pack.info->sides[1].hero);
 }
 
@@ -730,12 +729,12 @@ void ApplyClientNetPackVisitor::visitBattleStart(BattleStart & pack)
 
 void ApplyFirstClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleNewRoundFirst, pack.round);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleNewRoundFirst, pack.battleID);
 }
 
 void ApplyClientNetPackVisitor::visitBattleNextRound(BattleNextRound & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleNewRound, pack.round);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleNewRound, pack.battleID);
 }
 
 void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack & pack)
@@ -743,56 +742,56 @@ void ApplyClientNetPackVisitor::visitBattleSetActiveStack(BattleSetActiveStack &
 	if(!pack.askPlayerInterface)
 		return;
 
-	const CStack *activated = gs.curB->battleGetStackByID(pack.stack);
+	const CStack *activated = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);
 	PlayerColor playerToCall; //pack.player that will move activated stack
 	if (activated->hasBonusOfType(BonusType::HYPNOTIZED))
 	{
-		playerToCall = (gs.curB->sides[0].color == activated->unitOwner()
-			? gs.curB->sides[1].color
-			: gs.curB->sides[0].color);
+		playerToCall = (gs.getBattle(pack.battleID)->sides[0].color == activated->unitOwner()
+			? gs.getBattle(pack.battleID)->sides[1].color
+			: gs.getBattle(pack.battleID)->sides[0].color);
 	}
 	else
 	{
 		playerToCall = activated->unitOwner();
 	}
 
-	cl.startPlayerBattleAction(playerToCall);
+	cl.startPlayerBattleAction(pack.battleID, playerToCall);
 }
 
 void ApplyClientNetPackVisitor::visitBattleLogMessage(BattleLogMessage & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleLogMessage, pack.lines);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleLogMessage, pack.battleID, pack.lines);
 }
 
 void ApplyClientNetPackVisitor::visitBattleTriggerEffect(BattleTriggerEffect & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleTriggerEffect, pack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleTriggerEffect, pack.battleID, pack);
 }
 
 void ApplyFirstClientNetPackVisitor::visitBattleUpdateGateState(BattleUpdateGateState & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleGateStateChanged, pack.state);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleGateStateChanged, pack.battleID, pack.state);
 }
 
 void ApplyFirstClientNetPackVisitor::visitBattleResult(BattleResult & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleEnd, &pack, pack.queryID);
-	cl.battleFinished();
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleEnd, pack.battleID, &pack, pack.queryID);
+	cl.battleFinished(pack.battleID);
 }
 
 void ApplyFirstClientNetPackVisitor::visitBattleStackMoved(BattleStackMoved & pack)
 {
-	const CStack * movedStack = gs.curB->battleGetStackByID(pack.stack);
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, pack.tilesToMove, pack.distance, pack.teleporting);
+	const CStack * movedStack = gs.getBattle(pack.battleID)->battleGetStackByID(pack.stack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStackMoved, pack.battleID, movedStack, pack.tilesToMove, pack.distance, pack.teleporting);
 }
 
 void ApplyFirstClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleAttack, &pack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleAttack, pack.battleID, &pack);
 
 	// battleStacksAttacked should be excuted before BattleAttack.applyGs() to play animation before damaging unit
 	// so this has to be here instead of ApplyClientNetPackVisitor::visitBattleAttack()
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, pack.bsa, pack.shot());
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStacksAttacked, pack.battleID, pack.bsa, pack.shot());
 }
 
 void ApplyClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
@@ -802,23 +801,23 @@ void ApplyClientNetPackVisitor::visitBattleAttack(BattleAttack & pack)
 void ApplyFirstClientNetPackVisitor::visitStartAction(StartAction & pack)
 {
 	cl.currentBattleAction = std::make_unique<BattleAction>(pack.ba);
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionStarted, pack.ba);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::actionStarted, pack.battleID, pack.ba);
 }
 
 void ApplyClientNetPackVisitor::visitBattleSpellCast(BattleSpellCast & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleSpellCast, &pack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleSpellCast, pack.battleID, &pack);
 }
 
 void ApplyClientNetPackVisitor::visitSetStackEffect(SetStackEffect & pack)
 {
 	//informing about effects
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksEffectsSet, pack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStacksEffectsSet, pack.battleID, pack);
 }
 
 void ApplyClientNetPackVisitor::visitStacksInjured(StacksInjured & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, pack.stacks, false);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleStacksAttacked, pack.battleID, pack.stacks, false);
 }
 
 void ApplyClientNetPackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack)
@@ -830,24 +829,24 @@ void ApplyClientNetPackVisitor::visitBattleResultsApplied(BattleResultsApplied &
 
 void ApplyClientNetPackVisitor::visitBattleUnitsChanged(BattleUnitsChanged & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, pack.changedStacks);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleUnitsChanged, pack.battleID, pack.changedStacks);
 }
 
 void ApplyClientNetPackVisitor::visitBattleObstaclesChanged(BattleObstaclesChanged & pack)
 {
 	//inform interfaces about removed obstacles
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleObstaclesChanged, pack.changes);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleObstaclesChanged, pack.battleID, pack.changes);
 }
 
 void ApplyClientNetPackVisitor::visitCatapultAttack(CatapultAttack & pack)
 {
 	//inform interfaces about catapult attack
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleCatapultAttacked, pack);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::battleCatapultAttacked, pack.battleID, pack);
 }
 
 void ApplyClientNetPackVisitor::visitEndAction(EndAction & pack)
 {
-	callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::actionFinished, *cl.currentBattleAction);
+	callBattleInterfaceIfPresentForBothSides(cl, pack.battleID, &IBattleEventsReceiver::actionFinished, pack.battleID, *cl.currentBattleAction);
 	cl.currentBattleAction.reset();
 }
 
@@ -875,7 +874,7 @@ void ApplyClientNetPackVisitor::visitPlayerBlocked(PlayerBlocked & pack)
 
 void ApplyClientNetPackVisitor::visitYourTurn(YourTurn & pack)
 {
-	logNetwork->debug("Server gives turn to %s", pack.player.getStr());
+	logNetwork->debug("Server gives turn to %s", pack.player.toString());
 
 	callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, pack.player);
 	callOnlyThatInterface(cl, pack.player, &CGameInterface::yourTurn, pack.queryID);
@@ -883,12 +882,12 @@ void ApplyClientNetPackVisitor::visitYourTurn(YourTurn & pack)
 
 void ApplyClientNetPackVisitor::visitTurnTimeUpdate(TurnTimeUpdate & pack)
 {
-	logNetwork->debug("Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s", pack.turnTimer.turnTimer, pack.turnTimer.baseTimer, pack.turnTimer.battleTimer, pack.turnTimer.creatureTimer, pack.player.getStr());
+	logNetwork->debug("Server sets turn timer {turn: %d, base: %d, battle: %d, creature: %d} for %s", pack.turnTimer.turnTimer, pack.turnTimer.baseTimer, pack.turnTimer.battleTimer, pack.turnTimer.creatureTimer, pack.player.toString());
 }
 
 void ApplyClientNetPackVisitor::visitPlayerMessageClient(PlayerMessageClient & pack)
 {
-	logNetwork->debug("pack.player %s sends a message: %s", pack.player.getStr(), pack.text);
+	logNetwork->debug("pack.player %s sends a message: %s", pack.player.toString(), pack.text);
 
 	std::ostringstream str;
 	if(pack.player.isSpectator())

+ 19 - 0
client/adventureMap/AdventureMapInterface.cpp

@@ -153,15 +153,34 @@ void AdventureMapInterface::deactivate()
 void AdventureMapInterface::showAll(Canvas & to)
 {
 	CIntObject::showAll(to);
+	dim(to);
 	LOCPLINT->cingconsole->show(to);
 }
 
 void AdventureMapInterface::show(Canvas & to)
 {
 	CIntObject::show(to);
+	dim(to);
 	LOCPLINT->cingconsole->show(to);
 }
 
+void AdventureMapInterface::dim(Canvas & to)
+{
+	for (auto window : GH.windows().findWindows<IShowActivatable>())
+	{
+		std::shared_ptr<AdventureMapInterface> casted = std::dynamic_pointer_cast<AdventureMapInterface>(window);
+		if (!casted && !window->isPopupWindow())
+		{
+			int backgroundDimLevel = settings["adventure"]["backgroundDimLevel"].Integer();
+			Rect targetRect(0, 0, GH.screenDimensions().x, GH.screenDimensions().y);
+			ColorRGBA colorToFill(0, 0, 0, std::clamp<int>(backgroundDimLevel, 0, 255));
+			if(backgroundDimLevel > 0)
+				to.drawColor(targetRect, colorToFill);
+			return;
+		}
+	}
+}
+
 void AdventureMapInterface::tick(uint32_t msPassed)
 {
 	handleMapScrollingUpdate(msPassed);

+ 3 - 0
client/adventureMap/AdventureMapInterface.h

@@ -89,6 +89,9 @@ private:
 	/// casts current spell at specified location
 	void performSpellcasting(const int3 & castTarget);
 
+	/// dim interface if some windows opened
+	void dim(Canvas & to);
+
 protected:
 	/// CIntObject interface implementation
 

+ 20 - 24
client/adventureMap/AdventureMapWidget.cpp

@@ -22,6 +22,7 @@
 #include "../mapView/MapView.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
@@ -30,7 +31,7 @@
 #include "../PlayerLocalState.h"
 
 #include "../../lib/constants/StringConstants.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
 	: shortcuts(shortcuts)
@@ -56,13 +57,10 @@ AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> s
 	for (const auto & entry : shortcuts->getShortcuts())
 		addShortcut(entry.shortcut, entry.callback);
 
-	const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/adventureMap.json"));
 
 	for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
-	{
-		ResourceID resourceName(entry.String(), EResType::IMAGE);
-		playerColorerImages.push_back(resourceName.getName());
-	}
+		playerColorerImages.push_back(ImagePath::fromJson(entry));
 
 	build(config);
 	addUsedEvents(KEYBOARD);
@@ -127,24 +125,24 @@ Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & bounding
 	return Rect(topLeft + boundingBox.topLeft(), dimensions);
 }
 
-std::shared_ptr<IImage> AdventureMapWidget::loadImage(const std::string & name)
+std::shared_ptr<IImage> AdventureMapWidget::loadImage(const JsonNode & name)
 {
-	ResourceID resource(name, EResType::IMAGE);
+	ImagePath resource = ImagePath::fromJson(name);
 
-	if(images.count(resource.getName()) == 0)
-		images[resource.getName()] = IImage::createFromFile(resource.getName());
+	if(images.count(resource) == 0)
+		images[resource] = GH.renderHandler().loadImage(resource);
 
-	return images[resource.getName()];
+	return images[resource];
 }
 
-std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const std::string & name)
+std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const JsonNode & name)
 {
-	ResourceID resource(name, EResType::ANIMATION);
+	AnimationPath resource = AnimationPath::fromJson(name);
 
-	if(animations.count(resource.getName()) == 0)
-		animations[resource.getName()] = std::make_shared<CAnimation>(resource.getName());
+	if(animations.count(resource) == 0)
+		animations[resource] = GH.renderHandler().loadAnimation(resource);
 
-	return animations[resource.getName()];
+	return animations[resource];
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
@@ -158,15 +156,14 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & i
 {
 	Rect targetArea = readTargetArea(input["area"]);
 	Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
-	std::string image = input["image"].String();
 
-	return std::make_shared<CFilledTexture>(loadImage(image), targetArea, sourceArea);
+	return std::make_shared<CFilledTexture>(loadImage(input["image"]), targetArea, sourceArea);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
 {
 	auto position = readTargetArea(input["area"]);
-	auto image = input["image"].String();
+	auto image = AnimationPath::fromJson(input["image"]);
 	auto help = readHintText(input["help"]);
 	bool playerColored = input["playerColored"].Bool();
 
@@ -259,9 +256,8 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & in
 	Rect area = readTargetArea(input["area"]);
 	size_t index = input["index"].Integer();
 	size_t perPlayer = input["perPlayer"].Integer();
-	std::string image = input["image"].String();
 
-	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(image), index, perPlayer);
+	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(input["image"]), index, perPlayer);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
@@ -298,7 +294,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMinimap(const JsonNode & in
 std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonNode & input)
 {
 	Rect area = readTargetArea(input["area"]);
-	std::string image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 
 	auto result = std::make_shared<CResDataBar>(image, area.topLeft());
 
@@ -320,7 +316,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonN
 std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode & input)
 {
 	Rect area = readTargetArea(input["area"]);
-	std::string image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 
 	auto background = std::make_shared<CFilledTexture>(image, area);
 
@@ -330,7 +326,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode &
 std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 	Rect area = readTargetArea(input["area"]);
 	return std::make_shared<FilledTexturePlayerColored>(image, area);
 }

+ 5 - 5
client/adventureMap/AdventureMapWidget.h

@@ -29,11 +29,11 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	std::vector<Rect> subwidgetSizes;
 
 	/// list of images on which player-colored palette will be applied
-	std::vector<std::string> playerColorerImages;
+	std::vector<ImagePath> playerColorerImages;
 
 	/// list of named images shared between widgets
-	std::map<std::string, std::shared_ptr<IImage>> images;
-	std::map<std::string, std::shared_ptr<CAnimation>> animations;
+	std::map<ImagePath, std::shared_ptr<IImage>> images;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> animations;
 
 	/// Widgets that require access from adventure map
 	std::shared_ptr<CHeroList> heroList;
@@ -48,8 +48,8 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
 	Rect readArea(const JsonNode & source, const Rect & boundingBox);
 
-	std::shared_ptr<IImage> loadImage(const std::string & name);
-	std::shared_ptr<CAnimation> loadAnimation(const std::string & name);
+	std::shared_ptr<IImage> loadImage(const JsonNode & name);
+	std::shared_ptr<CAnimation> loadAnimation(const JsonNode & name);
 
 	std::shared_ptr<CIntObject> buildInfobox(const JsonNode & input);
 	std::shared_ptr<CIntObject> buildMapImage(const JsonNode & input);

+ 6 - 6
client/adventureMap/AdventureOptions.cpp

@@ -25,22 +25,22 @@
 #include "../../lib/StartInfo.h"
 
 AdventureOptions::AdventureOptions()
-	: CWindowObject(PLAYER_COLORED, "ADVOPTS")
+	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("ADVOPTS"))
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
+	viewWorld = std::make_shared<CButton>(Point(24, 23), AnimationPath::builtin("ADVVIEW.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
 	viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
 
-	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
+	exit = std::make_shared<CButton>(Point(204, 313), AnimationPath::builtin("IOK6432.DEF"), CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
 
-	scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
+	scenInfo = std::make_shared<CButton>(Point(24, 198), AnimationPath::builtin("ADVINFO.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
 	scenInfo->addCallback(AdventureOptions::showScenarioInfo);
 
-	puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
+	puzzle = std::make_shared<CButton>(Point(24, 81), AnimationPath::builtin("ADVPUZ.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
 	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
 
-	dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_DIG_GRAIL);
+	dig = std::make_shared<CButton>(Point(24, 139), AnimationPath::builtin("ADVDIG.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_DIG_GRAIL);
 	if(const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero())
 		dig->addCallback(std::bind(&CPlayerInterface::tryDigging, LOCPLINT, h));
 	else

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -105,7 +105,7 @@ void CInGameConsole::print(const std::string & txt)
 	}
 
 	GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
-	CCS->soundh->playSound("CHAT");
+	CCS->soundh->playSound(AudioPath::builtin("CHAT"));
 }
 
 bool CInGameConsole::captureThisKey(EShortcut key)

+ 19 - 19
client/adventureMap/CInfoBar.cpp

@@ -51,7 +51,7 @@ CInfoBar::EmptyVisibleInfo::EmptyVisibleInfo()
 CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATHR");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATHR"));
 
 	if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
 		heroTooltip = std::make_shared<CInteractableHeroTooltip>(Point(0,0), hero);
@@ -62,7 +62,7 @@ CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
 CInfoBar::VisibleTownInfo::VisibleTownInfo(const CGTownInstance * town)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATCS");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATCS"));
 
 	if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
 		townTooltip = std::make_shared<CInteractableTownTooltip>(Point(0,0), town);
@@ -88,36 +88,36 @@ CInfoBar::VisibleDateInfo::VisibleDateInfo()
 	forceRefresh.push_back(label);
 }
 
-std::string CInfoBar::VisibleDateInfo::getNewDayName()
+AnimationPath CInfoBar::VisibleDateInfo::getNewDayName()
 {
 	if(LOCPLINT->cb->getDate(Date::DAY) == 1)
-		return "NEWDAY";
+		return AnimationPath::builtin("NEWDAY");
 
 	if(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK) != 1)
-		return "NEWDAY";
+		return AnimationPath::builtin("NEWDAY");
 
 	switch(LOCPLINT->cb->getDate(Date::WEEK))
 	{
 	case 1:
-		return "NEWWEEK1";
+		return AnimationPath::builtin("NEWWEEK1");
 	case 2:
-		return "NEWWEEK2";
+		return AnimationPath::builtin("NEWWEEK2");
 	case 3:
-		return "NEWWEEK3";
+		return AnimationPath::builtin("NEWWEEK3");
 	case 4:
-		return "NEWWEEK4";
+		return AnimationPath::builtin("NEWWEEK4");
 	default:
-		return "";
+		return AnimationPath();
 	}
 }
 
 CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATNX");
-	banner = std::make_shared<CAnimImage>("CREST58", player.getNum(), 0, 20, 51);
-	sand = std::make_shared<CShowableAnim>(99, 51, "HOURSAND", 0, 100); // H3 uses around 100 ms per frame
-	glass = std::make_shared<CShowableAnim>(99, 51, "HOURGLAS", CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATNX"));
+	banner = std::make_shared<CAnimImage>(AnimationPath::builtin("CREST58"), player.getNum(), 0, 20, 51);
+	sand = std::make_shared<CShowableAnim>(99, 51, AnimationPath::builtin("HOURSAND"), 0, 100); // H3 uses around 100 ms per frame
+	glass = std::make_shared<CShowableAnim>(99, 51, AnimationPath::builtin("HOURGLAS"), CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
 }
 
 CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
@@ -148,14 +148,14 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
 	}
 
 	//generate widgets
-	background = std::make_shared<CPicture>("ADSTATIN");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATIN"));
 	allyLabel = std::make_shared<CLabel>(10, 106, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
 	enemyLabel = std::make_shared<CLabel>(10, 136, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
 
 	int posx = allyLabel->pos.w + allyLabel->pos.x - pos.x + 4;
 	for(PlayerColor & player : allies)
 	{
-		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 102);
+		auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), player.getNum(), 0, posx, 102);
 		posx += image->pos.w;
 		flags.push_back(image);
 	}
@@ -163,14 +163,14 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
 	posx = enemyLabel->pos.w + enemyLabel->pos.x - pos.x + 4;
 	for(PlayerColor & player : enemies)
 	{
-		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 132);
+		auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), player.getNum(), 0, posx, 132);
 		posx += image->pos.w;
 		flags.push_back(image);
 	}
 
 	for(size_t i=0; i<halls.size(); i++)
 	{
-		hallIcons.push_back(std::make_shared<CAnimImage>("itmtl", i, 0, 6 + 42 * (int)i , 11));
+		hallIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("itmtl"), i, 0, 6 + 42 * (int)i , 11));
 		if(halls[i])
 			hallLabels.push_back(std::make_shared<CLabel>( 26 + 42 * (int)i, 64, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(halls[i])));
 	}
@@ -180,7 +180,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	background = std::make_shared<CPicture>("ADSTATOT", 1, 0);
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATOT"), 1, 0);
 	auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
 	auto textRect = fullRect;
 	auto imageRect = fullRect;

+ 2 - 1
client/adventureMap/CInfoBar.h

@@ -11,6 +11,7 @@
 
 #include "../gui/CIntObject.h"
 #include "CConfigHandler.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -86,7 +87,7 @@ private:
 		std::shared_ptr<CShowableAnim> animation;
 		std::shared_ptr<CLabel> label;
 
-		std::string getNewDayName();
+		AnimationPath getNewDayName();
 	public:
 		VisibleDateInfo();
 	};

+ 10 - 10
client/adventureMap/CList.cpp

@@ -206,9 +206,9 @@ void CList::selectPrev()
 CHeroList::CEmptyHeroItem::CEmptyHeroItem()
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
-	portrait = std::make_shared<CPicture>("HPSXXX", movement->pos.w + 1, 0);
-	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
+	movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
+	portrait = std::make_shared<CPicture>(ImagePath::builtin("HPSXXX"), movement->pos.w + 1, 0);
+	mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
 
 	pos.w = mana->pos.w + mana->pos.x - pos.x;
 	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
@@ -219,9 +219,9 @@ CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
 	hero(Hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
-	portrait = std::make_shared<CAnimImage>("PortraitsSmall", hero->portrait, 0, movement->pos.w + 1);
-	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
+	movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
+	portrait = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), hero->portrait, 0, movement->pos.w + 1);
+	mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
 
 	pos.w = mana->pos.w + mana->pos.x - pos.x;
 	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
@@ -238,7 +238,7 @@ void CHeroList::CHeroItem::update()
 
 std::shared_ptr<CIntObject> CHeroList::CHeroItem::genSelection()
 {
-	return std::make_shared<CPicture>("HPSYYY", movement->pos.w + 1, 0);
+	return std::make_shared<CPicture>(ImagePath::builtin("HPSYYY"), movement->pos.w + 1, 0);
 }
 
 void CHeroList::CHeroItem::select(bool on)
@@ -319,7 +319,7 @@ std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
 {
 	if (LOCPLINT->localState->getOwnedTowns().size() > index)
 		return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
-	return std::make_shared<CAnimImage>("ITPA", 0);
+	return std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
 }
 
 CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
@@ -327,14 +327,14 @@ CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
 	town(Town)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	picture = std::make_shared<CAnimImage>("ITPA", 0);
+	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
 	pos = picture->pos;
 	update();
 }
 
 std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 {
-	return std::make_shared<CAnimImage>("ITPA", 1);
+	return std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 1);
 }
 
 void CTownList::CTownItem::update()

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -94,7 +94,7 @@ CMinimap::CMinimap(const Rect & position)
 	pos.w = position.w;
 	pos.h = position.h;
 
-	aiShield = std::make_shared<CPicture>("AIShield");
+	aiShield = std::make_shared<CPicture>(ImagePath::builtin("AIShield"));
 	aiShield->disable();
 }
 

+ 2 - 2
client/adventureMap/CResDataBar.cpp

@@ -24,7 +24,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/ResourceSet.h"
 
-CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
+CResDataBar::CResDataBar(const ImagePath & imageName, const Point & position)
 {
 	pos.x += position.x;
 	pos.y += position.y;
@@ -37,7 +37,7 @@ CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
 	pos.h = background->pos.h;
 }
 
-CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist):
+CResDataBar::CResDataBar(const ImagePath & defname, int x, int y, int offx, int offy, int resdist, int datedist):
 	CResDataBar(defname, Point(x,y))
 {
 	for (int i = 0; i < 7 ; i++)

+ 3 - 2
client/adventureMap/CResDataBar.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 /// Resources bar which shows information about how many gold, crystals,... you have
 /// Current date is displayed too
@@ -25,10 +26,10 @@ class CResDataBar : public CIntObject
 public:
 
 	/// For dynamically-sized UI windows, e.g. adventure map interface
-	CResDataBar(const std::string & imageName, const Point & position);
+	CResDataBar(const ImagePath & imageName, const Point & position);
 
 	/// For fixed-size UI windows, e.g. CastleInterface
-	CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
+	CResDataBar(const ImagePath & defname, int x, int y, int offx, int offy, int resdist, int datedist);
 
 	void setDatePosition(const Point & position);
 	void setResourcePosition(const GameResID & resource, const Point & position);

+ 5 - 5
client/adventureMap/MapAudioPlayer.cpp

@@ -123,9 +123,9 @@ void MapAudioPlayer::removeObject(const CGObjectInstance * obj)
 				vstd::erase(objects[z][x][y], obj->id);
 }
 
-std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
+std::vector<AudioPath> MapAudioPlayer::getAmbientSounds(const int3 & tile)
 {
-	std::vector<std::string> result;
+	std::vector<AudioPath> result;
 
 	for(auto & objectID : objects[tile.z][tile.x][tile.y])
 	{
@@ -140,15 +140,15 @@ std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
 	}
 
 	if(CGI->mh->getMap()->isCoastalTile(tile))
-		result.emplace_back("LOOPOCEA");
+		result.emplace_back(AudioPath::builtin("LOOPOCEA"));
 
 	return result;
 }
 
 void MapAudioPlayer::updateAmbientSounds()
 {
-	std::map<std::string, int> currentSounds;
-	auto updateSounds = [&](const std::string& soundId, int distance) -> void
+	std::map<AudioPath, int> currentSounds;
+	auto updateSounds = [&](const AudioPath& soundId, int distance) -> void
 	{
 		if(vstd::contains(currentSounds, soundId))
 			currentSounds[soundId] = std::min(currentSounds[soundId], distance);

+ 2 - 1
client/adventureMap/MapAudioPlayer.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../mapView/IMapRendererObserver.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class ObjectInstanceID;
@@ -29,7 +30,7 @@ class MapAudioPlayer : public IMapObjectObserver
 	void addObject(const CGObjectInstance * obj);
 	void removeObject(const CGObjectInstance * obj);
 
-	std::vector<std::string> getAmbientSounds(const int3 & tile);
+	std::vector<AudioPath> getAmbientSounds(const int3 & tile);
 	void updateAmbientSounds();
 	void updateMusic();
 	void update();

+ 3 - 3
client/adventureMap/TurnTimerWidget.cpp

@@ -25,7 +25,7 @@
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 TurnTimerWidget::DrawRect::DrawRect(const Rect & r, const ColorRGBA & c):
 	CIntObject(), rect(r), color(c)
@@ -47,7 +47,7 @@ TurnTimerWidget::TurnTimerWidget():
 	
 	recActions &= ~DEACTIVATE;
 	
-	const JsonNode config(ResourceID("config/widgets/turnTimer.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/turnTimer.json"));
 	
 	build(config);
 	
@@ -77,7 +77,7 @@ void TurnTimerWidget::setTime(PlayerColor player, int time)
 	   && newTime != turnTime
 	   && notifications.count(newTime))
 	{
-		CCS->soundh->playSound(variables["notificationSound"].String());
+		CCS->soundh->playSound(AudioPath::fromJson(variables["notificationSound"]));
 	}
 
 	turnTime = newTime;

+ 19 - 19
client/battle/BattleActionsController.cpp

@@ -171,7 +171,7 @@ void BattleActionsController::enterCreatureCastingMode()
 		spells::Target target;
 		target.emplace_back();
 
-		spells::BattleCast cast(owner.curInt->cb.get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
+		spells::BattleCast cast(owner.getBattle().get(), caster, spells::Mode::CREATURE_ACTIVE, spell);
 
 		auto m = spell->battleMechanics(&cast);
 		spells::detail::ProblemImpl ignored;
@@ -207,7 +207,7 @@ std::vector<PossiblePlayerBattleAction> BattleActionsController::getPossibleActi
 		data.creatureSpellsToCast.push_back(spell->id);
 
 	data.tacticsMode = owner.tacticsMode;
-	auto allActions = owner.curInt->cb->getClientActionsForStack(stack, data);
+	auto allActions = owner.getBattle()->getClientActionsForStack(stack, data);
 
 	allActions.push_back(PossiblePlayerBattleAction::HERO_INFO);
 	allActions.push_back(PossiblePlayerBattleAction::CREATURE_INFO);
@@ -231,7 +231,7 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
 			case PossiblePlayerBattleAction::OBSTACLE:
 				if(!stack->hasBonusOfType(BonusType::NO_SPELLCAST_BY_DEFAULT) && targetStack != nullptr)
 				{
-					PlayerColor stackOwner = owner.curInt->cb->battleGetOwner(targetStack);
+					PlayerColor stackOwner = owner.getBattle()->battleGetOwner(targetStack);
 					bool enemyTargetingPositiveSpellcast = item.spell().toSpell()->isPositive() && stackOwner != LOCPLINT->playerID;
 					bool friendTargetingNegativeSpellcast = item.spell().toSpell()->isNegative() && stackOwner == LOCPLINT->playerID;
 
@@ -300,12 +300,12 @@ void BattleActionsController::castThisSpell(SpellID spellID)
 	//choosing possible targets
 	const CGHeroInstance *castingHero = (owner.attackingHeroInstance->tempOwner == owner.curInt->playerID) ? owner.attackingHeroInstance : owner.defendingHeroInstance;
 	assert(castingHero); // code below assumes non-null hero
-	PossiblePlayerBattleAction spellSelMode = owner.curInt->cb->getCasterAction(spellID.toSpell(), castingHero, spells::Mode::HERO);
+	PossiblePlayerBattleAction spellSelMode = owner.getBattle()->getCasterAction(spellID.toSpell(), castingHero, spells::Mode::HERO);
 
 	if (spellSelMode.get() == PossiblePlayerBattleAction::NO_LOCATION) //user does not have to select location
 	{
 		heroSpellToCast->aimToHex(BattleHex::INVALID);
-		owner.curInt->cb->battleMakeSpellAction(*heroSpellToCast);
+		owner.curInt->cb->battleMakeSpellAction(owner.getBattleID(), *heroSpellToCast);
 		endCastingSpell();
 	}
 	else
@@ -353,10 +353,10 @@ const CSpell * BattleActionsController::getCurrentSpell(BattleHex hoveredHex)
 
 const CStack * BattleActionsController::getStackForHex(BattleHex hoveredHex)
 {
-	const CStack * shere = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	const CStack * shere = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 	if(shere)
 		return shere;
-	return owner.curInt->cb->battleGetStackByPos(hoveredHex, false);
+	return owner.getBattle()->battleGetStackByPos(hoveredHex, false);
 }
 
 void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action, BattleHex targetHex)
@@ -400,7 +400,7 @@ void BattleActionsController::actionSetCursor(PossiblePlayerBattleAction action,
 		}
 
 		case PossiblePlayerBattleAction::SHOOT:
-			if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), targetHex))
+			if (owner.getBattle()->battleHasShootingPenalty(owner.stacksController->getActiveStack(), targetHex))
 				CCS->curh->set(Cursor::Combat::SHOOT_PENALTY);
 			else
 				CCS->curh->set(Cursor::Combat::SHOOT);
@@ -482,7 +482,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
 			{
 				BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(targetHex);
-				DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
+				DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(owner.stacksController->getActiveStack(), targetStack, attackFromHex);
 				estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
 				estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
 
@@ -493,7 +493,7 @@ std::string BattleActionsController::actionGetStatusMessage(PossiblePlayerBattle
 		{
 			const auto * shooter = owner.stacksController->getActiveStack();
 
-			DamageEstimation estimation = owner.curInt->cb->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
+			DamageEstimation estimation = owner.getBattle()->battleEstimateDamage(shooter, targetStack, shooter->getPosition());
 			estimation.kills.max = std::min<int64_t>(estimation.kills.max, targetStack->getCount());
 			estimation.kills.min = std::min<int64_t>(estimation.kills.min, targetStack->getCount());
 
@@ -593,7 +593,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
 		case PossiblePlayerBattleAction::ATTACK:
 		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-			if(owner.curInt->cb->battleCanAttack(owner.stacksController->getActiveStack(), targetStack, targetHex))
+			if(owner.getBattle()->battleCanAttack(owner.stacksController->getActiveStack(), targetStack, targetHex))
 			{
 				if (owner.fieldController->isTileAttackable(targetHex)) // move isTileAttackable to be part of battleCanAttack?
 					return true;
@@ -601,7 +601,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
 			return false;
 
 		case PossiblePlayerBattleAction::SHOOT:
-			return owner.curInt->cb->battleCanShoot(owner.stacksController->getActiveStack(), targetHex);
+			return owner.getBattle()->battleCanShoot(owner.stacksController->getActiveStack(), targetHex);
 
 		case PossiblePlayerBattleAction::NO_LOCATION:
 			return false;
@@ -615,7 +615,7 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B
 		case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
 			if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures
 			{
-				int spellID = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE);
+				int spellID = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), targetStack, CBattleInfoCallback::RANDOM_GENIE);
 				return spellID > -1;
 			}
 			return false;
@@ -658,7 +658,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			{
-				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false);
+				std::vector<BattleHex> acc = owner.getBattle()->battleGetAvailableHexes(owner.stacksController->getActiveStack(), false);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
 				if(vstd::contains(acc, targetHex))
 					owner.giveCommand(EActionType::WALK, targetHex);
@@ -770,7 +770,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 						heroSpellToCast->aimToHex(targetHex);
 						break;
 				}
-				owner.curInt->cb->battleMakeSpellAction(*heroSpellToCast);
+				owner.curInt->cb->battleMakeSpellAction(owner.getBattleID(), *heroSpellToCast);
 				endCastingSpell();
 			}
 			selectedStack = nullptr;
@@ -886,7 +886,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 	{
 		// faerie dragon can cast only one, randomly selected spell until their next move
 		//TODO: faerie dragon type spell should be selected by server
-		const auto * spellToCast = owner.curInt->cb->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
+		const auto * spellToCast = owner.getBattle()->battleGetRandomStackSpell(CRandomGenerator::getDefault(), casterStack, CBattleInfoCallback::RANDOM_AIMED).toSpell();
 
 		if (spellToCast)
 			creatureSpells.push_back(spellToCast);
@@ -933,7 +933,7 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 		target.emplace_back(targetStack);
 	target.emplace_back(targetHex);
 
-	spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell);
+	spells::BattleCast cast(owner.getBattle().get(), caster, mode, currentSpell);
 
 	auto m = currentSpell->battleMechanics(&cast);
 	spells::detail::ProblemImpl problem; //todo: display problem in status bar
@@ -943,7 +943,7 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 {
-	std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove, false);
+	std::vector<BattleHex> acc = owner.getBattle()->battleGetAvailableHexes(stackToMove, false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 
 	if (vstd::contains(acc, myNumber))
@@ -1006,7 +1006,7 @@ void BattleActionsController::onHexRightClicked(BattleHex clickedHex)
 		return;
 	}
 
-	auto selectedStack = owner.curInt->cb->battleGetStackByPos(clickedHex, true);
+	auto selectedStack = owner.getBattle()->battleGetStackByPos(clickedHex, true);
 
 	if (selectedStack != nullptr)
 		GH.windows().createAndPushWindow<CStackWindow>(selectedStack, true);

+ 21 - 20
client/battle/BattleAnimationClasses.cpp

@@ -24,6 +24,7 @@
 #include "../CPlayerInterface.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -114,7 +115,7 @@ void StackActionAnimation::setGroup( ECreatureAnimType group )
 	currGroup = group;
 }
 
-void StackActionAnimation::setSound( std::string sound )
+void StackActionAnimation::setSound( const AudioPath & sound )
 {
 	this->sound = sound;
 }
@@ -179,7 +180,7 @@ HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
 	: StackActionAnimation(owner, stack)
 {
 	setGroup(ECreatureAnimType::HITTED);
-	setSound(battle_sound(stack->unitType(), wince));
+	setSound(stack->unitType()->sounds.wince);
 	logAnim->debug("Created HittedAnimation for %s", stack->getName());
 }
 
@@ -187,14 +188,14 @@ DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack
 	: StackActionAnimation(owner, stack)
 {
 	setGroup(ECreatureAnimType::DEFENCE);
-	setSound(battle_sound(stack->unitType(), defend));
+	setSound(stack->unitType()->sounds.defend);
 	logAnim->debug("Created DefenceAnimation for %s", stack->getName());
 }
 
 DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
 	StackActionAnimation(owner, stack)
 {
-	setSound(battle_sound(stack->unitType(), killed));
+	setSound(stack->unitType()->sounds.killed);
 
 	if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
 		setGroup(ECreatureAnimType::DEATH_RANGED);
@@ -315,7 +316,7 @@ MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack
 	: AttackAnimation(owner, attacker, _dest, _attacked)
 {
 	logAnim->debug("Created MeleeAttackAnimation for %s", attacker->getName());
-	setSound(battle_sound(getCreature(), attack));
+	setSound(getCreature()->sounds.attack);
 	setGroup(selectGroup(multiAttack));
 }
 
@@ -356,7 +357,7 @@ bool MovementAnimation::init()
 
 	if (moveSoundHander == -1)
 	{
-		moveSoundHander = CCS->soundh->playSound(battle_sound(stack->unitType(), move), -1);
+		moveSoundHander = CCS->soundh->playSound(stack->unitType()->sounds.move, -1);
 	}
 
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
@@ -453,7 +454,7 @@ bool MovementEndAnimation::init()
 	logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName());
 	myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
 
-	CCS->soundh->playSound(battle_sound(stack->unitType(), endMoving));
+	CCS->soundh->playSound(stack->unitType()->sounds.endMoving);
 
 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END))
 	{
@@ -494,7 +495,7 @@ bool MovementStartAnimation::init()
 	}
 
 	logAnim->debug("CMovementStartAnimation::init: stack %s", stack->getName());
-	CCS->soundh->playSound(battle_sound(stack->unitType(), startMoving));
+	CCS->soundh->playSound(stack->unitType()->sounds.startMoving);
 
 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
 	{
@@ -632,7 +633,7 @@ RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CSt
 	: AttackAnimation(owner_, attacker, dest_, defender),
 	  projectileEmitted(false)
 {
-	setSound(battle_sound(getCreature(), shoot));
+	setSound(getCreature()->sounds.shoot);
 }
 
 bool RangedAttackAnimation::init()
@@ -806,8 +807,8 @@ void CatapultAnimation::tick(uint32_t msPassed)
 	explosionEmitted = true;
 	Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack) + Point(225, 225) - Point(126, 105);
 
-	std::string soundFilename  = (catapultDamage > 0) ? "WALLHIT" : "WALLMISS";
-	std::string effectFilename = (catapultDamage > 0) ? "SGEXPL" : "CSGRCK";
+	auto soundFilename  = AudioPath::builtin((catapultDamage > 0) ? "WALLHIT" : "WALLMISS");
+	AnimationPath effectFilename = AnimationPath::builtin((catapultDamage > 0) ? "SGEXPL" : "CSGRCK");
 
 	CCS->soundh->playSound( soundFilename );
 	owner.stacksController->addNewAnim( new EffectAnimation(owner, effectFilename, shotTarget));
@@ -879,42 +880,42 @@ uint32_t CastAnimation::getAttackClimaxFrame() const
 	return maxFrames / 2;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed):
 	BattleAnimation(owner),
-	animation(std::make_shared<CAnimation>(animationName)),
+	animation(GH.renderHandler().loadAnimation(animationName)),
 	effectFlags(effects),
 	effectFinished(false),
 	reversed(reversed)
 {
-	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName);
+	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName());
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	battlehexes = hex;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	assert(hex.isValid());
 	battlehexes.push_back(hex);
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	positions = pos;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	positions.push_back(pos);
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	assert(hex.isValid());
@@ -970,7 +971,7 @@ bool EffectAnimation::init()
 		}
 		else
 		{
-			const auto * destStack = owner.getCurrentPlayerInterface()->cb->battleGetUnitByPos(battlehexes[i], false);
+			const auto * destStack = owner.getBattle()->battleGetUnitByPos(battlehexes[i], false);
 			Rect tilePos = owner.fieldController->hexPositionLocal(battlehexes[i]);
 
 			be.pos.x = tilePos.x + tilePos.w/2 - first->width()/2;

+ 9 - 8
client/battle/BattleAnimationClasses.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/battle/BattleHex.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -68,11 +69,11 @@ class StackActionAnimation : public BattleStackAnimation
 {
 	ECreatureAnimType nextGroup;
 	ECreatureAnimType currGroup;
-	std::string sound;
+	AudioPath sound;
 public:
 	void setNextGroup( ECreatureAnimType group );
 	void setGroup( ECreatureAnimType group );
-	void setSound( std::string sound );
+	void setSound( const AudioPath & sound );
 
 	ECreatureAnimType getGroup() const;
 
@@ -334,17 +335,17 @@ public:
 	};
 
 	/// Create animation with screen-wide effect
-	EffectAnimation(BattleInterface & owner, std::string animationName, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, bool reversed = false);
 
 	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
-	EffectAnimation(BattleInterface & owner, std::string animationName, Point pos                 , int effects = 0, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                 , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
 
 	/// Create animation positioned at certain hex(es)
-	EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex             , int effects = 0, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
 
-	EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
 	 ~EffectAnimation();
 
 	bool init() override;

+ 13 - 13
client/battle/BattleEffectsController.cpp

@@ -27,7 +27,7 @@
 
 #include "../../CCallback.h"
 #include "../../lib/battle/BattleAction.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/CStack.h"
 #include "../../lib/IGameEventsReceiver.h"
@@ -41,14 +41,14 @@ BattleEffectsController::BattleEffectsController(BattleInterface & owner):
 
 void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHex & destTile)
 {
-	displayEffect(effect, "", destTile);
+	displayEffect(effect, AudioPath(), destTile);
 }
 
-void BattleEffectsController::displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile)
+void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile)
 {
 	size_t effectID = static_cast<size_t>(effect);
 
-	std::string customAnim = graphics->battleACToDef[effectID][0];
+	AnimationPath customAnim = AnimationPath::builtinTODO(graphics->battleACToDef[effectID][0]);
 
 	CCS->soundh->playSound( soundFile );
 
@@ -59,7 +59,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 {
 	owner.checkForAnimations();
 
-	const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID);
+	const CStack * stack = owner.getBattle()->battleGetStackByID(bte.stackID);
 	if(!stack)
 	{
 		logGlobal->error("Invalid stack ID %d", bte.stackID);
@@ -69,22 +69,22 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 	switch(static_cast<BonusType>(bte.effect))
 	{
 		case BonusType::HP_REGENERATION:
-			displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
+			displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition());
 			break;
 		case BonusType::MANA_DRAIN:
-			displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
+			displayEffect(EBattleEffect::MANA_DRAIN, AudioPath::builtin("MANADRAI"), stack->getPosition());
 			break;
 		case BonusType::POISON:
-			displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
+			displayEffect(EBattleEffect::POISON, AudioPath::builtin("POISON"), stack->getPosition());
 			break;
 		case BonusType::FEAR:
-			displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
+			displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition());
 			break;
 		case BonusType::MORALE:
 		{
 			std::string hlp = CGI->generaltexth->allTexts[33];
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
-			displayEffect(EBattleEffect::GOOD_MORALE, "GOODMRLE", stack->getPosition());
+			displayEffect(EBattleEffect::GOOD_MORALE, AudioPath::builtin("GOODMRLE"), stack->getPosition());
 			owner.appendBattleLog(hlp);
 			break;
 		}
@@ -98,7 +98,7 @@ void BattleEffectsController::startAction(const BattleAction & action)
 {
 	owner.checkForAnimations();
 
-	const CStack *stack = owner.curInt->cb->battleGetStackByID(action.stackNumber);
+	const CStack *stack = owner.getBattle()->battleGetStackByID(action.stackNumber);
 
 	switch(action.actionType)
 	{
@@ -107,7 +107,7 @@ void BattleEffectsController::startAction(const BattleAction & action)
 		break;
 	case EActionType::BAD_MORALE:
 		owner.appendBattleLog(stack->formatGeneralMessage(-34));
-		displayEffect(EBattleEffect::BAD_MORALE, "BADMRLE", stack->getPosition());
+		displayEffect(EBattleEffect::BAD_MORALE, AudioPath::builtin("BADMRLE"), stack->getPosition());
 		break;
 	}
 
@@ -132,7 +132,7 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer
 
 void BattleEffectsController::loadColorMuxers()
 {
-	const JsonNode config(ResourceID("config/battleEffects.json"));
+	const JsonNode config(JsonPath::builtin("config/battleEffects.json"));
 
 	for(auto & muxer : config["colorMuxers"].Struct())
 	{

+ 2 - 1
client/battle/BattleEffectsController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -64,7 +65,7 @@ public:
 
 	//displays custom effect on the battlefield
 	void displayEffect(EBattleEffect effect, const BattleHex & destTile);
-	void displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile);
+	void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile);
 
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 

+ 22 - 21
client/battle/BattleFieldController.cpp

@@ -26,6 +26,7 @@
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
 #include "../renderSDL/SDL_Extensions.h"
+#include "../render/IRenderHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
 #include "../adventureMap/CInGameConsole.h"
@@ -120,20 +121,20 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
 	//preparing cells and hexes
-	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
-	cellShade = IImage::createFromFile("CCELLSHD.BMP");
-	cellUnitMovementHighlight = IImage::createFromFile("UnitMovementHighlight.PNG", EImageBlitMode::COLORKEY);
-	cellUnitMaxMovementHighlight = IImage::createFromFile("UnitMaxMovementHighlight.PNG", EImageBlitMode::COLORKEY);
+	cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
+	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"));
+	cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
+	cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 
-	attackCursors = std::make_shared<CAnimation>("CRCOMBAT");
+	attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"));
 	attackCursors->preload();
 
 	initializeHexEdgeMaskToFrameIndex();
 
-	rangedFullDamageLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsGreen.json");
+	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"));
 	rangedFullDamageLimitImages->preload();
 
-	shootingRangeLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsRed.json");
+	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"));
 	shootingRangeLimitImages->preload();
 
 	flipRangeLimitImagesIntoPositions(rangedFullDamageLimitImages);
@@ -141,17 +142,17 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 
 	if(!owner.siegeController)
 	{
-		auto bfieldType = owner.curInt->cb->battleGetBattlefieldType();
+		auto bfieldType = owner.getBattle()->battleGetBattlefieldType();
 
 		if(bfieldType == BattleField::NONE)
 			logGlobal->error("Invalid battlefield returned for current battle");
 		else
-			background = IImage::createFromFile(bfieldType.getInfo()->graphics, EImageBlitMode::OPAQUE);
+			background = GH.renderHandler().loadImage(bfieldType.getInfo()->graphics, EImageBlitMode::OPAQUE);
 	}
 	else
 	{
-		std::string backgroundName = owner.siegeController->getBattleBackgroundName();
-		background = IImage::createFromFile(backgroundName, EImageBlitMode::OPAQUE);
+		auto backgroundName = owner.siegeController->getBattleBackgroundName();
+		background = GH.renderHandler().loadImage(backgroundName, EImageBlitMode::OPAQUE);
 	}
 
 	pos.w = background->width();
@@ -284,7 +285,7 @@ void BattleFieldController::redrawBackgroundWithHexes()
 	const CStack *activeStack = owner.stacksController->getActiveStack();
 	std::vector<BattleHex> attackableHexes;
 	if(activeStack)
-		occupiableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
+		occupiableHexes = owner.getBattle()->battleGetAvailableHexes(activeStack, false, true, &attackableHexes);
 
 	// prepare background graphic with hexes and shaded hexes
 	backgroundWithHexes->draw(background, Point(0,0));
@@ -339,7 +340,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
 
 	auto hoveredHex = getHoveredHex();
 
-	std::set<BattleHex> set = owner.curInt->cb->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex);
+	std::set<BattleHex> set = owner.getBattle()->battleGetAttackedHexes(owner.stacksController->getActiveStack(), hoveredHex);
 	for(BattleHex hex : set)
 		result.insert(hex);
 
@@ -359,10 +360,10 @@ std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
 	auto hoveredHex = getHoveredHex();
 
 	// add possible movement hexes for stack under mouse
-	const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	const CStack * const hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 	if(hoveredStack)
 	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
+		std::vector<BattleHex> v = owner.getBattle()->battleGetAvailableHexes(hoveredStack, true, true, nullptr);
 		for(BattleHex hex : v)
 			result.insert(hex);
 	}
@@ -387,7 +388,7 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
 	if(caster && spell) //when casting spell
 	{
 		// printing shaded hex(es)
-		spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
+		spells::BattleCast event(owner.getBattle().get(), caster, mode, spell);
 		auto shadedHexes = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
 
 		for(BattleHex shadedHex : shadedHexes)
@@ -407,10 +408,10 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 	if(!stack)
 		return {};
 
-	std::vector<BattleHex> availableHexes = owner.curInt->cb->battleGetAvailableHexes(stack, false, false, nullptr);
+	std::vector<BattleHex> availableHexes = owner.getBattle()->battleGetAvailableHexes(stack, false, false, nullptr);
 
-	auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
-	if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
+	auto hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
+	if(owner.getBattle()->battleCanAttack(stack, hoveredStack, hoveredHex))
 	{
 		if(isTileAttackable(hoveredHex))
 		{
@@ -670,7 +671,7 @@ BattleHex BattleFieldController::getHoveredHex()
 const CStack* BattleFieldController::getHoveredStack()
 {
 	auto hoveredHex = getHoveredHex();
-	const CStack* hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	const CStack* hoveredStack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 
 	return hoveredStack;
 }
@@ -856,7 +857,7 @@ bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 
 void BattleFieldController::updateAccessibleHexes()
 {
-	auto accessibility = owner.curInt->cb->getAccesibility();
+	auto accessibility = owner.getBattle()->getAccesibility();
 
 	for(int i = 0; i < accessibility.size(); i++)
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE || (accessibility[i] == EAccessibility::SIDE_COLUMN));

+ 39 - 28
client/battle/BattleInterface.cpp

@@ -45,7 +45,7 @@
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/CThreadHelper.h"
 
-BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
+BattleInterface::BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2,
 		const CGHeroInstance *hero1, const CGHeroInstance *hero2,
 		std::shared_ptr<CPlayerInterface> att,
 		std::shared_ptr<CPlayerInterface> defen,
@@ -55,6 +55,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	, attackerInt(att)
 	, defenderInt(defen)
 	, curInt(att)
+	, battleID(battleID)
 	, battleOpeningDelayActive(true)
 {
 	if(spectatorInt)
@@ -68,9 +69,9 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	}
 
 	//hot-seat -> check tactics for both players (defender may be local human)
-	if(attackerInt && attackerInt->cb->battleGetTacticDist())
+	if(attackerInt && attackerInt->cb->getBattle(getBattleID())->battleGetTacticDist())
 		tacticianInterface = attackerInt;
-	else if(defenderInt && defenderInt->cb->battleGetTacticDist())
+	else if(defenderInt && defenderInt->cb->getBattle(getBattleID())->battleGetTacticDist())
 		tacticianInterface = defenderInt;
 
 	//if we found interface of player with tactics, then enter tactics mode
@@ -80,7 +81,7 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	this->army1 = army1;
 	this->army2 = army2;
 
-	const CGTownInstance *town = curInt->cb->battleGetDefendedTown();
+	const CGTownInstance *town = getBattle()->battleGetDefendedTown();
 	if(town && town->hasFort())
 		siegeController.reset(new BattleSiegeController(*this, town));
 
@@ -223,12 +224,12 @@ void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
 	stacksController->stackAttacking(attackInfo);
 }
 
-void BattleInterface::newRoundFirst( int round )
+void BattleInterface::newRoundFirst()
 {
 	waitForAnimations();
 }
 
-void BattleInterface::newRound(int number)
+void BattleInterface::newRound()
 {
 	console->addText(CGI->generaltexth->allTexts[412]);
 }
@@ -241,10 +242,10 @@ void BattleInterface::giveCommand(EActionType action, BattleHex tile, SpellID sp
 		actor = stacksController->getActiveStack();
 	}
 
-	auto side = curInt->cb->playerToSide(curInt->playerID);
+	auto side = getBattle()->playerToSide(curInt->playerID);
 	if(!side)
 	{
-		logGlobal->error("Player %s is not in battle", curInt->playerID.getStr());
+		logGlobal->error("Player %s is not in battle", curInt->playerID.toString());
 		return;
 	}
 
@@ -265,11 +266,11 @@ void BattleInterface::sendCommand(BattleAction command, const CStack * actor)
 	{
 		logGlobal->trace("Setting command for %s", (actor ? actor->nodeName() : "hero"));
 		stacksController->setActiveStack(nullptr);
-		curInt->cb->battleMakeUnitAction(command);
+		curInt->cb->battleMakeUnitAction(battleID, command);
 	}
 	else
 	{
-		curInt->cb->battleMakeTacticAction(command);
+		curInt->cb->battleMakeTacticAction(battleID, command);
 		stacksController->setActiveStack(nullptr);
 		//next stack will be activated when action ends
 	}
@@ -353,7 +354,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	if(!spell)
 		return;
 
-	const std::string & castSoundPath = spell->getCastSound();
+	const AudioPath & castSoundPath = spell->getCastSound();
 
 	if (!castSoundPath.empty())
 	{
@@ -368,13 +369,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 
 	if ( sc->activeCast )
 	{
-		const CStack * casterStack = curInt->cb->battleGetStackByID(sc->casterStack);
+		const CStack * casterStack = getBattle()->battleGetStackByID(sc->casterStack);
 
 		if(casterStack != nullptr )
 		{
 			addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]()
 			{
-				stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
+				stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, getBattle()->battleGetStackByPos(targetedTile), spell));
 				displaySpellCast(spell, casterStack->getPosition());
 			});
 		}
@@ -385,7 +386,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 
 			addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]()
 			{
-				stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
+				stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, getBattle()->battleGetStackByPos(targetedTile), spell));
 			});
 		}
 	}
@@ -397,7 +398,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	//queuing affect animation
 	for(auto & elem : sc->affectedCres)
 	{
-		auto stack = curInt->cb->battleGetStackByID(elem, false);
+		auto stack = getBattle()->battleGetStackByID(elem, false);
 		assert(stack);
 		if(stack)
 		{
@@ -409,7 +410,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 
 	for(auto & elem : sc->reflectedCres)
 	{
-		auto stack = curInt->cb->battleGetStackByID(elem, false);
+		auto stack = getBattle()->battleGetStackByID(elem, false);
 		assert(stack);
 		addToAnimationStage(EAnimationEvents::HIT, [=](){
 			effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition());
@@ -419,13 +420,13 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	if (!sc->resistedCres.empty())
 	{
 		addToAnimationStage(EAnimationEvents::HIT, [=](){
-			CCS->soundh->playSound("MAGICRES");
+			CCS->soundh->playSound(AudioPath::builtin("MAGICRES"));
 		});
 	}
 
 	for(auto & elem : sc->resistedCres)
 	{
-		auto stack = curInt->cb->battleGetStackByID(elem, false);
+		auto stack = getBattle()->battleGetStackByID(elem, false);
 		assert(stack);
 		addToAnimationStage(EAnimationEvents::HIT, [=](){
 			effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition());
@@ -440,8 +441,8 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 		bool side = sc->side;
 
 		addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
-			stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
-			stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
+			stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_A.DEF" : "SP07_B.DEF"), leftHero));
+			stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_B.DEF" : "SP07_A.DEF"), rightHero));
 		});
 	}
 
@@ -487,7 +488,7 @@ void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSp
 
 		if (!animation.effectName.empty())
 		{
-			const CStack * destStack = getCurrentPlayerInterface()->cb->battleGetStackByPos(destinationTile, false);
+			const CStack * destStack = getBattle()->battleGetStackByPos(destinationTile, false);
 
 			if (destStack)
 				stacksController->addNewAnim(new ColorTransformAnimation(*this, destStack, animation.effectName, spell ));
@@ -566,12 +567,22 @@ bool BattleInterface::makingTurn() const
 	return stacksController->getActiveStack() != nullptr;
 }
 
+BattleID BattleInterface::getBattleID() const
+{
+	return battleID;
+}
+
+std::shared_ptr<CPlayerBattleCallback> BattleInterface::getBattle() const
+{
+	return curInt->cb->getBattle(battleID);
+}
+
 void BattleInterface::endAction(const BattleAction &action)
 {
 	// it is possible that tactics mode ended while opening music is still playing
 	waitForAnimations();
 
-	const CStack *stack = curInt->cb->battleGetStackByID(action.stackNumber);
+	const CStack *stack = getBattle()->battleGetStackByID(action.stackNumber);
 
 	// Activate stack from stackToActivate because this might have been temporary disabled, e.g., during spell cast
 	activateStack();
@@ -606,7 +617,7 @@ void BattleInterface::startAction(const BattleAction & action)
 	if (!action.isUnitAction())
 		return;
 
-	assert(curInt->cb->battleGetStackByID(action.stackNumber));
+	assert(getBattle()->battleGetStackByID(action.stackNumber));
 	windowObject->updateQueue();
 	effectsController->startAction(action);
 }
@@ -616,10 +627,10 @@ void BattleInterface::tacticPhaseEnd()
 	stacksController->setActiveStack(nullptr);
 	tacticsMode = false;
 
-	auto side = tacticianInterface->cb->playerToSide(tacticianInterface->playerID);
+	auto side = tacticianInterface->cb->getBattle(battleID)->playerToSide(tacticianInterface->playerID);
 	auto action = BattleAction::makeEndOFTacticPhase(*side);
 
-	tacticianInterface->cb->battleMakeTacticAction(action);
+	tacticianInterface->cb->battleMakeTacticAction(battleID, action);
 }
 
 static bool immobile(const CStack *s)
@@ -635,7 +646,7 @@ void BattleInterface::tacticNextStack(const CStack * current)
 	//no switching stacks when the current one is moving
 	checkForAnimations();
 
-	TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
+	TStacks stacksOfMine = tacticianInterface->cb->getBattle(battleID)->battleGetStacks(CPlayerBattleCallback::ONLY_MINE);
 	vstd::erase_if (stacksOfMine, &immobile);
 	if (stacksOfMine.empty())
 	{
@@ -687,7 +698,7 @@ void BattleInterface::requestAutofightingAIToTakeAction()
 {
 	assert(curInt->isAutoFightOn);
 
-	if(curInt->cb->battleIsFinished())
+	if(getBattle()->battleIsFinished())
 	{
 		return; // battle finished with spellcast
 	}
@@ -716,7 +727,7 @@ void BattleInterface::requestAutofightingAIToTakeAction()
 			boost::thread aiThread([this, activeStack]()
 			{
 				setThreadName("autofightingAI");
-				curInt->autofightingAI->activeStack(activeStack);
+				curInt->autofightingAI->activeStack(battleID, activeStack);
 			});
 			aiThread.detach();
 		}

+ 10 - 3
client/battle/BattleInterface.h

@@ -30,6 +30,7 @@ struct BattleTriggerEffect;
 struct BattleHex;
 struct InfoAboutHero;
 class ObstacleChanges;
+class CPlayerBattleCallback;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -115,6 +116,9 @@ class BattleInterface
 	/// if set to true, battle is still starting and waiting for intro sound to end / key press from player
 	bool battleOpeningDelayActive;
 
+	/// ID of ongoing battle
+	BattleID battleID;
+
 	void playIntroSoundAndUnlockInterface();
 	void onIntroSoundPlayed();
 public:
@@ -149,7 +153,10 @@ public:
 
 	bool makingTurn() const;
 
-	BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
+	BattleID getBattleID() const;
+	std::shared_ptr<CPlayerBattleCallback> getBattle() const;
+
+	BattleInterface(const BattleID & battleID, const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
 	~BattleInterface();
 
 	void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
@@ -196,8 +203,8 @@ public:
 	void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport); //stack with id number moved to destHex
 	void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
 	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
-	void newRoundFirst( int round );
-	void newRound(int number); //caled when round is ended; number is the number of round
+	void newRoundFirst();
+	void newRound(); //caled when round is ended;
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 	void battleFinished(const BattleResult& br, QueryID queryID); //called when battle is finished - battleresult window should be printed
 	void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell

+ 37 - 36
client/battle/BattleInterfaceClasses.cpp

@@ -37,6 +37,7 @@
 #include "../windows/CMessage.h"
 #include "../windows/CSpellWindow.h"
 #include "../render/CAnimation.h"
+#include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
@@ -289,7 +290,7 @@ void BattleHero::heroLeftClicked()
 	if(!hero || !owner.makingTurn())
 		return;
 
-	if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
+	if(owner.getBattle()->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
 	{
 		CCS->curh->set(Cursor::Map::POINTER);
 		GH.windows().createAndPushWindow<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
@@ -342,7 +343,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	currentFrame(0.f),
 	flagCurrentFrame(0.f)
 {
-	std::string animationPath;
+	AnimationPath animationPath;
 
 	if(!hero->type->battleImage.empty())
 		animationPath = hero->type->battleImage;
@@ -352,7 +353,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	else
 		animationPath = hero->type->heroClass->imageBattleMale;
 
-	animation = std::make_shared<CAnimation>(animationPath);
+	animation = GH.renderHandler().loadAnimation(animationPath);
 	animation->preload();
 
 	pos.w = 64;
@@ -364,9 +365,9 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 		animation->verticalFlip();
 
 	if(defender)
-		flagAnimation = std::make_shared<CAnimation>("CMFLAGR");
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR"));
 	else
-		flagAnimation = std::make_shared<CAnimation>("CMFLAGL");
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL"));
 
 	flagAnimation->preload();
 	flagAnimation->playerColored(hero->tempOwner);
@@ -386,7 +387,7 @@ HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * posit
 
 	if(initializeBackground)
 	{
-		background = std::make_shared<CPicture>("CHRPOP");
+		background = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
 		background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
 		background->colorize(hero.owner);
 	}
@@ -406,7 +407,7 @@ void HeroInfoBasicPanel::initializeData(const InfoAboutHero & hero)
 	auto currentSpellPoints = hero.details->mana;
 	auto maxSpellPoints = hero.details->manaLimit;
 
-	icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 10, 6));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), hero.portrait, 0, 10, 6));
 
 	//primary stats
 	labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
@@ -423,8 +424,8 @@ void HeroInfoBasicPanel::initializeData(const InfoAboutHero & hero)
 	labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
 	labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
 
-	icons.push_back(std::make_shared<CAnimImage>("IMRL22", morale + 3, 0, 47, 131));
-	icons.push_back(std::make_shared<CAnimImage>("ILCK22", luck + 3, 0, 47, 143));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("IMRL22"), morale + 3, 0, 47, 131));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ILCK22"), luck + 3, 0, 47, 143));
 
 	//spell points
 	labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
@@ -446,7 +447,7 @@ void HeroInfoBasicPanel::show(Canvas & to)
 }
 
 HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
-	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, "CHRPOP")
+	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("CHRPOP"))
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	if (position != nullptr)
@@ -462,16 +463,16 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	background = std::make_shared<CPicture>("CPRESULT");
+	background = std::make_shared<CPicture>(ImagePath::builtin("CPRESULT"));
 	background->colorize(owner.playerID);
 	pos = center(background->pos);
 
-	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
+	exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
 	
 	if(allowReplay)
 	{
-		repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
+		repeat = std::make_shared<CButton>(Point(24, 505), AnimationPath::builtin("icn6432.def"), std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
 		repeat->setBorderColor(Colors::METALLIC_GOLD);
 		labels.push_back(std::make_shared<CLabel>(232, 520, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.battleResultsWindow.applyResultsLabel")));
 	}
@@ -502,17 +503,17 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 	for(int i = 0; i < 2; i++)
 	{
-		auto heroInfo = owner.cb->battleGetHeroInfo(i);
+		auto heroInfo = owner.cb->getBattle(br.battleID)->battleGetHeroInfo(i);
 		const int xs[] = {21, 392};
 
 		if(heroInfo.portrait >= 0) //attacking hero
 		{
-			icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", heroInfo.portrait, 0, xs[i], 38));
+			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), heroInfo.portrait, 0, xs[i], 38));
 			sideNames[i] = heroInfo.name;
 		}
 		else
 		{
-			auto stacks = owner.cb->battleGetAllStacks();
+			auto stacks = owner.cb->getBattle(br.battleID)->battleGetAllStacks();
 			vstd::erase_if(stacks, [i](const CStack * stack) //erase stack of other side and not coming from garrison
 			{
 				return stack->unitSide() != i || !stack->base;
@@ -525,7 +526,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)->unitType()->getIconIndex(), 0, xs[i], 38));
+				icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), (*best)->unitType()->getIconIndex(), 0, xs[i], 38));
 				sideNames[i] = (*best)->unitType()->getNamePluralTranslated();
 			}
 		}
@@ -552,7 +553,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 				if (creature->getId() == CreatureID::ARROW_TOWERS )
 					continue; // do not show destroyed towers in battle results
 
-				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, xPos, yPos));
+				icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), creature->getIconIndex(), 0, xPos, yPos));
 				std::ostringstream amount;
 				amount<<elem.second;
 				labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));
@@ -561,7 +562,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 		}
 	}
 	//printing result description
-	bool weAreAttacker = !(owner.cb->battleGetMySide());
+	bool weAreAttacker = !(owner.cb->getBattle(br.battleID)->battleGetMySide());
 	if((br.winner == 0 && weAreAttacker) || (br.winner == 1 && !weAreAttacker)) //we've won
 	{
 		int text = 304;
@@ -580,11 +581,11 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 			break;
 		}
 
-		CCS->musich->playMusic("Music/Win Battle", false, true);
-		CCS->videoh->open("WIN3.BIK");
+		CCS->musich->playMusic(AudioPath::builtin("Music/Win Battle"), false, true);
+		CCS->videoh->open(VideoPath::builtin("WIN3.BIK"));
 		std::string str = CGI->generaltexth->allTexts[text];
 
-		const CGHeroInstance * ourHero = owner.cb->battleGetMyHero();
+		const CGHeroInstance * ourHero = owner.cb->getBattle(br.battleID)->battleGetMyHero();
 		if (ourHero)
 		{
 			str += CGI->generaltexth->allTexts[305];
@@ -597,20 +598,20 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 	else // we lose
 	{
 		int text = 311;
-		std::string musicName = "Music/LoseCombat";
-		std::string videoName = "LBSTART.BIK";
+		AudioPath musicName = AudioPath::builtin("Music/LoseCombat");
+		VideoPath videoName = VideoPath::builtin("LBSTART.BIK");
 		switch(br.result)
 		{
 		case EBattleResult::NORMAL:
 			break;
 		case EBattleResult::ESCAPE:
-			musicName = "Music/Retreat Battle";
-			videoName = "RTSTART.BIK";
+			musicName = AudioPath::builtin("Music/Retreat Battle");
+			videoName = VideoPath::builtin("RTSTART.BIK");
 			text = 310;
 			break;
 		case EBattleResult::SURRENDER:
-			musicName = "Music/Surrender Battle";
-			videoName = "SURRENDER.BIK";
+			musicName = AudioPath::builtin("Music/Surrender Battle");
+			videoName = VideoPath::builtin("SURRENDER.BIK");
 			text = 309;
 			break;
 		default:
@@ -676,8 +677,8 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.x += parent->pos.w/2 - pos.w/2;
 		pos.y += 10;
 
-		icons = std::make_shared<CAnimation>("CPRSMALL");
-		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
+		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"));
+		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
 	}
 	else
 	{
@@ -686,12 +687,12 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.x += 0;
 		pos.y -= pos.h;
 
-		background = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, 0, pos.w, pos.h));
+		background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
 
-		icons = std::make_shared<CAnimation>("TWCRPORT");
-		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
+		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("TWCRPORT"));
+		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
 		//TODO: where use big icons?
-		//stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESBIG");
+		//stateIcons = GH.renderHandler().loadAnimation("VCMI/BATTLEQUEUE/STATESBIG");
 	}
 	stateIcons->preload();
 
@@ -714,7 +715,7 @@ void StackQueue::update()
 {
 	std::vector<battle::Units> queueData;
 
-	owner.getCurrentPlayerInterface()->cb->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
+	owner.getBattle()->battleGetTurnOrder(queueData, stackBoxes.size(), 0);
 
 	size_t boxIndex = 0;
 
@@ -750,7 +751,7 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 	CIntObject(SHOW_POPUP | HOVER), owner(owner)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(owner->embedded ? "StackQueueSmall" : "StackQueueLarge");
+	background = std::make_shared<CPicture>(ImagePath::builtin(owner->embedded ? "StackQueueSmall" : "StackQueueLarge"));
 
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;

+ 13 - 12
client/battle/BattleObstacleController.cpp

@@ -22,6 +22,7 @@
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
@@ -31,7 +32,7 @@ BattleObstacleController::BattleObstacleController(BattleInterface & owner):
 	owner(owner),
 	timePassed(0.f)
 {
-	auto obst = owner.curInt->cb->battleGetAllObstacles();
+	auto obst = owner.getBattle()->battleGetAllObstacles();
 	for(auto & elem : obst)
 	{
 		if ( elem->obstacleType == CObstacleInstance::MOAT )
@@ -42,21 +43,21 @@ BattleObstacleController::BattleObstacleController(BattleInterface & owner):
 
 void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
 {
-	std::string animationName = oi.getAnimation();
+	AnimationPath animationName = oi.getAnimation();
 
 	if (animationsCache.count(animationName) == 0)
 	{
 		if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 		{
 			// obstacle uses single bitmap image for animations
-			auto animation = std::make_shared<CAnimation>();
-			animation->setCustom(animationName, 0, 0);
+			auto animation = GH.renderHandler().createAnimation();
+			animation->setCustom(animationName.getName(), 0, 0);
 			animationsCache[animationName] = animation;
 			animation->preload();
 		}
 		else
 		{
-			auto animation = std::make_shared<CAnimation>(animationName);
+			auto animation = GH.renderHandler().loadAnimation(animationName);
 			animationsCache[animationName] = animation;
 			animation->preload();
 		}
@@ -76,7 +77,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 			continue;
 		}
 
-		auto animation = std::make_shared<CAnimation>(obstacle["appearAnimation"].String());
+		auto animation = GH.renderHandler().loadAnimation(AnimationPath::fromJson(obstacle["appearAnimation"]));
 		animation->preload();
 
 		auto first = animation->getImage(0, 0);
@@ -87,7 +88,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		// -> if we know how to blit obstacle, let's blit the effect in the same place
 		Point whereTo = getObstaclePosition(first, obstacle);
 		//AFAIK, in H3 there is no sound of obstacle removal
-		owner.stacksController->addNewAnim(new EffectAnimation(owner, obstacle["appearAnimation"].String(), whereTo, obstacle["position"].Integer(), 0, true));
+		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::fromJson(obstacle["appearAnimation"]), whereTo, obstacle["position"].Integer(), 0, true));
 
 		obstacleAnimations.erase(oi.id);
 		//so when multiple obstacles are removed, they show up one after another
@@ -99,12 +100,12 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
 {
 	for(const auto & oi : obstacles)
 	{
-		auto side = owner.curInt->cb->playerToSide(owner.curInt->playerID);
+		auto side = owner.getBattle()->playerToSide(owner.curInt->playerID);
 
-		if(!oi->visibleForSide(side.value(), owner.curInt->cb->battleHasNativeStack(side.value())))
+		if(!oi->visibleForSide(side.value(), owner.getBattle()->battleHasNativeStack(side.value())))
 			continue;
 
-		auto animation = std::make_shared<CAnimation>(oi->getAppearAnimation());
+		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation());
 		animation->preload();
 
 		auto first = animation->getImage(0, 0);
@@ -127,7 +128,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
 void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas)
 {
 	//Blit absolute obstacles
-	for(auto & obstacle : owner.curInt->cb->battleGetAllObstacles())
+	for(auto & obstacle : owner.getBattle()->battleGetAllObstacles())
 	{
 		if(obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 		{
@@ -153,7 +154,7 @@ void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas)
 
 void BattleObstacleController::collectRenderableObjects(BattleRenderer & renderer)
 {
-	for (auto obstacle : owner.curInt->cb->battleGetAllObstacles())
+	for (auto obstacle : owner.getBattle()->battleGetAllObstacles())
 	{
 		if (obstacle->obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 			continue;

+ 3 - 1
client/battle/BattleObstacleController.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../../lib/filesystem/ResourcePath.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct BattleHex;
@@ -35,7 +37,7 @@ class BattleObstacleController
 	float timePassed;
 
 	/// cached animations of all obstacles in current battle
-	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> animationsCache;
 
 	/// list of all obstacles that are currently being rendered
 	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;

+ 5 - 4
client/battle/BattleProjectileController.cpp

@@ -16,6 +16,7 @@
 #include "CreatureAnimation.h"
 
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../CGameInfo.h"
 
@@ -188,9 +189,9 @@ void BattleProjectileController::initStackProjectile(const CStack * stack)
 	projectilesCache[creature.animation.projectileImageName] = createProjectileImage(creature.animation.projectileImageName);
 }
 
-std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const std::string & path )
+std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const AnimationPath & path )
 {
-	std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(path);
+	std::shared_ptr<CAnimation> projectile = GH.renderHandler().loadAnimation(path);
 	projectile->preload();
 
 	if(projectile->size(1) != 0)
@@ -204,7 +205,7 @@ std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(co
 std::shared_ptr<CAnimation> BattleProjectileController::getProjectileImage(const CStack * stack)
 {
 	const CCreature & creature = getShooter(stack);
-	std::string imageName = creature.animation.projectileImageName;
+	AnimationPath imageName = creature.animation.projectileImageName;
 
 	if (!projectilesCache.count(imageName))
 		initStackProjectile(stack);
@@ -361,7 +362,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 void BattleProjectileController::createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell)
 {
 	double projectileAngle = std::abs(atan2(dest.x - from.x, dest.y - from.y));
-	std::string animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
+	AnimationPath animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
 
 	assert(!animToDisplay.empty());
 

+ 3 - 2
client/battle/BattleProjectileController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -83,13 +84,13 @@ class BattleProjectileController
 	BattleInterface & owner;
 
 	/// all projectiles loaded during current battle
-	std::map<std::string, std::shared_ptr<CAnimation>> projectilesCache;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> projectilesCache;
 
 	/// projectiles currently flying on battlefield
 	std::vector<std::shared_ptr<ProjectileBase>> projectiles;
 
 	std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack);
-	std::shared_ptr<CAnimation> createProjectileImage(const std::string & path );
+	std::shared_ptr<CAnimation> createProjectileImage(const AnimationPath & path );
 	void initStackProjectile(const CStack * stack);
 
 	bool stackUsesRayProjectile(const CStack * stack) const;

+ 38 - 36
client/battle/BattleSiegeController.cpp

@@ -20,15 +20,17 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/CStack.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
-std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
+ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
 {
 	auto getImageIndex = [&]() -> int
 	{
@@ -68,44 +70,44 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua
 			auto faction = town->town->faction->getIndex();
 
 			if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
-				return prefix + "TPW1.BMP";
+				return ImagePath::builtinTODO(prefix + "TPW1.BMP");
 			else
-				return prefix + "TPWL.BMP";
+				return ImagePath::builtinTODO(prefix + "TPWL.BMP");
 		}
 	case EWallVisual::KEEP:
-		return prefix + "MAN" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "MAN" + addit + ".BMP");
 	case EWallVisual::BOTTOM_TOWER:
-		return prefix + "TW1" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "TW1" + addit + ".BMP");
 	case EWallVisual::BOTTOM_WALL:
-		return prefix + "WA1" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA1" + addit + ".BMP");
 	case EWallVisual::WALL_BELLOW_GATE:
-		return prefix + "WA3" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA3" + addit + ".BMP");
 	case EWallVisual::WALL_OVER_GATE:
-		return prefix + "WA4" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA4" + addit + ".BMP");
 	case EWallVisual::UPPER_WALL:
-		return prefix + "WA6" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA6" + addit + ".BMP");
 	case EWallVisual::UPPER_TOWER:
-		return prefix + "TW2" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "TW2" + addit + ".BMP");
 	case EWallVisual::GATE:
-		return prefix + "DRW" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "DRW" + addit + ".BMP");
 	case EWallVisual::GATE_ARCH:
-		return prefix + "ARCH.BMP";
+		return ImagePath::builtinTODO(prefix + "ARCH.BMP");
 	case EWallVisual::BOTTOM_STATIC_WALL:
-		return prefix + "WA2.BMP";
+		return ImagePath::builtinTODO(prefix + "WA2.BMP");
 	case EWallVisual::UPPER_STATIC_WALL:
-		return prefix + "WA5.BMP";
+		return ImagePath::builtinTODO(prefix + "WA5.BMP");
 	case EWallVisual::MOAT:
-		return prefix + "MOAT.BMP";
+		return ImagePath::builtinTODO(prefix + "MOAT.BMP");
 	case EWallVisual::MOAT_BANK:
-		return prefix + "MLIP.BMP";
+		return ImagePath::builtinTODO(prefix + "MLIP.BMP");
 	case EWallVisual::KEEP_BATTLEMENT:
-		return prefix + "MANC.BMP";
+		return ImagePath::builtinTODO(prefix + "MANC.BMP");
 	case EWallVisual::BOTTOM_BATTLEMENT:
-		return prefix + "TW1C.BMP";
+		return ImagePath::builtinTODO(prefix + "TW1C.BMP");
 	case EWallVisual::UPPER_BATTLEMENT:
-		return prefix + "TW2C.BMP";
+		return ImagePath::builtinTODO(prefix + "TW2C.BMP");
 	default:
-		return "";
+		return ImagePath();
 	}
 }
 
@@ -118,10 +120,10 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis
 		canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));
 }
 
-std::string BattleSiegeController::getBattleBackgroundName() const
+ImagePath BattleSiegeController::getBattleBackgroundName() const
 {
 	const std::string & prefix = town->town->clientInfo.siegePrefix;
-	return prefix + "BACK.BMP";
+	return ImagePath::builtinTODO(prefix + "BACK.BMP");
 }
 
 bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
@@ -133,9 +135,9 @@ bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what)
 	{
 	case EWallVisual::MOAT:              return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
 	case EWallVisual::MOAT_BANK:         return town->hasBuilt(BuildingID::CITADEL) && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
-	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;
+	case EWallVisual::KEEP_BATTLEMENT:   return town->hasBuilt(BuildingID::CITADEL) && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
+	case EWallVisual::UPPER_BATTLEMENT:  return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
+	case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
 	default:                             return true;
 	}
 }
@@ -180,7 +182,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
 			continue;
 
-		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
+		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
 	}
 }
 
@@ -220,7 +222,7 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con
 
 void BattleSiegeController::gateStateChanged(const EGateState state)
 {
-	auto oldState = owner.curInt->cb->battleGetGateState();
+	auto oldState = owner.getBattle()->battleGetGateState();
 	bool playSound = false;
 	auto stateId = EWallState::NONE;
 	switch(state)
@@ -246,7 +248,7 @@ void BattleSiegeController::gateStateChanged(const EGateState state)
 		wallPieceImages[EWallVisual::GATE] = nullptr;
 
 	if (stateId != EWallState::NONE)
-		wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE,  stateId));
+		wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE,  stateId));
 
 	if (playSound)
 		CCS->soundh->playSound(soundBase::DRAWBRG);
@@ -275,7 +277,7 @@ BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wal
 
 const CStack * BattleSiegeController::getTurretStack(EWallVisual::EWallVisual wallPiece) const
 {
-	for (auto & stack : owner.curInt->cb->battleGetAllStacks(true))
+	for (auto & stack : owner.getBattle()->battleGetAllStacks(true))
 	{
 		if ( stack->initialPosition == getTurretBattleHex(wallPiece))
 			return stack;
@@ -318,15 +320,15 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
 	if (owner.tacticsMode)
 		return false;
 
-	auto wallPart = owner.curInt->cb->battleHexToWallPart(hex);
-	return owner.curInt->cb->isWallPartAttackable(wallPart);
+	auto wallPart = owner.getBattle()->battleHexToWallPart(hex);
+	return owner.getBattle()->isWallPartAttackable(wallPart);
 }
 
 void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 {
 	if (ca.attacker != -1)
 	{
-		const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);
+		const CStack *stack = owner.getBattle()->battleGetStackByID(ca.attacker);
 		for (auto attackInfo : ca.attackedParts)
 		{
 			owner.stacksController->addNewAnim(new CatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
@@ -340,8 +342,8 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 		for (auto attackInfo : ca.attackedParts)
 			positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
 
-		CCS->soundh->playSound( "WALLHIT" );
-		owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));
+		CCS->soundh->playSound( AudioPath::builtin("WALLHIT") );
+		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::builtin("SGEXPL.DEF"), positions));
 	}
 
 	owner.waitForAnimations();
@@ -353,9 +355,9 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 		if (wallId == EWallVisual::GATE)
 			continue;
 
-		auto wallState = EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));
+		auto wallState = EWallState(owner.getBattle()->battleGetWallState(attackInfo.attackedPart));
 
-		wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
+		wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
 	}
 }
 

+ 3 - 2
client/battle/BattleSiegeController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/GameConstants.h"
 #include "../../lib/battle/BattleHex.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -76,7 +77,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 state) const;
+	ImagePath getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const;
 
 	/// returns BattleHex to which chosen wall piece is bound
 	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
@@ -102,7 +103,7 @@ public:
 
 	/// queries from other battle controllers
 	bool isAttackableByCatapult(BattleHex hex) const;
-	std::string getBattleBackgroundName() const;
+	ImagePath getBattleBackgroundName() const;
 	const CCreature *getTurretCreature() const;
 	Point getTurretCreaturePosition( BattleHex position ) const;
 

+ 19 - 20
client/battle/BattleStacksController.cpp

@@ -28,6 +28,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../render/Colors.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
@@ -76,10 +77,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	animIDhelper(0)
 {
 	//preparing graphics for displaying amounts of creatures
-	amountNormal     = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountPositive   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
+	amountNormal     = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountPositive   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountNegative   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountEffNeutral = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
 
 	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 );
@@ -94,7 +95,7 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	amountNegative->adjustPalette(shifterNegative, ignoredMask);
 	amountEffNeutral->adjustPalette(shifterNeutral, ignoredMask);
 
-	std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
+	std::vector<const CStack*> stacks = owner.getBattle()->battleGetAllStacks(true);
 	for(const CStack * s : stacks)
 	{
 		stackAdded(s, true);
@@ -126,7 +127,7 @@ BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack)
 
 void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
 {
-	auto stacks = owner.curInt->cb->battleGetAllStacks(false);
+	auto stacks = owner.getBattle()->battleGetAllStacks(false);
 
 	for (auto stack : stacks)
 	{
@@ -359,7 +360,7 @@ void BattleStacksController::initializeBattleAnimations()
 
 void BattleStacksController::tickFrameBattleAnimations(uint32_t msPassed)
 {
-	for (auto stack : owner.curInt->cb->battleGetAllStacks(true))
+	for (auto stack : owner.getBattle()->battleGetAllStacks(true))
 	{
 		if (stackAnimation.find(stack->unitId()) == stackAnimation.end()) //e.g. for summoned but not yet handled stacks
 			continue;
@@ -462,7 +463,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 				addNewAnim(new HittedAnimation(owner, attackedInfo.defender));
 
 			if (attackedInfo.fireShield)
-				owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, "FIRESHIE", attackedInfo.attacker->getPosition());
+				owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, AudioPath::builtin("FIRESHIE"), attackedInfo.attacker->getPosition());
 
 			if (attackedInfo.spellEffect != SpellID::NONE)
 			{
@@ -481,7 +482,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 		if (attackedInfo.rebirth)
 		{
 			owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
-				owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition());
+				owner.effectsController->displayEffect(EBattleEffect::RESURRECT, AudioPath::builtin("RESURECT"), attackedInfo.defender->getPosition());
 				addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender));
 			});
 		}
@@ -552,9 +553,7 @@ 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,
-				defender);
+	bool mustReverse = owner.getBattle()->isToReverse(attacker, defender);
 
 	if (attacker->unitSide() == BattleSide::ATTACKER)
 		return !mustReverse;
@@ -594,7 +593,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
-			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, AudioPath::builtin("GOODLUCK"), attacker->getPosition());
 		});
 	}
 
@@ -602,7 +601,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
-			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, AudioPath::builtin("BADLUCK"), attacker->getPosition());
 		});
 	}
 
@@ -610,7 +609,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
-			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, AudioPath::builtin("DEATHBLO"), defender->getPosition());
 		});
 
 		for(auto elem : info.secondaryDefender)
@@ -645,7 +644,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]()
 		{
-			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition());
 		});
 	}
 
@@ -670,7 +669,7 @@ void BattleStacksController::endAction(const BattleAction & action)
 	owner.checkForAnimations();
 
 	//check if we should reverse stacks
-	TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
+	TStacks stacks = owner.getBattle()->battleGetStacks(CPlayerBattleCallback::MINE_AND_ENEMY);
 
 	for (const CStack *s : stacks)
 	{
@@ -847,7 +846,7 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 	auto hoveredQueueUnitId = owner.windowObject->getQueueHoveredUnitId();
 	if(hoveredQueueUnitId.has_value())
 	{
-		return { owner.curInt->cb->battleGetStackByID(hoveredQueueUnitId.value(), true) };
+		return { owner.getBattle()->battleGetStackByID(hoveredQueueUnitId.value(), true) };
 	}
 
 	auto hoveredHex = owner.fieldController->getHoveredHex();
@@ -867,14 +866,14 @@ std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 		spells::Target target;
 		target.emplace_back(hoveredHex);
 
-		spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
+		spells::BattleCast event(owner.getBattle().get(), caster, mode, spell);
 		auto mechanics = spell->battleMechanics(&event);
 		return mechanics->getAffectedStacks(target);
 	}
 
 	if(hoveredHex.isValid())
 	{
-		const CStack * const stack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+		const CStack * const stack = owner.getBattle()->battleGetStackByPos(hoveredHex, true);
 
 		if (stack)
 			return {stack};

+ 23 - 22
client/battle/BattleWindow.cpp

@@ -29,6 +29,7 @@
 #include "../windows/CMessage.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
@@ -37,7 +38,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "../windows/settings/SettingsMainWindow.h"
 
 BattleWindow::BattleWindow(BattleInterface & owner):
@@ -51,7 +52,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 
 	REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
 	
-	const JsonNode config(ResourceID("config/widgets/BattleWindow2.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
 	
 	addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
 	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
@@ -367,10 +368,10 @@ void BattleWindow::bSurrenderf()
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 
-	int cost = owner.curInt->cb->battleGetSurrenderCost();
+	int cost = owner.getBattle()->battleGetSurrenderCost();
 	if(cost >= 0)
 	{
-		std::string enemyHeroName = owner.curInt->cb->battleGetEnemyHero().name;
+		std::string enemyHeroName = owner.getBattle()->battleGetEnemyHero().name;
 		if(enemyHeroName.empty())
 		{
 			logGlobal->warn("Surrender performed without enemy hero, should not happen!");
@@ -387,7 +388,7 @@ void BattleWindow::bFleef()
 	if (owner.actionsController->spellcastingModeActive())
 		return;
 
-	if ( owner.curInt->cb->battleCanFlee() )
+	if ( owner.getBattle()->battleCanFlee() )
 	{
 		CFunctionList<void()> ony = std::bind(&BattleWindow::reallyFlee,this);
 		owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
@@ -398,10 +399,10 @@ void BattleWindow::bFleef()
 		std::string heroName;
 		//calculating fleeing hero's name
 		if (owner.attackingHeroInstance)
-			if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
+			if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getPlayerID())
 				heroName = owner.attackingHeroInstance->getNameTranslated();
 		if (owner.defendingHeroInstance)
-			if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
+			if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getPlayerID())
 				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!
@@ -419,7 +420,7 @@ void BattleWindow::reallyFlee()
 
 void BattleWindow::reallySurrender()
 {
-	if (owner.curInt->cb->getResourceAmount(EGameResID::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
+	if (owner.curInt->cb->getResourceAmount(EGameResID::GOLD) < owner.getBattle()->battleGetSurrenderCost())
 	{
 		owner.curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
 	}
@@ -436,23 +437,23 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 	if(!w)
 		return;
 	
-	std::string iconName = variables["actionIconDefault"].String();
+	AnimationPath iconName = AnimationPath::fromJson(variables["actionIconDefault"]);
 	switch(action.get())
 	{
 		case PossiblePlayerBattleAction::ATTACK:
-			iconName = variables["actionIconAttack"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconAttack"]);
 			break;
 			
 		case PossiblePlayerBattleAction::SHOOT:
-			iconName = variables["actionIconShoot"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconShoot"]);
 			break;
 			
 		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
-			iconName = variables["actionIconSpell"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
 			break;
 
 		case PossiblePlayerBattleAction::ANY_LOCATION:
-			iconName = variables["actionIconSpell"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
 			break;
 			
 		//TODO: figure out purpose of this icon
@@ -461,15 +462,15 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 			//break;
 			
 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-			iconName = variables["actionIconReturn"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconReturn"]);
 			break;
 			
 		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
-			iconName = variables["actionIconNoReturn"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconNoReturn"]);
 			break;
 	}
 		
-	auto anim = std::make_shared<CAnimation>(iconName);
+	auto anim = GH.renderHandler().loadAnimation(iconName);
 	w->setImage(anim);
 	w->redraw();
 }
@@ -509,7 +510,7 @@ void BattleWindow::bAutofightf()
 		autocombatPreferences.enableSpellsUsage = settings["battle"]["enableAutocombatSpells"].Bool();
 
 		ai->initBattleInterface(owner.curInt->env, owner.curInt->cb, autocombatPreferences);
-		ai->battleStart(owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.curInt->cb->battleGetMySide(), false);
+		ai->battleStart(owner.getBattleID(), owner.army1, owner.army2, int3(0,0,0), owner.attackingHeroInstance, owner.defendingHeroInstance, owner.getBattle()->battleGetMySide(), false);
 		owner.curInt->autofightingAI = ai;
 		owner.curInt->cb->registerBattleInterface(ai);
 
@@ -531,7 +532,7 @@ void BattleWindow::bSpellf()
 
 	CCS->curh->set(Cursor::Map::POINTER);
 
-	ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
+	ESpellCastProblem spellCastProblem = owner.getBattle()->battleCanCastSpell(myHero, spells::Mode::HERO);
 
 	if(spellCastProblem == ESpellCastProblem::OK)
 	{
@@ -633,11 +634,11 @@ void BattleWindow::bTacticPhaseEnd()
 void BattleWindow::blockUI(bool on)
 {
 	bool canCastSpells = false;
-	auto hero = owner.curInt->cb->battleGetMyHero();
+	auto hero = owner.getBattle()->battleGetMyHero();
 
 	if(hero)
 	{
-		ESpellCastProblem spellcastingProblem = owner.curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
+		ESpellCastProblem spellcastingProblem = owner.getBattle()->battleCanCastSpell(hero, spells::Mode::HERO);
 
 		//if magic is blocked, we leave button active, so the message can be displayed after button click
 		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
@@ -646,8 +647,8 @@ void BattleWindow::blockUI(bool on)
 	bool canWait = owner.stacksController->getActiveStack() ? !owner.stacksController->getActiveStack()->waitedThisTurn : false;
 
 	setShortcutBlocked(EShortcut::GLOBAL_OPTIONS, on);
-	setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.curInt->cb->battleCanFlee());
-	setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.curInt->cb->battleGetSurrenderCost() < 0);
+	setShortcutBlocked(EShortcut::BATTLE_RETREAT, on || !owner.getBattle()->battleCanFlee());
+	setShortcutBlocked(EShortcut::BATTLE_SURRENDER, on || owner.getBattle()->battleGetSurrenderCost() < 0);
 	setShortcutBlocked(EShortcut::BATTLE_CAST_SPELL, on || owner.tacticsMode || !canCastSpells);
 	setShortcutBlocked(EShortcut::BATTLE_WAIT, on || owner.tacticsMode || !canWait);
 	setShortcutBlocked(EShortcut::BATTLE_DEFEND, on || owner.tacticsMode);

+ 5 - 3
client/battle/CreatureAnimation.cpp

@@ -13,8 +13,10 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CCreatureHandler.h"
 
+#include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
+#include "../render/IRenderHandler.h"
 
 static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
 static const ColorRGBA creatureGoldBorder = { 255, 255, 0, 255 };
@@ -185,7 +187,7 @@ void CreatureAnimation::setType(ECreatureAnimType type)
 	speed = speedController(this, type);
 }
 
-CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
+CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedController controller)
 	: name(name_),
 	  speed(0.1f),
 	  shadowAlpha(128),
@@ -196,8 +198,8 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
 	  speedController(controller),
 	  once(false)
 {
-	forward = std::make_shared<CAnimation>(name_);
-	reverse = std::make_shared<CAnimation>(name_);
+	forward = GH.renderHandler().loadAnimation(name_);
+	reverse = GH.renderHandler().loadAnimation(name_);
 
 	//todo: optimize
 	forward->preload();

+ 2 - 2
client/battle/CreatureAnimation.h

@@ -70,7 +70,7 @@ public:
 	using TSpeedController = std::function<float(CreatureAnimation *, ECreatureAnimType)>;
 
 private:
-	std::string name;
+	AnimationPath name;
 
 	/// animation for rendering stack in default orientation - facing right
 	std::shared_ptr<CAnimation> forward;
@@ -122,7 +122,7 @@ public:
 	/// name - path to .def file, relative to SPRITES/ directory
 	/// controller - function that will return for how long *each* frame
 	/// in specified group of animation should be played, measured in seconds
-	CreatureAnimation(const std::string & name_, TSpeedController speedController);
+	CreatureAnimation(const AnimationPath & name_, TSpeedController speedController);
 
 	/// sets type of animation and resets framecount
 	void setType(ECreatureAnimType type);

+ 7 - 0
client/gui/CGuiHandler.cpp

@@ -25,6 +25,7 @@
 #include "../render/IFont.h"
 #include "../render/EFont.h"
 #include "../renderSDL/ScreenHandler.h"
+#include "../renderSDL/RenderHandler.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
@@ -75,6 +76,7 @@ void CGuiHandler::init()
 	eventDispatcherInstance = std::make_unique<EventDispatcher>();
 	windowHandlerInstance = std::make_unique<WindowHandler>();
 	screenHandlerInstance = std::make_unique<ScreenHandler>();
+	renderHandlerInstance = std::make_unique<RenderHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
 	framerateManagerInstance = std::make_unique<FramerateManager>(settings["video"]["targetfps"].Integer());
 }
@@ -206,6 +208,11 @@ IScreenHandler & CGuiHandler::screenHandler()
 	return *screenHandlerInstance;
 }
 
+IRenderHandler & CGuiHandler::renderHandler()
+{
+	return *renderHandlerInstance;
+}
+
 EventDispatcher & CGuiHandler::events()
 {
 	return *eventDispatcherInstance;

+ 3 - 1
client/gui/CGuiHandler.h

@@ -22,6 +22,7 @@ class IStatusBar;
 class CIntObject;
 class IUpdateable;
 class IShowActivatable;
+class IRenderHandler;
 class IScreenHandler;
 class WindowHandler;
 class EventDispatcher;
@@ -41,6 +42,7 @@ private:
 	std::unique_ptr<WindowHandler> windowHandlerInstance;
 
 	std::unique_ptr<IScreenHandler> screenHandlerInstance;
+	std::unique_ptr<IRenderHandler> renderHandlerInstance;
 	std::unique_ptr<FramerateManager> framerateManagerInstance;
 	std::unique_ptr<EventDispatcher> eventDispatcherInstance;
 	std::unique_ptr<InputHandler> inputHandlerInstance;
@@ -67,7 +69,7 @@ public:
 	void stopTextInput();
 
 	IScreenHandler & screenHandler();
-
+	IRenderHandler & renderHandler();
 	WindowHandler & windows();
 
 	/// Returns currently active status bar. Guaranteed to be non-null

+ 9 - 8
client/gui/CursorHandler.cpp

@@ -17,6 +17,7 @@
 #include "../renderSDL/CursorHardware.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../lib/CConfigHandler.h"
 
@@ -46,10 +47,10 @@ CursorHandler::CursorHandler()
 
 	cursors =
 	{
-		std::make_unique<CAnimation>("CRADVNTR"),
-		std::make_unique<CAnimation>("CRCOMBAT"),
-		std::make_unique<CAnimation>("CRDEFLT"),
-		std::make_unique<CAnimation>("CRSPELL")
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"))
 	};
 
 	for (auto & cursor : cursors)
@@ -100,11 +101,11 @@ void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
 	cursor->setImage(getCurrentImage(), getPivotOffset());
 }
 
-void CursorHandler::dragAndDropCursor (std::string path, size_t index)
+void CursorHandler::dragAndDropCursor (const AnimationPath & path, size_t index)
 {
-	CAnimation anim(path);
-	anim.load(index);
-	dragAndDropCursor(anim.getImage(index));
+	auto anim = GH.renderHandler().loadAnimation(path);
+	anim->load(index);
+	dragAndDropCursor(anim->getImage(index));
 }
 
 void CursorHandler::cursorMove(const int & x, const int & y)

+ 3 - 2
client/gui/CursorHandler.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 class ICursor;
 class IImage;
@@ -113,7 +114,7 @@ class CursorHandler final
 {
 	std::shared_ptr<IImage> dndObject; //if set, overrides currentCursor
 
-	std::array<std::unique_ptr<CAnimation>, 4> cursors;
+	std::array<std::shared_ptr<CAnimation>, 4> cursors;
 
 	bool showing;
 
@@ -143,7 +144,7 @@ public:
 	/// @param image Image to replace cursor with or nullptr to use the normal cursor.
 	void dragAndDropCursor(std::shared_ptr<IImage> image);
 
-	void dragAndDropCursor(std::string path, size_t index);
+	void dragAndDropCursor(const AnimationPath & path, size_t index);
 
 	/// Changes cursor to specified index
 	void set(Cursor::Default index);

+ 11 - 11
client/gui/InterfaceObjectConfigurable.cpp

@@ -31,7 +31,7 @@
 
 #include "../../lib//constants/StringConstants.h"
 #include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
 	InterfaceObjectConfigurable(used, offset)
@@ -110,7 +110,7 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 	{
 		if (!config["library"].isNull())
 		{
-			const JsonNode library(ResourceID(config["library"].String()));
+			const JsonNode library(JsonPath::fromJson(config["library"]));
 			loadCustomBuilders(library);
 		}
 
@@ -238,7 +238,7 @@ PlayerColor InterfaceObjectConfigurable::readPlayerColor(const JsonNode & config
 {
 	logGlobal->debug("Reading PlayerColor");
 	if(!config.isNull() && config.isString())
-		return PlayerColor(vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, config.String()));
+		return PlayerColor::decode(config.String());
 	
 	logGlobal->debug("Unknown PlayerColor attribute");
 	return PlayerColor::CANNOT_DETERMINE;
@@ -305,7 +305,7 @@ EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CPicture");
-	auto image = config["image"].String();
+	auto image = ImagePath::fromJson(config["image"]);
 	auto position = readPosition(config["position"]);
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
 	if(!config["visible"].isNull())
@@ -369,7 +369,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 {
 	logGlobal->debug("Building widget CToggleButton");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto button = std::make_shared<CToggleButton>(position, image, help);
 	if(!config["items"].isNull())
@@ -395,7 +395,7 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 {
 	logGlobal->debug("Building widget CButton");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto button = std::make_shared<CButton>(position, image, help);
 	if(!config["items"].isNull())
@@ -522,7 +522,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 {
 	logGlobal->debug("Building widget CAnimImage");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	int group = config["group"].isNull() ? 0 : config["group"].Integer();
 	int frame = config["frame"].isNull() ? 0 : config["frame"].Integer();
 	return std::make_shared<CAnimImage>(image, frame, group, position.x, position.y);
@@ -531,7 +531,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = config["image"].String();
+	auto image = ImagePath::fromJson(config["image"]);
 	auto rect = readRect(config["rect"]);
 	auto playerColor = readPlayerColor(config["color"]);
 	if(playerColor.isValidPlayer())
@@ -546,7 +546,7 @@ std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonN
 {
 	logGlobal->debug("Building widget ComboBox");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto result = std::make_shared<ComboBox>(position, image, help, config["dropDown"]);
 	if(!config["items"].isNull())
@@ -573,7 +573,7 @@ std::shared_ptr<CTextInput> InterfaceObjectConfigurable::buildTextInput(const Js
 	logGlobal->debug("Building widget CTextInput");
 	auto rect = readRect(config["rect"]);
 	auto offset = readPosition(config["backgroundOffset"]);
-	auto bgName = config["background"].String();
+	auto bgName = ImagePath::fromJson(config["background"]);
 	auto result = std::make_shared<CTextInput>(rect, offset, bgName, 0);
 	if(!config["alignment"].isNull())
 		result->alignment = readTextAlignment(config["alignment"]);
@@ -664,7 +664,7 @@ std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const
 {
 	logGlobal->debug("Building widget CShowableAnim");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	ui8 flags = 0;
 	if(!config["repeat"].Bool())
 		flags |= CShowableAnim::EFlags::PLAY_ONCE;

二進制
client/icons/vcmiclient.1024x1024.png


二進制
client/icons/vcmiclient.128x128.png


二進制
client/icons/vcmiclient.16x16.png


二進制
client/icons/vcmiclient.2048x2048.png


二進制
client/icons/vcmiclient.22x22.png


二進制
client/icons/vcmiclient.256x256.png


二進制
client/icons/vcmiclient.32x32.png


二進制
client/icons/vcmiclient.48x48.png


二進制
client/icons/vcmiclient.512x512.png


二進制
client/icons/vcmiclient.64x64.png


File diff suppressed because it is too large
+ 134 - 298
client/icons/vcmiclient.svg


+ 0 - 238
client/icons/vcmiclient.xpm

@@ -1,238 +0,0 @@
-/* XPM */
-static char *x[] = {
-/* columns rows colors chars-per-pixel */
-"32 32 200 2 ",
-"   c gray11",
-".  c #161616",
-"X  c #2C0E00",
-"o  c #2D1003",
-"O  c #25140C",
-"+  c #28130A",
-"@  c #311305",
-"#  c #331609",
-"$  c #34190C",
-"%  c #391C0D",
-"&  c #241610",
-"*  c #241A16",
-"=  c #251E1A",
-"-  c #2C1C14",
-";  c #391E11",
-":  c #2A241D",
-">  c #20201F",
-",  c #3D2112",
-"<  c #38231B",
-"1  c #3C351E",
-"2  c #242323",
-"3  c #2C2B2B",
-"4  c #282625",
-"5  c #332E20",
-"6  c #362D29",
-"7  c #302620",
-"8  c #363122",
-"9  c #3C3521",
-"0  c #3E352C",
-"q  c #36302D",
-"w  c #353433",
-"e  c #3D3B3B",
-"r  c #3B3431",
-"t  c #412516",
-"y  c #462A1B",
-"u  c #492E1E",
-"i  c #45271B",
-"p  c #443A1B",
-"a  c #4A3F1C",
-"s  c #4A2F20",
-"d  c #4D3323",
-"f  c #423B24",
-"g  c #48372E",
-"h  c #483F25",
-"j  c #503627",
-"k  c #513728",
-"l  c #543B2C",
-"z  c #593D2F",
-"x  c #483D37",
-"c  c #473C37",
-"v  c #573E30",
-"b  c #583F31",
-"n  c #5A4C1D",
-"m  c #63531C",
-"M  c #6F5C18",
-"N  c #725D13",
-"B  c #7D6515",
-"V  c #4E4426",
-"C  c #514623",
-"Z  c #44423F",
-"A  c #5C4234",
-"S  c #5A4438",
-"D  c #625220",
-"F  c #6C5A24",
-"G  c #6B5A29",
-"H  c #715E26",
-"J  c #674D3F",
-"K  c #604537",
-"L  c #7A6525",
-"P  c #746129",
-"I  c #444242",
-"U  c #494645",
-"Y  c #4C4B4A",
-"T  c #4A4845",
-"R  c #554A46",
-"E  c #5B4D45",
-"W  c #514742",
-"Q  c #555352",
-"!  c #595757",
-"~  c #5D5B5A",
-"^  c #694E40",
-"/  c #6B5549",
-"(  c #775D4F",
-")  c #705648",
-"_  c #6B5F58",
-"`  c #755E52",
-"'  c #62605E",
-"]  c #7E6557",
-"[  c #7A665A",
-"{  c #666564",
-"}  c #696766",
-"|  c #6D6B6A",
-" . c #6A6865",
-".. c #7B6B62",
-"X. c #726D69",
-"o. c #7A7674",
-"O. c #7D7B7B",
-"+. c #767471",
-"@. c #95750A",
-"#. c #98770A",
-"$. c #846B1B",
-"%. c #876D17",
-"&. c #8E731E",
-"*. c #93761D",
-"=. c #96781C",
-"-. c #9B7B10",
-";. c #A07F0F",
-":. c #A07D10",
-">. c #836C22",
-",. c #866F29",
-"<. c #8C7324",
-"1. c #997D22",
-"2. c #967A28",
-"3. c #826A5D",
-"4. c #80675A",
-"5. c #856D60",
-"6. c #897164",
-"7. c #8C7569",
-"8. c #8F786C",
-"9. c #927C6F",
-"0. c #827D79",
-"q. c #937E72",
-"w. c #A7840F",
-"e. c #AE8A16",
-"r. c #A5831A",
-"t. c #B08C1C",
-"y. c #B79015",
-"u. c #BD961A",
-"i. c #A88825",
-"p. c #B08E23",
-"a. c #BB9624",
-"s. c #BB9626",
-"d. c #C69D1B",
-"f. c #C29B22",
-"g. c #C29C2A",
-"h. c #CCA220",
-"j. c #CDA42A",
-"k. c #D9AD26",
-"l. c #D2A726",
-"z. c #E6B726",
-"x. c #86827D",
-"c. c #8A847F",
-"v. c #978175",
-"b. c #94867D",
-"n. c #9B867A",
-"m. c #9E897D",
-"M. c #998377",
-"N. c #848383",
-"B. c #8A8680",
-"V. c #8F8B86",
-"C. c #9B8D85",
-"Z. c #938E89",
-"A. c #918984",
-"S. c #96928E",
-"D. c #9A9693",
-"F. c #9C9996",
-"G. c #9D9B99",
-"H. c #939292",
-"J. c #A28E82",
-"K. c #A69287",
-"L. c #A5948A",
-"P. c #AC988D",
-"I. c #A9958A",
-"U. c #A69C96",
-"Y. c #AD9C93",
-"T. c #A29D99",
-"R. c #A89F99",
-"E. c #B19E93",
-"W. c #A5A29F",
-"Q. c #B3A196",
-"!. c #B8A69C",
-"~. c #B9A69C",
-"^. c #A4A3A2",
-"/. c #A9A6A3",
-"(. c #ADABA9",
-"). c #ABA8A5",
-"_. c #BCABA1",
-"`. c #B2AEAC",
-"'. c #B5A9A3",
-"]. c #B3B2AF",
-"[. c #B3B2B2",
-"{. c #B9B5B3",
-"}. c #BAB9B9",
-"|. c #BBB9B6",
-" X c #C0AFA5",
-".X c #C4B3AA",
-"XX c #C8B8AE",
-"oX c #C1BFBE",
-"OX c #CDBDB4",
-"+X c #CCC9C7",
-"@X c #CFCCCB",
-"#X c #C5C3C1",
-"$X c #D3D1CF",
-"%X c #D6D3D2",
-"&X c #DDDBDA",
-"*X c #EDECEB",
-"=X c #E4E3E3",
-"-X c #F3F3F3",
-";X c #F9F9F9",
-":X c None",
-/* pixels */
-":X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X",
-":X:X:XO.V.| Q :X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:XZ Q ' T :X:X",
-":X:XI G.(.}.}.}.(.^.H.N.O.| } ~ ! ' { { } X.o.0.B.x.x.x.x.x.:X:X",
-":X:X3 N.H.^.[.}.}.}.}.}.}.}.}.[.`.(./.^.T.T.D.S.Z.V.B.x.x.o.:X:X",
-":X:X:X| O. .^ ..b.D./.[.{.}.}.}.{.[.`.(.^.F.S.D.W.'.'.c.c.' :X:X",
-":X:X:X! { c % s b ^ ( 5.7.q.b.C.C.L.L.I.Y.E.~. X XXXOXZ.V.~ :X:X",
-":X:X:XU ~ c X @ t j K ) [ 3.6.7.q.b.n.J.L.P.E.E.~. XXXZ.D.Y :X:X",
-":X:X:Xw Q Z X X X % y v ^ ( ] 5.6.8.q.M.C.J.I.E.Q.~.~.F.F.:X:X:X",
-":X:X:X4 Y U X X X X @ t d A ) ] 3.6.7.9.q.n.J.K.P.E.W.W.D.:X:X:X",
-":X:X:X:XI U @ X X X X X % i z ^ ( 4.3.6.8.q.M.n.m.I.U.(.x.:X:X:X",
-":X    . w 3 * + X + O & & & - d K g x Z ^ 7...U E R N.N.e   :X:X",
-":XG j.9 2 ,.s.* + = s.j.j.j.P * i F j.j.0 [ R i.l.i.' Y g.2.:X:X",
-":XH z.f 2 2.k.* * p.f.,.,.2.l.G X L z.z.8 x q p.z.s.{ Y k.i.:X:X",
-":Xh i.f.>.d.L : = h.1.O X : L V X H k.p.a.4 <.f.a.p.{ e h.1.:X:X",
-":X:XF d.<.d.w k = d.&.+ X X X X o F d.B d.2 <.t.<.r.{ w u.*.:X:X",
-":X:XF y.$.y.I ' : e.$.- # & = * X D y.V =.e.e.D %.=.{ 2 e.$.:X:X",
-":X:X9 %.w.n Q G.: -.B : : 1 w.n X n w.: M w.-.2 B %.Y > -.B :X:X",
-":X:X:Xm #.  -X-X6 a @.@.@.#.M 5 o n @.: 1 a a = N B w   @.N :X:X",
-":X:X:X8 a   #X;XC.7 p p p p 8 , # 8 p * X X X + 1 p     p 9 :X:X",
-":X:X:X:X:X:XO.*X@Xk d h u u u y t % # o X X X < Q Y :X:X:X:X:X:X",
-":X:X:X:X:X:X:X&X*X[ d j j d s u u t , % @ o X r Y 3 :X:X:X:X:X:X",
-":X:X:X:X:X:X:XF.&XW.z k k j j s d u y , , # - c I :X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X$X%X/ z l k k d d d u u t , 6 w 3 :X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:[email protected] l l l k d d d d u S  .Y :X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:XI |.#X..A z l l k j j d v F.F.! :X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:XO.{.]./ A z l z l j z {.=X}.:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:XF.].).` A A z l S `.-X;XO.:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:Xe G.).].V.....b.$X=X*X^.:X:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:X:XT D.W.(.{.}.+X%X&X/.:X:X:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:X:X:Xe x.T./.].|.}.O.:X:X:X:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:X:X:X:X:XY | o. .:X:X:X:X:X:X:X:X:X:X:X:X:X:X",
-":X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X:X"
-};

二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


二進制
client/ios/Images.xcassets/AppIcon.appiconset/[email protected]


Some files were not shown because too many files changed in this diff