2
0
Эх сурвалжийг харах

Next step of code refactoring:

- refactoring of CBattleFieldController code (except for 2 arcane
methods)
- introduced class CCanvas for encapsulated rendering surface
- battleint rendering is now partially done with CCanvas
- removed unused includes of CBitmapHandler
Ivan Savenko 2 жил өмнө
parent
commit
abb553d975

+ 7 - 5
client/CMakeLists.txt

@@ -2,13 +2,13 @@ set(client_SRCS
 		StdInc.cpp
 		../CCallback.cpp
 
+		battle/CBattleActionsController.cpp
 		battle/CBattleAnimations.cpp
 		battle/CBattleControlPanel.cpp
-		battle/CBattleInterfaceClasses.cpp
-		battle/CBattleInterface.cpp
-		battle/CBattleActionsController.cpp
 		battle/CBattleEffectsController.cpp
 		battle/CBattleFieldController.cpp
+		battle/CBattleInterfaceClasses.cpp
+		battle/CBattleInterface.cpp
 		battle/CBattleObstacleController.cpp
 		battle/CBattleProjectileController.cpp
 		battle/CBattleSiegeController.cpp
@@ -16,6 +16,7 @@ set(client_SRCS
 		battle/CCreatureAnimation.cpp
 
 		gui/CAnimation.cpp
+		gui/CCanvas.cpp
 		gui/CCursorHandler.cpp
 		gui/CGuiHandler.cpp
 		gui/CIntObject.cpp
@@ -83,13 +84,13 @@ set(client_SRCS
 set(client_HEADERS
 		StdInc.h
 
+		battle/CBattleActionsController.h
 		battle/CBattleAnimations.h
 		battle/CBattleControlPanel.h
 		battle/CBattleEffectsController.h
+		battle/CBattleFieldController.h
 		battle/CBattleInterfaceClasses.h
 		battle/CBattleInterface.h
-		battle/CBattleActionsController.h
-		battle/CBattleFieldController.h
 		battle/CBattleObstacleController.h
 		battle/CBattleProjectileController.h
 		battle/CBattleSiegeController.h
@@ -97,6 +98,7 @@ set(client_HEADERS
 		battle/CCreatureAnimation.h
 
 		gui/CAnimation.h
+		gui/CCanvas.h
 		gui/CCursorHandler.h
 		gui/CGuiHandler.h
 		gui/CIntObject.h

+ 0 - 1
client/Graphics.cpp

@@ -29,7 +29,6 @@
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
 #include "../lib/CGeneralTextHandler.h"
-#include "CBitmapHandler.h"
 #include "../lib/CGameState.h"
 #include "../lib/JsonNode.h"
 #include "../lib/vcmi_endian.h"

+ 0 - 1
client/battle/CBattleControlPanel.cpp

@@ -16,7 +16,6 @@
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../CGameInfo.h"
-#include "../CBitmapHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../CPlayerInterface.h"

+ 3 - 3
client/battle/CBattleControlPanel.h

@@ -18,7 +18,6 @@ class CBattleConsole;
 
 class CBattleControlPanel : public CIntObject
 {
-
 	CBattleInterface * owner;
 
 	std::shared_ptr<CPicture> menu;
@@ -39,8 +38,6 @@ class CBattleControlPanel : public CIntObject
 	void bOptionsf();
 	void bSurrenderf();
 	void bFleef();
-	void reallyFlee(); //performs fleeing without asking player
-	void reallySurrender(); //performs surrendering without asking player
 	void bAutofightf();
 	void bSpellf();
 	void bWaitf();
@@ -50,6 +47,9 @@ class CBattleControlPanel : public CIntObject
 	void bTacticNextStack();
 	void bTacticPhaseEnd();
 
+	void reallyFlee(); //performs fleeing without asking player
+	void reallySurrender(); //performs surrendering without asking player
+
 public:
 	std::shared_ptr<CBattleConsole> console;
 

+ 123 - 149
client/battle/CBattleFieldController.cpp

@@ -15,10 +15,10 @@
 #include "CBattleSiegeController.h"
 #include "CBattleStacksController.h"
 #include "CBattleObstacleController.h"
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../../CCallback.h"
-#include "../gui/SDL_Extensions.h"
+#include "../gui/CAnimation.h"
+#include "../gui/CCanvas.h"
 #include "../gui/CGuiHandler.h"
 #include "../CPlayerInterface.h"
 #include "../gui/CCursorHandler.h"
@@ -29,8 +29,6 @@
 
 CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
 	owner(owner),
-	previouslyHoveredHex(-1),
-	currentlyHoveredHex(-1),
 	attackingHex(-1)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
@@ -38,58 +36,31 @@ CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
 	pos.h = owner->pos.h;
 
 	//preparing cells and hexes
-	cellBorder = BitmapHandler::loadBitmap("CCELLGRD.BMP");
-	CSDL_Ext::alphaTransform(cellBorder);
-	cellShade = BitmapHandler::loadBitmap("CCELLSHD.BMP");
-	CSDL_Ext::alphaTransform(cellShade);
-
+	cellBorder = IImage::createFromFile("CCELLGRD.BMP");
+	cellShade = IImage::createFromFile("CCELLSHD.BMP");
 
 	if(!owner->siegeController)
 	{
 		auto bfieldType = owner->curInt->cb->battleGetBattlefieldType();
 
 		if(bfieldType == BattleField::NONE)
-		{
 			logGlobal->error("Invalid battlefield returned for current battle");
-		}
 		else
-		{
-			background = BitmapHandler::loadBitmap(bfieldType.getInfo()->graphics, false);
-		}
+			background = IImage::createFromFile(bfieldType.getInfo()->graphics);
 	}
 	else
 	{
 		std::string backgroundName = owner->siegeController->getBattleBackgroundName();
-		background = BitmapHandler::loadBitmap(backgroundName, false);
+		background = IImage::createFromFile(backgroundName);
 	}
 
 	//preparing graphic with cell borders
-	cellBorders = CSDL_Ext::newSurface(background->w, background->h, cellBorder);
-	//copying palette
-	for (int g=0; g<cellBorder->format->palette->ncolors; ++g) //we assume that cellBorders->format->palette->ncolors == 256
-	{
-		cellBorders->format->palette->colors[g] = cellBorder->format->palette->colors[g];
-	}
-	//palette copied
-	for (int i=0; i<GameConstants::BFIELD_HEIGHT; ++i) //rows
-	{
-		for (int j=0; j<GameConstants::BFIELD_WIDTH-2; ++j) //columns
-		{
-			int x = 58 + (i%2==0 ? 22 : 0) + 44*j;
-			int y = 86 + 42 *i;
-			for (int cellX = 0; cellX < cellBorder->w; ++cellX)
-			{
-				for (int cellY = 0; cellY < cellBorder->h; ++cellY)
-				{
-					if (y+cellY < cellBorders->h && x+cellX < cellBorders->w)
-						* ((Uint8*)cellBorders->pixels + (y+cellY) *cellBorders->pitch + (x+cellX)) |= *((Uint8*)cellBorder->pixels + cellY *cellBorder->pitch + cellX);
-				}
-			}
-		}
-	}
+	cellBorders = std::make_shared<CCanvas>(Point(background->width(), background->height()));
 
-	backgroundWithHexes = CSDL_Ext::newSurface(background->w, background->h, screen);
+	for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+		cellBorders->draw(cellBorder, hexPositionLocal(i).topLeft());
 
+	backgroundWithHexes = std::make_shared<CCanvas>(Point(background->width(), background->height()));
 
 	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
 	{
@@ -99,38 +70,25 @@ CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
 		hex->myInterface = owner;
 		bfield.push_back(hex);
 	}
-
-	//for(auto hex : bfield)
-	//	addChild(hex.get());
-}
-
-CBattleFieldController::~CBattleFieldController()
-{
-	SDL_FreeSurface(background);
-	SDL_FreeSurface(cellBorders);
-	SDL_FreeSurface(backgroundWithHexes);
-	SDL_FreeSurface(cellBorder);
-	SDL_FreeSurface(cellShade);
 }
 
 void CBattleFieldController::showBackgroundImage(SDL_Surface *to)
 {
-	blitAt(background, owner->pos.x, owner->pos.y, to);
+	background->draw(to, owner->pos.x, owner->pos.y);
+
 	if (settings["battle"]["cellBorders"].Bool())
-	{
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, to, &owner->pos);
-	}
+		cellBorders->copyTo(to, owner->pos.topLeft());
 }
 
 void CBattleFieldController::showBackgroundImageWithHexes(SDL_Surface *to)
 {
-	blitAt(backgroundWithHexes, owner->pos.x, owner->pos.y, to);
+	backgroundWithHexes->copyTo(to, owner->pos.topLeft());
 }
 
 void CBattleFieldController::redrawBackgroundWithHexes()
 {
 	const CStack *activeStack = owner->stacksController->getActiveStack();
-	attackableHexes.clear();
+	std::vector<BattleHex> attackableHexes;
 	if (activeStack)
 		occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
 
@@ -140,8 +98,7 @@ void CBattleFieldController::redrawBackgroundWithHexes()
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
 
 	//prepare background graphic with hexes and shaded hexes
-	blitAt(background, 0, 0, backgroundWithHexes);
-
+	backgroundWithHexes->draw(background, Point(0,0));
 	owner->obstacleController->redrawBackgroundWithHexes(backgroundWithHexes);
 
 	if (settings["battle"]["stackRange"].Bool())
@@ -150,125 +107,142 @@ void CBattleFieldController::redrawBackgroundWithHexes()
 		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
 		for (BattleHex hex : hexesToShade)
 		{
-			int i = hex.getY(); //row
-			int j = hex.getX()-1; //column
-			int x = 58 + (i%2==0 ? 22 : 0) + 44*j;
-			int y = 86 + 42 *i;
-			SDL_Rect temp_rect = genRect(cellShade->h, cellShade->w, x, y);
-			CSDL_Ext::blit8bppAlphaTo24bpp(cellShade, nullptr, backgroundWithHexes, &temp_rect);
+			backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
 		}
 	}
 
 	if(settings["battle"]["cellBorders"].Bool())
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, backgroundWithHexes, nullptr);
+		backgroundWithHexes->draw(cellBorders, Point(0, 0));
 }
 
-void CBattleFieldController::showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder)
+void CBattleFieldController::showHighlightedHex(std::shared_ptr<CCanvas> to, BattleHex hex, bool darkBorder)
 {
-	int x = 14 + (hex.getY() % 2 == 0 ? 22 : 0) + 44 *(hex.getX()) + owner->pos.x;
-	int y = 86 + 42 *hex.getY() + owner->pos.y;
-	SDL_Rect temp_rect = genRect (cellShade->h, cellShade->w, x, y);
-	CSDL_Ext::blit8bppAlphaTo24bpp (cellShade, nullptr, to, &temp_rect);
+	Point hexPos = hexPosition(hex).topLeft();
+
+	to->draw(cellShade, hexPos);
 	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorder, nullptr, to, &temp_rect); //redraw border to make it light green instead of shaded
+		to->draw(cellBorder, hexPos);
 }
 
-void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
+std::set<BattleHex> CBattleFieldController::getHighlightedHexesStackRange()
 {
-	bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on
-	if(owner->stacksController->getActiveStack() && settings["battle"]["stackRange"].Bool())
+	std::set<BattleHex> result;
+
+	if ( !owner->stacksController->getActiveStack())
+		return result;
+
+	if ( !settings["battle"]["stackRange"].Bool())
+		return result;
+
+	auto hoveredHex = getHoveredHex();
+
+	std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->stacksController->getActiveStack(), hoveredHex, attackingHex);
+	for(BattleHex hex : set)
+		result.insert(hex);
+
+	// display the movement shadow of the stack at b (i.e. stack under mouse)
+	const CStack * const shere = owner->curInt->cb->battleGetStackByPos(hoveredHex, false);
+	if(shere && shere != owner->stacksController->getActiveStack() && shere->alive())
 	{
-		std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->stacksController->getActiveStack(), currentlyHoveredHex, attackingHex);
-		for(BattleHex hex : set)
-			if(hex != currentlyHoveredHex)
-				showHighlightedHex(to, hex, false);
-
-		// display the movement shadow of the stack at b (i.e. stack under mouse)
-		const CStack * const shere = owner->curInt->cb->battleGetStackByPos(currentlyHoveredHex, false);
-		if(shere && shere != owner->stacksController->getActiveStack() && shere->alive())
+		std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
+		for(BattleHex hex : v)
+			result.insert(hex);
+	}
+	return result;
+}
+
+std::set<BattleHex> CBattleFieldController::getHighlightedHexesSpellRange()
+{
+	std::set<BattleHex> result;
+	auto hoveredHex = getHoveredHex();
+
+	if(!settings["battle"]["mouseShadow"].Bool())
+		return result;
+
+	const spells::Caster *caster = nullptr;
+	const CSpell *spell = nullptr;
+
+	spells::Mode mode = spells::Mode::HERO;
+
+	if(owner->actionsController->spellcastingModeActive())//hero casts spell
+	{
+		spell = owner->actionsController->selectedSpell().toSpell();
+		caster = owner->getActiveHero();
+	}
+	else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
+	{
+		spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
+		caster = owner->stacksController->getActiveStack();
+		mode = spells::Mode::CREATURE_ACTIVE;
+	}
+
+	if(caster && spell) //when casting spell
+	{
+		// printing shaded hex(es)
+		spells::BattleCast event(owner->curInt->cb.get(), caster, mode, spell);
+		auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
+
+		for(BattleHex shadedHex : shaded)
 		{
-			std::vector<BattleHex> v = owner->curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
-			for(BattleHex hex : v)
-			{
-				if(hex != currentlyHoveredHex)
-					showHighlightedHex(to, hex, false);
-				else if(!settings["battle"]["mouseShadow"].Bool())
-					delayedBlit = true; //blit at the end of method to avoid graphic artifacts
-				else
-					showHighlightedHex(to, hex, true); //blit now and blit 2nd time later for darker shadow - avoids graphic artifacts
-			}
+			if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
+				result.insert(shadedHex);
 		}
 	}
+	else if(owner->active) //always highlight pointed hex
+	{
+		if(hoveredHex.getX() != 0 && hoveredHex.getX() != GameConstants::BFIELD_WIDTH - 1)
+			result.insert(hoveredHex);
+	}
+
+	return result;
+}
+
+void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
+{
+	auto canvas = std::make_shared<CCanvas>(to);
+
+	std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
+	std::set<BattleHex> hoveredMouse = getHighlightedHexesSpellRange();
 
 	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
 	{
-		if(bfield[b]->strictHovered && bfield[b]->hovered)
-		{
-			if(previouslyHoveredHex == -1)
-				previouslyHoveredHex = b; //something to start with
-			if(currentlyHoveredHex == -1)
-				currentlyHoveredHex = b; //something to start with
+		bool stack = hoveredStack.count(b);
+		bool mouse = hoveredMouse.count(b);
 
-			if(currentlyHoveredHex != b) //repair hover info
-			{
-				previouslyHoveredHex = currentlyHoveredHex;
-				currentlyHoveredHex = b;
-			}
-			if(settings["battle"]["mouseShadow"].Bool() || delayedBlit)
-			{
-				const spells::Caster *caster = nullptr;
-				const CSpell *spell = nullptr;
-
-				spells::Mode mode = spells::Mode::HERO;
-
-				if(owner->actionsController->spellcastingModeActive())//hero casts spell
-				{
-					spell = owner->actionsController->selectedSpell().toSpell();
-					caster = owner->getActiveHero();
-				}
-				else if(owner->stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
-				{
-					spell = SpellID(owner->stacksController->activeStackSpellToCast()).toSpell();
-					caster = owner->stacksController->getActiveStack();
-					mode = spells::Mode::CREATURE_ACTIVE;
-				}
-
-				if(caster && spell) //when casting spell
-				{
-					// printing shaded hex(es)
-					spells::BattleCast event(owner->curInt->cb.get(), caster, mode, spell);
-					auto shaded = spell->battleMechanics(&event)->rangeInHexes(currentlyHoveredHex);
-
-					for(BattleHex shadedHex : shaded)
-					{
-						if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
-							showHighlightedHex(to, shadedHex, true);
-					}
-				}
-				else if(owner->active || delayedBlit) //always highlight pointed hex, keep this condition last in this method for correct behavior
-				{
-					if(currentlyHoveredHex.getX() != 0
-					 && currentlyHoveredHex.getX() != GameConstants::BFIELD_WIDTH - 1)
-						showHighlightedHex(to, currentlyHoveredHex, true); //keep true for OH3 behavior: hovered hex frame "thinner"
-				}
-			}
+		if ( stack && mouse )
+		{
+			// area where enemy stack can move AND affected by mouse cursor - create darker highlight by blitting twice
+			showHighlightedHex(canvas, b, true);
+			showHighlightedHex(canvas, b, true);
+		}
+		if ( !stack && mouse )
+		{
+			showHighlightedHex(canvas, b, true);
+		}
+		if ( stack && !mouse )
+		{
+			showHighlightedHex(canvas, b, false);
 		}
 	}
 }
 
-Rect CBattleFieldController::hexPosition(BattleHex hex) const
+Rect CBattleFieldController::hexPositionLocal(BattleHex hex) const
 {
-	int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX() + owner->pos.x;
-	int y = 86 + 42 *hex.getY() + owner->pos.y;
-	int w = cellShade->w;
-	int h = cellShade->h;
+	int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX();
+	int y = 86 + 42 *hex.getY();
+	int w = cellShade->width();
+	int h = cellShade->height();
 	return Rect(x, y, w, h);
 }
 
+Rect CBattleFieldController::hexPosition(BattleHex hex) const
+{
+	return hexPositionLocal(hex) + owner->pos.topLeft();
+}
+
 bool CBattleFieldController::isPixelInHex(Point const & position)
 {
-	assert(cellShade);
-	return CSDL_Ext::SDL_GetPixel(cellShade, position.x, position.y) != 0;
+	return !cellShade->isTransparent(position);
 }
 
 BattleHex CBattleFieldController::getHoveredHex()

+ 17 - 10
client/battle/CBattleFieldController.h

@@ -17,32 +17,39 @@ struct Rect;
 struct Point;
 
 class CClickableHex;
+class CCanvas;
 class CStack;
+class IImage;
 class CBattleInterface;
 
 class CBattleFieldController : public CIntObject
 {
 	CBattleInterface * owner;
 
-	SDL_Surface *background;
-	SDL_Surface *backgroundWithHexes;
-	SDL_Surface *cellBorders;
-	SDL_Surface *cellBorder;
-	SDL_Surface *cellShade;
+	std::shared_ptr<IImage> background;
+	std::shared_ptr<IImage> cellBorder;
+	std::shared_ptr<IImage> cellShade;
 
-	BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
-	BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
+	std::shared_ptr<CCanvas> cellBorders;
+
+	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
+	std::shared_ptr<CCanvas> backgroundWithHexes;
+
+	//BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago
+	//BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon)
 	BattleHex attackingHex; //hex from which the stack would perform attack with current cursor
 
 	std::vector<BattleHex> occupyableHexes; //hexes available for active stack
-	std::vector<BattleHex> attackableHexes; //hexes attackable by active stack
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes; // hexes that when in front of a unit cause it's amount box to move back
 
 	std::vector<std::shared_ptr<CClickableHex>> bfield; //11 lines, 17 hexes on each
 
+	void showHighlightedHex(std::shared_ptr<CCanvas> to, BattleHex hex, bool darkBorder);
+
+	std::set<BattleHex> getHighlightedHexesStackRange();
+	std::set<BattleHex> getHighlightedHexesSpellRange();
 public:
 	CBattleFieldController(CBattleInterface * owner);
-	~CBattleFieldController();
 
 	void showBackgroundImage(SDL_Surface *to);
 	void showBackgroundImageWithHexes(SDL_Surface *to);
@@ -50,8 +57,8 @@ public:
 	void redrawBackgroundWithHexes();
 
 	void showHighlightedHexes(SDL_Surface *to);
-	void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder);
 
+	Rect hexPositionLocal(BattleHex hex) const;
 	Rect hexPosition(BattleHex hex) const;
 	bool isPixelInHex(Point const & position);
 

+ 0 - 1
client/battle/CBattleInterface.cpp

@@ -22,7 +22,6 @@
 #include "CBattleControlPanel.h"
 #include "CBattleStacksController.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CMessage.h"
 #include "../CMT.h"

+ 0 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -17,7 +17,6 @@
 #include "CBattleStacksController.h"
 #include "CBattleControlPanel.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CMessage.h"
 #include "../CMusicHandler.h"

+ 3 - 2
client/battle/CBattleObstacleController.cpp

@@ -18,6 +18,7 @@
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/ObstacleHandler.h"
 #include "../gui/CAnimation.h"
+#include "../gui/CCanvas.h"
 
 CBattleObstacleController::CBattleObstacleController(CBattleInterface * owner):
 	owner(owner)
@@ -196,7 +197,7 @@ Point CBattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> ima
 	return r.topLeft();
 }
 
-void CBattleObstacleController::redrawBackgroundWithHexes(SDL_Surface * to)
+void CBattleObstacleController::redrawBackgroundWithHexes(std::shared_ptr<CCanvas> to)
 {
 	//draw absolute obstacles (cliffs and so on)
 	for(auto & oi : owner->curInt->cb->battleGetAllObstacles())
@@ -205,7 +206,7 @@ void CBattleObstacleController::redrawBackgroundWithHexes(SDL_Surface * to)
 		{
 			auto img = getObstacleImage(*oi);
 			if(img)
-				img->draw(to, oi->getInfo().width, oi->getInfo().height);
+				to->draw(img, Point(oi->getInfo().width, oi->getInfo().height));
 		}
 	}
 }

+ 2 - 1
client/battle/CBattleObstacleController.h

@@ -13,6 +13,7 @@ struct SDL_Surface;
 struct BattleObjectsByHex;
 struct BattleHex;
 class IImage;
+class CCanvas;
 class CAnimation;
 class CBattleInterface;
 class CObstacleInstance;
@@ -38,5 +39,5 @@ public:
 
 	void showBattlefieldObjects(SDL_Surface *to, const BattleHex & location );
 
-	void redrawBackgroundWithHexes(SDL_Surface * to);
+	void redrawBackgroundWithHexes(std::shared_ptr<CCanvas> to);
 };

+ 12 - 0
client/gui/CAnimation.cpp

@@ -98,6 +98,8 @@ public:
 	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;
 	int width() const override;
 	int height() const override;
 
@@ -745,6 +747,16 @@ int SDLImage::height() const
 	return fullSize.y;
 }
 
+bool SDLImage::isTransparent(const Point & coords) const
+{
+	return CSDL_Ext::isTransparent(surf, coords.x, coords.y);
+}
+
+Point SDLImage::dimensions() const
+{
+	return fullSize;
+}
+
 void SDLImage::horizontalFlip()
 {
 	margins.y = fullSize.y - surf->h - margins.y;

+ 4 - 0
client/gui/CAnimation.h

@@ -53,6 +53,10 @@ public:
 	//set special color for flag
 	virtual void setFlagColor(PlayerColor player)=0;
 
+	//test transparency of specific pixel
+	virtual bool isTransparent(const Point & coords) const = 0;
+
+	virtual Point dimensions() const = 0;
 	virtual int width() const=0;
 	virtual int height() const=0;
 

+ 46 - 0
client/gui/CCanvas.cpp

@@ -0,0 +1,46 @@
+/*
+ * CCanvas.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 "CCanvas.h"
+
+#include "SDL_Extensions.h"
+#include "Geometries.h"
+#include "CAnimation.h"
+
+CCanvas::CCanvas(SDL_Surface * surface):
+	surface(surface)
+{
+	surface->refcount++;
+}
+
+CCanvas::CCanvas(const Point & size)
+{
+	surface = CSDL_Ext::newSurface(size.x, size.y);
+}
+
+CCanvas::~CCanvas()
+{
+	SDL_FreeSurface(surface);
+}
+
+void CCanvas::draw(std::shared_ptr<IImage> image, const Point & pos)
+{
+	image->draw(surface, pos.x, pos.y);
+}
+
+void CCanvas::draw(std::shared_ptr<CCanvas> image, const Point & pos)
+{
+	image->copyTo(surface, pos);
+}
+
+void CCanvas::copyTo(SDL_Surface * to, const Point & pos)
+{
+	blitAt(to, pos.x, pos.y, surface);
+}

+ 35 - 0
client/gui/CCanvas.h

@@ -0,0 +1,35 @@
+/*
+ * CCanvas.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
+
+struct SDL_Surface;
+struct Point;
+class IImage;
+
+/// Class that represents surface for drawing on
+class CCanvas
+{
+	SDL_Surface * surface;
+public:
+	// constructs canvas using existing surface. Caller maintains ownership on the surface
+	CCanvas(SDL_Surface * surface);
+
+	CCanvas(const Point & size);
+	~CCanvas();
+
+	// renders image onto this canvas
+	void draw(std::shared_ptr<IImage> image, const Point & pos);
+
+	// renders another canvas onto this canvas
+	void draw(std::shared_ptr<CCanvas> image, const Point & pos);
+
+	// for compatibility, copies content of this canvas onto SDL_Surface
+	void copyTo(SDL_Surface * to, const Point & pos);
+};

+ 0 - 1
client/lobby/CBonusSelection.cpp

@@ -18,7 +18,6 @@
 
 #include "../CGameInfo.h"
 #include "../CMessage.h"
-#include "../CBitmapHandler.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"

+ 0 - 1
client/lobby/CSelectionBase.cpp

@@ -21,7 +21,6 @@
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../CMessage.h"
-#include "../CBitmapHandler.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"

+ 0 - 1
client/lobby/OptionsTab.cpp

@@ -12,7 +12,6 @@
 #include "CSelectionBase.h"
 #include "OptionsTab.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CServerHandler.h"
 #include "../gui/CAnimation.h"

+ 0 - 1
client/lobby/SelectionTab.cpp

@@ -15,7 +15,6 @@
 
 #include "../CGameInfo.h"
 #include "../CMessage.h"
-#include "../CBitmapHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CServerHandler.h"
 #include "../gui/CAnimation.h"

+ 0 - 1
client/mainmenu/CCampaignScreen.cpp

@@ -15,7 +15,6 @@
 
 #include "../CGameInfo.h"
 #include "../CMessage.h"
-#include "../CBitmapHandler.h"
 #include "../CMusicHandler.h"
 #include "../CVideoHandler.h"
 #include "../CPlayerInterface.h"

+ 0 - 1
client/mainmenu/CMainMenu.cpp

@@ -37,7 +37,6 @@
 #include "../CPlayerInterface.h"
 #include "../../CCallback.h"
 #include "../CMessage.h"
-#include "../CBitmapHandler.h"
 #include "../Client.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CAnimation.h"

+ 0 - 1
client/widgets/MiscWidgets.cpp

@@ -15,7 +15,6 @@
 #include "../gui/CGuiHandler.h"
 #include "../gui/CCursorHandler.h"
 
-#include "../CBitmapHandler.h"
 #include "../CPlayerInterface.h"
 #include "../CMessage.h"
 #include "../CGameInfo.h"

+ 0 - 1
client/windows/CHeroWindow.cpp

@@ -15,7 +15,6 @@
 #include "CKingdomInterface.h"
 #include "GUIClasses.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CMessage.h"
 #include "../CMT.h"

+ 0 - 1
client/windows/CQuestLog.cpp

@@ -12,7 +12,6 @@
 
 #include "CAdvmapInterface.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
 #include "../Graphics.h"

+ 0 - 1
client/windows/CWindowObject.cpp

@@ -23,7 +23,6 @@
 #include "../battle/CBattleInterface.h"
 #include "../battle/CBattleInterfaceClasses.h"
 
-#include "../CBitmapHandler.h"
 #include "../Graphics.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"

+ 0 - 1
client/windows/GUIClasses.cpp

@@ -17,7 +17,6 @@
 #include "CreatureCostBox.h"
 #include "InfoWindows.h"
 
-#include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
 #include "../CMessage.h"
 #include "../CMusicHandler.h"

+ 0 - 1
client/windows/InfoWindows.cpp

@@ -12,7 +12,6 @@
 
 #include "CAdvmapInterface.h"
 
-#include "../CBitmapHandler.h"
 #include "../Graphics.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"