ソースを参照

Merge remote-tracking branch 'upstream/develop' into develop

Xilmi 1 年間 前
コミット
fa2f883361
100 ファイル変更1596 行追加1558 行削除
  1. 31 11
      AI/BattleAI/StackWithBonuses.h
  2. 12 0
      AI/Nullkiller/AIGateway.cpp
  3. 9 0
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  4. 23 2
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  5. 8 5
      AI/Nullkiller/Pathfinding/AINodeStorage.h
  6. 39 13
      AI/Nullkiller/Pathfinding/GraphPaths.cpp
  7. 1 1
      AI/Nullkiller/Pathfinding/GraphPaths.h
  8. 10 0
      ChangeLog.md
  9. 19 19
      Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json
  10. 19 19
      Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json
  11. 3 3
      Mods/vcmi/config/vcmi/portuguese.json
  12. 2 0
      client/CMT.cpp
  13. 2 0
      client/CMakeLists.txt
  14. 23 15
      client/CPlayerInterface.cpp
  15. 7 0
      client/Client.cpp
  16. 7 1
      client/Client.h
  17. 19 13
      client/ClientCommandManager.cpp
  18. 7 2
      client/HeroMovementController.cpp
  19. 1 1
      client/NetPacksClient.cpp
  20. 4 3
      client/adventureMap/AdventureMapInterface.cpp
  21. 0 1
      client/adventureMap/AdventureMapInterface.h
  22. 18 37
      client/adventureMap/AdventureMapWidget.cpp
  23. 4 13
      client/adventureMap/AdventureMapWidget.h
  24. 3 3
      client/adventureMap/CResDataBar.cpp
  25. 1 1
      client/adventureMap/CResDataBar.h
  26. 7 2
      client/adventureMap/MapAudioPlayer.cpp
  27. 25 19
      client/battle/BattleActionsController.cpp
  28. 3 0
      client/battle/BattleActionsController.h
  29. 2 3
      client/battle/BattleAnimationClasses.cpp
  30. 30 70
      client/battle/BattleFieldController.cpp
  31. 0 3
      client/battle/BattleFieldController.h
  32. 18 2
      client/battle/BattleInterface.cpp
  33. 0 1
      client/battle/BattleInterface.h
  34. 92 28
      client/battle/BattleInterfaceClasses.cpp
  35. 22 3
      client/battle/BattleInterfaceClasses.h
  36. 21 35
      client/battle/BattleObstacleController.cpp
  37. 3 3
      client/battle/BattleObstacleController.h
  38. 2 2
      client/battle/BattleProjectileController.cpp
  39. 3 3
      client/battle/BattleSiegeController.cpp
  40. 115 21
      client/battle/BattleWindow.cpp
  41. 12 1
      client/battle/BattleWindow.h
  42. 3 6
      client/battle/CreatureAnimation.cpp
  43. 0 1
      client/battle/CreatureAnimation.h
  44. 1 1
      client/globalLobby/GlobalLobbyClient.cpp
  45. 1 1
      client/globalLobby/GlobalLobbyInviteWindow.cpp
  46. 1 1
      client/globalLobby/GlobalLobbyLoginWindow.cpp
  47. 1 1
      client/globalLobby/GlobalLobbyRoomWindow.cpp
  48. 1 1
      client/globalLobby/GlobalLobbyServerSetup.cpp
  49. 9 0
      client/gui/CIntObject.h
  50. 5 9
      client/gui/CursorHandler.cpp
  51. 2 2
      client/gui/InterfaceObjectConfigurable.cpp
  52. 13 0
      client/gui/Shortcut.h
  53. 13 0
      client/gui/ShortcutHandler.cpp
  54. 5 8
      client/lobby/CSelectionBase.cpp
  55. 1 2
      client/lobby/CSelectionBase.h
  56. 1 2
      client/lobby/OptionsTab.cpp
  57. 6 9
      client/lobby/SelectionTab.cpp
  58. 2 1
      client/lobby/SelectionTab.h
  59. 0 1
      client/mainmenu/CMainMenu.h
  60. 21 32
      client/mapView/MapRenderer.cpp
  61. 1 5
      client/mapView/MapViewCache.cpp
  62. 12 1
      client/media/CMusicHandler.cpp
  63. 8 8
      client/media/CVideoHandler.cpp
  64. 69 229
      client/render/CAnimation.cpp
  65. 14 31
      client/render/CAnimation.h
  66. 1 47
      client/render/CDefFile.cpp
  67. 3 3
      client/render/Canvas.cpp
  68. 21 107
      client/render/Graphics.cpp
  69. 3 13
      client/render/Graphics.h
  70. 26 15
      client/render/IImage.h
  71. 0 2
      client/render/IImageLoader.h
  72. 11 6
      client/render/IRenderHandler.h
  73. 56 0
      client/render/ImageLocator.cpp
  74. 31 0
      client/render/ImageLocator.h
  75. 1 1
      client/renderSDL/CBitmapFont.cpp
  76. 1 1
      client/renderSDL/CBitmapHanFont.cpp
  77. 1 1
      client/renderSDL/CursorHardware.cpp
  78. 0 1
      client/renderSDL/CursorHardware.h
  79. 1 1
      client/renderSDL/CursorSoftware.cpp
  80. 0 1
      client/renderSDL/CursorSoftware.h
  81. 213 9
      client/renderSDL/RenderHandler.cpp
  82. 32 5
      client/renderSDL/RenderHandler.h
  83. 152 153
      client/renderSDL/SDLImage.cpp
  84. 58 30
      client/renderSDL/SDLImage.h
  85. 3 3
      client/renderSDL/SDLImageLoader.cpp
  86. 6 2
      client/renderSDL/SDLImageLoader.h
  87. 49 187
      client/renderSDL/SDL_Extensions.cpp
  88. 5 12
      client/renderSDL/SDL_Extensions.h
  89. 9 143
      client/renderSDL/SDL_PixelAccess.h
  90. 10 4
      client/widgets/Buttons.cpp
  91. 2 0
      client/widgets/Buttons.h
  92. 2 3
      client/widgets/CGarrisonInt.cpp
  93. 39 52
      client/widgets/Images.cpp
  94. 19 8
      client/widgets/Images.h
  95. 2 3
      client/widgets/MiscWidgets.cpp
  96. 0 1
      client/widgets/ObjectLists.h
  97. 8 8
      client/windows/CCastleInterface.cpp
  98. 5 11
      client/windows/CExchangeWindow.cpp
  99. 2 3
      client/windows/CHeroOverview.cpp
  100. 7 11
      client/windows/CHeroWindow.cpp

+ 31 - 11
AI/BattleAI/StackWithBonuses.h

@@ -21,23 +21,43 @@
 class HypotheticBattle;
 
 ///Fake random generator, used by AI to evaluate random server behavior
-class RNGStub : public vstd::RNG
+class RNGStub final : public vstd::RNG
 {
 public:
-	vstd::TRandI64 getInt64Range(int64_t lower, int64_t upper) override
+	virtual int nextInt() override
 	{
-		return [=]()->int64_t
-		{
-			return (lower + upper)/2;
-		};
+		return 0;
 	}
 
-	vstd::TRand getDoubleRange(double lower, double upper) override
+	int nextBinomialInt(int coinsCount, double coinChance) override
 	{
-		return [=]()->double
-		{
-			return (lower + upper)/2;
-		};
+		return coinsCount * coinChance;
+	}
+
+	int nextInt(int lower, int upper) override
+	{
+		return (lower + upper) / 2;
+	}
+	int64_t nextInt64(int64_t lower, int64_t upper) override
+	{
+		return (lower + upper) / 2;
+	}
+	double nextDouble(double lower, double upper) override
+	{
+		return (lower + upper) / 2;
+	}
+
+	int nextInt(int upper) override
+	{
+		return upper / 2;
+	}
+	int64_t nextInt64(int64_t upper) override
+	{
+		return upper / 2;
+	}
+	double nextDouble(double upper) override
+	{
+		return upper / 2;
 	}
 };
 

+ 12 - 0
AI/Nullkiller/AIGateway.cpp

@@ -28,6 +28,7 @@
 #include "../../lib/networkPacks/StackLocation.h"
 #include "../../lib/battle/BattleStateInfoForRetreat.h"
 #include "../../lib/battle/BattleInfo.h"
+#include "../../lib/CPlayerState.h"
 
 #include "AIGateway.h"
 #include "Goals/Goals.h"
@@ -1166,6 +1167,17 @@ void AIGateway::battleEnd(const BattleID & battleID, const BattleResult * br, Qu
 	battlename.clear();
 
 	CAdventureAI::battleEnd(battleID, br, queryID);
+
+	// gosolo
+	if(queryID != QueryID::NONE && myCb->getPlayerState(playerID)->isHuman())
+	{
+		status.addQuery(queryID, "Confirm battle query");
+
+		requestActionASAP([=]()
+			{
+				answerQuery(queryID, 0);
+			});
+	}
 }
 
 void AIGateway::waitTillFree()

+ 9 - 0
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -26,7 +26,12 @@ ExecuteHeroChain::ExecuteHeroChain(const AIPath & path, const CGObjectInstance *
 	if(obj)
 	{
 		objid = obj->id.getNum();
+
+#if NKAI_TRACE_LEVEL >= 1
+		targetName = obj->getObjectName() + tile.toString();
+#else
 		targetName = obj->typeName + tile.toString();
+#endif
 	}
 	else
 	{
@@ -260,7 +265,11 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 std::string ExecuteHeroChain::toString() const
 {
+#if NKAI_TRACE_LEVEL >= 1
+	return "ExecuteHeroChain " + targetName + " by " + chainPath.toString();
+#else
 	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated();
+#endif
 }
 
 bool ExecuteHeroChain::moveHeroToTile(AIGateway * ai, const CGHeroInstance * hero, const int3 & tile)

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

@@ -25,7 +25,7 @@ namespace NKAI
 {
 
 std::shared_ptr<boost::multi_array<AIPathNode, 4>> AISharedStorage::shared;
-uint64_t AISharedStorage::version = 0;
+uint32_t AISharedStorage::version = 0;
 boost::mutex AISharedStorage::locker;
 std::set<int3> committedTiles;
 std::set<int3> committedTilesInitial;
@@ -224,7 +224,6 @@ std::vector<CGPathNode *> AINodeStorage::getInitialNodes()
 
 		AIPathNode * initialNode = allocated.value();
 
-		initialNode->inPQ = false;
 		initialNode->pq = nullptr;
 		initialNode->turns = actor->initialTurn;
 		initialNode->moveRemains = actor->initialMovement;
@@ -1386,6 +1385,28 @@ void AINodeStorage::calculateChainInfo(std::vector<AIPath> & paths, const int3 &
 		path.armyLoss = node.armyLoss;
 		path.targetObjectDanger = evaluateDanger(pos, path.targetHero, !node.actor->allowBattle);
 
+		if(path.targetObjectDanger > 0)
+		{
+			if(node.theNodeBefore)
+			{
+				auto prevNode = getAINode(node.theNodeBefore);
+
+				if(node.coord == prevNode->coord && node.actor->hero == prevNode->actor->hero)
+				{
+					paths.pop_back();
+					continue;
+				}
+				else
+				{
+					path.armyLoss = prevNode->armyLoss;
+				}
+			}
+			else
+			{
+				path.armyLoss = 0;
+			}
+		}
+
 		path.targetObjectArmyLoss = evaluateArmyLoss(
 			path.targetHero,
 			getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy),

+ 8 - 5
AI/Nullkiller/Pathfinding/AINodeStorage.h

@@ -44,14 +44,17 @@ enum DayFlags : ui8
 
 struct AIPathNode : public CGPathNode
 {
+	std::shared_ptr<const SpecialAction> specialAction;
+
+	const AIPathNode * chainOther;
+	const ChainActor * actor;
+
 	uint64_t danger;
 	uint64_t armyLoss;
+	uint32_t version;
+
 	int16_t manaCost;
 	DayFlags dayFlags;
-	const AIPathNode * chainOther;
-	std::shared_ptr<const SpecialAction> specialAction;
-	const ChainActor * actor;
-	uint64_t version;
 
 	void addSpecialAction(std::shared_ptr<const SpecialAction> action);
 
@@ -152,7 +155,7 @@ class AISharedStorage
 	std::shared_ptr<boost::multi_array<AIPathNode, 4>> nodes;
 public:
 	static boost::mutex locker;
-	static uint64_t version;
+	static uint32_t version;
 
 	AISharedStorage(int3 mapSize);
 	~AISharedStorage();

+ 39 - 13
AI/Nullkiller/Pathfinding/GraphPaths.cpp

@@ -160,7 +160,7 @@ void GraphPaths::dumpToLog() const
 							node.previous.coord.toString(),
 							tile.first.toString(),
 							node.cost,
-							node.danger);
+							node.linkDanger);
 					}
 
 					logBuilder.addLine(node.previous.coord, tile.first);
@@ -169,14 +169,17 @@ void GraphPaths::dumpToLog() const
 		});
 }
 
-bool GraphPathNode::tryUpdate(const GraphPathNodePointer & pos, const GraphPathNode & prev, const ObjectLink & link)
+bool GraphPathNode::tryUpdate(
+	const GraphPathNodePointer & pos,
+	const GraphPathNode & prev,
+	const ObjectLink & link)
 {
 	auto newCost = prev.cost + link.cost;
 
 	if(newCost < cost)
 	{
 		previous = pos;
-		danger = prev.danger + link.danger;
+		linkDanger = link.danger;
 		cost = newCost;
 
 		return true;
@@ -199,7 +202,7 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 
 		std::vector<GraphPathNodePointer> tilesToPass;
 
-		uint64_t danger = node.danger;
+		uint64_t danger = node.linkDanger;
 		float cost = node.cost;
 		bool allowBattle = false;
 
@@ -212,13 +215,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 			if(currentTile == pathNodes.end())
 				break;
 
-			auto currentNode = currentTile->second[current.nodeType];
+			auto & currentNode = currentTile->second[current.nodeType];
 
 			if(!currentNode.previous.valid())
 				break;
 
 			allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
-			vstd::amax(danger, currentNode.danger);
+			vstd::amax(danger, currentNode.linkDanger);
 			vstd::amax(cost, currentNode.cost);
 
 			tilesToPass.push_back(current);
@@ -239,9 +242,13 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 			if(path.targetHero != hero)
 				continue;
 
-			for(auto graphTile = tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
+			uint64_t loss = 0;
+			uint64_t strength = getHeroArmyStrengthWithCommander(path.targetHero, path.heroArmy);
+
+			for(auto graphTile = ++tilesToPass.rbegin(); graphTile != tilesToPass.rend(); graphTile++)
 			{
 				AIPathNodeInfo n;
+				auto & node = getNode(*graphTile);
 
 				n.coord = graphTile->coord;
 				n.cost = cost;
@@ -249,7 +256,21 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 				n.danger = danger;
 				n.targetHero = hero;
 				n.parentIndex = -1;
-				n.specialAction = getNode(*graphTile).specialAction;
+				n.specialAction = node.specialAction;
+				
+				if(node.linkDanger > 0)
+				{
+					auto additionalLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, strength, node.linkDanger);
+					loss += additionalLoss;
+
+					if(strength > additionalLoss)
+						strength -= additionalLoss;
+					else
+					{
+						strength = 0;
+						break;
+					}
+				}
 
 				if(n.specialAction)
 				{
@@ -264,7 +285,12 @@ void GraphPaths::addChainInfo(std::vector<AIPath> & paths, int3 tile, const CGHe
 				path.nodes.insert(path.nodes.begin(), n);
 			}
 
-			path.armyLoss += ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), danger);
+			if(strength == 0)
+			{
+				continue;
+			}
+
+			path.armyLoss += loss;
 			path.targetObjectDanger = ai->pathfinder->getStorage()->evaluateDanger(tile, path.targetHero, !allowBattle);
 			path.targetObjectArmyLoss = ai->pathfinder->getStorage()->evaluateArmyLoss(path.targetHero, path.heroArmy->getArmyStrength(), path.targetObjectDanger);
 
@@ -287,7 +313,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
 
 		std::vector<GraphPathNodePointer> tilesToPass;
 
-		uint64_t danger = targetNode.danger;
+		uint64_t danger = targetNode.linkDanger;
 		float cost = targetNode.cost;
 		bool allowBattle = false;
 
@@ -303,7 +329,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
 			auto currentNode = currentTile->second[current.nodeType];
 
 			allowBattle = allowBattle || currentNode.nodeType == GrapthPathNodeType::BATTLE;
-			vstd::amax(danger, currentNode.danger);
+			vstd::amax(danger, currentNode.linkDanger);
 			vstd::amax(cost, currentNode.cost);
 
 			tilesToPass.push_back(current);
@@ -341,7 +367,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
 			// final node
 			n.coord = tile;
 			n.cost = targetNode.cost;
-			n.danger = targetNode.danger;
+			n.danger = danger;
 			n.parentIndex = path.nodes.size();
 			path.nodes.push_back(n);
 
@@ -368,7 +394,7 @@ void GraphPaths::quickAddChainInfoWithBlocker(std::vector<AIPath> & paths, int3
 				n.coord = graphTile->coord;
 				n.cost = node.cost;
 				n.turns = static_cast<ui8>(node.cost);
-				n.danger = node.danger;
+				n.danger = danger;
 				n.specialAction = node.specialAction;
 				n.parentIndex = path.nodes.size();
 

+ 1 - 1
AI/Nullkiller/Pathfinding/GraphPaths.h

@@ -67,7 +67,7 @@ struct GraphPathNode
 	GrapthPathNodeType nodeType = GrapthPathNodeType::NORMAL;
 	GraphPathNodePointer previous;
 	float cost = BAD_COST;
-	uint64_t danger = 0;
+	uint64_t linkDanger = 0;
 	const CGObjectInstance * obj = nullptr;
 	std::shared_ptr<SpecialAction> specialAction;
 

+ 10 - 0
ChangeLog.md

@@ -1,3 +1,13 @@
+# 1.5.4 -> 1.5.5
+
+* Fixed crash when advancing to the next scenario in campaigns when the hero not transferring has a combination artefact that can be transferred to the next scenario.
+* Fixed game not updating information such as hero path and current music on new day
+* Changed default battle queue hotkey from Q to Z to match HD Mod / HotA
+* Changed default hotkey for finishing battle with quick combat from E to Z to match HD Mod / HotA
+* Creature casting now uses both F and G keyboard hotkeys
+* Shift+left click now directly opens the hero window when two heroes are in town
+* Fixed handling of alternative actions for creatures that have more than two potential actions, such as move, shoot, and cast spells.
+
 # 1.5.3 -> 1.5.4
 
 ### Stability

+ 19 - 19
Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json

@@ -2,33 +2,33 @@
 	"basepath" : "battle/rangeHighlights/green/",
 	"images" :
 	[
-		{ "frame" :  0, "file" : "empty.png"},						// 000001 -> 00 empty frame
+		{ "frame" :  0, "file" : "empty.png"},                                                              // 000001 -> 00 empty frame
 			
 		// load single edges
-		{ "frame" :  1, "file" : "topLeft.png"},                    //000001 -> 01 topLeft
-		{ "frame" :  2, "file" : "topLeft.png"},                    //000010 -> 02 topRight
-		{ "frame" :  3, "file" : "left.png"},                       //000100 -> 04 right
-		{ "frame" :  4, "file" : "topLeft.png"},                    //001000 -> 08 bottomRight
-		{ "frame" :  5, "file" : "topLeft.png"},                    //010000 -> 16 bottomLeft
-		{ "frame" :  6, "file" : "left.png"},                       //100000 -> 32 left
+		{ "frame" :  1, "file" : "topLeft.png"},                                                            //000001 -> 01 topLeft
+		{ "frame" :  2, "file" : "topLeft.png", "verticalFlip" : true },                                    //000010 -> 02 topRight
+		{ "frame" :  3, "file" : "left.png",    "verticalFlip" : true },                                    //000100 -> 04 right
+		{ "frame" :  4, "file" : "topLeft.png", "verticalFlip" : true, "horizontalFlip" : true },           //001000 -> 08 bottomRight
+		{ "frame" :  5, "file" : "topLeft.png", "horizontalFlip" : true },                                  //010000 -> 16 bottomLeft
+		{ "frame" :  6, "file" : "left.png"},                                                               //100000 -> 32 left
 		
 		// load double edges
-		{ "frame" :  7, "file" : "top.png"},                        //000011 -> 03 top
-		{ "frame" :  8, "file" : "top.png"},                        //011000 -> 24 bottom
-		{ "frame" :  9, "file" : "topLeftHalfCorner.png"},          //000110 -> 06 topRightHalfCorner
-		{ "frame" : 10, "file" : "topLeftHalfCorner.png"},          //001100 -> 12 bottomRightHalfCorner
-		{ "frame" : 11, "file" : "topLeftHalfCorner.png"},          //110000 -> 48 bottomLeftHalfCorner
-		{ "frame" : 12, "file" : "topLeftHalfCorner.png"},          //100001 -> 33 topLeftHalfCorner
+		{ "frame" :  7, "file" : "top.png"},                                                                //000011 -> 03 top
+		{ "frame" :  8, "file" : "top.png",               "horizontalFlip" : true },                        //011000 -> 24 bottom
+		{ "frame" :  9, "file" : "topLeftHalfCorner.png", "verticalFlip" : true },                          //000110 -> 06 topRightHalfCorner
+		{ "frame" : 10, "file" : "topLeftHalfCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //001100 -> 12 bottomRightHalfCorner
+		{ "frame" : 11, "file" : "topLeftHalfCorner.png", "horizontalFlip" : true },                        //110000 -> 48 bottomLeftHalfCorner
+		{ "frame" : 12, "file" : "topLeftHalfCorner.png"},                                                  //100001 -> 33 topLeftHalfCorner
 		
 		// load halves
-		{ "frame" : 13, "file" : "leftHalf.png"},                   //001110 -> 14 rightHalf
-		{ "frame" : 14, "file" : "leftHalf.png"},                   //110001 -> 49 leftHalf
+		{ "frame" : 13, "file" : "leftHalf.png", "verticalFlip" : true},                                    //001110 -> 14 rightHalf
+		{ "frame" : 14, "file" : "leftHalf.png"},                                                           //110001 -> 49 leftHalf
 		
 		// load corners
-		{ "frame" : 15, "file" : "topLeftCorner.png"},              //000111 -> 07 topRightCorner
-		{ "frame" : 16, "file" : "topLeftCorner.png"},              //011100 -> 28 bottomRightCorner
-		{ "frame" : 17, "file" : "topLeftCorner.png"},              //111000 -> 56 bottomLeftCorner
-		{ "frame" : 18, "file" : "topLeftCorner.png"}               //100011 -> 35 topLeftCorner
+		{ "frame" : 15, "file" : "topLeftCorner.png", "verticalFlip" : true },                              //000111 -> 07 topRightCorner
+		{ "frame" : 16, "file" : "topLeftCorner.png", "verticalFlip" : true, "horizontalFlip" : true },     //011100 -> 28 bottomRightCorner
+		{ "frame" : 17, "file" : "topLeftCorner.png", "horizontalFlip" : true },                            //111000 -> 56 bottomLeftCorner
+		{ "frame" : 18, "file" : "topLeftCorner.png"}                                                       //100011 -> 35 topLeftCorner
 	]
 }
 

+ 19 - 19
Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json

@@ -2,33 +2,33 @@
 	"basepath" : "battle/rangeHighlights/red/",
 	"images" :
 	[
-		{ "frame" :  0, "file" : "empty.png"},						// 000001 -> 00 empty frame
+		{ "frame" :  0, "file" : "empty.png"},                                                              // 000001 -> 00 empty frame
 			
 		// load single edges
-		{ "frame" :  1, "file" : "topLeft.png"},                    //000001 -> 01 topLeft
-		{ "frame" :  2, "file" : "topLeft.png"},                    //000010 -> 02 topRight
-		{ "frame" :  3, "file" : "left.png"},                       //000100 -> 04 right
-		{ "frame" :  4, "file" : "topLeft.png"},                    //001000 -> 08 bottomRight
-		{ "frame" :  5, "file" : "topLeft.png"},                    //010000 -> 16 bottomLeft
-		{ "frame" :  6, "file" : "left.png"},                       //100000 -> 32 left
+		{ "frame" :  1, "file" : "topLeft.png"},                                                            //000001 -> 01 topLeft
+		{ "frame" :  2, "file" : "topLeft.png", "verticalFlip" : true },                                    //000010 -> 02 topRight
+		{ "frame" :  3, "file" : "left.png",    "verticalFlip" : true },                                    //000100 -> 04 right
+		{ "frame" :  4, "file" : "topLeft.png", "verticalFlip" : true, "horizontalFlip" : true },           //001000 -> 08 bottomRight
+		{ "frame" :  5, "file" : "topLeft.png", "horizontalFlip" : true },                                  //010000 -> 16 bottomLeft
+		{ "frame" :  6, "file" : "left.png"},                                                               //100000 -> 32 left
 		
 		// load double edges
-		{ "frame" :  7, "file" : "top.png"},                        //000011 -> 03 top
-		{ "frame" :  8, "file" : "top.png"},                        //011000 -> 24 bottom
-		{ "frame" :  9, "file" : "topLeftHalfCorner.png"},          //000110 -> 06 topRightHalfCorner
-		{ "frame" : 10, "file" : "topLeftHalfCorner.png"},          //001100 -> 12 bottomRightHalfCorner
-		{ "frame" : 11, "file" : "topLeftHalfCorner.png"},          //110000 -> 48 bottomLeftHalfCorner
-		{ "frame" : 12, "file" : "topLeftHalfCorner.png"},          //100001 -> 33 topLeftHalfCorner
+		{ "frame" :  7, "file" : "top.png"},                                                                //000011 -> 03 top
+		{ "frame" :  8, "file" : "top.png",               "horizontalFlip" : true },                        //011000 -> 24 bottom
+		{ "frame" :  9, "file" : "topLeftHalfCorner.png", "verticalFlip" : true },                          //000110 -> 06 topRightHalfCorner
+		{ "frame" : 10, "file" : "topLeftHalfCorner.png", "verticalFlip" : true, "horizontalFlip" : true }, //001100 -> 12 bottomRightHalfCorner
+		{ "frame" : 11, "file" : "topLeftHalfCorner.png", "horizontalFlip" : true },                        //110000 -> 48 bottomLeftHalfCorner
+		{ "frame" : 12, "file" : "topLeftHalfCorner.png"},                                                  //100001 -> 33 topLeftHalfCorner
 		
 		// load halves
-		{ "frame" : 13, "file" : "leftHalf.png"},                   //001110 -> 14 rightHalf
-		{ "frame" : 14, "file" : "leftHalf.png"},                   //110001 -> 49 leftHalf
+		{ "frame" : 13, "file" : "leftHalf.png", "verticalFlip" : true},                                    //001110 -> 14 rightHalf
+		{ "frame" : 14, "file" : "leftHalf.png"},                                                           //110001 -> 49 leftHalf
 		
 		// load corners
-		{ "frame" : 15, "file" : "topLeftCorner.png"},              //000111 -> 07 topRightCorner
-		{ "frame" : 16, "file" : "topLeftCorner.png"},              //011100 -> 28 bottomRightCorner
-		{ "frame" : 17, "file" : "topLeftCorner.png"},              //111000 -> 56 bottomLeftCorner
-		{ "frame" : 18, "file" : "topLeftCorner.png"}               //100011 -> 35 topLeftCorner
+		{ "frame" : 15, "file" : "topLeftCorner.png", "verticalFlip" : true },                              //000111 -> 07 topRightCorner
+		{ "frame" : 16, "file" : "topLeftCorner.png", "verticalFlip" : true, "horizontalFlip" : true },     //011100 -> 28 bottomRightCorner
+		{ "frame" : 17, "file" : "topLeftCorner.png", "horizontalFlip" : true },                            //111000 -> 56 bottomLeftCorner
+		{ "frame" : 18, "file" : "topLeftCorner.png"}                                                       //100011 -> 35 topLeftCorner
 	]
 }
 

+ 3 - 3
Mods/vcmi/config/vcmi/portuguese.json

@@ -33,7 +33,7 @@
 	
 	"vcmi.heroOverview.startingArmy" : "Unidades Iniciais",
 	"vcmi.heroOverview.warMachine" : "Máquinas de Guerra",
-	"vcmi.heroOverview.secondarySkills" : "Habilidades Secundárias",
+	"vcmi.heroOverview.secondarySkills" : "Habilid. Secundárias",
 	"vcmi.heroOverview.spells" : "Feitiços",
 	
 	"vcmi.radialWheel.mergeSameUnit" : "Mesclar criaturas iguais",
@@ -259,7 +259,7 @@
 	"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
 	"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
 	
-	"vcmi.battleWindow.killed" : "Eliminados",
+	"vcmi.battleWindow.killed" : "Mortos",
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s morreram por tiros precisos!",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s morreu com um tiro preciso!",
 	"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s morreram por tiros precisos!",
@@ -612,7 +612,7 @@
 	"core.bonus.SPELL_LIKE_ATTACK.description" : "Ataques com ${subtype.spell}",
 	"core.bonus.SPELL_RESISTANCE_AURA.name" : "Aura de Resistência a Feitiços",
 	"core.bonus.SPELL_RESISTANCE_AURA.description" : "Pilhas próximas ganham ${val}% de resistência a magia",
-	"core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardiões",
+	"core.bonus.SUMMON_GUARDIANS.name" : "Invocar Guardas",
 	"core.bonus.SUMMON_GUARDIANS.description" : "No início da batalha, invoca ${subtype.creature} (${val}%)",
 	"core.bonus.SYNERGY_TARGET.name" : "Alvo Sinergizável",
 	"core.bonus.SYNERGY_TARGET.description" : "Esta criatura é vulnerável ao efeito de sinergia",

+ 2 - 0
client/CMT.cpp

@@ -28,6 +28,7 @@
 #include "windows/CMessage.h"
 #include "windows/InfoWindows.h"
 #include "render/IScreenHandler.h"
+#include "render/IRenderHandler.h"
 #include "render/Graphics.h"
 
 #include "../lib/CConfigHandler.h"
@@ -347,6 +348,7 @@ int main(int argc, char * argv[])
 	{
 		pomtime.getDiff();
 		graphics = new Graphics(); // should be before curh
+		GH.renderHandler().onLibraryLoadingFinished(CGI);
 
 		CCS->curh = new CursorHandler();
 		logGlobal->info("Screen handler: %d ms", pomtime.getDiff());

+ 2 - 0
client/CMakeLists.txt

@@ -88,6 +88,7 @@ set(client_SRCS
 	render/Colors.cpp
 	render/Graphics.cpp
 	render/IFont.cpp
+	render/ImageLocator.cpp
 
 	renderSDL/CBitmapFont.cpp
 	renderSDL/CBitmapHanFont.cpp
@@ -287,6 +288,7 @@ set(client_HEADERS
 	render/IFont.h
 	render/IImage.h
 	render/IImageLoader.h
+	render/ImageLocator.h
 	render/IRenderHandler.h
 	render/IScreenHandler.h
 

+ 23 - 15
client/CPlayerInterface.cpp

@@ -70,6 +70,7 @@
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
 #include "../lib/CPlayerState.h"
+#include "../lib/CRandomGenerator.h"
 #include "../lib/CStack.h"
 #include "../lib/CStopWatch.h"
 #include "../lib/CThreadHelper.h"
@@ -413,8 +414,9 @@ void CPlayerInterface::heroVisit(const CGHeroInstance * visitor, const CGObjectI
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	if(start && visitedObj)
 	{
-		if(visitedObj->getVisitSound())
-			CCS->soundh->playSound(visitedObj->getVisitSound().value());
+		auto visitSound = visitedObj->getVisitSound(CRandomGenerator::getDefault());
+		if (visitSound)
+			CCS->soundh->playSound(visitSound.value());
 	}
 }
 
@@ -440,18 +442,20 @@ void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, Prim
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	if (which == PrimarySkill::EXPERIENCE)
 	{
-		for(auto ctw : GH.windows().findWindows<CMarketWindow>())
-			ctw->updateHero();
+		for(auto ctw : GH.windows().findWindows<IMarketHolder>())
+			ctw->updateExperience();
 	}
 	else
+	{
 		adventureInt->onHeroChanged(hero);
+	}
 }
 
 void CPlayerInterface::heroSecondarySkillChanged(const CGHeroInstance * hero, int which, int val)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	for (auto cuw : GH.windows().findWindows<CUniversityWindow>())
-		cuw->redraw();
+	for (auto cuw : GH.windows().findWindows<IMarketHolder>())
+		cuw->updateSecondarySkills();
 }
 
 void CPlayerInterface::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -470,8 +474,8 @@ void CPlayerInterface::heroMovePointsChanged(const CGHeroInstance * hero)
 void CPlayerInterface::receivedResource()
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	for (auto mw : GH.windows().findWindows<CMarketWindow>())
-		mw->updateResource();
+	for (auto mw : GH.windows().findWindows<IMarketHolder>())
+		mw->updateResources();
 
 	GH.windows().totalRedraw();
 }
@@ -1141,9 +1145,9 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 		const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj));
 		if(t)
 		{
-			std::shared_ptr<CAnimation> a = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITPA"));
-			a->preload();
-			images.push_back(a->getImage(t->town->clientInfo.icons[t->hasFort()][false] + 2)->scaleFast(Point(35, 23)));
+			auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
+			image->scaleFast(Point(35, 23));
+			images.push_back(image);
 		}
 	}
 
@@ -1410,10 +1414,14 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 void CPlayerInterface::objectRemoved(const CGObjectInstance * obj, const PlayerColor & initiator)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	if(playerID == initiator && obj->getRemovalSound())
+	if(playerID == initiator)
 	{
-		waitWhileDialog();
-		CCS->soundh->playSound(obj->getRemovalSound().value());
+		auto removalSound = obj->getRemovalSound(CRandomGenerator::getDefault());
+		if (removalSound)
+		{
+			waitWhileDialog();
+			CCS->soundh->playSound(removalSound.value());
+		}
 	}
 	CGI->mh->waitForOngoingAnimations();
 
@@ -1669,7 +1677,7 @@ void CPlayerInterface::showHillFortWindow(const CGObjectInstance *object, const
 void CPlayerInterface::availableArtifactsChanged(const CGBlackMarket * bm)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	for (auto cmw : GH.windows().findWindows<CMarketWindow>())
+	for (auto cmw : GH.windows().findWindows<IMarketHolder>())
 		cmw->updateArtifacts();
 }
 

+ 7 - 0
client/Client.cpp

@@ -556,6 +556,13 @@ void CClient::invalidatePaths()
 	pathCache.clear();
 }
 
+vstd::RNG & CClient::getRandomGenerator()
+{
+	// Client should use CRandomGenerator::getDefault() for UI logic
+	// Gamestate should never call this method on client!
+	throw std::runtime_error("Illegal access to random number generator from client code!");
+}
+
 std::shared_ptr<const CPathsInfo> CClient::getPathsInfo(const CGHeroInstance * h)
 {
 	assert(h);

+ 7 - 1
client/Client.h

@@ -25,6 +25,7 @@ class BinaryDeserializer;
 class BinarySerializer;
 class BattleAction;
 class BattleInfo;
+struct BankConfig;
 
 template<typename T> class CApplier;
 
@@ -161,7 +162,7 @@ public:
 
 	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
 	bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;};
-	void createObject(const int3 & visitablePosition, const PlayerColor & initiator, MapObjectID type, MapObjectSubID subtype) override {};
+	void createBoat(const int3 & visitablePosition, BoatId type, PlayerColor initiator) override {};
 	void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
 	void giveExperience(const CGHeroInstance * hero, TExpType val) override {};
 	void changePrimSkill(const CGHeroInstance * hero, PrimarySkill which, si64 val, bool abs = false) override {};
@@ -214,10 +215,15 @@ public:
 
 	void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override {};
 	void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {};
+	void setBankObjectConfiguration(ObjectInstanceID objid, const BankConfig & configuration) override {};
+	void setRewardableObjectConfiguration(ObjectInstanceID objid, const Rewardable::Configuration & configuration) override {};
+	void setRewardableObjectConfiguration(ObjectInstanceID townInstanceID, BuildingID buildingID, const Rewardable::Configuration & configuration) override{};
 
 	void showInfoDialog(InfoWindow * iw) override {};
 	void removeGUI() const;
 
+	vstd::RNG & getRandomGenerator() override;
+
 #if SCRIPTING_ENABLED
 	scripting::Pool * getGlobalContextPool() const override;
 #endif

+ 19 - 13
client/ClientCommandManager.cpp

@@ -39,6 +39,7 @@
 #include "../lib/VCMIDirs.h"
 #include "../lib/logging/VisualLogger.h"
 #include "CMT.h"
+#include "../lib/serializer/Connection.h"
 
 #ifdef SCRIPTING_ENABLED
 #include "../lib/ScriptHandler.h"
@@ -82,31 +83,37 @@ void ClientCommandManager::handleGoSoloCommand()
 		printCommandMessage("Game is not in playing state");
 		return;
 	}
-	PlayerColor color;
+
 	if(session["aiSolo"].Bool())
 	{
-		for(auto & elem : CSH->client->gameState()->players)
+		// unlikely it will work but just in case to be consistent
+		for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID))
 		{
-			if(elem.second.human)
-				CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
+			if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman())
+			{
+				CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
+			}
 		}
 	}
 	else
 	{
-		color = LOCPLINT->playerID;
+		PlayerColor currentColor = LOCPLINT->playerID;
 		CSH->client->removeGUI();
-		for(auto & elem : CSH->client->gameState()->players)
+		
+		for(auto & color : CSH->getAllClientPlayers(CSH->logicConnection->connectionID))
 		{
-			if(elem.second.human)
+			if(color.isValidPlayer() && CSH->client->getStartInfo()->playerInfos.at(color).isControlledByHuman())
 			{
-				auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false, false);
-				printCommandMessage("Player " + elem.first.toString() + " will be lead by " + AiToGive, ELogLevel::INFO);
-				CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
+				auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(color), false, false);
+				printCommandMessage("Player " + color.toString() + " will be lead by " + AiToGive, ELogLevel::INFO);
+				CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
 			}
 		}
+
 		GH.windows().totalRedraw();
-		giveTurn(color);
+		giveTurn(currentColor);
 	}
+
 	session["aiSolo"].Bool() = !session["aiSolo"].Bool();
 }
 
@@ -381,8 +388,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu
 {
 	std::string URI;
 	singleWordBuffer >> URI;
-	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI));
-	anim->preload();
+	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI), EImageBlitMode::ALPHA);
 	anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
 }
 

+ 7 - 2
client/HeroMovementController.cpp

@@ -24,6 +24,7 @@
 
 #include "ConditionalWait.h"
 #include "../lib/CConfigHandler.h"
+#include "../lib/CRandomGenerator.h"
 #include "../lib/pathfinder/CGPathNode.h"
 #include "../lib/mapObjects/CGHeroInstance.h"
 #include "../lib/networkPacks/PacksForClient.h"
@@ -153,8 +154,12 @@ void HeroMovementController::onTryMoveHero(const CGHeroInstance * hero, const Tr
 
 	if(details.result == TryMoveHero::EMBARK || details.result == TryMoveHero::DISEMBARK)
 	{
-		if(hero->getRemovalSound() && hero->tempOwner == LOCPLINT->playerID)
-			CCS->soundh->playSound(hero->getRemovalSound().value());
+		if (hero->tempOwner == LOCPLINT->playerID)
+		{
+			auto removalSound = hero->getRemovalSound(CRandomGenerator::getDefault());
+			if (removalSound)
+				CCS->soundh->playSound(removalSound.value());
+		}
 	}
 
 	bool directlyAttackingCreature =

+ 1 - 1
client/NetPacksClient.cpp

@@ -1048,7 +1048,7 @@ void ApplyClientNetPackVisitor::visitNewObject(NewObject & pack)
 {
 	cl.invalidatePaths();
 
-	const CGObjectInstance *obj = cl.getObj(pack.createdObjectID);
+	const CGObjectInstance *obj = pack.newObject;
 	if(CGI->mh)
 		CGI->mh->onObjectFadeIn(obj, pack.initiator);
 

+ 4 - 3
client/adventureMap/AdventureMapInterface.cpp

@@ -31,6 +31,7 @@
 #include "../gui/Shortcut.h"
 #include "../gui/WindowHandler.h"
 #include "../render/Canvas.h"
+#include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
 #include "../CMT.h"
 #include "../PlayerLocalState.h"
@@ -178,7 +179,7 @@ void AdventureMapInterface::dim(Canvas & to)
 		{
 			if(!std::dynamic_pointer_cast<AdventureMapInterface>(window) && std::dynamic_pointer_cast<CIntObject>(window) && isBigWindow(window))
 			{
-				to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck")));
+				to.fillTexture(GH.renderHandler().loadImage(ImagePath::builtin("DiBoxBck"), EImageBlitMode::OPAQUE));
 				return;
 			}
 		}
@@ -399,7 +400,7 @@ void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
 		return;
 
 	currentPlayerID = playerID;
-	widget->setPlayer(playerID);
+	widget->setPlayerColor(playerID);
 }
 
 void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
@@ -914,7 +915,7 @@ void AdventureMapInterface::onScreenResize()
 
 	widget = std::make_shared<AdventureMapWidget>(shortcuts);
 	widget->getMapView()->onViewMapActivated();
-	widget->setPlayer(currentPlayerID);
+	widget->setPlayerColor(currentPlayerID);
 	widget->updateActiveState();
 	widget->getMinimap()->update();
 	widget->getInfoBar()->showSelection();

+ 0 - 1
client/adventureMap/AdventureMapInterface.h

@@ -31,7 +31,6 @@ class CAnimImage;
 class CGStatusBar;
 class AdventureMapWidget;
 class AdventureMapShortcuts;
-class CAnimation;
 class MapView;
 class CResDataBar;
 class CHeroList;

+ 18 - 37
client/adventureMap/AdventureMapWidget.cpp

@@ -20,7 +20,6 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/Shortcut.h"
 #include "../mapView/MapView.h"
-#include "../render/CAnimation.h"
 #include "../render/IImage.h"
 #include "../render/IRenderHandler.h"
 #include "../widgets/Buttons.h"
@@ -60,7 +59,7 @@ AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> s
 	const JsonNode config(JsonPath::builtin("config/widgets/adventureMap.json"));
 
 	for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
-		playerColorerImages.push_back(ImagePath::fromJson(entry));
+		playerColoredImages.push_back(ImagePath::fromJson(entry));
 
 	build(config);
 	addUsedEvents(KEYBOARD);
@@ -125,26 +124,6 @@ Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & bounding
 	return Rect(topLeft + boundingBox.topLeft(), dimensions);
 }
 
-std::shared_ptr<IImage> AdventureMapWidget::loadImage(const JsonNode & name)
-{
-	ImagePath resource = ImagePath::fromJson(name);
-
-	if(images.count(resource) == 0)
-		images[resource] = GH.renderHandler().loadImage(resource);
-
-	return images[resource];
-}
-
-std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const JsonNode & name)
-{
-	AnimationPath resource = AnimationPath::fromJson(name);
-
-	if(animations.count(resource) == 0)
-		animations[resource] = GH.renderHandler().loadAnimation(resource);
-
-	return animations[resource];
-}
-
 std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
 {
 	Rect area = readTargetArea(input["area"]);
@@ -156,8 +135,12 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & i
 {
 	Rect targetArea = readTargetArea(input["area"]);
 	Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
+	ImagePath path = ImagePath::fromJson(input["image"]);
 
-	return std::make_shared<CFilledTexture>(loadImage(input["image"]), targetArea, sourceArea);
+	if (vstd::contains(playerColoredImages, path))
+		return std::make_shared<FilledTexturePlayerIndexed>(path, targetArea, sourceArea);
+	else
+		return std::make_shared<CFilledTexture>(path, targetArea, sourceArea);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
@@ -257,7 +240,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & in
 	size_t index = input["index"].Integer();
 	size_t perPlayer = input["perPlayer"].Integer();
 
-	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(input["image"]), index, perPlayer);
+	return std::make_shared<CAdventureMapIcon>(area.topLeft(), AnimationPath::fromJson(input["image"]), index, perPlayer);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
@@ -356,7 +339,7 @@ std::shared_ptr<CInfoBar> AdventureMapWidget::getInfoBar()
 	return infoBar;
 }
 
-void AdventureMapWidget::setPlayer(const PlayerColor & player)
+void AdventureMapWidget::setPlayerColor(const PlayerColor & player)
 {
 	setPlayerChildren(this, player);
 }
@@ -369,34 +352,32 @@ void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColo
 		auto icon = dynamic_cast<CAdventureMapIcon *>(entry);
 		auto button = dynamic_cast<CButton *>(entry);
 		auto resDataBar = dynamic_cast<CResDataBar *>(entry);
-		auto texture = dynamic_cast<FilledTexturePlayerColored *>(entry);
+		auto textureColored = dynamic_cast<FilledTexturePlayerColored *>(entry);
+		auto textureIndexed = dynamic_cast<FilledTexturePlayerIndexed *>(entry);
 
 		if(button)
 			button->setPlayerColor(player);
 
 		if(resDataBar)
-			resDataBar->colorize(player);
+			resDataBar->setPlayerColor(player);
 
 		if(icon)
-			icon->setPlayer(player);
+			icon->setPlayerColor(player);
 
 		if(container)
 			setPlayerChildren(container, player);
 
-		if (texture)
-			texture->playerColored(player);
-	}
+		if (textureColored)
+			textureColored->setPlayerColor(player);
 
-	for(const auto & entry : playerColorerImages)
-	{
-		if(images.count(entry))
-			images[entry]->playerColored(player);
+		if (textureIndexed)
+			textureIndexed->setPlayerColor(player);
 	}
 
 	redraw();
 }
 
-CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> animation, size_t index, size_t iconsPerPlayer)
+CAdventureMapIcon::CAdventureMapIcon(const Point & position, const AnimationPath & animation, size_t index, size_t iconsPerPlayer)
 	: index(index)
 	, iconsPerPlayer(iconsPerPlayer)
 {
@@ -405,7 +386,7 @@ CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr<CAn
 	image = std::make_shared<CAnimImage>(animation, index);
 }
 
-void CAdventureMapIcon::setPlayer(const PlayerColor & player)
+void CAdventureMapIcon::setPlayerColor(const PlayerColor & player)
 {
 	image->setFrame(index + player.getNum() * iconsPerPlayer);
 }

+ 4 - 13
client/adventureMap/AdventureMapWidget.h

@@ -11,7 +11,6 @@
 
 #include "../gui/InterfaceObjectConfigurable.h"
 
-class CAnimation;
 class CHeroList;
 class CTownList;
 class CMinimap;
@@ -29,11 +28,7 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	std::vector<Rect> subwidgetSizes;
 
 	/// list of images on which player-colored palette will be applied
-	std::vector<ImagePath> playerColorerImages;
-
-	/// list of named images shared between widgets
-	std::map<ImagePath, std::shared_ptr<IImage>> images;
-	std::map<AnimationPath, std::shared_ptr<CAnimation>> animations;
+	std::vector<ImagePath> playerColoredImages;
 
 	/// Widgets that require access from adventure map
 	std::shared_ptr<CHeroList> heroList;
@@ -48,9 +43,6 @@ 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 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);
 	std::shared_ptr<CIntObject> buildMapButton(const JsonNode & input);
@@ -64,7 +56,6 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	std::shared_ptr<CIntObject> buildStatusBar(const JsonNode & input);
 	std::shared_ptr<CIntObject> buildTexturePlayerColored(const JsonNode &);
 
-
 	void setPlayerChildren(CIntObject * widget, const PlayerColor & player);
 	void updateActiveStateChildden(CIntObject * widget);
 public:
@@ -76,7 +67,7 @@ public:
 	std::shared_ptr<MapView> getMapView();
 	std::shared_ptr<CInfoBar> getInfoBar();
 
-	void setPlayer(const PlayerColor & player);
+	void setPlayerColor(const PlayerColor & player);
 
 	void onMapViewMoved(const Rect & visibleArea, int mapLevel);
 	void updateActiveState();
@@ -104,7 +95,7 @@ class CAdventureMapIcon : public CIntObject
 	size_t index;
 	size_t iconsPerPlayer;
 public:
-	CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> image, size_t index, size_t iconsPerPlayer);
+	CAdventureMapIcon(const Point & position, const AnimationPath & image, size_t index, size_t iconsPerPlayer);
 
-	void setPlayer(const PlayerColor & player);
+	void setPlayerColor(const PlayerColor & player);
 };

+ 3 - 3
client/adventureMap/CResDataBar.cpp

@@ -31,7 +31,7 @@ CResDataBar::CResDataBar(const ImagePath & imageName, const Point & position)
 
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	background = std::make_shared<CPicture>(imageName, 0, 0);
-	background->colorize(LOCPLINT->playerID);
+	background->setPlayerColor(LOCPLINT->playerID);
 
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;
@@ -84,7 +84,7 @@ void CResDataBar::showAll(Canvas & to)
 		to.drawText(pos.topLeft() + *datePosition, FONT_SMALL, Colors::WHITE, ETextAlignment::TOPLEFT, buildDateString());
 }
 
-void CResDataBar::colorize(PlayerColor player)
+void CResDataBar::setPlayerColor(PlayerColor player)
 {
-	background->colorize(player);
+	background->setPlayerColor(player);
 }

+ 1 - 1
client/adventureMap/CResDataBar.h

@@ -34,7 +34,7 @@ public:
 	void setDatePosition(const Point & position);
 	void setResourcePosition(const GameResID & resource, const Point & position);
 
-	void colorize(PlayerColor player);
+	void setPlayerColor(PlayerColor player);
 	void showAll(Canvas & to) override;
 };
 

+ 7 - 2
client/adventureMap/MapAudioPlayer.cpp

@@ -17,6 +17,7 @@
 #include "../media/IMusicPlayer.h"
 #include "../media/ISoundPlayer.h"
 
+#include "../../lib/CRandomGenerator.h"
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/mapObjects/CArmedInstance.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
@@ -136,8 +137,12 @@ std::vector<AudioPath> MapAudioPlayer::getAmbientSounds(const int3 & tile)
 		if (!object)
 			logGlobal->warn("Already removed object %d found on tile! (%d %d %d)", objectID.getNum(), tile.x, tile.y, tile.z);
 
-		if(object && object->getAmbientSound())
-			result.push_back(object->getAmbientSound().value());
+		if(object)
+		{
+			auto ambientSound = object->getAmbientSound(CRandomGenerator::getDefault());
+			if (ambientSound)
+				result.push_back(ambientSound.value());
+		}
 	}
 
 	if(CGI->mh->getMap()->isCoastalTile(tile))

+ 25 - 19
client/battle/BattleActionsController.cpp

@@ -985,26 +985,27 @@ void BattleActionsController::activateStack()
 		std::list<PossiblePlayerBattleAction> actionsToSelect;
 		if(!possibleActions.empty())
 		{
-			switch(possibleActions.front().get())
+			auto primaryAction = possibleActions.front().get();
+
+			if(primaryAction == PossiblePlayerBattleAction::SHOOT || primaryAction == PossiblePlayerBattleAction::AIMED_SPELL_CREATURE
+				|| primaryAction == PossiblePlayerBattleAction::ANY_LOCATION || primaryAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN)
 			{
-				case PossiblePlayerBattleAction::SHOOT:
-					actionsToSelect.push_back(possibleActions.front());
-					actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
-					break;
-					
-				case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-					actionsToSelect.push_back(possibleActions.front());
-					actionsToSelect.push_back(PossiblePlayerBattleAction::WALK_AND_ATTACK);
-					break;
-					
-				case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
-					actionsToSelect.push_back(possibleActions.front());
-					actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
-					break;
-				case PossiblePlayerBattleAction::ANY_LOCATION:
-					actionsToSelect.push_back(possibleActions.front());
-					actionsToSelect.push_back(PossiblePlayerBattleAction::ATTACK);
-					break;
+				actionsToSelect.push_back(possibleActions.front());
+
+				auto shootActionPredicate = [](const PossiblePlayerBattleAction& action)
+				{
+					return action.get() == PossiblePlayerBattleAction::SHOOT;
+				};
+				bool hasShootSecondaryAction = std::any_of(possibleActions.begin() + 1, possibleActions.end(), shootActionPredicate);
+
+				if(hasShootSecondaryAction) //casters may have shooting capabilities, for example storm elementals
+					actionsToSelect.emplace_back(PossiblePlayerBattleAction::SHOOT);
+
+				/* TODO: maybe it would also make sense to check spellcast as non-top priority action ("NO_SPELLCAST_BY_DEFAULT" bonus)?
+				 * it would require going beyond this "if" block for melee casters
+				 * F button helps, but some mod creatures may have that bonus and more than 1 castable spell */
+
+				actionsToSelect.emplace_back(PossiblePlayerBattleAction::ATTACK); //always allow melee attack as last option
 			}
 		}
 		owner.windowObject->setAlternativeActions(actionsToSelect);
@@ -1071,3 +1072,8 @@ void BattleActionsController::pushFrontPossibleAction(PossiblePlayerBattleAction
 {
 	possibleActions.insert(possibleActions.begin(), action);
 }
+
+void BattleActionsController::resetCurrentStackPossibleActions()
+{
+	possibleActions = getPossibleActionsForStack(owner.stacksController->getActiveStack());
+}

+ 3 - 0
client/battle/BattleActionsController.h

@@ -122,4 +122,7 @@ public:
 	
 	/// inserts possible action in the beginning in order to prioritize it
 	void pushFrontPossibleAction(PossiblePlayerBattleAction);
+
+	/// resets possible actions to original state
+	void resetCurrentStackPossibleActions();
 };

+ 2 - 3
client/battle/BattleAnimationClasses.cpp

@@ -24,6 +24,7 @@
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../media/ISoundPlayer.h"
+#include "../render/CAnimation.h"
 #include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
@@ -882,7 +883,7 @@ uint32_t CastAnimation::getAttackClimaxFrame() const
 
 EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed):
 	BattleAnimation(owner),
-	animation(GH.renderHandler().loadAnimation(animationName)),
+	animation(GH.renderHandler().loadAnimation(animationName, EImageBlitMode::ALPHA)),
 	effectFlags(effects),
 	effectFinished(false),
 	reversed(reversed)
@@ -925,8 +926,6 @@ EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath &
 
 bool EffectAnimation::init()
 {
-	animation->preload();
-
 	auto first = animation->getImage(0, 0, true);
 	if(!first)
 	{

+ 30 - 70
client/battle/BattleFieldController.cpp

@@ -82,39 +82,30 @@ namespace HexMasks
 	};
 }
 
-std::map<int, int> hexEdgeMaskToFrameIndex;
-
-// Maps HexEdgesMask to "Frame" indexes for range highlight images
-void initializeHexEdgeMaskToFrameIndex()
-{
-	hexEdgeMaskToFrameIndex[HexMasks::empty] = 0;
-
-    hexEdgeMaskToFrameIndex[HexMasks::topLeft] = 1;
-    hexEdgeMaskToFrameIndex[HexMasks::topRight] = 2;
-    hexEdgeMaskToFrameIndex[HexMasks::right] = 3;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomRight] = 4;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomLeft] = 5;
-    hexEdgeMaskToFrameIndex[HexMasks::left] = 6;
-
-    hexEdgeMaskToFrameIndex[HexMasks::top] = 7;
-    hexEdgeMaskToFrameIndex[HexMasks::bottom] = 8;
-
-    hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner] = 9;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner] = 10;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner] = 11;
-    hexEdgeMaskToFrameIndex[HexMasks::topLeftHalfCorner] = 12;
-
-    hexEdgeMaskToFrameIndex[HexMasks::rightTopAndBottom] = 13;
-    hexEdgeMaskToFrameIndex[HexMasks::leftTopAndBottom] = 14;
-	
-    hexEdgeMaskToFrameIndex[HexMasks::rightHalf] = 13;
-    hexEdgeMaskToFrameIndex[HexMasks::leftHalf] = 14;
-
-    hexEdgeMaskToFrameIndex[HexMasks::topRightCorner] = 15;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner] = 16;
-    hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner] = 17;
-    hexEdgeMaskToFrameIndex[HexMasks::topLeftCorner] = 18;
-}
+static const std::map<int, int> hexEdgeMaskToFrameIndex =
+{
+    { HexMasks::empty, 0 },
+    { HexMasks::topLeft, 1 },
+    { HexMasks::topRight, 2 },
+    { HexMasks::right, 3 },
+    { HexMasks::bottomRight, 4 },
+    { HexMasks::bottomLeft, 5 },
+    { HexMasks::left, 6 },
+    { HexMasks::top, 7 },
+    { HexMasks::bottom, 8 },
+    { HexMasks::topRightHalfCorner, 9 },
+    { HexMasks::bottomRightHalfCorner, 10 },
+    { HexMasks::bottomLeftHalfCorner, 11 },
+    { HexMasks::topLeftHalfCorner, 12 },
+    { HexMasks::rightTopAndBottom, 13 },
+    { HexMasks::leftTopAndBottom, 14 },
+    { HexMasks::rightHalf, 13 },
+    { HexMasks::leftHalf, 14 },
+    { HexMasks::topRightCorner, 15 },
+    { HexMasks::bottomRightCorner, 16 },
+    { HexMasks::bottomLeftCorner, 17 },
+    { HexMasks::topLeftCorner, 18 }
+};
 
 BattleFieldController::BattleFieldController(BattleInterface & owner):
 	owner(owner)
@@ -123,26 +114,15 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 
 	//preparing cells and hexes
 	cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
-	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"));
+	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"), EImageBlitMode::ALPHA);
 	cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 	cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 
-	attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"));
-	attackCursors->preload();
-
-	spellCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"));
-	spellCursors->preload();
-
-	initializeHexEdgeMaskToFrameIndex();
+	attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"), EImageBlitMode::COLORKEY);
+	spellCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"), EImageBlitMode::COLORKEY);
 
-	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"));
-	rangedFullDamageLimitImages->preload();
-
-	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"));
-	shootingRangeLimitImages->preload();
-
-	flipRangeLimitImagesIntoPositions(rangedFullDamageLimitImages);
-	flipRangeLimitImagesIntoPositions(shootingRangeLimitImages);
+	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"), EImageBlitMode::COLORKEY);
+	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"), EImageBlitMode::COLORKEY);
 
 	if(!owner.siegeController)
 	{
@@ -542,7 +522,7 @@ std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangeLimitH
 			mask.set(direction);
 
 		uint8_t imageKey = static_cast<uint8_t>(mask.to_ulong());
-		output.push_back(limitImages->getImage(hexEdgeMaskToFrameIndex[imageKey]));
+		output.push_back(limitImages->getImage(hexEdgeMaskToFrameIndex.at(imageKey)));
 	}
 
 	return output;
@@ -556,26 +536,6 @@ void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distan
 		rangeLimitHexesHighlights = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
 }
 
-void BattleFieldController::flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images)
-{
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip();
-
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip();
-
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
-
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
-
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
-	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
-}
-
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 {
 	std::vector<BattleHex> rangedFullDamageLimitHexes;

+ 0 - 3
client/battle/BattleFieldController.h

@@ -84,9 +84,6 @@ class BattleFieldController : public CIntObject
 	/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
 	void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighlights);
 
-	/// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones
-	void flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images);
-
 	void showBackground(Canvas & canvas);
 	void showBackgroundImage(Canvas & canvas);
 	void showBackgroundImageWithHexes(Canvas & canvas);

+ 18 - 2
client/battle/BattleInterface.cpp

@@ -35,6 +35,7 @@
 #include "../adventureMap/AdventureMapInterface.h"
 
 #include "../../CCallback.h"
+#include "../../lib/BattleFieldHandler.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -113,6 +114,9 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
 			onIntroSoundPlayed();
 	};
 
+	auto bfieldType = getBattle()->battleGetBattlefieldType();
+	const auto & battlefieldSound = bfieldType.getInfo()->musicFilename;
+
 	std::vector<soundBase::soundID> battleIntroSounds =
 	{
 		soundBase::battle00, soundBase::battle01,
@@ -120,7 +124,13 @@ void BattleInterface::playIntroSoundAndUnlockInterface()
 		soundBase::battle05, soundBase::battle06, soundBase::battle07
 	};
 
-	int battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds);
+	int battleIntroSoundChannel = -1;
+
+	if (!battlefieldSound.empty())
+		battleIntroSoundChannel = CCS->soundh->playSound(battlefieldSound);
+	else
+		battleIntroSoundChannel = CCS->soundh->playSoundFromSet(battleIntroSounds);
+
 	if (battleIntroSoundChannel != -1)
 	{
 		CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
@@ -144,7 +154,13 @@ void BattleInterface::onIntroSoundPlayed()
 	if (openingPlaying())
 		openingEnd();
 
-	CCS->musich->playMusicFromSet("battle", true, true);
+	auto bfieldType = getBattle()->battleGetBattlefieldType();
+	const auto & battlefieldMusic = bfieldType.getInfo()->musicFilename;
+
+	if (!battlefieldMusic.empty())
+		CCS->musich->playMusic(battlefieldMusic, true, true);
+	else
+		CCS->musich->playMusicFromSet("battle", true, true);
 }
 
 void BattleInterface::openingEnd()

+ 0 - 1
client/battle/BattleInterface.h

@@ -39,7 +39,6 @@ class Canvas;
 class BattleResultWindow;
 class StackQueue;
 class CPlayerInterface;
-class CAnimation;
 struct BattleEffect;
 class IImage;
 class StackQueue;

+ 92 - 28
client/battle/BattleInterfaceClasses.cpp

@@ -31,6 +31,7 @@
 #include "../render/IFont.h"
 #include "../render/Graphics.h"
 #include "../widgets/Buttons.h"
+#include "../widgets/CComponent.h"
 #include "../widgets/Images.h"
 #include "../widgets/Slider.h"
 #include "../widgets/TextControls.h"
@@ -39,9 +40,11 @@
 #include "../windows/CMessage.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/CSpellWindow.h"
+#include "../windows/InfoWindows.h"
 #include "../render/CAnimation.h"
 #include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
+#include "../eventsSDL/InputHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -55,6 +58,8 @@
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
 #include "../../lib/TextOperations.h"
+#include "../../lib/json/JsonUtils.h"
+
 
 void BattleConsole::showAll(Canvas & to)
 {
@@ -392,8 +397,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	else
 		animationPath = hero->type->heroClass->imageBattleMale;
 
-	animation = GH.renderHandler().loadAnimation(animationPath);
-	animation->preload();
+	animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
 
 	pos.w = 64;
 	pos.h = 136;
@@ -404,11 +408,10 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 		animation->verticalFlip();
 
 	if(defender)
-		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR"));
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR"), EImageBlitMode::COLORKEY);
 	else
-		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL"));
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL"), EImageBlitMode::COLORKEY);
 
-	flagAnimation->preload();
 	flagAnimation->playerColored(hero->tempOwner);
 
 	switchToNextPhase();
@@ -417,6 +420,79 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	addUsedEvents(TIME);
 }
 
+QuickSpellPanel::QuickSpellPanel(BattleInterface & owner)
+	: CIntObject(0), owner(owner)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	addUsedEvents(LCLICK | SHOW_POPUP | MOVE);
+
+	pos = Rect(0, 0, 52, 600);
+	background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
+	rect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w + 1, pos.h + 1), ColorRGBA(0, 0, 0, 0), ColorRGBA(241, 216, 120, 255));
+
+	create();
+}
+
+void QuickSpellPanel::create()
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+
+	const JsonNode config = JsonUtils::assembleFromFiles("config/shortcutsConfig");
+
+	labels.clear();
+	buttons.clear();
+	buttonsDisabled.clear();
+
+	auto hero = owner.getBattle()->battleGetMyHero();
+	if(!hero)
+		return;
+
+	for(int i = 0; i < 12; i++) {
+		std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(i)].String();
+
+		SpellID id;
+		try
+		{
+			id = SpellID::decode(spellIdentifier);
+		}
+		catch(const IdentifierResolutionException& e)
+		{
+			id = SpellID::NONE;
+		}
+
+		auto button = std::make_shared<CButton>(Point(2, 7 + 50 * i), AnimationPath::builtin("spellint"), CButton::tooltip(), [this, id, hero](){
+			if(id.hasValue() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
+			{
+				owner.castThisSpell(id);
+			}
+		});
+		button->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("spellint"), !spellIdentifier.empty() ? id.num + 1 : 0));
+		button->addPopupCallback([this, i, hero](){
+			GH.input().hapticFeedback();
+			GH.windows().createAndPushWindow<CSpellWindow>(hero, owner.curInt.get(), true, [this, i](SpellID spell){
+				Settings configID = persistentStorage.write["quickSpell"][std::to_string(i)];
+				configID->String() = spell.toSpell()->identifier;
+				create();
+			});
+		});
+
+		if(!id.hasValue() || !id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, hero))
+		{
+			buttonsDisabled.push_back(std::make_shared<TransparentFilledRectangle>(Rect(2, 7 + 50 * i, 48, 36), ColorRGBA(0, 0, 0, 172)));
+		}
+		labels.push_back(std::make_shared<CLabel>(7, 10 + 50 * i, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, config["keyboard"]["battleSpellShortcut" + std::to_string(i)].String()));
+
+		buttons.push_back(button);
+	}
+}
+
+void QuickSpellPanel::show(Canvas & to)
+{
+	showAll(to);
+	CIntObject::show(to);
+}
+
 HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * position, bool initializeBackground)
 	: CIntObject(0)
 {
@@ -427,8 +503,7 @@ HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * posit
 	if(initializeBackground)
 	{
 		background = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
-		background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
-		background->colorize(hero.owner);
+		background->setPlayerColor(hero.owner);
 	}
 
 	initializeData(hero);
@@ -495,11 +570,9 @@ StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, bool initializeBa
 	{
 		background = std::make_shared<CPicture>(ImagePath::builtin("CCRPOP"));
 		background->pos.y += 37;
-		background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
-		background->colorize(stack->getOwner());
+		background->setPlayerColor(stack->getOwner());
 		background2 = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
-		background2->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
-		background2->colorize(stack->getOwner());
+		background2->setPlayerColor(stack->getOwner());
 	}
 
 	initializeData(stack);
@@ -603,7 +676,7 @@ HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
 	if (position != nullptr)
 		moveTo(*position);
 
-	background->colorize(hero.owner); //maybe add this functionality to base class?
+	background->setPlayerColor(hero.owner); //maybe add this functionality to base class?
 
 	content = std::make_shared<HeroInfoBasicPanel>(hero, nullptr, false);
 }
@@ -614,7 +687,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 	background = std::make_shared<CPicture>(ImagePath::builtin("CPRESULT"));
-	background->colorize(owner.playerID);
+	background->setPlayerColor(owner.playerID);
 	pos = center(background->pos);
 
 	exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
@@ -741,7 +814,7 @@ BattleResultResources BattleResultWindow::getResources(const BattleResult & br)
 		else
 		{
 			resources.musicName = AudioPath::builtin("Music/Win Battle");
-			resources.prologueVideo = VideoPath();
+			resources.prologueVideo = VideoPath::builtin("WIN3.BIK");
 			resources.loopedVideo = VideoPath::builtin("WIN3.BIK");
 		}
 
@@ -856,9 +929,6 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.h = 49;
 		pos.x += parent->pos.w/2 - pos.w/2;
 		pos.y += queueSmallOutside ? -queueSmallOutsideYOffset : 10;
-
-		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"));
-		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
 	}
 	else
 	{
@@ -868,13 +938,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.y -= pos.h;
 
 		background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
-
-		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("TWCRPORT"));
-		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
-		//TODO: where use big icons?
-		//stateIcons = GH.renderHandler().loadAnimation("VCMI/BATTLEQUEUE/STATESBIG");
 	}
-	stateIcons->preload();
 
 	stackBoxes.resize(queueSize);
 	for (int i = 0; i < stackBoxes.size(); i++)
@@ -943,13 +1007,13 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 
 	if(owner->embedded)
 	{
-		icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 5, 2);
+		icon = std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), 0, 0, 5, 2);
 		amount = std::make_shared<CLabel>(pos.w/2, pos.h - 7, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 		roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 2, 48), ColorRGBA(0, 0, 0, 255), ColorRGBA(0, 255, 0, 255));
 	}
 	else
 	{
-		icon = std::make_shared<CAnimImage>(owner->icons, 0, 0, 9, 1);
+		icon = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), 0, 0, 9, 1);
 		amount = std::make_shared<CLabel>(pos.w/2, pos.h - 8, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
 		roundRect = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, 15, 18), ColorRGBA(0, 0, 0, 255), ColorRGBA(241, 216, 120, 255));
 		round = std::make_shared<CLabel>(4, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
@@ -957,7 +1021,7 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 		int icon_x = pos.w - 17;
 		int icon_y = pos.h - 18;
 
-		stateIcon = std::make_shared<CAnimImage>(owner->stateIcons, 0, 0, icon_x, icon_y);
+		stateIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"), 0, 0, icon_x, icon_y);
 		stateIcon->visible = false;
 	}
 	roundRect->disable();
@@ -968,7 +1032,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std::
 	if(unit)
 	{
 		boundUnitID = unit->unitId();
-		background->colorize(unit->unitOwner());
+		background->setPlayerColor(unit->unitOwner());
 		icon->visible = true;
 
 		// temporary code for mod compatibility:
@@ -1014,7 +1078,7 @@ void StackQueue::StackBox::setUnit(const battle::Unit * unit, size_t turn, std::
 	else
 	{
 		boundUnitID = std::nullopt;
-		background->colorize(PlayerColor::NEUTRAL);
+		background->setPlayerColor(PlayerColor::NEUTRAL);
 		icon->visible = false;
 		icon->setFrame(0);
 		amount->setText("");

+ 22 - 3
client/battle/BattleInterfaceClasses.h

@@ -22,6 +22,7 @@ class CGHeroInstance;
 struct BattleResult;
 struct InfoAboutHero;
 class CStack;
+class CPlayerBattleCallback;
 
 namespace battle
 {
@@ -44,6 +45,7 @@ class TransparentFilledRectangle;
 class CPlayerInterface;
 class BattleRenderer;
 class VideoWidget;
+class QuickSpellPanel;
 
 /// Class which shows the console at the bottom of the battle screen and manages the text of the console
 class BattleConsole : public CIntObject, public IStatusBar
@@ -147,6 +149,26 @@ public:
 	BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
 };
 
+class QuickSpellPanel : public CIntObject
+{
+private:
+	std::shared_ptr<CFilledTexture> background;
+	std::shared_ptr<TransparentFilledRectangle> rect;
+	std::vector<std::shared_ptr<CButton>> buttons;
+	std::vector<std::shared_ptr<TransparentFilledRectangle>> buttonsDisabled;
+	std::vector<std::shared_ptr<CLabel>> labels;
+
+	BattleInterface & owner;
+public:
+	bool isEnabled; // isActive() is not working on multiple conditions, because of this we need a seperate flag
+
+	QuickSpellPanel(BattleInterface & owner);
+
+	void create();
+
+	void show(Canvas & to) override;
+};
+
 class HeroInfoBasicPanel : public CIntObject //extracted from InfoWindow to fit better as non-popup embed element
 {
 private:
@@ -252,9 +274,6 @@ class StackQueue : public CIntObject
 	std::vector<std::shared_ptr<StackBox>> stackBoxes;
 	BattleInterface & owner;
 
-	std::shared_ptr<CAnimation> icons;
-	std::shared_ptr<CAnimation> stateIcons;
-
 	int32_t getSiegeShooterIconID();
 public:
 	const bool embedded;

+ 21 - 35
client/battle/BattleObstacleController.cpp

@@ -21,6 +21,7 @@
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../media/ISoundPlayer.h"
+#include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 
@@ -46,24 +47,16 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
 {
 	AnimationPath animationName = oi.getAnimation();
 
-	if (animationsCache.count(animationName) == 0)
+	if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 	{
-		if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
-		{
-			// obstacle uses single bitmap image for animations
-			auto animation = GH.renderHandler().createAnimation();
-			animation->setCustom(animationName.getName(), 0, 0);
-			animationsCache[animationName] = animation;
-			animation->preload();
-		}
-		else
-		{
-			auto animation = GH.renderHandler().loadAnimation(animationName);
-			animationsCache[animationName] = animation;
-			animation->preload();
-		}
+		// obstacle uses single bitmap image for animations
+		obstacleImages[oi.uniqueID] = GH.renderHandler().loadImage(animationName.toType<EResType::IMAGE>(), EImageBlitMode::COLORKEY);
+	}
+	else
+	{
+		obstacleAnimations[oi.uniqueID] = GH.renderHandler().loadAnimation(animationName, EImageBlitMode::COLORKEY);
+		obstacleImages[oi.uniqueID] = obstacleAnimations[oi.uniqueID]->getImage(0);
 	}
-	obstacleAnimations[oi.uniqueID] = animationsCache[animationName];
 }
 
 void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges> & obstacles)
@@ -85,9 +78,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		if(animationPath.empty())
 			continue;
 
-		auto animation = GH.renderHandler().loadAnimation(animationPath);
-		animation->preload();
-
+		auto animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::COLORKEY);
 		auto first = animation->getImage(0, 0);
 		if(!first)
 			continue;
@@ -99,6 +90,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		owner.stacksController->addNewAnim(new EffectAnimation(owner, animationPath, whereTo, obstacle["position"].Integer(), 0, true));
 
 		obstacleAnimations.erase(oi.id);
+		obstacleImages.erase(oi.id);
 		//so when multiple obstacles are removed, they show up one after another
 		owner.waitForAnimations();
 	}
@@ -113,9 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
 		if(!oi->visibleForSide(side.value(), owner.getBattle()->battleHasNativeStack(side.value())))
 			continue;
 
-		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation());
-		animation->preload();
-
+		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation(), EImageBlitMode::ALPHA);
 		auto first = animation->getImage(0, 0);
 		if(!first)
 			continue;
@@ -187,26 +177,22 @@ void BattleObstacleController::collectRenderableObjects(BattleRenderer & rendere
 void BattleObstacleController::tick(uint32_t msPassed)
 {
 	timePassed += msPassed / 1000.f;
+	int framesCount = timePassed * AnimationControls::getObstaclesSpeed();
+
+	for(auto & animation : obstacleAnimations)
+	{
+		int frameIndex = framesCount % animation.second->size(0);
+		obstacleImages[animation.first] = animation.second->getImage(frameIndex, 0);
+	}
 }
 
 std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
 {
-	int framesCount = timePassed * AnimationControls::getObstaclesSpeed();
-	std::shared_ptr<CAnimation> animation;
-
 	// obstacle is not loaded yet, don't show anything
-	if (obstacleAnimations.count(oi.uniqueID) == 0)
+	if (obstacleImages.count(oi.uniqueID) == 0)
 		return nullptr;
 
-	animation = obstacleAnimations[oi.uniqueID];
-	assert(animation);
-
-	if(animation)
-	{
-		int frameIndex = framesCount % animation->size(0);
-		return animation->getImage(frameIndex, 0);
-	}
-	return nullptr;
+	return obstacleImages[oi.uniqueID];
 }
 
 Point BattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle)

+ 3 - 3
client/battle/BattleObstacleController.h

@@ -36,12 +36,12 @@ class BattleObstacleController
 	/// total time, in seconds, since start of battle. Used for animating obstacles
 	float timePassed;
 
-	/// cached animations of all obstacles in current battle
-	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;
 
+	/// Current images for all present obstacles
+	std::map<si32, std::shared_ptr<IImage>> obstacleImages;
+
 	void loadObstacleImage(const CObstacleInstance & oi);
 
 	std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);

+ 2 - 2
client/battle/BattleProjectileController.cpp

@@ -15,6 +15,7 @@
 #include "BattleStacksController.h"
 #include "CreatureAnimation.h"
 
+#include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 #include "../gui/CGuiHandler.h"
@@ -191,8 +192,7 @@ void BattleProjectileController::initStackProjectile(const CStack * stack)
 
 std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const AnimationPath & path )
 {
-	std::shared_ptr<CAnimation> projectile = GH.renderHandler().loadAnimation(path);
-	projectile->preload();
+	std::shared_ptr<CAnimation> projectile = GH.renderHandler().loadAnimation(path, EImageBlitMode::COLORKEY);
 
 	if(projectile->size(1) != 0)
 		logAnim->error("Expected empty group 1 in stack projectile");

+ 3 - 3
client/battle/BattleSiegeController.cpp

@@ -182,7 +182,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 		if ( !getWallPieceExistence(EWallVisual::EWallVisual(g)) )
 			continue;
 
-		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
+		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED), EImageBlitMode::COLORKEY);
 	}
 }
 
@@ -248,7 +248,7 @@ void BattleSiegeController::gateStateChanged(const EGateState state)
 		wallPieceImages[EWallVisual::GATE] = nullptr;
 
 	if (stateId != EWallState::NONE)
-		wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE,  stateId));
+		wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE,  stateId), EImageBlitMode::COLORKEY);
 
 	if (playSound)
 		CCS->soundh->playSound(soundBase::DRAWBRG);
@@ -357,7 +357,7 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 
 		auto wallState = EWallState(owner.getBattle()->battleGetWallState(attackInfo.attackedPart));
 
-		wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
+		wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState), EImageBlitMode::COLORKEY);
 	}
 }
 

+ 115 - 21
client/battle/BattleWindow.cpp

@@ -45,9 +45,9 @@
 #include "../../lib/CPlayerState.h"
 #include "../windows/settings/SettingsMainWindow.h"
 
-BattleWindow::BattleWindow(BattleInterface & owner):
-	owner(owner),
-	defaultAction(PossiblePlayerBattleAction::INVALID)
+BattleWindow::BattleWindow(BattleInterface & Owner):
+	owner(Owner),
+	lastAlternativeAction(PossiblePlayerBattleAction::INVALID)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	pos.w = 800;
@@ -64,6 +64,20 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	
 	const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
 	
+	addShortcut(EShortcut::BATTLE_TOGGLE_QUICKSPELL, [this](){ this->toggleStickyQuickSpellVisibility();});
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_0,  [this](){ useSpellIfPossible(0);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_1,  [this](){ useSpellIfPossible(1);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_2,  [this](){ useSpellIfPossible(2);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_3,  [this](){ useSpellIfPossible(3);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_4,  [this](){ useSpellIfPossible(4);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_5,  [this](){ useSpellIfPossible(5);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_6,  [this](){ useSpellIfPossible(6);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_7,  [this](){ useSpellIfPossible(7);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_8,  [this](){ useSpellIfPossible(8);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_9,  [this](){ useSpellIfPossible(9);  });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_10, [this](){ useSpellIfPossible(10); });
+	addShortcut(EShortcut::BATTLE_SPELL_SHORTCUT_11, [this](){ useSpellIfPossible(11); });
+
 	addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
 	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
 	addShortcut(EShortcut::BATTLE_RETREAT, std::bind(&BattleWindow::bFleef, this));
@@ -95,6 +109,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 	owner.fieldController->createHeroes();
 
 	createQueue();
+	createQuickSpellWindow();
 	createStickyHeroInfoWindows();
 	createTimerInfoWindows();
 
@@ -164,10 +179,67 @@ void BattleWindow::createStickyHeroInfoWindows()
 	setPositionInfoWindow();
 }
 
+void BattleWindow::createQuickSpellWindow()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	quickSpellWindow = std::make_shared<QuickSpellPanel>(owner);
+	quickSpellWindow->moveTo(Point(pos.x - 67, pos.y));
+
+	if(settings["battle"]["enableQuickSpellPanel"].Bool())
+		showStickyQuickSpellWindow();
+	else
+		hideStickyQuickSpellWindow();
+}
+
+void BattleWindow::toggleStickyQuickSpellVisibility()
+{
+	if(settings["battle"]["enableQuickSpellPanel"].Bool())
+		hideStickyQuickSpellWindow();
+	else
+		showStickyQuickSpellWindow();
+}
+
+void BattleWindow::hideStickyQuickSpellWindow()
+{
+	Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
+	showStickyQuickSpellWindow->Bool() = false;
+
+	quickSpellWindow->disable();
+	quickSpellWindow->isEnabled = false;
+
+	setPositionInfoWindow();
+	createTimerInfoWindows();
+	GH.windows().totalRedraw();
+}
+
+void BattleWindow::showStickyQuickSpellWindow()
+{
+	Settings showStickyQuickSpellWindow = settings.write["battle"]["enableQuickSpellPanel"];
+	showStickyQuickSpellWindow->Bool() = true;
+
+	if(GH.screenDimensions().x >= 1050)
+	{
+		quickSpellWindow->enable();
+		quickSpellWindow->isEnabled = true;
+	}
+	else
+	{
+		quickSpellWindow->disable();
+		quickSpellWindow->isEnabled = false;
+	}
+
+	setPositionInfoWindow();
+	createTimerInfoWindows();
+	GH.windows().totalRedraw();
+}
+
 void BattleWindow::createTimerInfoWindows()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
+	int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0;
+
 	if(LOCPLINT->cb->getStartInfo()->turnTimerInfo.battleTimer != 0 || LOCPLINT->cb->getStartInfo()->turnTimerInfo.unitTimer != 0)
 	{
 		PlayerColor attacker = owner.getBattle()->sideToPlayer(BattleSide::ATTACKER);
@@ -176,7 +248,7 @@ void BattleWindow::createTimerInfoWindows()
 		if (attacker.isValidPlayer())
 		{
 			if (GH.screenDimensions().x >= 1000)
-				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92, 1), attacker);
+				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(-92 + xOffsetAttacker, 1), attacker);
 			else
 				attackerTimerWidget = std::make_shared<TurnTimerWidget>(Point(1, 135), attacker);
 		}
@@ -199,6 +271,24 @@ std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode &
 	return std::make_shared<BattleConsole>(owner, background, rect.topLeft(), offset, rect.dimensions() );
 }
 
+void BattleWindow::useSpellIfPossible(int slot)
+{
+	std::string spellIdentifier = persistentStorage["quickSpell"][std::to_string(slot)].String();
+	SpellID id;
+	try
+	{
+		id = SpellID::decode(spellIdentifier);
+	}
+	catch(const IdentifierResolutionException& e)
+	{
+		return;
+	}
+	if(id.hasValue() && owner.getBattle()->battleGetMyHero() && id.toSpell()->canBeCast(owner.getBattle().get(), spells::Mode::HERO, owner.getBattle()->battleGetMyHero()))
+	{
+		owner.castThisSpell(id);
+	}
+};
+
 void BattleWindow::toggleQueueVisibility()
 {
 	if(settings["battle"]["showQueue"].Bool())
@@ -283,10 +373,12 @@ void BattleWindow::showStickyHeroWindows()
 void BattleWindow::updateQueue()
 {
 	queue->update();
+	createQuickSpellWindow();
 }
 
 void BattleWindow::setPositionInfoWindow()
 {
+	int xOffsetAttacker = quickSpellWindow->isEnabled ? -53 : 0;
 	if(defenderHeroWindow)
 	{
 		Point position = (GH.screenDimensions().x >= 1000)
@@ -297,7 +389,7 @@ void BattleWindow::setPositionInfoWindow()
 	if(attackerHeroWindow)
 	{
 		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x - 93, pos.y + 60)
+				? Point(pos.x - 93 + xOffsetAttacker, pos.y + 60)
 				: Point(pos.x + 1, pos.y + 195);
 		attackerHeroWindow->moveTo(position);
 	}
@@ -311,7 +403,7 @@ void BattleWindow::setPositionInfoWindow()
 	if(attackerStackWindow)
 	{
 		Point position = (GH.screenDimensions().x >= 1000)
-				? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
+				? Point(pos.x - 93 + xOffsetAttacker, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y + 60)
 				: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 195);
 		attackerStackWindow->moveTo(position);
 	}
@@ -346,6 +438,7 @@ void BattleWindow::updateStackInfoWindow(const CStack * stack)
 		attackerStackWindow = nullptr;
 	
 	setPositionInfoWindow();
+	createTimerInfoWindows();
 }
 
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
@@ -567,14 +660,18 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 
 void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
 {
+	assert(actions.size() != 1);
+
 	alternativeActions = actions;
-	defaultAction = PossiblePlayerBattleAction::INVALID;
+	lastAlternativeAction = PossiblePlayerBattleAction::INVALID;
+
 	if(alternativeActions.size() > 1)
-		defaultAction = alternativeActions.back();
-	if(!alternativeActions.empty())
+	{
+		lastAlternativeAction = alternativeActions.back();
 		showAlternativeActionIcon(alternativeActions.front());
+	}
 	else
-		showAlternativeActionIcon(defaultAction);
+		showAlternativeActionIcon(PossiblePlayerBattleAction::INVALID);
 }
 
 void BattleWindow::bAutofightf()
@@ -669,23 +766,18 @@ void BattleWindow::bSwitchActionf()
 {
 	if(alternativeActions.empty())
 		return;
-	
-	if(alternativeActions.front() == defaultAction)
-	{
-		alternativeActions.push_back(alternativeActions.front());
-		alternativeActions.pop_front();
-	}
-	
+
 	auto actions = owner.actionsController->getPossibleActions();
-	if(!actions.empty() && actions.front() == alternativeActions.front())
+
+	if(!actions.empty() && actions.front() != lastAlternativeAction)
 	{
 		owner.actionsController->removePossibleAction(alternativeActions.front());
-		showAlternativeActionIcon(defaultAction);
+		showAlternativeActionIcon(*std::next(alternativeActions.begin()));
 	}
 	else
 	{
-		owner.actionsController->pushFrontPossibleAction(alternativeActions.front());
-		showAlternativeActionIcon(alternativeActions.front());
+		owner.actionsController->resetCurrentStackPossibleActions();
+		showAlternativeActionIcon(owner.actionsController->getPossibleActions().front());
 	}
 	
 	alternativeActions.push_back(alternativeActions.front());
@@ -766,6 +858,8 @@ void BattleWindow::blockUI(bool on)
 	setShortcutBlocked(EShortcut::BATTLE_TACTICS_NEXT, on || !owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_DOWN, on && !owner.tacticsMode);
 	setShortcutBlocked(EShortcut::BATTLE_CONSOLE_UP, on && !owner.tacticsMode);
+
+	quickSpellWindow->setInputEnabled(!on);
 }
 
 void BattleWindow::bOpenActiveUnit()

+ 12 - 1
client/battle/BattleWindow.h

@@ -27,6 +27,7 @@ class StackQueue;
 class TurnTimerWidget;
 class HeroInfoBasicPanel;
 class StackInfoBasicPanel;
+class QuickSpellPanel;
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
 class BattleWindow : public InterfaceObjectConfigurable
@@ -40,6 +41,8 @@ class BattleWindow : public InterfaceObjectConfigurable
 	std::shared_ptr<StackInfoBasicPanel> attackerStackWindow;
 	std::shared_ptr<StackInfoBasicPanel> defenderStackWindow;
 
+	std::shared_ptr<QuickSpellPanel> quickSpellWindow;
+
 	std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
 	std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
 
@@ -65,15 +68,19 @@ class BattleWindow : public InterfaceObjectConfigurable
 	
 	/// management of alternative actions
 	std::list<PossiblePlayerBattleAction> alternativeActions;
-	PossiblePlayerBattleAction defaultAction;
+	PossiblePlayerBattleAction lastAlternativeAction;
 	void showAlternativeActionIcon(PossiblePlayerBattleAction);
 
+	void useSpellIfPossible(int slot);
+
 	/// flip battle queue visibility to opposite
 	void toggleQueueVisibility();
 	void createQueue();
 
 	void toggleStickyHeroWindowsVisibility();
+	void toggleStickyQuickSpellVisibility();
 	void createStickyHeroInfoWindows();
+	void createQuickSpellWindow();
 	void createTimerInfoWindows();
 
 	std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
@@ -94,6 +101,10 @@ public:
 	void hideStickyHeroWindows();
 	void showStickyHeroWindows();
 
+	/// Toggle permanent quickspell windows visibility
+	void hideStickyQuickSpellWindow();
+	void showStickyQuickSpellWindow();
+
 	/// Event handler for netpack changing hero mana points
 	void heroManaPointsChanged(const CGHeroInstance * hero);
 

+ 3 - 6
client/battle/CreatureAnimation.cpp

@@ -14,6 +14,7 @@
 #include "../../lib/CCreatureHandler.h"
 
 #include "../gui/CGuiHandler.h"
+#include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
 #include "../render/IRenderHandler.h"
@@ -198,12 +199,8 @@ CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedControll
 	  speedController(controller),
 	  once(false)
 {
-	forward = GH.renderHandler().loadAnimation(name_);
-	reverse = GH.renderHandler().loadAnimation(name_);
-
-	//todo: optimize
-	forward->preload();
-	reverse->preload();
+	forward = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA);
+	reverse = GH.renderHandler().loadAnimation(name_, EImageBlitMode::ALPHA);
 
 	// if necessary, add one frame into vcmi-only group DEAD
 	if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)

+ 0 - 1
client/battle/CreatureAnimation.h

@@ -12,7 +12,6 @@
 #include "../../lib/FunctionList.h"
 #include "../../lib/Color.h"
 #include "../widgets/Images.h"
-#include "../render/CAnimation.h"
 #include "../render/IImage.h"
 
 class CIntObject;

+ 1 - 1
client/globalLobby/GlobalLobbyClient.cpp

@@ -43,7 +43,7 @@ void GlobalLobbyClient::onPacketReceived(const std::shared_ptr<INetworkConnectio
 {
 	boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
 
-	JsonNode json(message.data(), message.size());
+	JsonNode json(message.data(), message.size(), "<lobby network packet>");
 
 	if(json["type"].String() == "accountCreated")
 		return receiveAccountCreated(json);

+ 1 - 1
client/globalLobby/GlobalLobbyInviteWindow.cpp

@@ -79,7 +79,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
 	pos.h = 420;
 
 	filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
-	filledBackground->playerColored(PlayerColor(1));
+	filledBackground->setPlayerColor(PlayerColor(1));
 	labelTitle = std::make_shared<CLabel>(
 		pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.invite.header").toString()
 	);

+ 1 - 1
client/globalLobby/GlobalLobbyLoginWindow.cpp

@@ -70,7 +70,7 @@ GlobalLobbyLoginWindow::GlobalLobbyLoginWindow()
 	else
 		toggleMode->setSelected(1);
 
-	filledBackground->playerColored(PlayerColor(1));
+	filledBackground->setPlayerColor(PlayerColor(1));
 	inputUsername->setCallback([this](const std::string & text)
 	{
 		this->buttonLogin->block(text.empty());

+ 1 - 1
client/globalLobby/GlobalLobbyRoomWindow.cpp

@@ -188,7 +188,7 @@ GlobalLobbyRoomWindow::GlobalLobbyRoomWindow(GlobalLobbyWindow * window, const s
 	modListTitle = std::make_shared<CLabel>( 182, 59, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.preview.mods").toString());
 
 	buttonJoin->block(!errorMessage.empty());
-	filledBackground->playerColored(PlayerColor(1));
+	filledBackground->setPlayerColor(PlayerColor(1));
 	center();
 }
 

+ 1 - 1
client/globalLobby/GlobalLobbyServerSetup.cpp

@@ -78,7 +78,7 @@ GlobalLobbyServerSetup::GlobalLobbyServerSetup()
 	buttonCreate = std::make_shared<CButton>(Point(10, 300), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ onCreate(); }, EShortcut::GLOBAL_ACCEPT);
 	buttonClose = std::make_shared<CButton>(Point(210, 300), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ onClose(); }, EShortcut::GLOBAL_CANCEL);
 
-	filledBackground->playerColored(PlayerColor(1));
+	filledBackground->setPlayerColor(PlayerColor(1));
 
 	updateDescription();
 	center();

+ 9 - 0
client/gui/CIntObject.h

@@ -165,6 +165,15 @@ public:
 	virtual void updateGarrisons() = 0;
 };
 
+class IMarketHolder
+{
+public:
+	virtual void updateResources() {};
+	virtual void updateExperience() {};
+	virtual void updateSecondarySkills() {};
+	virtual void updateArtifacts() {};
+};
+
 class ITownHolder
 {
 public:

+ 5 - 9
client/gui/CursorHandler.cpp

@@ -47,15 +47,12 @@ CursorHandler::CursorHandler()
 
 	cursors =
 	{
-		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR")),
-		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT")),
-		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT")),
-		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"))
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR"), EImageBlitMode::COLORKEY),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"), EImageBlitMode::COLORKEY),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT"), EImageBlitMode::COLORKEY),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"), EImageBlitMode::COLORKEY)
 	};
 
-	for (auto & cursor : cursors)
-		cursor->preload();
-
 	set(Cursor::Map::POINTER);
 	showType = dynamic_cast<CursorSoftware *>(cursor.get()) ? Cursor::ShowType::SOFTWARE : Cursor::ShowType::HARDWARE;
 }
@@ -104,8 +101,7 @@ void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
 
 void CursorHandler::dragAndDropCursor (const AnimationPath & path, size_t index)
 {
-	auto anim = GH.renderHandler().loadAnimation(path);
-	anim->load(index);
+	auto anim = GH.renderHandler().loadAnimation(path, EImageBlitMode::COLORKEY);
 	dragAndDropCursor(anim->getImage(index));
 }
 

+ 2 - 2
client/gui/InterfaceObjectConfigurable.cpp

@@ -333,7 +333,7 @@ std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNo
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
 
 	if ( config["playerColored"].Bool() && LOCPLINT)
-		pic->colorize(LOCPLINT->playerID);
+		pic->setPlayerColor(LOCPLINT->playerID);
 	return pic;
 }
 
@@ -571,7 +571,7 @@ std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const
 	if(playerColor.isValidPlayer())
 	{
 		auto result = std::make_shared<FilledTexturePlayerColored>(image, rect);
-		result->playerColored(playerColor);
+		result->setPlayerColor(playerColor);
 		return result;
 	}
 	return std::make_shared<CFilledTexture>(image, rect);

+ 13 - 0
client/gui/Shortcut.h

@@ -186,6 +186,19 @@ enum class EShortcut
 	BATTLE_TOGGLE_HEROES_STATS,
 	BATTLE_OPEN_ACTIVE_UNIT,
 	BATTLE_OPEN_HOVERED_UNIT,
+	BATTLE_TOGGLE_QUICKSPELL,
+	BATTLE_SPELL_SHORTCUT_0,
+	BATTLE_SPELL_SHORTCUT_1,
+	BATTLE_SPELL_SHORTCUT_2,
+	BATTLE_SPELL_SHORTCUT_3,
+	BATTLE_SPELL_SHORTCUT_4,
+	BATTLE_SPELL_SHORTCUT_5,
+	BATTLE_SPELL_SHORTCUT_6,
+	BATTLE_SPELL_SHORTCUT_7,
+	BATTLE_SPELL_SHORTCUT_8,
+	BATTLE_SPELL_SHORTCUT_9,
+	BATTLE_SPELL_SHORTCUT_10,
+	BATTLE_SPELL_SHORTCUT_11,
 
 	MARKET_DEAL,
 	MARKET_MAX_AMOUNT,

+ 13 - 0
client/gui/ShortcutHandler.cpp

@@ -222,6 +222,19 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
 		{"battleTacticsNext",        EShortcut::BATTLE_TACTICS_NEXT       },
 		{"battleTacticsEnd",         EShortcut::BATTLE_TACTICS_END        },
 		{"battleSelectAction",       EShortcut::BATTLE_SELECT_ACTION      },
+		{"battleToggleQuickSpell",   EShortcut::BATTLE_TOGGLE_QUICKSPELL   },
+		{"battleSpellShortcut0",     EShortcut::BATTLE_SPELL_SHORTCUT_0   },
+		{"battleSpellShortcut1",     EShortcut::BATTLE_SPELL_SHORTCUT_1   },
+		{"battleSpellShortcut2",     EShortcut::BATTLE_SPELL_SHORTCUT_2   },
+		{"battleSpellShortcut3",     EShortcut::BATTLE_SPELL_SHORTCUT_3   },
+		{"battleSpellShortcut4",     EShortcut::BATTLE_SPELL_SHORTCUT_4   },
+		{"battleSpellShortcut5",     EShortcut::BATTLE_SPELL_SHORTCUT_5   },
+		{"battleSpellShortcut6",     EShortcut::BATTLE_SPELL_SHORTCUT_6   },
+		{"battleSpellShortcut7",     EShortcut::BATTLE_SPELL_SHORTCUT_7   },
+		{"battleSpellShortcut8",     EShortcut::BATTLE_SPELL_SHORTCUT_8   },
+		{"battleSpellShortcut9",     EShortcut::BATTLE_SPELL_SHORTCUT_9   },
+		{"battleSpellShortcut10",    EShortcut::BATTLE_SPELL_SHORTCUT_10  },
+		{"battleSpellShortcut11",    EShortcut::BATTLE_SPELL_SHORTCUT_11  },
 		{"spectateTrackHero",        EShortcut::SPECTATE_TRACK_HERO       },
 		{"spectateSkipBattle",       EShortcut::SPECTATE_SKIP_BATTLE      },
 		{"spectateSkipBattleResult", EShortcut::SPECTATE_SKIP_BATTLE_RESULT },

+ 5 - 8
client/lobby/CSelectionBase.cpp

@@ -401,7 +401,7 @@ PvPBox::PvPBox(const Rect & rect)
 	setRedrawParent(true);
 
 	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, rect.w, rect.h));
-	backgroundTexture->playerColored(PlayerColor(1));
+	backgroundTexture->setPlayerColor(PlayerColor(1));
 	backgroundBorder = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, rect.w, rect.h), ColorRGBA(0, 0, 0, 64), ColorRGBA(96, 96, 96, 255), 1);
 
 	townSelector = std::make_shared<TownSelector>(Point(5, 3));
@@ -515,9 +515,6 @@ CFlagBox::CFlagBox(const Rect & rect)
 
 	labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
 	labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
-
-	iconsTeamFlags = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITGFLAGS.DEF"));
-	iconsTeamFlags->preload();
 }
 
 void CFlagBox::recreate()
@@ -529,7 +526,7 @@ void CFlagBox::recreate()
 	const int enemiesX = 5 + 133 + (int)labelEnemies->getWidth();
 	for(auto i = CSH->si->playerInfos.cbegin(); i != CSH->si->playerInfos.cend(); i++)
 	{
-		auto flag = std::make_shared<CAnimImage>(iconsTeamFlags, i->first.getNum(), 0);
+		auto flag = std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS.DEF"), i->first.getNum(), 0);
 		if(i->first == CSH->myFirstColor() || CSH->getPlayerTeamId(i->first) == CSH->getPlayerTeamId(CSH->myFirstColor()))
 		{
 			flag->moveTo(Point(pos.x + alliesX + (int)flagsAllies.size()*flag->pos.w, pos.y));
@@ -546,10 +543,10 @@ void CFlagBox::recreate()
 void CFlagBox::showPopupWindow(const Point & cursorPosition)
 {
 	if(SEL->getMapInfo())
-		GH.windows().createAndPushWindow<CFlagBoxTooltipBox>(iconsTeamFlags);
+		GH.windows().createAndPushWindow<CFlagBoxTooltipBox>();
 }
 
-CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons)
+CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox()
 	: CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("DIBOXBCK"))
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@@ -577,7 +574,7 @@ CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> ico
 		int curx = 128 - 9 * team.size();
 		for(const auto & player : team)
 		{
-			iconsFlags.push_back(std::make_shared<CAnimImage>(icons, player, 0, curx, 75 + 50 * curIdx));
+			iconsFlags.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS.DEF"), player, 0, curx, 75 + 50 * curIdx));
 			curx += 18;
 		}
 		++curIdx;

+ 1 - 2
client/lobby/CSelectionBase.h

@@ -174,7 +174,6 @@ public:
 
 class CFlagBox : public CIntObject
 {
-	std::shared_ptr<CAnimation> iconsTeamFlags;
 	std::shared_ptr<CLabel> labelAllies;
 	std::shared_ptr<CLabel> labelEnemies;
 	std::vector<std::shared_ptr<CAnimImage>> flagsAllies;
@@ -192,7 +191,7 @@ public:
 		std::shared_ptr<CLabelGroup> labelGroupTeams;
 		std::vector<std::shared_ptr<CAnimImage>> iconsFlags;
 	public:
-		CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons);
+		CFlagBoxTooltipBox();
 	};
 };
 

+ 1 - 2
client/lobby/OptionsTab.cpp

@@ -518,9 +518,8 @@ void OptionsTab::SelectionWindow::recreate(int sliderPos)
 	int sliderWidth = ((amountLines > MAX_LINES) ? 16 : 0);
 
 	pos = Rect(pos.x, pos.y, x + sliderWidth, y);
-
 	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w - sliderWidth, pos.h));
-	backgroundTexture->playerColored(PlayerColor(1));
+	backgroundTexture->setPlayerColor(PlayerColor(1));
 	updateShadow();
 
 	if(type == SelType::TOWN)

+ 6 - 9
client/lobby/SelectionTab.cpp

@@ -227,11 +227,8 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 		buttonsSortBy.push_back(sortByDate);
 	}
 
-	iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF"));
-	iconsVictoryCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRVICT.DEF"));
-	iconsLossCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRLOSS.DEF"));
 	for(int i = 0; i < positionsToShow; i++)
-		listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
+		listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25)));
 
 	labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, tabTitle);
 	slider = std::make_shared<CSlider>(Point(372, 86 + (enableUiEnhancements ? 30 : 0)), (tabType != ESelectionScreen::saveGame ? 480 : 430) - (enableUiEnhancements ? 30 : 0), std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, (int)curItems.size(), 0, Orientation::VERTICAL, CSlider::BLUE);
@@ -883,11 +880,11 @@ std::unordered_set<ResourcePath> SelectionTab::getFiles(std::string dirURI, ERes
 	return ret;
 }
 
-SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
+SelectionTab::ListItem::ListItem(Point position)
 	: CIntObject(LCLICK, position)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	pictureEmptyLine = std::make_shared<CPicture>(GH.renderHandler().loadImage(ImagePath::builtin("camcust")), Rect(25, 121, 349, 26), -8, -14);
+	pictureEmptyLine = std::make_shared<CPicture>(ImagePath::builtin("camcust"), Rect(25, 121, 349, 26), -8, -14);
 	labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185);
 	labelName->setAutoRedraw(false);
 	labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
@@ -898,9 +895,9 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> ico
 	labelMapSizeLetter->setAutoRedraw(false);
 	// FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise
 	iconFolder = std::make_shared<CPicture>(ImagePath::builtin("lobby/iconFolder.png"), -8, -12);
-	iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);
-	iconVictoryCondition = std::make_shared<CAnimImage>(iconsVictory, 0, 0, 277, -12);
-	iconLossCondition = std::make_shared<CAnimImage>(iconsLoss, 0, 0, 310, -12);
+	iconFormat = std::make_shared<CAnimImage>(AnimationPath::builtin("SCSELC.DEF"), 0, 0, 59, -12);
+	iconVictoryCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRVICT.DEF"), 0, 0, 277, -12);
+	iconLossCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRLOSS.DEF"), 0, 0, 310, -12);
 }
 
 void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool selected)

+ 2 - 1
client/lobby/SelectionTab.h

@@ -20,6 +20,7 @@ class CSlider;
 class CLabel;
 class CPicture;
 class IImage;
+class CAnimation;
 
 enum ESortBy
 {
@@ -58,7 +59,7 @@ class SelectionTab : public CIntObject
 		std::shared_ptr<CPicture> pictureEmptyLine;
 		std::shared_ptr<CLabel> labelName;
 
-		ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss);
+		ListItem(Point position);
 		void updateItem(std::shared_ptr<ElementInfo> info = {}, bool selected = false);
 	};
 	std::vector<std::shared_ptr<ListItem>> listItems;

+ 0 - 1
client/mainmenu/CMainMenu.h

@@ -24,7 +24,6 @@ class CGStatusBar;
 class CTextBox;
 class CTabbedInt;
 class CAnimImage;
-class CAnimation;
 class CButton;
 class CFilledTexture;
 class CLabel;

+ 21 - 32
client/mapView/MapRenderer.cpp

@@ -104,31 +104,29 @@ void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBl
 	for(auto & entry : terrainAnimations)
 	{
 		if (!filename.empty())
-		{
-			entry = GH.renderHandler().loadAnimation(filename);
-			entry->preload();
-		}
-		else
-			entry = GH.renderHandler().createAnimation();
-
-		for(size_t i = 0; i < entry->size(); ++i)
-			entry->getImage(i)->setBlitMode(blitMode);
+			entry = GH.renderHandler().loadAnimation(filename, blitMode);
 	}
 
-	for(size_t i = 0; i < terrainAnimations[0]->size(); ++i)
-	{
-		terrainAnimations[1]->getImage(i)->verticalFlip();
-		terrainAnimations[3]->getImage(i)->verticalFlip();
+	if (terrainAnimations[1])
+		terrainAnimations[1]->verticalFlip();
 
-		terrainAnimations[2]->getImage(i)->horizontalFlip();
-		terrainAnimations[3]->getImage(i)->horizontalFlip();
-	}
+	if (terrainAnimations[3])
+		terrainAnimations[3]->verticalFlip();
+
+	if (terrainAnimations[2])
+		terrainAnimations[2]->horizontalFlip();
+
+	if (terrainAnimations[3])
+		terrainAnimations[3]->horizontalFlip();
 }
 
 std::shared_ptr<IImage> MapTileStorage::find(size_t fileIndex, size_t rotationIndex, size_t imageIndex)
 {
 	const auto & animation = animations[fileIndex][rotationIndex];
-	return animation->getImage(imageIndex);
+	if (animation)
+		return animation->getImage(imageIndex);
+	else
+		return nullptr;
 }
 
 MapRendererTerrain::MapRendererTerrain()
@@ -255,8 +253,7 @@ uint8_t MapRendererRoad::checksum(IMapRendererContext & context, const int3 & co
 
 MapRendererBorder::MapRendererBorder()
 {
-	animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG"));
-	animation->preload();
+	animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG"), EImageBlitMode::OPAQUE);
 }
 
 size_t MapRendererBorder::getIndexForTile(IMapRendererContext & context, const int3 & tile)
@@ -317,13 +314,8 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 &
 
 MapRendererFow::MapRendererFow()
 {
-	fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"));
-	fogOfWarFullHide->preload();
-	fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"));
-	fogOfWarPartialHide->preload();
-
-	for(size_t i = 0; i < fogOfWarFullHide->size(); ++i)
-		fogOfWarFullHide->getImage(i)->setBlitMode(EImageBlitMode::OPAQUE);
+	fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"), EImageBlitMode::OPAQUE);
+	fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"), EImageBlitMode::ALPHA);
 
 	static const std::vector<int> rotations = {22, 15, 2, 13, 12, 16, 28, 17, 20, 19, 7, 24, 26, 25, 30, 32, 27};
 
@@ -332,8 +324,7 @@ MapRendererFow::MapRendererFow()
 	for(const int rotation : rotations)
 	{
 		fogOfWarPartialHide->duplicateImage(0, rotation, 0);
-		auto image = fogOfWarPartialHide->getImage(size, 0);
-		image->verticalFlip();
+		fogOfWarPartialHide->verticalFlip(size, 0);
 		size++;
 	}
 }
@@ -408,9 +399,8 @@ std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath
 	if(it != animations.end())
 		return it->second;
 
-	auto ret = GH.renderHandler().loadAnimation(filename);
+	auto ret = GH.renderHandler().loadAnimation(filename, EImageBlitMode::ALPHA);
 	animations[filename] = ret;
-	ret->preload();
 
 	if(generateMovementGroups)
 	{
@@ -630,9 +620,8 @@ uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 &
 }
 
 MapRendererPath::MapRendererPath()
-	: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG")))
+	: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG"), EImageBlitMode::ALPHA))
 {
-	pathNodes->preload();
 }
 
 size_t MapRendererPath::selectImageReachability(bool reachableToday, size_t imageIndex)

+ 1 - 5
client/mapView/MapViewCache.cpp

@@ -31,15 +31,11 @@ MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
 	: model(model)
 	, cachedLevel(0)
 	, mapRenderer(new MapRenderer())
-	, iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol")))
+	, iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol"), EImageBlitMode::COLORKEY))
 	, intermediate(new Canvas(Point(32, 32)))
 	, terrain(new Canvas(model->getCacheDimensionsPixels()))
 	, terrainTransition(new Canvas(model->getPixelsVisibleDimensions()))
 {
-	iconsStorage->preload();
-	for(size_t i = 0; i < iconsStorage->size(); ++i)
-		iconsStorage->getImage(i)->setBlitMode(EImageBlitMode::COLORKEY);
-
 	Point visibleSize = model->getTilesVisibleDimensions();
 	terrainChecksum.resize(boost::extents[visibleSize.x][visibleSize.y]);
 	tilesUpToDate.resize(boost::extents[visibleSize.x][visibleSize.y]);

+ 12 - 1
client/media/CMusicHandler.cpp

@@ -16,6 +16,7 @@
 #include "../renderSDL/SDLRWwrapper.h"
 
 #include "../../lib/CRandomGenerator.h"
+#include "../../lib/CTownHandler.h"
 #include "../../lib/TerrainHandler.h"
 #include "../../lib/filesystem/Filesystem.h"
 
@@ -64,7 +65,17 @@ void CMusicHandler::loadTerrainMusicThemes()
 {
 	for(const auto & terrain : CGI->terrainTypeHandler->objects)
 	{
-		addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
+		for(const auto & filename : terrain->musicFilename)
+			addEntryToSet("terrain_" + terrain->getJsonKey(), filename);
+	}
+
+	for(const auto & faction : CGI->townh->objects)
+	{
+		if (!faction || !faction->hasTown())
+			continue;
+
+		for(const auto & filename : faction->town->clientInfo.musicTheme)
+			addEntryToSet("faction_" + faction->getJsonKey(), filename);
 	}
 }
 

+ 8 - 8
client/media/CVideoHandler.cpp

@@ -494,7 +494,11 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
 	if (!openInput(videoToOpen))
 		return { nullptr, 0};
 	openContext();
-	openCodec(findAudioStream());
+
+	int audioStreamIndex = findAudioStream();
+	if (audioStreamIndex == -1)
+		return { nullptr, 0};
+	openCodec(audioStreamIndex);
 
 	const auto * codecpar = getCodecParameters();
 
@@ -547,7 +551,7 @@ std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const Vide
 		ui16 NumOfChan = 2;
 		ui32 SamplesPerSec = 22050;
 		ui32 bytesPerSec = 22050 * 2;
-		ui16 blockAlign = 2;
+		ui16 blockAlign = 1;
 		ui16 bitsPerSample = 32;
 		ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
 		ui32 Subchunk2Size;
@@ -582,7 +586,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po
 		return true;
 
 	instance.openVideo();
-	instance.prepareOutput(scale, useOverlay);
+	instance.prepareOutput(scale, true);
 
 	auto lastTimePoint = boost::chrono::steady_clock::now();
 
@@ -604,10 +608,7 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po
 		rect.w = instance.dimensions.x;
 		rect.h = instance.dimensions.y;
 
-		if(useOverlay)
-			SDL_RenderFillRect(mainRenderer, &rect);
-		else
-			SDL_RenderClear(mainRenderer);
+		SDL_RenderFillRect(mainRenderer, &rect);
 
 		if(instance.textureYUV)
 			SDL_RenderCopy(mainRenderer, instance.textureYUV, nullptr, &rect);
@@ -623,7 +624,6 @@ bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & po
 		auto timePointAfterPresent = boost::chrono::steady_clock::now();
 		auto timeSpentBusy = boost::chrono::duration_cast<boost::chrono::milliseconds>(timePointAfterPresent - lastTimePoint);
 
-		logGlobal->info("Sleeping for %d", (targetFrameTime - timeSpentBusy).count());
 		if(targetFrameTime > timeSpentBusy)
 			boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
 

+ 69 - 229
client/render/CAnimation.cpp

@@ -10,34 +10,12 @@
 #include "StdInc.h"
 #include "CAnimation.h"
 
-#include "CDefFile.h"
+#include "../gui/CGuiHandler.h"
+#include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
-#include "Graphics.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/json/JsonUtils.h"
-#include "../renderSDL/SDLImage.h"
-
-std::shared_ptr<IImage> CAnimation::getFromExtraDef(std::string filename)
-{
-	size_t pos = filename.find(':');
-	if (pos == -1)
-		return nullptr;
-	CAnimation anim(AnimationPath::builtinTODO(filename.substr(0, pos)));
-	pos++;
-	size_t frame = atoi(filename.c_str()+pos);
-	size_t group = 0;
-	pos = filename.find(':', pos);
-	if (pos != -1)
-	{
-		pos++;
-		group = frame;
-		frame = atoi(filename.c_str()+pos);
-	}
-	anim.load(frame ,group);
-	auto ret = anim.images[group][frame];
-	anim.images.clear();
-	return ret;
-}
 
 bool CAnimation::loadFrame(size_t frame, size_t group)
 {
@@ -47,40 +25,23 @@ bool CAnimation::loadFrame(size_t frame, size_t group)
 		return false;
 	}
 
-	auto image = getImage(frame, group, false);
+	if(auto image = getImageImpl(frame, group, false))
+		return true;
+
+	std::shared_ptr<IImage> image = GH.renderHandler().loadImage(getImageLocator(frame, group), mode);
+
 	if(image)
 	{
+		images[group][frame] = image;
 		return true;
 	}
-
-	//try to get image from def
-	if(source[group][frame].getType() == JsonNode::JsonType::DATA_NULL)
+	else
 	{
-		if(defFile)
-		{
-			auto frameList = defFile->getEntries();
-
-			if(vstd::contains(frameList, group) && frameList.at(group) > frame) // frame is present
-			{
-				images[group][frame] = std::make_shared<SDLImage>(defFile.get(), frame, group);
-				return true;
-			}
-		}
-		// still here? image is missing
-
+		// image is missing
 		printError(frame, group, "LoadFrame");
-		images[group][frame] = std::make_shared<SDLImage>(ImagePath::builtin("DEFAULT"), EImageBlitMode::ALPHA);
-	}
-	else //load from separate file
-	{
-		auto img = getFromExtraDef(source[group][frame]["file"].String());
-		if(!img)
-			img = std::make_shared<SDLImage>(source[group][frame], EImageBlitMode::ALPHA);
-
-		images[group][frame] = img;
-		return true;
+		images[group][frame] = GH.renderHandler().loadImage(ImagePath::builtin("DEFAULT"), EImageBlitMode::OPAQUE);
+		return false;
 	}
-	return false;
 }
 
 bool CAnimation::unloadFrame(size_t frame, size_t group)
@@ -97,45 +58,6 @@ bool CAnimation::unloadFrame(size_t frame, size_t group)
 	return false;
 }
 
-void CAnimation::initFromJson(const JsonNode & config)
-{
-	std::string basepath;
-	basepath = config["basepath"].String();
-
-	JsonNode base;
-	base["margins"] = config["margins"];
-	base["width"] = config["width"];
-	base["height"] = config["height"];
-
-	for(const JsonNode & group : config["sequences"].Vector())
-	{
-		size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
-		source[groupID].clear();
-
-		for(const JsonNode & frame : group["frames"].Vector())
-		{
-			JsonNode toAdd;
-			JsonUtils::inherit(toAdd, base);
-			toAdd["file"].String() = basepath + frame.String();
-			source[groupID].push_back(toAdd);
-		}
-	}
-
-	for(const JsonNode & node : config["images"].Vector())
-	{
-		size_t group = node["group"].Integer();
-		size_t frame = node["frame"].Integer();
-
-		if (source[group].size() <= frame)
-			source[group].resize(frame+1);
-
-		JsonNode toAdd;
-		JsonUtils::inherit(toAdd, base);
-		toAdd["file"].String() = basepath + node["file"].String();
-		source[group][frame] = toAdd;
-	}
-}
-
 void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
 {
 	if(images.empty())
@@ -169,109 +91,36 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
 	logGlobal->info("Exported %d frames to %s", counter, actualPath.string());
 }
 
-void CAnimation::init()
-{
-	if(defFile)
-	{
-		const std::map<size_t, size_t> defEntries = defFile->getEntries();
-
-		for (auto & defEntry : defEntries)
-			source[defEntry.first].resize(defEntry.second);
-	}
-
-	if (vstd::contains(graphics->imageLists, name.getName()))
-		initFromJson(graphics->imageLists[name.getName()]);
-
-	auto jsonResource = name.toType<EResType::JSON>();
-	auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource);
-
-	for(auto & loader : configList)
-	{
-		auto stream = loader->load(jsonResource);
-		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
-		stream->read(textData.get(), stream->getSize());
-
-		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize());
-
-		initFromJson(config);
-	}
-}
-
 void CAnimation::printError(size_t frame, size_t group, std::string type) const
 {
 	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name.getOriginalName(), group, frame);
 }
 
-CAnimation::CAnimation(const AnimationPath & Name):
+CAnimation::CAnimation(const AnimationPath & Name, std::map<size_t, std::vector <ImageLocator> > layout, EImageBlitMode mode):
 	name(boost::starts_with(Name.getName(), "SPRITES") ? Name : Name.addPrefix("SPRITES/")),
-	preloaded(false)
+	source(layout),
+	mode(mode)
 {
-	if(CResourceHandler::get()->existsResource(name))
-	{
-		try
-		{
-			defFile = std::make_shared<CDefFile>(name);
-		}
-		catch ( const std::runtime_error & e)
-		{
-			logAnim->error("Def file %s failed to load! Reason: %s", Name.getOriginalName(), e.what());
-		}
-	}
-
-	init();
-
 	if(source.empty())
 		logAnim->error("Animation %s failed to load", Name.getOriginalName());
 }
 
-CAnimation::CAnimation():
-	preloaded(false)
-{
-	init();
-}
-
 CAnimation::~CAnimation() = default;
 
 void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup)
 {
-	if(!source.count(sourceGroup))
-	{
-		logAnim->error("Group %d missing in %s", sourceGroup, name.getName());
-		return;
-	}
-
-	if(source[sourceGroup].size() <= sourceFrame)
-	{
-		logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name.getName());
-		return;
-	}
-
-	//todo: clone actual loaded Image object
-	JsonNode clone(source[sourceGroup][sourceFrame]);
-
-	if(clone.getType() == JsonNode::JsonType::DATA_NULL)
-	{
-		std::string temp =  name.getName()+":"+std::to_string(sourceGroup)+":"+std::to_string(sourceFrame);
-		clone["file"].String() = temp;
-	}
-
+	ImageLocator clone(getImageLocator(sourceFrame, sourceGroup));
 	source[targetGroup].push_back(clone);
-
-	size_t index = source[targetGroup].size() - 1;
-
-	if(preloaded)
-		load(index, targetGroup);
 }
 
-void CAnimation::setCustom(std::string filename, size_t frame, size_t group)
+std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose)
 {
-	if (source[group].size() <= frame)
-		source[group].resize(frame+1);
-	source[group][frame]["file"].String() = filename;
-	//FIXME: update image if already loaded
+	if (!loadFrame(frame, group))
+		return nullptr;
+	return getImageImpl(frame, group, verbose);
 }
 
-std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool verbose) const
+std::shared_ptr<IImage> CAnimation::getImageImpl(size_t frame, size_t group, bool verbose)
 {
 	auto groupIter = images.find(group);
 	if (groupIter != images.end())
@@ -285,74 +134,58 @@ std::shared_ptr<IImage> CAnimation::getImage(size_t frame, size_t group, bool ve
 	return nullptr;
 }
 
-void CAnimation::load()
-{
-	for (auto & elem : source)
-		for (size_t image=0; image < elem.second.size(); image++)
-			loadFrame(image, elem.first);
-}
-
-void CAnimation::unload()
-{
-	for (auto & elem : source)
-		for (size_t image=0; image < elem.second.size(); image++)
-			unloadFrame(image, elem.first);
-
-}
-
-void CAnimation::preload()
-{
-	if(!preloaded)
-	{
-		preloaded = true;
-		load();
-	}
-}
-
-void CAnimation::loadGroup(size_t group)
+size_t CAnimation::size(size_t group) const
 {
-	if (vstd::contains(source, group))
-		for (size_t image=0; image < source[group].size(); image++)
-			loadFrame(image, group);
+	auto iter = source.find(group);
+	if (iter != source.end())
+		return iter->second.size();
+	return 0;
 }
 
-void CAnimation::unloadGroup(size_t group)
+void CAnimation::horizontalFlip()
 {
-	if (vstd::contains(source, group))
-		for (size_t image=0; image < source[group].size(); image++)
-			unloadFrame(image, group);
+	for(auto & group : source)
+		for(size_t i = 0; i < group.second.size(); ++i)
+			horizontalFlip(i, group.first);
 }
 
-void CAnimation::load(size_t frame, size_t group)
+void CAnimation::verticalFlip()
 {
-	loadFrame(frame, group);
+	for(auto & group : source)
+		for(size_t i = 0; i < group.second.size(); ++i)
+			verticalFlip(i, group.first);
 }
 
-void CAnimation::unload(size_t frame, size_t group)
+void CAnimation::horizontalFlip(size_t frame, size_t group)
 {
-	unloadFrame(frame, group);
-}
+	try
+	{
+		images.at(group).at(frame) = nullptr;
+	}
+	catch (const std::out_of_range &)
+	{
+		// ignore - image not loaded
+	}
 
-size_t CAnimation::size(size_t group) const
-{
-	auto iter = source.find(group);
-	if (iter != source.end())
-		return iter->second.size();
-	return 0;
+	auto locator = getImageLocator(frame, group);
+	locator.horizontalFlip = !locator.horizontalFlip;
+	source[group][frame] = locator;
 }
 
-void CAnimation::horizontalFlip()
+void CAnimation::verticalFlip(size_t frame, size_t group)
 {
-	for(auto & group : images)
-		for(auto & image : group.second)
-			image.second->horizontalFlip();
-}
+	try
+	{
+		images.at(group).at(frame) = nullptr;
+	}
+	catch (const std::out_of_range &)
+	{
+		// ignore - image not loaded
+	}
 
-void CAnimation::verticalFlip()
-{
-	for(auto & group : images)
-		for(auto & image : group.second)
-			image.second->verticalFlip();
+	auto locator = getImageLocator(frame, group);
+	locator.verticalFlip = !locator.verticalFlip;
+	source[group][frame] = locator;
 }
 
 void CAnimation::playerColored(PlayerColor player)
@@ -367,9 +200,16 @@ void CAnimation::createFlippedGroup(const size_t sourceGroup, const size_t targe
 	for(size_t frame = 0; frame < size(sourceGroup); ++frame)
 	{
 		duplicateImage(sourceGroup, frame, targetGroup);
-
-		auto image = getImage(frame, targetGroup);
-		image->verticalFlip();
+		verticalFlip(frame, targetGroup);
 	}
 }
 
+ImageLocator CAnimation::getImageLocator(size_t frame, size_t group) const
+{
+	const ImageLocator & locator = source.at(group).at(frame);
+
+	if (!locator.empty())
+		return locator;
+
+	return ImageLocator(name, frame, group);
+}

+ 14 - 31
client/render/CAnimation.h

@@ -9,6 +9,9 @@
  */
 #pragma once
 
+#include "IImage.h"
+#include "ImageLocator.h"
+
 #include "../../lib/GameConstants.h"
 #include "../../lib/filesystem/ResourcePath.h"
 
@@ -17,15 +20,14 @@ class JsonNode;
 VCMI_LIB_NAMESPACE_END
 
 class CDefFile;
-class IImage;
 class RenderHandler;
 
 /// Class for handling animation
 class CAnimation
 {
 private:
-	//source[group][position] - file with this frame, if string is empty - image located in def file
-	std::map<size_t, std::vector <JsonNode> > source;
+	//source[group][position] - location of this frame
+	std::map<size_t, std::vector <ImageLocator> > source;
 
 	//bitmap[group][position], store objects with loaded bitmaps
 	std::map<size_t, std::map<size_t, std::shared_ptr<IImage> > > images;
@@ -33,9 +35,10 @@ private:
 	//animation file name
 	AnimationPath name;
 
-	bool preloaded;
+	EImageBlitMode mode;
 
-	std::shared_ptr<CDefFile> defFile;
+	// current player color, if any
+	PlayerColor player = PlayerColor::CANNOT_DETERMINE;
 
 	//loader, will be called by load(), require opened def file for loading from it. Returns true if image is loaded
 	bool loadFrame(size_t frame, size_t group);
@@ -43,49 +46,29 @@ private:
 	//unloadFrame, returns true if image has been unloaded ( either deleted or decreased refCount)
 	bool unloadFrame(size_t frame, size_t group);
 
-	//initialize animation from file
-	void initFromJson(const JsonNode & input);
-	void init();
-
 	//to get rid of copy-pasting error message :]
 	void printError(size_t frame, size_t group, std::string type) const;
 
-	//not a very nice method to get image from another def file
-	//TODO: remove after implementing resource manager
-	std::shared_ptr<IImage> getFromExtraDef(std::string filename);
+	std::shared_ptr<IImage> getImageImpl(size_t frame, size_t group=0, bool verbose=true);
 
+	ImageLocator getImageLocator(size_t frame, size_t group) const;
 public:
-	CAnimation(const AnimationPath & Name);
-	CAnimation();
+	CAnimation(const AnimationPath & Name, std::map<size_t, std::vector <ImageLocator> > layout, EImageBlitMode mode);
 	~CAnimation();
 
 	//duplicates frame at [sourceGroup, sourceFrame] as last frame in targetGroup
 	//and loads it if animation is preloaded
 	void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup);
 
-	//add custom surface to the selected position.
-	void setCustom(std::string filename, size_t frame, size_t group=0);
-
-	std::shared_ptr<IImage> getImage(size_t frame, size_t group=0, bool verbose=true) const;
+	std::shared_ptr<IImage> getImage(size_t frame, size_t group=0, bool verbose=true);
 
 	void exportBitmaps(const boost::filesystem::path & path) const;
 
-	//all available frames
-	void load  ();
-	void unload();
-	void preload();
-
-	//all frames from group
-	void loadGroup  (size_t group);
-	void unloadGroup(size_t group);
-
-	//single image
-	void load  (size_t frame, size_t group=0);
-	void unload(size_t frame, size_t group=0);
-
 	//total count of frames in group (including not loaded)
 	size_t size(size_t group=0) const;
 
+	void horizontalFlip(size_t frame, size_t group=0);
+	void verticalFlip(size_t frame, size_t group=0);
 	void horizontalFlip();
 	void verticalFlip();
 	void playerColored(PlayerColor player);

+ 1 - 47
client/render/CDefFile.cpp

@@ -18,50 +18,6 @@
 
 #include <SDL_pixels.h>
 
-// Extremely simple file cache. TODO: smarter, more general solution
-class CFileCache
-{
-	static const int cacheSize = 50; //Max number of cached files
-	struct FileData
-	{
-		AnimationPath             name;
-		size_t                 size;
-		std::unique_ptr<ui8[]> data;
-
-		std::unique_ptr<ui8[]> getCopy()
-		{
-			auto ret = std::unique_ptr<ui8[]>(new ui8[size]);
-			std::copy(data.get(), data.get() + size, ret.get());
-			return ret;
-		}
-		FileData(AnimationPath name_, size_t size_, std::unique_ptr<ui8[]> data_):
-			name{std::move(name_)},
-			size{size_},
-			data{std::move(data_)}
-		{}
-	};
-
-	std::deque<FileData> cache;
-public:
-	std::unique_ptr<ui8[]> getCachedFile(AnimationPath rid)
-	{
-		for(auto & file : cache)
-		{
-			if (file.name == rid)
-				return file.getCopy();
-		}
-		// Still here? Cache miss
-		if (cache.size() > cacheSize)
-			cache.pop_front();
-
-		auto data =  CResourceHandler::get()->load(rid)->readAll();
-
-		cache.emplace_back(std::move(rid), data.second, std::move(data.first));
-
-		return cache.back().getCopy();
-	}
-};
-
 enum class DefType : uint32_t
 {
 	SPELL = 0x40,
@@ -76,8 +32,6 @@ enum class DefType : uint32_t
 	BATTLE_HERO = 0x49
 };
 
-static CFileCache animationCache;
-
 /*************************************************************************
  *  DefFile, class used for def loading                                  *
  *************************************************************************/
@@ -124,7 +78,7 @@ CDefFile::CDefFile(const AnimationPath & Name):
 		{0, 0, 0, 64 }  // shadow border below selection ( used in battle def's )
 	};
 
-	data = animationCache.getCachedFile(Name);
+	data = CResourceHandler::get()->load(Name)->readAll().first;
 
 	palette = std::unique_ptr<SDL_Color[]>(new SDL_Color[256]);
 	int it = 0;

+ 3 - 3
client/render/Canvas.cpp

@@ -81,14 +81,14 @@ void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos)
 {
 	assert(image);
 	if (image)
-		image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y);
+		image->draw(surface, pos + renderArea.topLeft());
 }
 
 void Canvas::draw(const std::shared_ptr<IImage>& image, const Point & pos, const Rect & sourceRect)
 {
 	assert(image);
 	if (image)
-		image->draw(surface, renderArea.x + pos.x, renderArea.y + pos.y, &sourceRect);
+		image->draw(surface, pos + renderArea.topLeft(), &sourceRect);
 }
 
 void Canvas::draw(const Canvas & image, const Point & pos)
@@ -192,7 +192,7 @@ void Canvas::fillTexture(const std::shared_ptr<IImage>& image)
 	for (int y=0; y < surface->h; y+= imageArea.h)
 	{
 		for (int x=0; x < surface->w; x+= imageArea.w)
-			image->draw(surface, renderArea.x + x, renderArea.y + y);
+			image->draw(surface, Point(renderArea.x + x, renderArea.y + y));
 	}
 }
 

+ 21 - 107
client/render/Graphics.cpp

@@ -131,76 +131,38 @@ Graphics::Graphics()
 	loadPaletteAndColors();
 	initializeBattleGraphics();
 	loadErmuToPicture();
-	initializeImageLists();
 
 	//(!) do not load any CAnimation here
 }
 
-void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player)
+void Graphics::setPlayerPalette(SDL_Palette * targetPalette, PlayerColor player)
 {
-	if(sur->format->palette)
+	SDL_Color palette[32];
+	if(player.isValidPlayer())
 	{
-		SDL_Color palette[32];
-		if(player.isValidPlayer())
-		{
-			for(int i=0; i<32; ++i)
-				palette[i] = CSDL_Ext::toSDL(playerColorPalette[player][i]);
-		}
-		else if(player == PlayerColor::NEUTRAL)
-		{
-			for(int i=0; i<32; ++i)
-				palette[i] = CSDL_Ext::toSDL(neutralColorPalette[i]);
-		}
-		else
-		{
-			logGlobal->error("Wrong player id in blueToPlayersAdv (%s)!", player.toString());
-			return;
-		}
-//FIXME: not all player colored images have player palette at last 32 indexes
-//NOTE: following code is much more correct but still not perfect (bugged with status bar)
-		CSDL_Ext::setColors(sur, palette, 224, 32);
-
-
-#if 0
-
-		SDL_Color * bluePalette = playerColorPalette + 32;
-
-		SDL_Palette * oldPalette = sur->format->palette;
-
-		SDL_Palette * newPalette = SDL_AllocPalette(256);
-
-		for(size_t destIndex = 0; destIndex < 256; destIndex++)
-		{
-			SDL_Color old = oldPalette->colors[destIndex];
-
-			bool found = false;
-
-			for(size_t srcIndex = 0; srcIndex < 32; srcIndex++)
-			{
-				if(old.b == bluePalette[srcIndex].b && old.g == bluePalette[srcIndex].g && old.r == bluePalette[srcIndex].r)
-				{
-					found = true;
-					newPalette->colors[destIndex] = palette[srcIndex];
-					break;
-				}
-			}
-			if(!found)
-				newPalette->colors[destIndex] = old;
-		}
-
-		SDL_SetSurfacePalette(sur, newPalette);
+		for(int i=0; i<32; ++i)
+			palette[i] = CSDL_Ext::toSDL(playerColorPalette[player][i]);
+	}
+	else
+	{
+		for(int i=0; i<32; ++i)
+			palette[i] = CSDL_Ext::toSDL(neutralColorPalette[i]);
+	}
 
-		SDL_FreePalette(newPalette);
+	SDL_SetPaletteColors(targetPalette, palette, 224, 32);
+}
 
-#endif // 0
+void Graphics::setPlayerFlagColor(SDL_Palette * targetPalette, PlayerColor player)
+{
+	if(player.isValidPlayer())
+	{
+		SDL_Color color = CSDL_Ext::toSDL(playerColors[player.getNum()]);
+		SDL_SetPaletteColors(targetPalette, &color, 5, 1);
 	}
 	else
 	{
-		//TODO: implement. H3 method works only for images with palettes.
-		// Add some kind of player-colored overlay?
-		// Or keep palette approach here and replace only colors of specific value(s)
-		// Or just wait for OpenGL support?
-		logGlobal->warn("Image must have palette to be player-colored!");
+		SDL_Color color = CSDL_Ext::toSDL(neutralColor);
+		SDL_SetPaletteColors(targetPalette, &color, 5, 1);
 	}
 }
 
@@ -244,51 +206,3 @@ void Graphics::loadErmuToPicture()
 	}
 	assert (etp_idx == 44);
 }
-
-void Graphics::addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName)
-{
-	if (!imageName.empty())
-	{
-		JsonNode entry;
-		if (group != 0)
-			entry["group"].Integer() = group;
-		entry["frame"].Integer() = index;
-		entry["file"].String() = imageName;
-
-		imageLists["SPRITES/" + listName]["images"].Vector().push_back(entry);
-	}
-}
-
-void Graphics::addImageListEntries(const EntityService * service)
-{
-	auto cb = std::bind(&Graphics::addImageListEntry, this, _1, _2, _3, _4);
-
-	auto loopCb = [&](const Entity * entity, bool & stop)
-	{
-		entity->registerIcons(cb);
-	};
-
-	service->forEachBase(loopCb);
-}
-
-void Graphics::initializeImageLists()
-{
-	addImageListEntries(CGI->creatures());
-	addImageListEntries(CGI->heroTypes());
-	addImageListEntries(CGI->artifacts());
-	addImageListEntries(CGI->factions());
-	addImageListEntries(CGI->spells());
-	addImageListEntries(CGI->skills());
-}
-
-std::shared_ptr<CAnimation> Graphics::getAnimation(const AnimationPath & path)
-{
-	if (cachedAnimations.count(path) != 0)
-		return cachedAnimations.at(path);
-
-	auto newAnimation = GH.renderHandler().loadAnimation(path);
-
-	newAnimation->preload();
-	cachedAnimations[path] = newAnimation;
-	return newAnimation;
-}

+ 3 - 13
client/render/Graphics.h

@@ -27,27 +27,18 @@ class JsonNode;
 
 VCMI_LIB_NAMESPACE_END
 
-struct SDL_Surface;
-class CAnimation;
+struct SDL_Palette;
 class IFont;
 
 /// Handles fonts, hero images, town images, various graphics
 class Graphics
 {
-	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
-	void addImageListEntries(const EntityService * service);
-
 	void initializeBattleGraphics();
 	void loadPaletteAndColors();
 	void loadErmuToPicture();
 	void loadFonts();
-	void initializeImageLists();
-
-	std::map<AnimationPath, std::shared_ptr<CAnimation>> cachedAnimations;
 
 public:
-	std::shared_ptr<CAnimation> getAnimation(const AnimationPath & path);
-
 	//Fonts
 	static const int FONTS_NUMBER = 9;
 	std::array< std::shared_ptr<IFont>, FONTS_NUMBER> fonts;
@@ -61,8 +52,6 @@ public:
 	PlayerPalette neutralColorPalette;
 	ColorRGBA neutralColor;
 
-	std::map<std::string, JsonNode> imageLists;
-
 	//towns
 	std::map<int, std::string> ERMUtoPicture[GameConstants::F_NUMBER]; //maps building ID to it's picture's name for each town type
 	//for battles
@@ -71,7 +60,8 @@ public:
 	//functions
 	Graphics();
 
-	void blueToPlayersAdv(SDL_Surface * sur, PlayerColor player); //replaces blue interface colour with a color of player
+	void setPlayerPalette(SDL_Palette * sur, PlayerColor player);
+	void setPlayerFlagColor(SDL_Palette * sur, PlayerColor player);
 };
 
 extern Graphics * graphics;

+ 26 - 15
client/render/IImage.h

@@ -26,13 +26,17 @@ class ColorFilter;
 /// Defines which blit method will be selected when image is used for rendering
 enum class EImageBlitMode
 {
-	/// Image can have no transparency and can be only used as background
+	/// Preferred for images that don't need any background
+	/// Indexed or RGBA: Image can have no transparency and can be only used as background
 	OPAQUE,
 
-	/// Image can have only a single color as transparency and has no semi-transparent areas
+	/// Preferred for images that may need transparency
+	/// Indexed: Image can have only a single color as transparency and has no semi-transparent areas
+	/// RGBA: full alpha transparency range, e.g. shadows
 	COLORKEY,
 
-	/// Image might have full alpha transparency range, e.g. shadows
+	/// Should be avoided if possible, use only for images that use def's with semi-transparency
+	/// Indexed or RGBA: Image might have full alpha transparency range, e.g. shadows
 	ALPHA
 };
 
@@ -46,18 +50,17 @@ public:
 	static constexpr int32_t SPECIAL_PALETTE_MASK_CREATURES = 0b11110011;
 
 	//draws image on surface "where" at position
-	virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr) const = 0;
-	virtual void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const = 0;
+	virtual void draw(SDL_Surface * where, const Point & pos, const Rect * src = nullptr) const = 0;
 
-	virtual std::shared_ptr<IImage> scaleFast(const Point & size) const = 0;
+	virtual void scaleFast(const Point & size) = 0;
 
 	virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
 
 	//Change palette to specific player
-	virtual void playerColored(PlayerColor player)=0;
+	virtual void playerColored(PlayerColor player) = 0;
 
 	//set special color for flag
-	virtual void setFlagColor(PlayerColor player)=0;
+	virtual void setFlagColor(PlayerColor player) = 0;
 
 	//test transparency of specific pixel
 	virtual bool isTransparent(const Point & coords) const = 0;
@@ -69,8 +72,6 @@ public:
 	//only indexed bitmaps, 16 colors maximum
 	virtual void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) = 0;
 	virtual void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) = 0;
-	virtual void resetPalette(int colorID) = 0;
-	virtual void resetPalette() = 0;
 
 	virtual void setAlpha(uint8_t value) = 0;
 	virtual void setBlitMode(EImageBlitMode mode) = 0;
@@ -78,11 +79,21 @@ public:
 	//only indexed bitmaps with 7 special colors
 	virtual void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) = 0;
 
-	virtual void horizontalFlip() = 0;
-	virtual void verticalFlip() = 0;
-	virtual void doubleFlip() = 0;
-
-	IImage() = default;
 	virtual ~IImage() = default;
 };
 
+class IConstImage
+{
+public:
+	virtual Point dimensions() const = 0;
+	virtual void exportBitmap(const boost::filesystem::path & path) const = 0;
+	virtual bool isTransparent(const Point & coords) const = 0;
+
+	virtual std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) = 0;
+
+	virtual std::shared_ptr<IConstImage> horizontalFlip() const = 0;
+	virtual std::shared_ptr<IConstImage> verticalFlip() const = 0;
+
+
+	virtual ~IConstImage() = default;
+};

+ 0 - 2
client/render/IImageLoader.h

@@ -14,8 +14,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 class Point;
 VCMI_LIB_NAMESPACE_END
 
-class SDLImage;
-
 struct SDL_Color;
 
 class IImageLoader

+ 11 - 6
client/render/IRenderHandler.h

@@ -9,7 +9,11 @@
  */
 #pragma once
 
-#include "../../lib/filesystem/ResourcePath.h"
+#include "ImageLocator.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+class Services;
+VCMI_LIB_NAMESPACE_END
 
 struct SDL_Surface;
 
@@ -22,17 +26,18 @@ class IRenderHandler : public boost::noncopyable
 public:
 	virtual ~IRenderHandler() = default;
 
+	/// Must be called once VLC loading is over to initialize icons
+	virtual void onLibraryLoadingFinished(const Services * services) = 0;
+
 	/// Loads image using given path
-	virtual std::shared_ptr<IImage> loadImage(const ImagePath & path) = 0;
+	virtual std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) = 0;
 	virtual std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) = 0;
+	virtual std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) = 0;
 
 	/// temporary compatibility method. Creates IImage from existing SDL_Surface
 	/// Surface will be shared, caller must still free it with SDL_FreeSurface
 	virtual std::shared_ptr<IImage> createImage(SDL_Surface * source) = 0;
 
 	/// Loads animation using given path
-	virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path) = 0;
-
-	/// Creates empty CAnimation
-	virtual std::shared_ptr<CAnimation> createAnimation() = 0;
+	virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) = 0;
 };

+ 56 - 0
client/render/ImageLocator.cpp

@@ -0,0 +1,56 @@
+/*
+ * ImageLocator.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "ImageLocator.h"
+
+#include "../../lib/json/JsonNode.h"
+
+
+ImageLocator::ImageLocator(const JsonNode & config)
+	: image(ImagePath::fromJson(config["file"]))
+	, defFile(AnimationPath::fromJson(config["defFile"]))
+	, defFrame(config["defFrame"].Integer())
+	, defGroup(config["defGroup"].Integer())
+	, verticalFlip(config["verticalFlip"].Bool())
+	, horizontalFlip(config["horizontalFlip"].Bool())
+{
+}
+
+ImageLocator::ImageLocator(const ImagePath & path)
+	: image(path)
+{
+}
+
+ImageLocator::ImageLocator(const AnimationPath & path, int frame, int group)
+	: defFile(path)
+	, defFrame(frame)
+	, defGroup(group)
+{
+}
+
+bool ImageLocator::operator<(const ImageLocator & other) const
+{
+	if(image != other.image)
+		return image < other.image;
+	if(defFile != other.defFile)
+		return defFile < other.defFile;
+	if(defGroup != other.defGroup)
+		return defGroup < other.defGroup;
+	if(defFrame != other.defFrame)
+		return defFrame < other.defFrame;
+	if(verticalFlip != other.verticalFlip)
+		return verticalFlip < other.verticalFlip;
+	return horizontalFlip < other.horizontalFlip;
+}
+
+bool ImageLocator::empty() const
+{
+	return !image.has_value() && !defFile.has_value();
+}

+ 31 - 0
client/render/ImageLocator.h

@@ -0,0 +1,31 @@
+/*
+ * ImageLocator.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/filesystem/ResourcePath.h"
+
+struct ImageLocator
+{
+	std::optional<ImagePath> image;
+	std::optional<AnimationPath> defFile;
+	int defFrame = -1;
+	int defGroup = -1;
+
+	bool verticalFlip = false;
+	bool horizontalFlip = false;
+
+	ImageLocator() = default;
+	ImageLocator(const AnimationPath & path, int frame, int group);
+	explicit ImageLocator(const JsonNode & config);
+	explicit ImageLocator(const ImagePath & path);
+
+	bool operator < (const ImageLocator & other) const;
+	bool empty() const;
+};

+ 1 - 1
client/renderSDL/CBitmapFont.cpp

@@ -125,7 +125,7 @@ void CBitmapFont::renderCharacter(SDL_Surface * surface, const BitmapChar & char
 
 	posX += character.leftOffset;
 
-	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
+	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface);
 
 	uint8_t bpp = surface->format->BytesPerPixel;
 

+ 1 - 1
client/renderSDL/CBitmapHanFont.cpp

@@ -41,7 +41,7 @@ void CBitmapHanFont::renderCharacter(SDL_Surface * surface, int characterIndex,
 	Rect clipRect;
 	CSDL_Ext::getClipRect(surface, clipRect);
 
-	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0);
+	CSDL_Ext::TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface);
 	uint8_t bpp = surface->format->BytesPerPixel;
 
 	// start of line, may differ from 0 due to end of surface or clipped surface

+ 1 - 1
client/renderSDL/CursorHardware.cpp

@@ -49,7 +49,7 @@ void CursorHardware::setImage(std::shared_ptr<IImage> image, const Point & pivot
 
 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
 
-	image->draw(cursorSurface);
+	image->draw(cursorSurface, Point(0,0));
 
 	auto oldCursor = cursor;
 	cursor = SDL_CreateColorCursor(cursorSurface, pivotOffset.x, pivotOffset.y);

+ 0 - 1
client/renderSDL/CursorHardware.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 
-class CAnimation;
 class IImage;
 struct SDL_Surface;
 struct SDL_Texture;

+ 1 - 1
client/renderSDL/CursorSoftware.cpp

@@ -58,7 +58,7 @@ void CursorSoftware::updateTexture()
 
 	CSDL_Ext::fillSurface(cursorSurface, CSDL_Ext::toSDL(Colors::TRANSPARENCY));
 
-	cursorImage->draw(cursorSurface);
+	cursorImage->draw(cursorSurface, Point(0,0));
 	SDL_UpdateTexture(cursorTexture, nullptr, cursorSurface->pixels, cursorSurface->pitch);
 	needUpdate = false;
 }

+ 0 - 1
client/renderSDL/CursorSoftware.h

@@ -9,7 +9,6 @@
  */
 #pragma once
 
-class CAnimation;
 class IImage;
 struct SDL_Surface;
 struct SDL_Texture;

+ 213 - 9
client/renderSDL/RenderHandler.cpp

@@ -10,31 +10,235 @@
 #include "StdInc.h"
 #include "RenderHandler.h"
 
-#include "../render/CAnimation.h"
 #include "SDLImage.h"
 
+#include "../render/CAnimation.h"
+#include "../render/CDefFile.h"
+
+#include "../../lib/json/JsonUtils.h"
+#include "../../lib/filesystem/Filesystem.h"
+
+#include <vcmi/ArtifactService.h>
+#include <vcmi/CreatureService.h>
+#include <vcmi/Entity.h>
+#include <vcmi/FactionService.h>
+#include <vcmi/HeroTypeService.h>
+#include <vcmi/Services.h>
+#include <vcmi/SkillService.h>
+#include <vcmi/spells/Service.h>
+
+std::shared_ptr<CDefFile> RenderHandler::getAnimationFile(const AnimationPath & path)
+{
+	AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
+
+	auto it = animationFiles.find(actualPath);
+
+	if (it != animationFiles.end())
+		return it->second;
+
+	if (!CResourceHandler::get()->existsResource(actualPath))
+	{
+		animationFiles[actualPath] = nullptr;
+		return nullptr;
+	}
+
+	auto result = std::make_shared<CDefFile>(actualPath);
+
+	animationFiles[actualPath] = result;
+	return result;
+}
+
+void RenderHandler::initFromJson(AnimationLayoutMap & source, const JsonNode & config)
+{
+	std::string basepath;
+	basepath = config["basepath"].String();
+
+	JsonNode base;
+	base["margins"] = config["margins"];
+	base["width"] = config["width"];
+	base["height"] = config["height"];
+
+	for(const JsonNode & group : config["sequences"].Vector())
+	{
+		size_t groupID = group["group"].Integer();//TODO: string-to-value conversion("moving" -> MOVING)
+		source[groupID].clear();
+
+		for(const JsonNode & frame : group["frames"].Vector())
+		{
+			JsonNode toAdd = frame;
+			JsonUtils::inherit(toAdd, base);
+			toAdd["file"].String() = basepath + frame.String();
+			source[groupID].emplace_back(toAdd);
+		}
+	}
+
+	for(const JsonNode & node : config["images"].Vector())
+	{
+		size_t group = node["group"].Integer();
+		size_t frame = node["frame"].Integer();
+
+		if (source[group].size() <= frame)
+			source[group].resize(frame+1);
+
+		JsonNode toAdd = node;
+		JsonUtils::inherit(toAdd, base);
+		toAdd["file"].String() = basepath + node["file"].String();
+		source[group][frame] = ImageLocator(toAdd);
+	}
+}
+
+RenderHandler::AnimationLayoutMap & RenderHandler::getAnimationLayout(const AnimationPath & path)
+{
+	AnimationPath actualPath = boost::starts_with(path.getName(), "SPRITES") ? path : path.addPrefix("SPRITES/");
+
+	auto it = animationLayouts.find(actualPath);
+
+	if (it != animationLayouts.end())
+		return it->second;
+
+	AnimationLayoutMap result;
+
+	auto defFile = getAnimationFile(actualPath);
+	if(defFile)
+	{
+		const std::map<size_t, size_t> defEntries = defFile->getEntries();
+
+		for (const auto & defEntry : defEntries)
+			result[defEntry.first].resize(defEntry.second);
+	}
+
+	auto jsonResource = actualPath.toType<EResType::JSON>();
+	auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource);
+
+	for(auto & loader : configList)
+	{
+		auto stream = loader->load(jsonResource);
+		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
+		stream->read(textData.get(), stream->getSize());
+
+		const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), path.getOriginalName());
+
+		initFromJson(result, config);
+	}
 
-std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path)
+	animationLayouts[actualPath] = result;
+	return animationLayouts[actualPath];
+}
+
+std::shared_ptr<IConstImage> RenderHandler::loadImageFromSingleFile(const ImagePath & path)
+{
+	auto result = std::make_shared<SDLImageConst>(path);
+	imageFiles[ImageLocator(path)] = result;
+	return result;
+}
+
+std::shared_ptr<IConstImage> RenderHandler::loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group)
+{
+	const auto & layout = getAnimationLayout(path);
+	if (!layout.count(group))
+		return loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+
+	if (frame >= layout.at(group).size())
+		return loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+
+	const auto & locator = layout.at(group).at(frame);
+	if (locator.image)
+	{
+		return loadImageImpl(locator);
+	}
+	else
+	{
+		auto defFile = getAnimationFile(path);
+		return std::make_shared<SDLImageConst>(defFile.get(), frame, group);
+	}
+}
+
+std::shared_ptr<IConstImage> RenderHandler::loadImageFromAnimationFile(const AnimationPath & path, int frame, int group)
+{
+	auto result = loadImageFromAnimationFileUncached(path, frame, group);
+	imageFiles[ImageLocator(path, frame, group)] = result;
+	return result;
+}
+
+std::shared_ptr<IConstImage> RenderHandler::loadImageImpl(const ImageLocator & locator)
+{
+	auto it = imageFiles.find(locator);
+	if (it != imageFiles.end())
+		return it->second;
+
+	std::shared_ptr<IConstImage> result;
+
+	if (locator.image)
+		result = loadImageFromSingleFile(*locator.image);
+	else if (locator.defFile)
+		result = loadImageFromAnimationFile(*locator.defFile, locator.defFrame, locator.defGroup);
+
+	if (!result)
+		result = loadImageFromSingleFile(ImagePath::builtin("DEFAULT"));
+
+	if (locator.verticalFlip)
+		result = result->verticalFlip();
+
+	if (locator.horizontalFlip)
+		result = result->horizontalFlip();
+
+	imageFiles[locator] = result;
+	return result;
+}
+
+std::shared_ptr<IImage> RenderHandler::loadImage(const ImageLocator & locator, EImageBlitMode mode)
+{
+	return loadImageImpl(locator)->createImageReference(mode);
+}
+
+std::shared_ptr<IImage> RenderHandler::loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode)
 {
-	return loadImage(path, EImageBlitMode::ALPHA);
+	return loadImageFromAnimationFile(path, frame, group)->createImageReference(mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
 {
-	return std::make_shared<SDLImage>(path, mode);
+	return loadImageImpl(ImageLocator(path))->createImageReference(mode);
 }
 
 std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
 {
-	return std::make_shared<SDLImage>(source, EImageBlitMode::ALPHA);
+	return std::make_shared<SDLImageConst>(source)->createImageReference(EImageBlitMode::ALPHA);
+}
+
+std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path, EImageBlitMode mode)
+{
+	return std::make_shared<CAnimation>(path, getAnimationLayout(path), mode);
 }
 
-std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path)
+void RenderHandler::addImageListEntries(const EntityService * service)
 {
-	return std::make_shared<CAnimation>(path);
+	service->forEachBase([this](const Entity * entity, bool & stop)
+	{
+		entity->registerIcons([this](size_t index, size_t group, const std::string & listName, const std::string & imageName)
+		{
+			if (imageName.empty())
+				return;
+
+			auto & layout = getAnimationLayout(AnimationPath::builtin("SPRITES/" + listName));
+
+			JsonNode entry;
+			entry["file"].String() = imageName;
+
+			if (index >= layout[group].size())
+				layout[group].resize(index + 1);
+
+			layout[group][index] = ImageLocator(entry);
+		});
+	});
 }
 
-std::shared_ptr<CAnimation> RenderHandler::createAnimation()
+void RenderHandler::onLibraryLoadingFinished(const Services * services)
 {
-	return std::make_shared<CAnimation>();
+	addImageListEntries(services->creatures());
+	addImageListEntries(services->heroTypes());
+	addImageListEntries(services->artifacts());
+	addImageListEntries(services->factions());
+	addImageListEntries(services->spells());
+	addImageListEntries(services->skills());
 }

+ 32 - 5
client/renderSDL/RenderHandler.h

@@ -11,15 +11,42 @@
 
 #include "../render/IRenderHandler.h"
 
+VCMI_LIB_NAMESPACE_BEGIN
+class EntityService;
+VCMI_LIB_NAMESPACE_END
+
+class CDefFile;
+class IConstImage;
+
 class RenderHandler : public IRenderHandler
 {
+	using AnimationLayoutMap = std::map<size_t, std::vector<ImageLocator>>;
+
+	std::map<AnimationPath, std::shared_ptr<CDefFile>> animationFiles;
+	std::map<AnimationPath, AnimationLayoutMap> animationLayouts;
+	std::map<ImageLocator, std::shared_ptr<IConstImage>> imageFiles;
+
+	std::shared_ptr<CDefFile> getAnimationFile(const AnimationPath & path);
+	AnimationLayoutMap & getAnimationLayout(const AnimationPath & path);
+	void initFromJson(AnimationLayoutMap & layout, const JsonNode & config);
+
+	void addImageListEntry(size_t index, size_t group, const std::string & listName, const std::string & imageName);
+	void addImageListEntries(const EntityService * service);
+
+	std::shared_ptr<IConstImage> loadImageFromSingleFile(const ImagePath & path);
+	std::shared_ptr<IConstImage> loadImageFromAnimationFileUncached(const AnimationPath & path, int frame, int group);
+	std::shared_ptr<IConstImage> loadImageFromAnimationFile(const AnimationPath & path, int frame, int group);
+	std::shared_ptr<IConstImage> loadImageImpl(const ImageLocator & config);
 public:
-	std::shared_ptr<IImage> loadImage(const ImagePath & path) override;
-	std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) override;
 
-	std::shared_ptr<IImage> createImage(SDL_Surface * source) override;
+	// IRenderHandler implementation
+	void onLibraryLoadingFinished(const Services * services) override;
 
-	std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path) override;
+	std::shared_ptr<IImage> loadImage(const ImageLocator & locator, EImageBlitMode mode) override;
+	std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) override;
+	std::shared_ptr<IImage> loadImage(const AnimationPath & path, int frame, int group, EImageBlitMode mode) override;
 
-	std::shared_ptr<CAnimation> createAnimation() override;
+	std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path, EImageBlitMode mode) override;
+
+	std::shared_ptr<IImage> createImage(SDL_Surface * source) override;
 };

+ 152 - 153
client/renderSDL/SDLImage.cpp

@@ -1,4 +1,4 @@
-/*
+/*
  * SDLImage.cpp, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
@@ -18,8 +18,6 @@
 #include "../render/CDefFile.h"
 #include "../render/Graphics.h"
 
-#include "../../lib/json/JsonNode.h"
-
 #include <SDL_surface.h>
 
 class SDLImageLoader;
@@ -34,7 +32,7 @@ int IImage::height() const
 	return dimensions().y;
 }
 
-SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
+SDLImageConst::SDLImageConst(CDefFile * data, size_t frame, size_t group)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -44,10 +42,9 @@ SDLImage::SDLImage(CDefFile * data, size_t frame, size_t group)
 	data->loadFrame(frame, group, loader);
 
 	savePalette();
-	setBlitMode(EImageBlitMode::ALPHA);
 }
 
-SDLImage::SDLImage(SDL_Surface * from, EImageBlitMode mode)
+SDLImageConst::SDLImageConst(SDL_Surface * from)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -58,47 +55,13 @@ SDLImage::SDLImage(SDL_Surface * from, EImageBlitMode mode)
 		return;
 
 	savePalette();
-	setBlitMode(mode);
 
 	surf->refcount++;
 	fullSize.x = surf->w;
 	fullSize.y = surf->h;
 }
 
-SDLImage::SDLImage(const JsonNode & conf, EImageBlitMode mode)
-	: surf(nullptr),
-	margins(0, 0),
-	fullSize(0, 0),
-	originalPalette(nullptr)
-{
-	surf = BitmapHandler::loadBitmap(ImagePath::fromJson(conf["file"]));
-
-	if(surf == nullptr)
-		return;
-
-	savePalette();
-	setBlitMode(mode);
-
-	const JsonNode & jsonMargins = conf["margins"];
-
-	margins.x = static_cast<int>(jsonMargins["left"].Integer());
-	margins.y = static_cast<int>(jsonMargins["top"].Integer());
-
-	fullSize.x = static_cast<int>(conf["width"].Integer());
-	fullSize.y = static_cast<int>(conf["height"].Integer());
-
-	if(fullSize.x == 0)
-	{
-		fullSize.x = margins.x + surf->w + (int)jsonMargins["right"].Integer();
-	}
-
-	if(fullSize.y == 0)
-	{
-		fullSize.y = margins.y + surf->h + (int)jsonMargins["bottom"].Integer();
-	}
-}
-
-SDLImage::SDLImage(const ImagePath & filename, EImageBlitMode mode)
+SDLImageConst::SDLImageConst(const ImagePath & filename)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -114,22 +77,13 @@ SDLImage::SDLImage(const ImagePath & filename, EImageBlitMode mode)
 	else
 	{
 		savePalette();
-		setBlitMode(mode);
 		fullSize.x = surf->w;
 		fullSize.y = surf->h;
 	}
 }
 
-void SDLImage::draw(SDL_Surface *where, int posX, int posY, const Rect *src) const
-{
-	if(!surf)
-		return;
-
-	Rect destRect(posX, posY, surf->w, surf->h);
-	draw(where, &destRect, src);
-}
 
-void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) const
+void SDLImageConst::draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const
 {
 	if (!surf)
 		return;
@@ -153,16 +107,21 @@ void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) cons
 	else
 		destShift = margins;
 
-	if(dest)
-		destShift += dest->topLeft();
+	destShift += dest;
+
+	SDL_SetSurfaceAlphaMod(surf, alpha);
 
-	uint8_t perSurfaceAlpha;
-	if (SDL_GetSurfaceAlphaMod(surf, &perSurfaceAlpha) != 0)
-		logGlobal->error("SDL_GetSurfaceAlphaMod faied! %s", SDL_GetError());
+	if (alpha != SDL_ALPHA_OPAQUE || (mode != EImageBlitMode::OPAQUE && surf->format->Amask != 0))
+		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
+	else
+		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
+
+	if (palette && surf->format->palette)
+		SDL_SetSurfacePalette(surf, palette);
 
-	if(surf->format->BitsPerPixel == 8 && perSurfaceAlpha == SDL_ALPHA_OPAQUE && blitMode == EImageBlitMode::ALPHA)
+	if(surf->format->palette && mode == EImageBlitMode::ALPHA)
 	{
-		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift);
+		CSDL_Ext::blit8bppAlphaTo24bpp(surf, sourceRect, where, destShift, alpha);
 	}
 	else
 	{
@@ -170,12 +129,20 @@ void SDLImage::draw(SDL_Surface* where, const Rect * dest, const Rect* src) cons
 	}
 }
 
-std::shared_ptr<IImage> SDLImage::scaleFast(const Point & size) const
+const SDL_Palette * SDLImageConst::getPalette() const
+{
+	if (originalPalette == nullptr)
+		throw std::runtime_error("Palette not found!");
+
+	return originalPalette;
+}
+
+std::shared_ptr<SDLImageConst> SDLImageConst::scaleFast(const Point & size) const
 {
-	float scaleX = float(size.x) / width();
-	float scaleY = float(size.y) / height();
+	float scaleX = float(size.x) / dimensions().x;
+	float scaleY = float(size.y) / dimensions().y;
 
-	auto scaled = CSDL_Ext::scaleSurfaceFast(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY));
+	auto scaled = CSDL_Ext::scaleSurface(surf, (int)(surf->w * scaleX), (int)(surf->h * scaleY));
 
 	if (scaled->format && scaled->format->palette) // fix color keying, because SDL loses it at this point
 		CSDL_Ext::setColorKey(scaled, scaled->format->palette->colors[0]);
@@ -184,7 +151,7 @@ std::shared_ptr<IImage> SDLImage::scaleFast(const Point & size) const
 	else
 		CSDL_Ext::setDefaultColorKey(scaled);//just in case
 
-	auto * ret = new SDLImage(scaled, EImageBlitMode::ALPHA);
+	auto ret = std::make_shared<SDLImageConst>(scaled);
 
 	ret->fullSize.x = (int) round((float)fullSize.x * scaleX);
 	ret->fullSize.y = (int) round((float)fullSize.y * scaleY);
@@ -195,45 +162,26 @@ std::shared_ptr<IImage> SDLImage::scaleFast(const Point & size) const
 	// erase our own reference
 	SDL_FreeSurface(scaled);
 
-	return std::shared_ptr<IImage>(ret);
+	return ret;
 }
 
-void SDLImage::exportBitmap(const boost::filesystem::path& path) const
+void SDLImageConst::exportBitmap(const boost::filesystem::path& path) const
 {
 	SDL_SaveBMP(surf, path.string().c_str());
 }
 
-void SDLImage::playerColored(PlayerColor player)
+void SDLImageIndexed::playerColored(PlayerColor player)
 {
-	if (!surf)
-		return;
-	graphics->blueToPlayersAdv(surf, player);
-}
-
-void SDLImage::setAlpha(uint8_t value)
-{
-	CSDL_Ext::setAlpha (surf, value);
-	if (value != 255)
-		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
+	graphics->setPlayerPalette(currentPalette, player);
 }
 
-void SDLImage::setBlitMode(EImageBlitMode mode)
-{
-	blitMode = mode;
-
-	if (blitMode != EImageBlitMode::OPAQUE && surf->format->Amask != 0)
-		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_BLEND);
-	else
-		SDL_SetSurfaceBlendMode(surf, SDL_BLENDMODE_NONE);
-}
-
-void SDLImage::setFlagColor(PlayerColor player)
+void SDLImageIndexed::setFlagColor(PlayerColor player)
 {
 	if(player.isValidPlayer() || player==PlayerColor::NEUTRAL)
-		CSDL_Ext::setPlayerColor(surf, player);
+		graphics->setPlayerFlagColor(currentPalette, player);
 }
 
-bool SDLImage::isTransparent(const Point & coords) const
+bool SDLImageConst::isTransparent(const Point & coords) const
 {
 	if (surf)
 		return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
@@ -241,121 +189,172 @@ bool SDLImage::isTransparent(const Point & coords) const
 		return true;
 }
 
-Point SDLImage::dimensions() const
+Point SDLImageConst::dimensions() const
 {
 	return fullSize;
 }
 
-void SDLImage::horizontalFlip()
+std::shared_ptr<IImage> SDLImageConst::createImageReference(EImageBlitMode mode)
 {
-	margins.y = fullSize.y - surf->h - margins.y;
-
-	//todo: modify in-place
-	SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
-	SDL_FreeSurface(surf);
-	surf = flipped;
+	if (surf->format->palette)
+		return std::make_shared<SDLImageIndexed>(shared_from_this(), mode);
+	else
+		return std::make_shared<SDLImageRGB>(shared_from_this(), mode);
 }
 
-void SDLImage::verticalFlip()
+std::shared_ptr<IConstImage> SDLImageConst::horizontalFlip() const
 {
-	margins.x = fullSize.x - surf->w - margins.x;
+	SDL_Surface * flipped = CSDL_Ext::horizontalFlip(surf);
+	auto ret = std::make_shared<SDLImageConst>(flipped);
+	ret->fullSize = fullSize;
+	ret->margins.x = margins.x;
+	ret->margins.y = fullSize.y - surf->h - margins.y;
+	ret->fullSize = fullSize;
 
-	//todo: modify in-place
-	SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
-	SDL_FreeSurface(surf);
-	surf = flipped;
+	return ret;
 }
 
-void SDLImage::doubleFlip()
+std::shared_ptr<IConstImage> SDLImageConst::verticalFlip() const
 {
-	horizontalFlip();
-	verticalFlip();
+	SDL_Surface * flipped = CSDL_Ext::verticalFlip(surf);
+	auto ret = std::make_shared<SDLImageConst>(flipped);
+	ret->fullSize = fullSize;
+	ret->margins.x = fullSize.x - surf->w - margins.x;
+	ret->margins.y = margins.y;
+	ret->fullSize = fullSize;
+
+	return ret;
 }
 
 // Keep the original palette, in order to do color switching operation
-void SDLImage::savePalette()
+void SDLImageConst::savePalette()
 {
 	// For some images that don't have palette, skip this
 	if(surf->format->palette == nullptr)
 		return;
 
 	if(originalPalette == nullptr)
-		originalPalette = SDL_AllocPalette(DEFAULT_PALETTE_COLORS);
+		originalPalette = SDL_AllocPalette(surf->format->palette->ncolors);
 
-	SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, DEFAULT_PALETTE_COLORS);
+	SDL_SetPaletteColors(originalPalette, surf->format->palette->colors, 0, surf->format->palette->ncolors);
 }
 
-void SDLImage::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
+void SDLImageIndexed::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
 {
-	if(surf->format->palette)
-	{
-		std::vector<SDL_Color> shifterColors(colorsToMove);
+	const SDL_Palette * originalPalette = image->getPalette();
 
-		for(uint32_t i=0; i<colorsToMove; ++i)
-		{
-			shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
-		}
-		CSDL_Ext::setColors(surf, shifterColors.data(), firstColorID, colorsToMove);
-	}
+	std::vector<SDL_Color> shifterColors(colorsToMove);
+
+	for(uint32_t i=0; i<colorsToMove; ++i)
+		shifterColors[(i+distanceToMove)%colorsToMove] = originalPalette->colors[firstColorID + i];
+
+	SDL_SetPaletteColors(currentPalette, shifterColors.data(), firstColorID, colorsToMove);
 }
 
-void SDLImage::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
+void SDLImageIndexed::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
 {
-	if(originalPalette == nullptr)
-		return;
-
-	SDL_Palette* palette = surf->format->palette;
+	const SDL_Palette * originalPalette = image->getPalette();
 
 	// Note: here we skip first colors in the palette that are predefined in H3 images
-	for(int i = 0; i < palette->ncolors; i++)
+	for(int i = 0; i < currentPalette->ncolors; i++)
 	{
 		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
 			continue;
 
-		palette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i])));
+		currentPalette->colors[i] = CSDL_Ext::toSDL(shifter.shiftColor(CSDL_Ext::fromSDL(originalPalette->colors[i])));
 	}
 }
 
-void SDLImage::resetPalette()
+SDLImageIndexed::SDLImageIndexed(const std::shared_ptr<SDLImageConst> & image, EImageBlitMode mode)
+	:SDLImageBase::SDLImageBase(image, mode)
 {
-	if(originalPalette == nullptr)
-		return;
-	
-	// Always keep the original palette not changed, copy a new palette to assign to surface
-	SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
+	auto originalPalette = image->getPalette();
+
+	currentPalette = SDL_AllocPalette(originalPalette->ncolors);
+	SDL_SetPaletteColors(currentPalette, originalPalette->colors, 0, originalPalette->ncolors);
 }
 
-void SDLImage::resetPalette( int colorID )
+SDLImageIndexed::~SDLImageIndexed()
 {
-	if(originalPalette == nullptr)
-		return;
-
-	// Always keep the original palette not changed, copy a new palette to assign to surface
-	SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
+	SDL_FreePalette(currentPalette);
 }
 
-void SDLImage::setSpecialPalette(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask)
+void SDLImageIndexed::setSpecialPalette(const IImage::SpecialPalette & specialPalette, uint32_t colorsToSkipMask)
 {
-	if(surf->format->palette)
-	{
-		size_t last = std::min<size_t>(specialPalette.size(), surf->format->palette->ncolors);
+	size_t last = std::min<size_t>(specialPalette.size(), currentPalette->ncolors);
 
-		for (size_t i = 0; i < last; ++i)
-		{
-			if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
-				surf->format->palette->colors[i] = CSDL_Ext::toSDL(specialPalette[i]);
-		}
+	for (size_t i = 0; i < last; ++i)
+	{
+		if(i < std::numeric_limits<uint32_t>::digits && ((colorsToSkipMask >> i) & 1) == 1)
+			currentPalette->colors[i] = CSDL_Ext::toSDL(specialPalette[i]);
 	}
 }
 
-SDLImage::~SDLImage()
+SDLImageConst::~SDLImageConst()
 {
 	SDL_FreeSurface(surf);
+	SDL_FreePalette(originalPalette);
+}
 
-	if(originalPalette != nullptr)
-	{
-		SDL_FreePalette(originalPalette);
-		originalPalette = nullptr;
-	}
+SDLImageBase::SDLImageBase(const std::shared_ptr<SDLImageConst> & image, EImageBlitMode mode)
+	:image(image)
+	, alphaValue(SDL_ALPHA_OPAQUE)
+	, blitMode(mode)
+{}
+
+void SDLImageRGB::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
+{
+	image->draw(where, nullptr, pos, src, alphaValue, blitMode);
 }
 
+void SDLImageIndexed::draw(SDL_Surface * where, const Point & pos, const Rect * src) const
+{
+	image->draw(where, currentPalette, pos, src, alphaValue, blitMode);
+}
+
+void SDLImageBase::scaleFast(const Point & size)
+{
+	image = image->scaleFast(size);
+}
+
+void SDLImageBase::exportBitmap(const boost::filesystem::path & path) const
+{
+	image->exportBitmap(path);
+}
+
+bool SDLImageBase::isTransparent(const Point & coords) const
+{
+	return image->isTransparent(coords);
+}
+
+Point SDLImageBase::dimensions() const
+{
+	return image->dimensions();
+}
+
+void SDLImageBase::setAlpha(uint8_t value)
+{
+	alphaValue = value;
+}
+
+void SDLImageBase::setBlitMode(EImageBlitMode mode)
+{
+	blitMode = mode;
+}
+
+void SDLImageRGB::setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask)
+{}
+
+void SDLImageRGB::playerColored(PlayerColor player)
+{}
+
+void SDLImageRGB::setFlagColor(PlayerColor player)
+{}
+
+void SDLImageRGB::shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove)
+{}
+
+void SDLImageRGB::adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask)
+{}
+
+

+ 58 - 30
client/renderSDL/SDLImage.h

@@ -24,60 +24,88 @@ struct SDL_Palette;
 /*
  * Wrapper around SDL_Surface
  */
-class SDLImage : public IImage
+class SDLImageConst final : public IConstImage, public std::enable_shared_from_this<SDLImageConst>, boost::noncopyable
 {
-public:
-	
-	const static int DEFAULT_PALETTE_COLORS = 256;
-	
 	//Surface without empty borders
 	SDL_Surface * surf;
+
+	SDL_Palette * originalPalette;
 	//size of left and top borders
 	Point margins;
 	//total size including borders
 	Point fullSize;
 
-	EImageBlitMode blitMode;
+	// Keep the original palette, in order to do color switching operation
+	void savePalette();
 
 public:
 	//Load image from def file
-	SDLImage(CDefFile *data, size_t frame, size_t group=0);
+	SDLImageConst(CDefFile *data, size_t frame, size_t group=0);
 	//Load from bitmap file
-	SDLImage(const ImagePath & filename, EImageBlitMode blitMode);
-
-	SDLImage(const JsonNode & conf, EImageBlitMode blitMode);
+	SDLImageConst(const ImagePath & filename);
 	//Create using existing surface, extraRef will increase refcount on SDL_Surface
-	SDLImage(SDL_Surface * from, EImageBlitMode blitMode);
-	~SDLImage();
+	SDLImageConst(SDL_Surface * from);
+	~SDLImageConst();
 
-	// Keep the original palette, in order to do color switching operation
-	void savePalette();
+	void draw(SDL_Surface * where, SDL_Palette * palette, const Point & dest, const Rect * src, uint8_t alpha, EImageBlitMode mode) const;
 
-	void draw(SDL_Surface * where, int posX=0, int posY=0, const Rect *src=nullptr) const override;
-	void draw(SDL_Surface * where, const Rect * dest, const Rect * src) const override;
-	std::shared_ptr<IImage> scaleFast(const Point & size) const override;
 	void exportBitmap(const boost::filesystem::path & path) const override;
-	void playerColored(PlayerColor player) override;
-	void setFlagColor(PlayerColor player) override;
-	bool isTransparent(const Point & coords) const override;
 	Point dimensions() const override;
+	bool isTransparent(const Point & coords) const override;
+	std::shared_ptr<IImage> createImageReference(EImageBlitMode mode) override;
+	std::shared_ptr<IConstImage> horizontalFlip() const override;
+	std::shared_ptr<IConstImage> verticalFlip() const override;
+	std::shared_ptr<SDLImageConst> scaleFast(const Point & size) const;
 
-	void horizontalFlip() override;
-	void verticalFlip() override;
-	void doubleFlip() override;
+	const SDL_Palette * getPalette() const;
 
-	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
-	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
-	void resetPalette(int colorID) override;
-	void resetPalette() override;
+	friend class SDLImageLoader;
+};
+
+class SDLImageBase : public IImage, boost::noncopyable
+{
+protected:
+	std::shared_ptr<SDLImageConst> image;
+
+	uint8_t alphaValue;
+	EImageBlitMode blitMode;
+
+public:
+	SDLImageBase(const std::shared_ptr<SDLImageConst> & image, EImageBlitMode mode);
 
+	void scaleFast(const Point & size) override;
+	void exportBitmap(const boost::filesystem::path & path) const override;
+	bool isTransparent(const Point & coords) const override;
+	Point dimensions() const override;
 	void setAlpha(uint8_t value) override;
 	void setBlitMode(EImageBlitMode mode) override;
+};
+
+class SDLImageIndexed final : public SDLImageBase
+{
+	SDL_Palette * currentPalette = nullptr;
 
+public:
+	SDLImageIndexed(const std::shared_ptr<SDLImageConst> & image, EImageBlitMode mode);
+	~SDLImageIndexed();
+
+	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
 	void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
+	void playerColored(PlayerColor player) override;
+	void setFlagColor(PlayerColor player) override;
+	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
+	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
+};
 
-	friend class SDLImageLoader;
+class SDLImageRGB final : public SDLImageBase
+{
+public:
+	using SDLImageBase::SDLImageBase;
 
-private:
-	SDL_Palette * originalPalette;
+	void draw(SDL_Surface * where, const Point & pos, const Rect * src) const override;
+	void setSpecialPalette(const SpecialPalette & SpecialPalette, uint32_t colorsToSkipMask) override;
+	void playerColored(PlayerColor player) override;
+	void setFlagColor(PlayerColor player) override;
+	void shiftPalette(uint32_t firstColorID, uint32_t colorsToMove, uint32_t distanceToMove) override;
+	void adjustPalette(const ColorFilter & shifter, uint32_t colorsToSkipMask) override;
 };

+ 3 - 3
client/renderSDL/SDLImageLoader.cpp

@@ -17,7 +17,7 @@
 
 #include <SDL_surface.h>
 
-SDLImageLoader::SDLImageLoader(SDLImage * Img):
+SDLImageLoader::SDLImageLoader(SDLImageConst * Img):
 	image(Img),
 	lineStart(nullptr),
 	position(nullptr)
@@ -32,8 +32,8 @@ void SDLImageLoader::init(Point SpriteSize, Point Margins, Point FullSize, SDL_C
 	image->fullSize = FullSize;
 
 	//Prepare surface
-	SDL_Palette * p = SDL_AllocPalette(SDLImage::DEFAULT_PALETTE_COLORS);
-	SDL_SetPaletteColors(p, pal, 0, SDLImage::DEFAULT_PALETTE_COLORS);
+	SDL_Palette * p = SDL_AllocPalette(DEFAULT_PALETTE_COLORS);
+	SDL_SetPaletteColors(p, pal, 0, DEFAULT_PALETTE_COLORS);
 	SDL_SetSurfacePalette(image->surf, p);
 	SDL_FreePalette(p);
 

+ 6 - 2
client/renderSDL/SDLImageLoader.h

@@ -11,9 +11,13 @@
 
 #include "../render/IImageLoader.h"
 
+class SDLImageConst;
+
 class SDLImageLoader : public IImageLoader
 {
-	SDLImage * image;
+	static constexpr int DEFAULT_PALETTE_COLORS = 256;
+
+	SDLImageConst * image;
 	ui8 * lineStart;
 	ui8 * position;
 public:
@@ -25,7 +29,7 @@ public:
 	//init image with these sizes and palette
 	void init(Point SpriteSize, Point Margins, Point FullSize, SDL_Color *pal);
 
-	SDLImageLoader(SDLImage * Img);
+	SDLImageLoader(SDLImageConst * Img);
 	~SDLImageLoader();
 };
 

+ 49 - 187
client/renderSDL/SDL_Extensions.cpp

@@ -19,6 +19,8 @@
 #include "../../lib/GameConstants.h"
 
 #include <SDL_render.h>
+#include <SDL_surface.h>
+#include <SDL_version.h>
 
 Rect CSDL_Ext::fromSDL(const SDL_Rect & rect)
 {
@@ -61,19 +63,6 @@ void CSDL_Ext::setAlpha(SDL_Surface * bg, int value)
 	SDL_SetSurfaceAlphaMod(bg, value);
 }
 
-void CSDL_Ext::updateRect(SDL_Surface *surface, const Rect & rect )
-{
-	SDL_Rect rectSDL = CSDL_Ext::toSDL(rect);
-	if(0 !=SDL_UpdateTexture(screenTexture, &rectSDL, surface->pixels, surface->pitch))
-		logGlobal->error("%sSDL_UpdateTexture %s", __FUNCTION__, SDL_GetError());
-
-	SDL_RenderClear(mainRenderer);
-	if(0 != SDL_RenderCopy(mainRenderer, screenTexture, nullptr, nullptr))
-		logGlobal->error("%sSDL_RenderCopy %s", __FUNCTION__, SDL_GetError());
-	SDL_RenderPresent(mainRenderer);
-
-}
-
 SDL_Surface * CSDL_Ext::newSurface(int w, int h)
 {
 	return newSurface(w, h, screen);
@@ -221,8 +210,8 @@ uint32_t CSDL_Ext::getPixel(SDL_Surface *surface, const int & x, const int & y,
 	}
 }
 
-template<int bpp>
-int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput)
+template<int bpp, bool useAlpha>
+int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRectInput, SDL_Surface * dst, const Point & dstPointInput, [[maybe_unused]] uint8_t alpha)
 {
 	SDL_Rect srcRectInstance = CSDL_Ext::toSDL(srcRectInput);
 	SDL_Rect dstRectInstance = CSDL_Ext::toSDL(Rect(dstPointInput, srcRectInput.dimensions()));
@@ -337,15 +326,20 @@ int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRec
 			uint8_t *colory = (uint8_t*)src->pixels + srcy*src->pitch + srcx;
 			uint8_t *py = (uint8_t*)dst->pixels + dstRect->y*dst->pitch + dstRect->x*bpp;
 
-			for(int y=h; y; y--, colory+=src->pitch, py+=dst->pitch)
+			for(int y=0; y<h; ++y, colory+=src->pitch, py+=dst->pitch)
 			{
 				uint8_t *color = colory;
 				uint8_t *p = py;
 
-				for(int x = w; x; x--)
+				for(int x = 0; x < w; ++x)
 				{
 					const SDL_Color &tbc = colors[*color++]; //color to blit
-					ColorPutter<bpp, +1>::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, tbc.a);
+					if constexpr (useAlpha)
+						ColorPutter<bpp>::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, int(alpha) * tbc.a / 255 );
+					else
+						ColorPutter<bpp>::PutColorAlphaSwitch(p, tbc.r, tbc.g, tbc.b, tbc.a);
+
+					p += bpp;
 				}
 			}
 			SDL_UnlockSurface(dst);
@@ -354,17 +348,27 @@ int CSDL_Ext::blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRec
 	return 0;
 }
 
-int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint)
+int CSDL_Ext::blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha)
 {
-	switch(dst->format->BytesPerPixel)
+	if (alpha == SDL_ALPHA_OPAQUE)
 	{
-	case 2: return blit8bppAlphaTo24bppT<2>(src, srcRect, dst, dstPoint);
-	case 3: return blit8bppAlphaTo24bppT<3>(src, srcRect, dst, dstPoint);
-	case 4: return blit8bppAlphaTo24bppT<4>(src, srcRect, dst, dstPoint);
-	default:
-		logGlobal->error("%d bpp is not supported!", (int)dst->format->BitsPerPixel);
-		return -1;
+		switch(dst->format->BytesPerPixel)
+		{
+		case 3: return blit8bppAlphaTo24bppT<3, false>(src, srcRect, dst, dstPoint, alpha);
+		case 4: return blit8bppAlphaTo24bppT<4, false>(src, srcRect, dst, dstPoint, alpha);
+		}
 	}
+	else
+	{
+		switch(dst->format->BytesPerPixel)
+		{
+			case 3: return blit8bppAlphaTo24bppT<3, true>(src, srcRect, dst, dstPoint, alpha);
+			case 4: return blit8bppAlphaTo24bppT<4, true>(src, srcRect, dst, dstPoint, alpha);
+		}
+	}
+
+	logGlobal->error("%d bpp is not supported!", (int)dst->format->BitsPerPixel);
+	return -1;
 }
 
 uint32_t CSDL_Ext::colorTouint32_t(const SDL_Color * color)
@@ -422,7 +426,7 @@ static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
 		uint8_t a = vstd::lerp(color1.a, color2.a, f);
 
 		uint8_t *p = CSDL_Ext::getPxPtr(sur, x, y);
-		ColorPutter<4, 0>::PutColor(p, r,g,b,a);
+		ColorPutter<4>::PutColor(p, r,g,b,a);
 	}
 }
 
@@ -440,7 +444,7 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
 		uint8_t a = vstd::lerp(color1.a, color2.a, f);
 
 		uint8_t *p = CSDL_Ext::getPxPtr(sur, x, y);
-		ColorPutter<4, 0>::PutColor(p, r,g,b,a);
+		ColorPutter<4>::PutColor(p, r,g,b,a);
 	}
 }
 
@@ -453,7 +457,7 @@ void CSDL_Ext::drawLine(SDL_Surface * sur, const Point & from, const Point & des
 	if ( width == 0 && height == 0)
 	{
 		uint8_t *p = CSDL_Ext::getPxPtr(sur, from.x, from.y);
-		ColorPutter<4, 0>::PutColorAlpha(p, color1);
+		ColorPutter<4>::PutColorAlpha(p, color1);
 		return;
 	}
 
@@ -524,59 +528,18 @@ void CSDL_Ext::drawBorder( SDL_Surface * sur, const Rect &r, const SDL_Color &co
 	drawBorder(sur, r.x, r.y, r.w, r.h, color, depth);
 }
 
-void CSDL_Ext::setPlayerColor(SDL_Surface * sur, const PlayerColor & player)
-{
-	if(player==PlayerColor::UNFLAGGABLE)
-		return;
-	if(sur->format->BitsPerPixel==8)
-	{
-		ColorRGBA color = (player == PlayerColor::NEUTRAL
-							? graphics->neutralColor
-							: graphics->playerColors[player.getNum()]);
-
-		SDL_Color colorSDL = toSDL(color);
-		CSDL_Ext::setColors(sur, &colorSDL, 5, 1);
-	}
-	else
-		logGlobal->warn("Warning, setPlayerColor called on not 8bpp surface!");
-}
-
-CSDL_Ext::TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest, int incrementing)
-{
-#define CASE_BPP(BytesPerPixel)							\
-case BytesPerPixel:									\
-	if(incrementing > 0)								\
-		return ColorPutter<BytesPerPixel, 1>::PutColor;	\
-	else if(incrementing == 0)							\
-		return ColorPutter<BytesPerPixel, 0>::PutColor;	\
-	else												\
-		return ColorPutter<BytesPerPixel, -1>::PutColor;\
-	break;
-
-	switch(dest->format->BytesPerPixel)
-	{
-		CASE_BPP(2)
-		CASE_BPP(3)
-		CASE_BPP(4)
-	default:
-		logGlobal->error("%d bpp is not supported!", (int)dest->format->BitsPerPixel);
-		return nullptr;
-	}
-
-}
-
-CSDL_Ext::TColorPutterAlpha CSDL_Ext::getPutterAlphaFor(SDL_Surface * const &dest, int incrementing)
+CSDL_Ext::TColorPutter CSDL_Ext::getPutterFor(SDL_Surface * const &dest)
 {
 	switch(dest->format->BytesPerPixel)
 	{
-		CASE_BPP(2)
-		CASE_BPP(3)
-		CASE_BPP(4)
+		case 3:
+			return ColorPutter<3>::PutColor;
+		case 4:
+			return ColorPutter<4>::PutColor;
 	default:
 		logGlobal->error("%d bpp is not supported!", (int)dest->format->BitsPerPixel);
 		return nullptr;
 	}
-#undef CASE_BPP
 }
 
 uint8_t * CSDL_Ext::getPxPtr(const SDL_Surface * const &srf, const int x, const int y)
@@ -607,11 +570,10 @@ bool CSDL_Ext::isTransparent( SDL_Surface * srf, int x, int y )
 void CSDL_Ext::putPixelWithoutRefresh(SDL_Surface *ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A)
 {
 	uint8_t *p = getPxPtr(ekran, x, y);
-	getPutterFor(ekran, false)(p, R, G, B);
+	getPutterFor(ekran)(p, R, G, B);
 
 	switch(ekran->format->BytesPerPixel)
 	{
-	case 2: Channels::px<2>::a.set(p, A); break;
 	case 3: Channels::px<3>::a.set(p, A); break;
 	case 4: Channels::px<4>::a.set(p, A); break;
 	}
@@ -655,126 +617,28 @@ void CSDL_Ext::convertToGrayscale( SDL_Surface * surf, const Rect & rect )
 {
 	switch(surf->format->BytesPerPixel)
 	{
-		case 2: convertToGrayscaleBpp<2>(surf, rect); break;
 		case 3: convertToGrayscaleBpp<3>(surf, rect); break;
 		case 4: convertToGrayscaleBpp<4>(surf, rect); break;
 	}
 }
 
-template<int bpp>
-void scaleSurfaceFastInternal(SDL_Surface *surf, SDL_Surface *ret)
-{
-	const float factorX = static_cast<float>(surf->w) / static_cast<float>(ret->w);
-	const float factorY = static_cast<float>(surf->h) / static_cast<float>(ret->h);
-
-	for(int y = 0; y < ret->h; y++)
-	{
-		for(int x = 0; x < ret->w; x++)
-		{
-			//coordinates we want to calculate
-			auto origX = static_cast<int>(floor(factorX * x));
-			auto origY = static_cast<int>(floor(factorY * y));
-
-			// Get pointers to source pixels
-			uint8_t *srcPtr = (uint8_t*)surf->pixels + origY * surf->pitch + origX * bpp;
-			uint8_t *destPtr = (uint8_t*)ret->pixels + y * ret->pitch + x * bpp;
-
-			memcpy(destPtr, srcPtr, bpp);
-		}
-	}
-}
-
-SDL_Surface * CSDL_Ext::scaleSurfaceFast(SDL_Surface *surf, int width, int height)
-{
-	if (!surf || !width || !height)
-		return nullptr;
-
-	//Same size? return copy - this should more be faster
-	if (width == surf->w && height == surf->h)
-		return copySurface(surf);
-
-	SDL_Surface *ret = newSurface(width, height, surf);
-
-	switch(surf->format->BytesPerPixel)
-	{
-		case 1: scaleSurfaceFastInternal<1>(surf, ret); break;
-		case 2: scaleSurfaceFastInternal<2>(surf, ret); break;
-		case 3: scaleSurfaceFastInternal<3>(surf, ret); break;
-		case 4: scaleSurfaceFastInternal<4>(surf, ret); break;
-	}
-	return ret;
-}
-
-template<int bpp>
-void scaleSurfaceInternal(SDL_Surface *surf, SDL_Surface *ret)
-{
-	const float factorX = float(surf->w - 1) / float(ret->w),
-				factorY = float(surf->h - 1) / float(ret->h);
-
-	for(int y = 0; y < ret->h; y++)
-	{
-		for(int x = 0; x < ret->w; x++)
-		{
-			//coordinates we want to interpolate
-			float origX = factorX * x,
-				  origY = factorY * y;
-
-			float x1 = floor(origX), x2 = floor(origX+1),
-				  y1 = floor(origY), y2 = floor(origY+1);
-			//assert( x1 >= 0 && y1 >= 0 && x2 < surf->w && y2 < surf->h);//All pixels are in range
-
-			// Calculate weights of each source pixel
-			float w11 = ((origX - x1) * (origY - y1));
-			float w12 = ((origX - x1) * (y2 - origY));
-			float w21 = ((x2 - origX) * (origY - y1));
-			float w22 = ((x2 - origX) * (y2 - origY));
-			//assert( w11 + w12 + w21 + w22 > 0.99 && w11 + w12 + w21 + w22 < 1.01);//total weight is ~1.0
-
-			// Get pointers to source pixels
-			uint8_t *p11 = (uint8_t*)surf->pixels + int(y1) * surf->pitch + int(x1) * bpp;
-			uint8_t *p12 = p11 + bpp;
-			uint8_t *p21 = p11 + surf->pitch;
-			uint8_t *p22 = p21 + bpp;
-			// Calculate resulting channels
-#define PX(X, PTR) Channels::px<bpp>::X.get(PTR)
-			int resR = static_cast<int>(PX(r, p11) * w11 + PX(r, p12) * w12 + PX(r, p21) * w21 + PX(r, p22) * w22);
-			int resG = static_cast<int>(PX(g, p11) * w11 + PX(g, p12) * w12 + PX(g, p21) * w21 + PX(g, p22) * w22);
-			int resB = static_cast<int>(PX(b, p11) * w11 + PX(b, p12) * w12 + PX(b, p21) * w21 + PX(b, p22) * w22);
-			int resA = static_cast<int>(PX(a, p11) * w11 + PX(a, p12) * w12 + PX(a, p21) * w21 + PX(a, p22) * w22);
-			//assert(resR < 256 && resG < 256 && resB < 256 && resA < 256);
-#undef PX
-			uint8_t *dest = (uint8_t*)ret->pixels + y * ret->pitch + x * bpp;
-			Channels::px<bpp>::r.set(dest, resR);
-			Channels::px<bpp>::g.set(dest, resG);
-			Channels::px<bpp>::b.set(dest, resB);
-			Channels::px<bpp>::a.set(dest, resA);
-		}
-	}
-}
-
 // scaling via bilinear interpolation algorithm.
 // NOTE: best results are for scaling in range 50%...200%.
 // And upscaling looks awful right now - should be fixed somehow
-SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface *surf, int width, int height)
+SDL_Surface * CSDL_Ext::scaleSurface(SDL_Surface * surf, int width, int height)
 {
-	if (!surf || !width || !height)
+	if(!surf || !width || !height)
 		return nullptr;
 
-	if (surf->format->palette)
-		return scaleSurfaceFast(surf, width, height);
+	SDL_Surface * intermediate = SDL_ConvertSurface(surf, screen->format, 0);
+	SDL_Surface * ret = newSurface(width, height, intermediate);
 
-	//Same size? return copy - this should more be faster
-	if (width == surf->w && height == surf->h)
-		return copySurface(surf);
-
-	SDL_Surface *ret = newSurface(width, height, surf);
-
-	switch(surf->format->BytesPerPixel)
-	{
-	case 2: scaleSurfaceInternal<2>(surf, ret); break;
-	case 3: scaleSurfaceInternal<3>(surf, ret); break;
-	case 4: scaleSurfaceInternal<4>(surf, ret); break;
-	}
+#if SDL_VERSION_ATLEAST(2,0,16)
+	SDL_SoftStretchLinear(intermediate, nullptr, ret, nullptr);
+#else
+	SDL_SoftStretch(intermediate, nullptr, ret, nullptr);
+#endif
+	SDL_FreeSurface(intermediate);
 
 	return ret;
 }
@@ -868,7 +732,5 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other)
 	other = CSDL_Ext::fromSDL(rect);
 }
 
-template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int);
 template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
 template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);
-

+ 5 - 12
client/renderSDL/SDL_Extensions.h

@@ -61,8 +61,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 	void fillRect(SDL_Surface * dst, const Rect & dstrect, const SDL_Color & color);
 	void fillRectBlended(SDL_Surface * dst, const Rect & dstrect, const SDL_Color & color);
 
-	void updateRect(SDL_Surface * surface, const Rect & rect);
-
 	void putPixelWithoutRefresh(SDL_Surface * ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A = 255);
 	void putPixelWithoutRefreshIfInSurf(SDL_Surface *ekran, const int & x, const int & y, const uint8_t & R, const uint8_t & G, const uint8_t & B, uint8_t A = 255);
 
@@ -73,12 +71,11 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 	bool isTransparent(SDL_Surface * srf, const Point & position); //checks if surface is transparent at given position
 
 	uint8_t * getPxPtr(const SDL_Surface * const & srf, const int x, const int y);
-	TColorPutter getPutterFor(SDL_Surface * const & dest, int incrementing); //incrementing: -1, 0, 1
-	TColorPutterAlpha getPutterAlphaFor(SDL_Surface * const & dest, int incrementing); //incrementing: -1, 0, 1
+	TColorPutter getPutterFor(SDL_Surface * const & dest);
 
-	template<int bpp>
-	int blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface
-	int blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint); //blits 8 bpp surface with alpha channel to 24 bpp surface
+	template<int bpp, bool useAlpha>
+	int blit8bppAlphaTo24bppT(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha); //blits 8 bpp surface with alpha channel to 24 bpp surface
+	int blit8bppAlphaTo24bpp(const SDL_Surface * src, const Rect & srcRect, SDL_Surface * dst, const Point & dstPoint, uint8_t alpha); //blits 8 bpp surface with alpha channel to 24 bpp surface
 	uint32_t colorTouint32_t(const SDL_Color * color); //little endian only
 
 	void drawLine(SDL_Surface * sur, const Point & from, const Point & dest, const SDL_Color & color1, const SDL_Color & color2);
@@ -86,7 +83,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 
 	void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const SDL_Color & color, int depth = 1);
 	void drawBorder(SDL_Surface * sur, const Rect & r, const SDL_Color & color, int depth = 1);
-	void setPlayerColor(SDL_Surface * sur, const PlayerColor & player); //sets correct color of flags; -1 for neutral
 
 	SDL_Surface * newSurface(int w, int h, SDL_Surface * mod); //creates new surface, with flags/format same as in surface given
 	SDL_Surface * newSurface(int w, int h); //creates new surface, with flags/format same as in screen surface
@@ -94,10 +90,7 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
 	template<int bpp>
 	SDL_Surface * createSurfaceWithBpp(int width, int height); //create surface with give bits per pixels value
 
-	//scale surface to required size.
-	//nearest neighbour algorithm
-	SDL_Surface * scaleSurfaceFast(SDL_Surface * surf, int width, int height);
-	// bilinear filtering. Uses fallback to scaleSurfaceFast in case of indexed surfaces
+	// bilinear filtering. Always returns rgba surface
 	SDL_Surface * scaleSurface(SDL_Surface * surf, int width, int height);
 
 	template<int bpp>

+ 9 - 143
client/renderSDL/SDL_PixelAccess.h

@@ -109,58 +109,29 @@ namespace Channels
 	};
 
 #endif
-
-	template<>
-	struct px<2>
-	{
-		static channel_subpx<5, 0xF800, 11> r;
-		static channel_subpx<6, 0x07E0, 5 > g;
-		static channel_subpx<5, 0x001F, 0 > b;
-		static channel_empty a;
-	};
 }
 
-template<int bpp, int incrementPtr>
+template<int bpp>
 struct ColorPutter
 {
 	static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B);
 	static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A);
 	static STRONG_INLINE void PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A);
-	static STRONG_INLINE void PutColor(uint8_t *&ptr, const SDL_Color & Color);
 	static STRONG_INLINE void PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color);
-	static STRONG_INLINE void PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count);
 };
 
-template <int incrementPtr>
-struct ColorPutter<2, incrementPtr>
-{
-	static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B);
-	static STRONG_INLINE void PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A);
-	static STRONG_INLINE void PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A);
-	static STRONG_INLINE void PutColor(uint8_t *&ptr, const SDL_Color & Color);
-	static STRONG_INLINE void PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color);
-	static STRONG_INLINE void PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count);
-};
-
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color)
+template<int bpp>
+STRONG_INLINE void ColorPutter<bpp>::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color)
 {
 	PutColor(ptr, Color.r, Color.g, Color.b, Color.a);
 }
 
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColor(uint8_t *&ptr, const SDL_Color & Color)
-{
-	PutColor(ptr, Color.r, Color.g, Color.b);
-}
-
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
+template<int bpp>
+STRONG_INLINE void ColorPutter<bpp>::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
 {
 	switch (A)
 	{
 	case 0:
-		ptr += bpp * incrementPtr;
 		return;
 	case 255:
 		PutColor(ptr, R, G, B);
@@ -177,124 +148,19 @@ STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColorAlphaSwitch(uint8_t *
 	}
 }
 
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
+template<int bpp>
+STRONG_INLINE void ColorPutter<bpp>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
 {
 	PutColor(ptr, ((((uint32_t)R - (uint32_t)Channels::px<bpp>::r.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px<bpp>::r.get(ptr),
 				  ((((uint32_t)G - (uint32_t)Channels::px<bpp>::g.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px<bpp>::g.get(ptr),
 				  ((((uint32_t)B - (uint32_t)Channels::px<bpp>::b.get(ptr))*(uint32_t)A) >> 8 ) + (uint32_t)Channels::px<bpp>::b.get(ptr));
 }
 
-
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B)
+template<int bpp>
+STRONG_INLINE void ColorPutter<bpp>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B)
 {
-	static_assert(incrementPtr >= -1 && incrementPtr <= +1, "Invalid incrementPtr value!");
-
-	if (incrementPtr < 0)
-		ptr -= bpp;
-
 	Channels::px<bpp>::r.set(ptr, R);
 	Channels::px<bpp>::g.set(ptr, G);
 	Channels::px<bpp>::b.set(ptr, B);
 	Channels::px<bpp>::a.set(ptr, 255);
-
-	if (incrementPtr > 0)
-		ptr += bpp;
-
-}
-
-template<int bpp, int incrementPtr>
-STRONG_INLINE void ColorPutter<bpp, incrementPtr>::PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count)
-{
-	if (count)
-	{
-		uint8_t *pixel = ptr;
-		PutColor(ptr, Color.r, Color.g, Color.b);
-
-		for (size_t i=0; i<count-1; i++)
-		{
-			memcpy(ptr, pixel, bpp);
-			ptr += bpp * incrementPtr;
-		}
-	}
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B)
-{
-	if(incrementPtr == -1)
-		ptr -= 2;
-
-	auto * const px = (uint16_t *)ptr;
-	*px = (B>>3) + ((G>>2) << 5) + ((R>>3) << 11); //drop least significant bits of 24 bpp encoded color
-
-	if(incrementPtr == 1)
-		ptr += 2; //bpp
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorAlphaSwitch(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
-{
-	switch (A)
-	{
-	case 0:
-		ptr += 2 * incrementPtr;
-		return;
-	case 255:
-		PutColor(ptr, R, G, B);
-		return;
-	default:
-		PutColor(ptr, R, G, B, A);
-		return;
-	}
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const uint8_t & R, const uint8_t & G, const uint8_t & B, const uint8_t & A)
-{
-	const int rbit = 5, gbit = 6, bbit = 5; //bits per color
-	const int rmask = 0xF800, gmask = 0x7E0, bmask = 0x1F;
-	const int rshift = 11, gshift = 5, bshift = 0;
-
-	const uint8_t r5 = (*((uint16_t *)ptr) & rmask) >> rshift,
-		b5 = (*((uint16_t *)ptr) & bmask) >> bshift,
-		g5 = (*((uint16_t *)ptr) & gmask) >> gshift;
-
-	const uint32_t r8 = (r5 << (8 - rbit)) | (r5 >> (2*rbit - 8)),
-		g8 = (g5 << (8 - gbit)) | (g5 >> (2*gbit - 8)),
-		b8 = (b5 << (8 - bbit)) | (b5 >> (2*bbit - 8));
-
-	PutColor(ptr,
-		(((R-r8)*A) >> 8) + r8,
-		(((G-g8)*A) >> 8) + g8,
-		(((B-b8)*A) >> 8) + b8);
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorAlpha(uint8_t *&ptr, const SDL_Color & Color)
-{
-	PutColor(ptr, Color.r, Color.g, Color.b, Color.a);
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColor(uint8_t *&ptr, const SDL_Color & Color)
-{
-	PutColor(ptr, Color.r, Color.g, Color.b);
-}
-
-template <int incrementPtr>
-STRONG_INLINE void ColorPutter<2, incrementPtr>::PutColorRow(uint8_t *&ptr, const SDL_Color & Color, size_t count)
-{
-	//drop least significant bits of 24 bpp encoded color
-	uint16_t pixel = (Color.b>>3) + ((Color.g>>2) << 5) + ((Color.r>>3) << 11);
-
-	for (size_t i=0; i<count; i++)
-	{
-		memcpy(ptr, &pixel, 2);
-		if(incrementPtr == -1)
-			ptr -= 2;
-		if(incrementPtr == 1)
-			ptr += 2;
-	}
 }

+ 10 - 4
client/widgets/Buttons.cpp

@@ -24,7 +24,6 @@
 #include "../gui/InterfaceObjectConfigurable.h"
 #include "../media/ISoundPlayer.h"
 #include "../windows/InfoWindows.h"
-#include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 
@@ -67,6 +66,11 @@ void CButton::addCallback(const std::function<void()> & callback)
 	this->callback += callback;
 }
 
+void CButton::addPopupCallback(const std::function<void()> & callback)
+{
+	this->callbackPopup += callback;
+}
+
 void ButtonBase::setTextOverlay(const std::string & Text, EFonts font, ColorRGBA color)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
@@ -95,7 +99,7 @@ void ButtonBase::setImage(const AnimationPath & defName, bool playerColoredButto
 	pos = image->pos;
 
 	if (playerColoredButton)
-		image->playerColored(LOCPLINT->playerID);
+		image->setPlayerColor(LOCPLINT->playerID);
 }
 
 const JsonNode & ButtonBase::getCurrentConfig() const
@@ -130,7 +134,7 @@ void ButtonBase::setConfigurable(const JsonPath & jsonName, bool playerColoredBu
 	pos = configurable->pos;
 
 	if (playerColoredButton)
-		image->playerColored(LOCPLINT->playerID);
+		image->setPlayerColor(LOCPLINT->playerID);
 }
 
 void CButton::addHoverText(EButtonState state, const std::string & text)
@@ -289,6 +293,8 @@ void CButton::clickCancel(const Point & cursorPosition)
 
 void CButton::showPopupWindow(const Point & cursorPosition)
 {
+	callbackPopup();
+
 	if(!helpBox.empty()) //there is no point to show window with nothing inside...
 		CRClickPopup::createAndPush(helpBox);
 }
@@ -358,7 +364,7 @@ CButton::CButton(Point position, const AnimationPath &defName, const std::pair<s
 void ButtonBase::setPlayerColor(PlayerColor player)
 {
 	if (image && image->isPlayerColored())
-		image->playerColored(player);
+		image->setPlayerColor(player);
 }
 
 void CButton::showAll(Canvas & to)

+ 2 - 0
client/widgets/Buttons.h

@@ -69,6 +69,7 @@ public:
 class CButton : public ButtonBase
 {
 	CFunctionList<void()> callback;
+	CFunctionList<void()> callbackPopup;
 
 	std::array<std::string, 4> hoverTexts; //texts for statusbar, if empty - first entry will be used
 	std::optional<ColorRGBA> borderColor; // mapping of button state to border color
@@ -90,6 +91,7 @@ public:
 
 	/// adds one more callback to on-click actions
 	void addCallback(const std::function<void()> & callback);
+	void addPopupCallback(const std::function<void()> & callback);
 
 	void addHoverText(EButtonState state, const std::string & text);
 

+ 2 - 3
client/widgets/CGarrisonInt.cpp

@@ -17,7 +17,6 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/WindowHandler.h"
 #include "../render/IImage.h"
-#include "../render/Graphics.h"
 #include "../windows/CCreatureWindow.h"
 #include "../windows/CWindowWithArtifacts.h"
 #include "../windows/GUIClasses.h"
@@ -437,10 +436,10 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa
 
 	AnimationPath imgName = AnimationPath::builtin(owner->smallIcons ? "cprsmall" : "TWCRPORT");
 
-	creatureImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 0);
+	creatureImage = std::make_shared<CAnimImage>(imgName, 0);
 	creatureImage->disable();
 
-	selectionImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 1);
+	selectionImage = std::make_shared<CAnimImage>(imgName, 1);
 	selectionImage->disable();
 	selectionImage->center(creatureImage->pos.center());
 

+ 39 - 52
client/widgets/Images.cpp

@@ -19,7 +19,6 @@
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
-#include "../render/Graphics.h"
 
 #include "../battle/BattleInterface.h"
 #include "../battle/BattleInterfaceClasses.h"
@@ -51,7 +50,7 @@ CPicture::CPicture( const ImagePath & bmpname )
 {}
 
 CPicture::CPicture( const ImagePath & bmpname, const Point & position )
-	: bg(GH.renderHandler().loadImage(bmpname))
+	: bg(GH.renderHandler().loadImage(bmpname, EImageBlitMode::COLORKEY))
 	, needRefresh(false)
 {
 	pos.x += position.x;
@@ -69,6 +68,14 @@ CPicture::CPicture( const ImagePath & bmpname, const Point & position )
 	}
 }
 
+CPicture::CPicture(const ImagePath & bmpname, const Rect &SrcRect, int x, int y)
+	: CPicture(bmpname, Point(x,y))
+{
+	srcRect = SrcRect;
+	pos.w = srcRect->w;
+	pos.h = srcRect->h;
+}
+
 CPicture::CPicture(std::shared_ptr<IImage> image, const Rect &SrcRect, int x, int y)
 	: CPicture(image, Point(x,y))
 {
@@ -101,29 +108,29 @@ void CPicture::setAlpha(uint8_t value)
 
 void CPicture::scaleTo(Point size)
 {
-	bg = bg->scaleFast(size);
+	bg->scaleFast(size);
 
 	pos.w = bg->width();
 	pos.h = bg->height();
 }
 
-void CPicture::colorize(PlayerColor player)
+void CPicture::setPlayerColor(PlayerColor player)
 {
 	bg->playerColored(player);
 }
 
-CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position):
-    CIntObject(0, position.topLeft()),
-	texture(GH.renderHandler().loadImage(imageName))
+CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position)
+	: CIntObject(0, position.topLeft())
+	, texture(GH.renderHandler().loadImage(imageName, EImageBlitMode::COLORKEY))
 {
 	pos.w = position.w;
 	pos.h = position.h;
 	imageArea = Rect(Point(), texture->dimensions());
 }
 
-CFilledTexture::CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea)
+CFilledTexture::CFilledTexture(const ImagePath & imageName, Rect position, Rect imageArea)
 	: CIntObject(0, position.topLeft())
-	, texture(image)
+	, texture(GH.renderHandler().loadImage(imageName, EImageBlitMode::COLORKEY))
 	, imageArea(imageArea)
 {
 	pos.w = position.w;
@@ -141,12 +148,12 @@ void CFilledTexture::showAll(Canvas & to)
 	}
 }
 
-FilledTexturePlayerColored::FilledTexturePlayerColored(const ImagePath & imageName, Rect position)
-	: CFilledTexture(imageName, position)
+void FilledTexturePlayerIndexed::setPlayerColor(PlayerColor player)
 {
+	texture->playerColored(player);
 }
 
-void FilledTexturePlayerColored::playerColored(PlayerColor player)
+void FilledTexturePlayerColored::setPlayerColor(PlayerColor player)
 {
 	// Color transform to make color of brown DIBOX.PCX texture match color of specified player
 	std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
@@ -177,23 +184,23 @@ CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, size_t Group, i
 {
 	pos.x += x;
 	pos.y += y;
-	anim = graphics->getAnimation(name);
+	anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY);
 	init();
 }
 
-CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags):
-	anim(Anim),
-	frame(Frame),
-	group(Group),
-	flags(Flags)
-{
-	pos.x += x;
-	pos.y += y;
-	init();
-}
+//CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Group, int x, int y, ui8 Flags):
+//	anim(Anim),
+//	frame(Frame),
+//	group(Group),
+//	flags(Flags)
+//{
+//	pos.x += x;
+//	pos.y += y;
+//	init();
+//}
 
-CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targetPos, size_t Group, ui8 Flags):
-	anim(Anim),
+CAnimImage::CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group, ui8 Flags):
+	anim(GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY)),
 	frame(Frame),
 	group(Group),
 	flags(Flags),
@@ -233,10 +240,6 @@ void CAnimImage::setSizeFromImage(const IImage &img)
 void CAnimImage::init()
 {
 	visible = true;
-	anim->load(frame, group);
-	if (flags & CShowableAnim::BASE)
-		anim->load(0,group);
-
 	auto img = anim->getImage(frame, group);
 	if (img)
 		setSizeFromImage(*img);
@@ -263,12 +266,9 @@ void CAnimImage::showAll(Canvas & to)
 		if(auto img = anim->getImage(targetFrame, group))
 		{
 			if(isScaled())
-			{
-				auto scaled = img->scaleFast(scaledSize);
-				to.draw(scaled, pos.topLeft());
-			}
-			else
-				to.draw(img, pos.topLeft());
+				img->scaleFast(scaledSize);
+
+			to.draw(img, pos.topLeft());
 		}
 	}
 }
@@ -276,7 +276,7 @@ void CAnimImage::showAll(Canvas & to)
 void CAnimImage::setAnimationPath(const AnimationPath & name, size_t frame)
 {
 	this->frame = frame;
-	anim = GH.renderHandler().loadAnimation(name);
+	anim = GH.renderHandler().loadAnimation(name, EImageBlitMode::COLORKEY);
 	init();
 }
 
@@ -291,7 +291,6 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
 		return;
 	if (anim->size(Group) > Frame)
 	{
-		anim->load(Frame, Group);
 		frame = Frame;
 		group = Group;
 		if(auto img = anim->getImage(frame, group))
@@ -305,7 +304,7 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
 		logGlobal->error("Error: accessing unavailable frame %d:%d in CAnimation!", Group, Frame);
 }
 
-void CAnimImage::playerColored(PlayerColor currPlayer)
+void CAnimImage::setPlayerColor(PlayerColor currPlayer)
 {
 	player = currPlayer;
 	anim->getImage(frame, group)->playerColored(*player);
@@ -319,7 +318,7 @@ bool CAnimImage::isPlayerColored() const
 }
 
 CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):
-	anim(GH.renderHandler().loadAnimation(name)),
+	anim(GH.renderHandler().loadAnimation(name, (Flags & PALETTE_ALPHA) ? EImageBlitMode::ALPHA : EImageBlitMode::COLORKEY)),
 	group(Group),
 	frame(0),
 	first(0),
@@ -330,7 +329,6 @@ CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags
 	yOffset(0),
 	alpha(alpha)
 {
-	anim->loadGroup(group);
 	last = anim->size(group);
 
 	auto image = anim->getImage(0, group);
@@ -345,11 +343,6 @@ CShowableAnim::CShowableAnim(int x, int y, const AnimationPath & name, ui8 Flags
 	addUsedEvents(TIME);
 }
 
-CShowableAnim::~CShowableAnim()
-{
-	anim->unloadGroup(group);
-}
-
 void CShowableAnim::setAlpha(ui32 alphaValue)
 {
 	alpha = std::min<ui32>(alphaValue, 255);
@@ -365,9 +358,6 @@ bool CShowableAnim::set(size_t Group, size_t from, size_t to)
 	if (max < from || max == 0)
 		return false;
 
-	anim->unloadGroup(group);
-	anim->loadGroup(Group);
-
 	group = Group;
 	frame = first = from;
 	last = max;
@@ -381,9 +371,6 @@ bool CShowableAnim::set(size_t Group)
 		return false;
 	if (group != Group)
 	{
-		anim->unloadGroup(group);
-		anim->loadGroup(Group);
-
 		first = 0;
 		group = Group;
 		last = anim->size(Group);
@@ -464,7 +451,7 @@ void CShowableAnim::setDuration(int durationMs)
 }
 
 CCreatureAnim::CCreatureAnim(int x, int y, const AnimationPath & name, ui8 flags, ECreatureAnimType type):
-	CShowableAnim(x, y, name, flags, 100, size_t(type)) // H3 uses 100 ms per frame, irregardless of battle speed settings
+	CShowableAnim(x, y, name, flags | PALETTE_ALPHA, 100, size_t(type)) // H3 uses 100 ms per frame, irregardless of battle speed settings
 {
 	xOffset = 0;
 	yOffset = 0;

+ 19 - 8
client/widgets/Images.h

@@ -44,6 +44,7 @@ public:
 
 	/// wrap section of an existing Image
 	CPicture(std::shared_ptr<IImage> image, const Rect &SrcRext, int x = 0, int y = 0); //wrap subrect of given surface
+	CPicture(const ImagePath & bmpname, const Rect &SrcRext, int x = 0, int y = 0); //wrap subrect of given surface
 
 	/// Loads image from specified file name
 	CPicture(const ImagePath & bmpname);
@@ -54,7 +55,7 @@ public:
 	/// 0=transparent, 255=opaque
 	void setAlpha(uint8_t value);
 	void scaleTo(Point size);
-	void colorize(PlayerColor player);
+	void setPlayerColor(PlayerColor player);
 
 	void show(Canvas & to) override;
 	void showAll(Canvas & to) override;
@@ -68,18 +69,28 @@ protected:
 	Rect imageArea;
 
 public:
+	CFilledTexture(const ImagePath & imageName, Rect position, Rect imageArea);
 	CFilledTexture(const ImagePath & imageName, Rect position);
-	CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea);
 
 	void showAll(Canvas & to) override;
 };
 
+/// area filled with specific texture, colorized to player color if image is indexed
+class FilledTexturePlayerIndexed : public CFilledTexture
+{
+public:
+	using CFilledTexture::CFilledTexture;
+
+	void setPlayerColor(PlayerColor player);
+};
+
+/// area filled with specific texture, with applied color filter to colorize it to specific player
 class FilledTexturePlayerColored : public CFilledTexture
 {
 public:
-	FilledTexturePlayerColored(const ImagePath & imageName, Rect position);
+	using CFilledTexture::CFilledTexture;
 
-	void playerColored(PlayerColor player);
+	void setPlayerColor(PlayerColor player);
 };
 
 /// Class for displaying one image from animation
@@ -103,8 +114,8 @@ public:
 	bool visible;
 
 	CAnimImage(const AnimationPath & name, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0);
-	CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0);
-	CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0);
+//	CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Group=0, int x=0, int y=0, ui8 Flags=0);
+	CAnimImage(const AnimationPath & name, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0);
 	~CAnimImage();
 
 	/// size of animation
@@ -114,7 +125,7 @@ public:
 	void setFrame(size_t Frame, size_t Group=0);
 
 	/// makes image player-colored to specific player
-	void playerColored(PlayerColor player);
+	void setPlayerColor(PlayerColor player);
 
 	/// returns true if image has player-colored effect applied
 	bool isPlayerColored() const;
@@ -135,6 +146,7 @@ public:
 		BASE=1,            //base frame will be blitted before current one
 		HORIZONTAL_FLIP=2, //TODO: will be displayed rotated
 		VERTICAL_FLIP=4,   //TODO: will be displayed rotated
+		PALETTE_ALPHA=8,   // use alpha channel for images with palette. Required for creatures in battle and map objects
 		PLAY_ONCE=32       //play animation only once and stop at last frame
 	};
 protected:
@@ -168,7 +180,6 @@ public:
 	void setAlpha(ui32 alphaValue);
 
 	CShowableAnim(int x, int y, const AnimationPath & name, ui8 flags, ui32 frameTime, size_t Group=0, uint8_t alpha = UINT8_MAX);
-	~CShowableAnim();
 
 	//set animation to group or part of group
 	bool set(size_t Group);

+ 2 - 3
client/widgets/MiscWidgets.cpp

@@ -28,7 +28,6 @@
 #include "../windows/CCastleInterface.h"
 #include "../windows/InfoWindows.h"
 #include "../render/Canvas.h"
-#include "../render/Graphics.h"
 
 #include "../../CCallback.h"
 
@@ -249,7 +248,7 @@ CMinorResDataBar::CMinorResDataBar()
 	pos.y = 575;
 
 	background = std::make_shared<CPicture>(ImagePath::builtin("KRESBAR.bmp"));
-	background->colorize(LOCPLINT->playerID);
+	background->setPlayerColor(LOCPLINT->playerID);
 
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;
@@ -535,7 +534,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
 	auto creatureID = creature->getCreature();
 	int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex();
 
-	creatureImage = std::make_shared<CAnimImage>(graphics->getAnimation(AnimationPath::builtin("TWCRPORT")), creatureIconIndex);
+	creatureImage = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), creatureIconIndex);
 	creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11));
 
 	bool isHeroSelected = LOCPLINT->localState->getCurrentHero() != nullptr;

+ 0 - 1
client/widgets/ObjectLists.h

@@ -18,7 +18,6 @@ VCMI_LIB_NAMESPACE_END
 class CAnimImage;
 class CSlider;
 class CLabel;
-class CAnimation;
 
 /// Used as base for Tabs and List classes
 class CObjectList : public CIntObject

+ 8 - 8
client/windows/CCastleInterface.cpp

@@ -95,7 +95,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
 	}
 
 	if(!str->borderName.empty())
-		border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::ALPHA);
+		border = GH.renderHandler().loadImage(str->borderName, EImageBlitMode::COLORKEY);
 
 	if(!str->areaName.empty())
 		area = GH.renderHandler().loadImage(str->areaName, EImageBlitMode::ALPHA);
@@ -272,7 +272,7 @@ CDwellingInfoBox::CDwellingInfoBox(int centerX, int centerY, const CGTownInstanc
 	: CWindowObject(RCLICK_POPUP, ImagePath::builtin("CRTOINFO"), Point(centerX, centerY))
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background->colorize(Town->tempOwner);
+	background->setPlayerColor(Town->tempOwner);
 
 	const CCreature * creature = Town->creatures.at(level).second.back().toCreature();
 
@@ -430,7 +430,7 @@ void CHeroGSlot::clickPressed(const Point & cursorPosition)
 	{
 		setHighlight(false);
 
-		if(other->hero)
+		if(other->hero && !GH.isKeyboardShiftDown())
 			LOCPLINT->showHeroExchange(hero->id, other->hero->id);
 		else
 			LOCPLINT->openHeroWindow(hero);
@@ -885,9 +885,9 @@ void CCastleBuildings::enterCastleGate()
 			availableTowns.push_back(t->id.getNum());//add to the list
 			if(settings["general"]["enableUiEnhancements"].Bool())
 			{
-				std::shared_ptr<CAnimation> a = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITPA"));
-				a->preload();
-				images.push_back(a->getImage(t->town->clientInfo.icons[t->hasFort()][false] + 2)->scaleFast(Point(35, 23)));
+				auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
+				image->scaleFast(Point(35, 23));
+				images.push_back(image);
 			}
 		}
 	}
@@ -1245,7 +1245,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 
 	builds = std::make_shared<CCastleBuildings>(town);
 	panel = std::make_shared<CPicture>(ImagePath::builtin("TOWNSCRN"), 0, builds->pos.h);
-	panel->colorize(LOCPLINT->playerID);
+	panel->setPlayerColor(LOCPLINT->playerID);
 	pos.w = panel->pos.w;
 	pos.h = builds->pos.h + panel->pos.h;
 	center();
@@ -1283,7 +1283,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	recreateIcons();
 	if (!from)
 		adventureInt->onAudioPaused();
-	CCS->musich->playMusic(town->town->clientInfo.musicTheme, true, false);
+	CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false);
 }
 
 CCastleInterface::~CCastleInterface()

+ 5 - 11
client/windows/CExchangeWindow.cpp

@@ -26,7 +26,6 @@
 #include "../widgets/TextControls.h"
 
 #include "../render/IRenderHandler.h"
-#include "../render/CAnimation.h"
 
 #include "../../CCallback.h"
 
@@ -66,17 +65,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	titles[0] = std::make_shared<CLabel>(147, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[0]));
 	titles[1] = std::make_shared<CLabel>(653, 25, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, genTitle(heroInst[1]));
 
-	auto PSKIL32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("PSKIL32"));
-	PSKIL32->preload();
-
-	auto SECSK32 = GH.renderHandler().loadAnimation(AnimationPath::builtin("SECSK32"));
-
 	for(int g = 0; g < 4; ++g)
 	{
 		if (qeLayout)
-			primSkillImages.push_back(std::make_shared<CAnimImage>(PSKIL32, g, Rect(389, 12 + 26 * g, 22, 22)));
+			primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), g, Rect(389, 12 + 26 * g, 22, 22)));
 		else
-			primSkillImages.push_back(std::make_shared<CAnimImage>(PSKIL32, g, 0, 385, 19 + 36 * g));
+			primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), g, 0, 385, 19 + 36 * g));
 	}
 
 	for(int leftRight : {0, 1})
@@ -88,14 +82,14 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 
 		for(int m=0; m < hero->secSkills.size(); ++m)
-			secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(SECSK32, 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
+			secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
 
 		specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
 
-		expImages[leftRight] = std::make_shared<CAnimImage>(PSKIL32, 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45);
+		expImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45);
 		expValues[leftRight] = std::make_shared<CLabel>(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 
-		manaImages[leftRight] = std::make_shared<CAnimImage>(PSKIL32, 5, 0, 139 + 490 * leftRight, qeLayout ? 41 : 45);
+		manaImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), 5, 0, 139 + 490 * leftRight, qeLayout ? 41 : 45);
 		manaValues[leftRight] = std::make_shared<CLabel>(155 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	}
 

+ 2 - 3
client/windows/CHeroOverview.cpp

@@ -14,7 +14,6 @@
 #include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/Colors.h"
-#include "../render/Graphics.h"
 #include "../render/IImage.h"
 #include "../renderSDL/RenderHandler.h"
 #include "../widgets/CComponent.h"
@@ -225,12 +224,12 @@ void CHeroOverview::genControls()
         {
             if((*CGI->heroh)[heroIdx]->haveSpellBook)
             {
-                imageSpells.push_back(std::make_shared<CAnimImage>(GH.renderHandler().loadAnimation(AnimationPath::builtin("ARTIFACT")), 0, Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0));
+                imageSpells.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), 0, Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0));
             }
             i++;
         }
 
-        imageSpells.push_back(std::make_shared<CAnimImage>(GH.renderHandler().loadAnimation(AnimationPath::builtin("SPELLBON")), (*CGI->spellh)[spell]->getIconIndex(), Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0));
+        imageSpells.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SPELLBON"), (*CGI->spellh)[spell]->getIconIndex(), Rect(302 + (292 / 2) + 2 * borderOffset, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset), 32, 32), 0));
         labelSpellsNames.push_back(std::make_shared<CLabel>(302 + (292 / 2) + 3 * borderOffset + 32 + borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 3, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->spellh)[spell]->getNameTranslated()));
         i++;
     }

+ 7 - 11
client/windows/CHeroWindow.cpp

@@ -28,7 +28,6 @@
 #include "../widgets/CGarrisonInt.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/Buttons.h"
-#include "../render/CAnimation.h"
 #include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
@@ -131,14 +130,12 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 		primSkillValues.push_back(value);
 	}
 
-	auto primSkills = GH.renderHandler().loadAnimation(AnimationPath::builtin("PSKIL42"));
-	primSkills->preload();
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 0, 0, 32, 111));
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 1, 0, 102, 111));
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 2, 0, 172, 111));
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 3, 0, 162, 230));
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 4, 0, 20, 230));
-	primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 5, 0, 242, 111));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 0, 0, 32, 111));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 1, 0, 102, 111));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 2, 0, 172, 111));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 3, 0, 162, 230));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 4, 0, 20, 230));
+	primSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 5, 0, 242, 111));
 
 	specImage = std::make_shared<CAnimImage>(AnimationPath::builtin("UN44"), 0, 0, 18, 180);
 	specArea = std::make_shared<LRClickableAreaWText>(Rect(18, 180, 136, 42), CGI->generaltexth->heroscrn[27]);
@@ -152,12 +149,11 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	expValue = std::make_shared<CLabel>(68, 252);
 	manaValue = std::make_shared<CLabel>(211, 252);
 
-	auto secSkills = GH.renderHandler().loadAnimation(AnimationPath::builtin("SECSKILL"));
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	{
 		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
 		secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, ComponentType::SEC_SKILL));
-		secSkillImages.push_back(std::make_shared<CAnimImage>(secSkills, 0, 0, r.x, r.y));
+		secSkillImages.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSKILL"), 0, 0, r.x, r.y));
 
 		int x = (i % 2) ? 212 : 68;
 		int y = 280 + 48 * (i/2);

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません