Переглянути джерело

Separated battlefield background&hexes handling into a new class

Ivan Savenko 3 роки тому
батько
коміт
3c5858f01e

+ 2 - 0
client/CMakeLists.txt

@@ -5,6 +5,7 @@ set(client_SRCS
 		battle/CBattleAnimations.cpp
 		battle/CBattleInterfaceClasses.cpp
 		battle/CBattleInterface.cpp
+		battle/CBattleFieldController.cpp
 		battle/CBattleObstacleController.cpp
 		battle/CBattleProjectileController.cpp
 		battle/CBattleSiegeController.cpp
@@ -81,6 +82,7 @@ set(client_HEADERS
 		battle/CBattleAnimations.h
 		battle/CBattleInterfaceClasses.h
 		battle/CBattleInterface.h
+		battle/CBattleFieldController.h
 		battle/CBattleObstacleController.h
 		battle/CBattleProjectileController.h
 		battle/CBattleSiegeController.h

+ 2 - 7
client/CPlayerInterface.cpp

@@ -13,6 +13,7 @@
 
 #include "windows/CAdvmapInterface.h"
 #include "battle/CBattleInterface.h"
+#include "battle/CBattleFieldController.h"
 #include "battle/CBattleInterfaceClasses.h"
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
@@ -794,8 +795,6 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	BATTLE_EVENT_POSSIBLE_RETURN;
 
-	bool needUpdate = false;
-
 	for(auto & change : obstacles)
 	{
 		if(change.operation == BattleChanges::EOperation::ADD)
@@ -808,13 +807,9 @@ void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges>
 		}
 		else
 		{
-			needUpdate = true;
+			battleInt->fieldController->redrawBackgroundWithHexes(battleInt->activeStack);
 		}
 	}
-
-	if(needUpdate)
-		//update accessible hexes
-		battleInt->redrawBackgroundWithHexes(battleInt->activeStack);
 }
 
 void CPlayerInterface::battleCatapultAttacked(const CatapultAttack & ca)

+ 2 - 1
client/battle/CBattleAnimations.cpp

@@ -16,6 +16,7 @@
 #include "CBattleInterface.h"
 #include "CBattleProjectileController.h"
 #include "CBattleSiegeController.h"
+#include "CBattleFieldController.h"
 #include "CCreatureAnimation.h"
 
 #include "../CGameInfo.h"
@@ -1056,7 +1057,7 @@ bool CEffectAnimation::init()
 		be.y = y;
 		if(destTile.isValid())
 		{
-			Rect & tilePos = owner->bfield[destTile]->pos;
+			Rect tilePos = owner->fieldController->hexPosition(destTile);
 			if(x == -1)
 				be.x = tilePos.x + tilePos.w/2 - first->width()/2;
 			if(y == -1)

+ 614 - 0
client/battle/CBattleFieldController.cpp

@@ -0,0 +1,614 @@
+/*
+ * CBattleFieldController.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 "CBattleFieldController.h"
+#include "CBattleInterface.h"
+#include "CBattleInterfaceClasses.h"
+#include "CBattleSiegeController.h"
+#include "CBattleObstacleController.h"
+#include "../CBitmapHandler.h"
+#include "../CGameInfo.h"
+#include "../../CCallback.h"
+#include "../gui/SDL_Extensions.h"
+#include "../gui/CGuiHandler.h"
+#include "../CPlayerInterface.h"
+#include "../gui/CCursorHandler.h"
+#include "../../lib/BattleFieldHandler.h"
+#include "../../lib/CConfigHandler.h"
+#include "../../lib/CStack.h"
+#include "../../lib/spells/ISpellMechanics.h"
+
+CBattleFieldController::CBattleFieldController(CBattleInterface * owner):
+	owner(owner),
+	previouslyHoveredHex(-1),
+	currentlyHoveredHex(-1),
+	attackingHex(-1)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos.w = owner->pos.w;
+	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);
+
+
+	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);
+		}
+	}
+	else
+	{
+		std::string backgroundName = owner->siegeController->getBattleBackgroundName();
+		background = BitmapHandler::loadBitmap(backgroundName, false);
+	}
+
+	//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);
+				}
+			}
+		}
+	}
+
+	backgroundWithHexes = CSDL_Ext::newSurface(background->w, background->h, screen);
+
+
+	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
+	{
+		auto hex = std::make_shared<CClickableHex>();
+		hex->myNumber = h;
+		hex->pos = hexPosition(h);
+		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);
+	if (settings["battle"]["cellBorders"].Bool())
+	{
+		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, to, &owner->pos);
+	}
+}
+
+void CBattleFieldController::showBackgroundImageWithHexes(SDL_Surface *to)
+{
+	blitAt(backgroundWithHexes, owner->pos.x, owner->pos.y, to);
+}
+
+void CBattleFieldController::redrawBackgroundWithHexes(const CStack *activeStack)
+{
+	attackableHexes.clear();
+	if (activeStack)
+		occupyableHexes = owner->curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
+
+	auto accessibility = owner->curInt->cb->getAccesibility();
+
+	for(int i = 0; i < accessibility.size(); i++)
+		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
+
+	//prepare background graphic with hexes and shaded hexes
+	blitAt(background, 0, 0, backgroundWithHexes);
+
+	owner->obstacleController->redrawBackgroundWithHexes(backgroundWithHexes);
+
+	if (settings["battle"]["stackRange"].Bool())
+	{
+		std::vector<BattleHex> hexesToShade = occupyableHexes;
+		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);
+		}
+	}
+
+	if(settings["battle"]["cellBorders"].Bool())
+		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, backgroundWithHexes, nullptr);
+}
+
+void CBattleFieldController::showHighlightedHex(SDL_Surface *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);
+	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
+		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorder, nullptr, to, &temp_rect); //redraw border to make it light green instead of shaded
+}
+
+void CBattleFieldController::showHighlightedHexes(SDL_Surface *to)
+{
+	bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on
+	if(owner->activeStack && settings["battle"]["stackRange"].Bool())
+	{
+		std::set<BattleHex> set = owner->curInt->cb->battleGetAttackedHexes(owner->activeStack, 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->activeStack && shere->alive())
+		{
+			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
+			}
+		}
+	}
+
+	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
+
+			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->spellToCast)//hero casts spell
+				{
+					spell = SpellID(owner->spellToCast->actionSubtype).toSpell();
+					caster = owner->getActiveHero();
+				}
+				else if(owner->creatureSpellToCast >= 0 && owner->stackCanCastSpell && owner->creatureCasting)//stack casts spell
+				{
+					spell = SpellID(owner->creatureSpellToCast).toSpell();
+					caster = owner->activeStack;
+					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"
+				}
+			}
+		}
+	}
+}
+
+Rect CBattleFieldController::hexPosition(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;
+	return Rect(x, y, w, h);
+}
+
+bool CBattleFieldController::isPixelInHex(Point const & position)
+{
+	assert(cellShade);
+	return CSDL_Ext::SDL_GetPixel(cellShade, position.x, position.y) != 0;
+}
+
+BattleHex CBattleFieldController::getHoveredHex()
+{
+	for ( auto const & hex : bfield)
+		if (hex->hovered && hex->strictHovered)
+			return hex->myNumber;
+
+	return BattleHex::INVALID;
+}
+
+void CBattleFieldController::setBattleCursor(BattleHex myNumber)
+{
+	Rect hoveredHexPos = hexPosition(myNumber);
+	CCursorHandler *cursor = CCS->curh;
+
+	const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
+	const double hexMidX = hoveredHexPos.x + hoveredHexPos.w/2.0;
+	const double hexMidY = hoveredHexPos.y + hoveredHexPos.h/2.0;
+	const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
+	const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
+	const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
+
+	std::vector<int> sectorCursor; // From left to bottom left.
+	sectorCursor.push_back(8);
+	sectorCursor.push_back(9);
+	sectorCursor.push_back(10);
+	sectorCursor.push_back(11);
+	sectorCursor.push_back(12);
+	sectorCursor.push_back(7);
+
+	const bool doubleWide = owner->activeStack->doubleWide();
+	bool aboveAttackable = true, belowAttackable = true;
+
+	// Exclude directions which cannot be attacked from.
+	// Check to the left.
+	if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
+	{
+		sectorCursor[0] = -1;
+	}
+	// Check top left, top right as well as above for 2-hex creatures.
+	if (myNumber/GameConstants::BFIELD_WIDTH == 0)
+	{
+			sectorCursor[1] = -1;
+			sectorCursor[2] = -1;
+			aboveAttackable = false;
+	}
+	else
+	{
+		if (doubleWide)
+		{
+			bool attackRow[4] = {true, true, true, true};
+
+			if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
+				attackRow[0] = false;
+			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
+				attackRow[1] = false;
+			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
+				attackRow[2] = false;
+			if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
+				attackRow[3] = false;
+
+			if (!(attackRow[0] && attackRow[1]))
+				sectorCursor[1] = -1;
+			if (!(attackRow[1] && attackRow[2]))
+				aboveAttackable = false;
+			if (!(attackRow[2] && attackRow[3]))
+				sectorCursor[2] = -1;
+		}
+		else
+		{
+			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
+				sectorCursor[1] = -1;
+			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
+				sectorCursor[2] = -1;
+		}
+	}
+	// Check to the right.
+	if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
+	{
+		sectorCursor[3] = -1;
+	}
+	// Check bottom right, bottom left as well as below for 2-hex creatures.
+	if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
+	{
+		sectorCursor[4] = -1;
+		sectorCursor[5] = -1;
+		belowAttackable = false;
+	}
+	else
+	{
+		if (doubleWide)
+		{
+			bool attackRow[4] = {true, true, true, true};
+
+			if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
+				attackRow[0] = false;
+			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
+				attackRow[1] = false;
+			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
+				attackRow[2] = false;
+			if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
+				attackRow[3] = false;
+
+			if (!(attackRow[0] && attackRow[1]))
+				sectorCursor[5] = -1;
+			if (!(attackRow[1] && attackRow[2]))
+				belowAttackable = false;
+			if (!(attackRow[2] && attackRow[3]))
+				sectorCursor[4] = -1;
+		}
+		else
+		{
+			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
+				sectorCursor[4] = -1;
+			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
+				sectorCursor[5] = -1;
+		}
+	}
+
+	// Determine index from sector.
+	int cursorIndex;
+	if (doubleWide)
+	{
+		sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
+		sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
+
+		if (sector < 1.5)
+			cursorIndex = static_cast<int>(sector);
+		else if (sector >= 1.5 && sector < 2.5)
+			cursorIndex = 2;
+		else if (sector >= 2.5 && sector < 4.5)
+			cursorIndex = (int) sector + 1;
+		else if (sector >= 4.5 && sector < 5.5)
+			cursorIndex = 6;
+		else
+			cursorIndex = (int) sector + 2;
+	}
+	else
+	{
+		cursorIndex = static_cast<int>(sector);
+	}
+
+	// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
+	if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
+	{
+		logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
+		attackingHex = -1;
+		return;
+	}
+
+	// Find the closest direction attackable, starting with the right one.
+	// FIXME: Is this really how the original H3 client does it?
+	int i = 0;
+	while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
+		i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
+	int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
+	cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
+	switch (index)
+	{
+		case 0:
+			attackingHex = myNumber - 1; //left
+			break;
+		case 1:
+			attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
+			break;
+		case 2:
+			attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
+			break;
+		case 3:
+			attackingHex = myNumber + 1; //right
+			break;
+		case 4:
+			attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
+			break;
+		case 5:
+			attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
+			break;
+	}
+	BattleHex hex(attackingHex);
+	if (!hex.isValid())
+		attackingHex = -1;
+}
+
+BattleHex CBattleFieldController::fromWhichHexAttack(BattleHex myNumber)
+{
+	//TODO far too much repeating code
+	BattleHex destHex;
+	switch(CCS->curh->frame)
+	{
+	case 12: //from bottom right
+		{
+			bool doubleWide = owner->activeStack->doubleWide();
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
+				(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if (vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 7: //from bottom left
+		{
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
+			if (vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 8: //from left
+		{
+			if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::DEFENDER)
+			{
+				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack);
+				if (vstd::contains(acc, myNumber))
+					return myNumber - 1;
+				else
+					return myNumber - 2;
+			}
+			else
+			{
+				return myNumber - 1;
+			}
+			break;
+		}
+	case 9: //from top left
+		{
+			destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 10: //from top right
+		{
+			bool doubleWide = owner->activeStack->doubleWide();
+			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
+				(owner->activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 11: //from right
+		{
+			if(owner->activeStack->doubleWide() && owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				std::vector<BattleHex> acc = owner->curInt->cb->battleGetAvailableHexes(owner->activeStack);
+				if(vstd::contains(acc, myNumber))
+					return myNumber + 1;
+				else
+					return myNumber + 2;
+			}
+			else
+			{
+				return myNumber + 1;
+			}
+			break;
+		}
+	case 13: //from bottom
+		{
+			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
+			if(vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	case 14: //from top
+		{
+			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
+			if (vstd::contains(occupyableHexes, destHex))
+				return destHex;
+			else if(owner->activeStack->side == BattleSide::ATTACKER)
+			{
+				if(vstd::contains(occupyableHexes, destHex+1))
+					return destHex+1;
+			}
+			else //if we are defender
+			{
+				if(vstd::contains(occupyableHexes, destHex-1))
+					return destHex-1;
+			}
+			break;
+		}
+	}
+	return -1;
+}
+
+bool CBattleFieldController::isTileAttackable(const BattleHex & number) const
+{
+	for (auto & elem : occupyableHexes)
+	{
+		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
+			return true;
+	}
+	return false;
+}
+
+bool CBattleFieldController::stackCountOutsideHex(const BattleHex & number) const
+{
+	return stackCountOutsideHexes[number];
+}

+ 64 - 0
client/battle/CBattleFieldController.h

@@ -0,0 +1,64 @@
+/*
+ * CBattleFieldController.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/battle/BattleHex.h"
+#include "../gui/CIntObject.h"
+
+struct SDL_Surface;
+struct Rect;
+struct Point;
+
+class CClickableHex;
+class CStack;
+class CBattleInterface;
+
+class CBattleFieldController : public CIntObject
+{
+	CBattleInterface * owner;
+
+	SDL_Surface *background;
+	SDL_Surface *backgroundWithHexes;
+	SDL_Surface *cellBorders;
+	SDL_Surface *cellBorder;
+	SDL_Surface *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)
+	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
+
+public:
+	CBattleFieldController(CBattleInterface * owner);
+	~CBattleFieldController();
+
+	void showBackgroundImage(SDL_Surface *to);
+	void showBackgroundImageWithHexes(SDL_Surface *to);
+
+	void redrawBackgroundWithHexes(const CStack *activeStack);
+
+	void showHighlightedHexes(SDL_Surface *to);
+	void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder);
+
+	Rect hexPosition(BattleHex hex) const;
+	bool isPixelInHex(Point const & position);
+
+	BattleHex getHoveredHex();
+
+	void setBattleCursor(BattleHex myNumber);
+	BattleHex fromWhichHexAttack(BattleHex myNumber);
+	bool isTileAttackable(const BattleHex & number) const;
+	bool stackCountOutsideHex(const BattleHex & number) const;
+};

+ 35 - 597
client/battle/CBattleInterface.cpp

@@ -16,6 +16,7 @@
 #include "CBattleProjectileController.h"
 #include "CBattleObstacleController.h"
 #include "CBattleSiegeController.h"
+#include "CBattleFieldController.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -107,9 +108,9 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 		const CGHeroInstance *hero1, const CGHeroInstance *hero2,
 		const SDL_Rect & myRect,
 		std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
-	: background(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
-	activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
-	currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
+	: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
+	activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr),
+	stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
 	creatureSpellToCast(-1),
 	attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
 	myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
@@ -180,19 +181,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	}
 
 	//preparing menu background and terrain
-	if(!siegeController)
-	{
-		auto bfieldType = curInt->cb->battleGetBattlefieldType();
-
-		if(bfieldType == BattleField::NONE)
-		{
-			logGlobal->error("Invalid battlefield returned for current battle");
-		}
-		else
-		{
-			background = BitmapHandler::loadBitmap(bfieldType.getInfo()->graphics, false);
-		}
-	}
+	fieldController.reset( new CBattleFieldController(this));
 
 	//preparing graphics for displaying amounts of creatures
 	amountNormal = BitmapHandler::loadBitmap("CMNUMWIN.BMP");
@@ -289,58 +278,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 			defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
 	}
 
-
-	//preparing cells and hexes
-	cellBorder = BitmapHandler::loadBitmap("CCELLGRD.BMP");
-	CSDL_Ext::alphaTransform(cellBorder);
-	cellShade = BitmapHandler::loadBitmap("CCELLSHD.BMP");
-	CSDL_Ext::alphaTransform(cellShade);
-	for (int h = 0; h < GameConstants::BFIELD_SIZE; ++h)
-	{
-		auto hex = std::make_shared<CClickableHex>();
-		hex->myNumber = h;
-		hex->pos = hexPosition(h);
-		hex->accessible = true;
-		hex->myInterface = this;
-		bfield.push_back(hex);
-	}
-	//locking occupied positions on batlefield
-	for(const CStack * s : stacks)  //stacks gained at top of this function
-		if(s->initialPosition >= 0) //turrets have position < 0
-			bfield[s->getPosition()]->accessible = false;
-
-	//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);
-				}
-			}
-		}
-	}
-
-	backgroundWithHexes = CSDL_Ext::newSurface(background->w, background->h, screen);
-
 	obstacleController.reset(new CBattleObstacleController(this));
 
-	for(auto hex : bfield)
-		addChild(hex.get());
-
 	if(tacticsMode)
 		bTacticNextStack();
 
@@ -374,18 +313,11 @@ CBattleInterface::~CBattleInterface()
 	{
 		deactivate();
 	}
-	SDL_FreeSurface(background);
 	SDL_FreeSurface(menu);
 	SDL_FreeSurface(amountNormal);
 	SDL_FreeSurface(amountNegative);
 	SDL_FreeSurface(amountPositive);
 	SDL_FreeSurface(amountEffNeutral);
-	SDL_FreeSurface(cellBorders);
-	SDL_FreeSurface(backgroundWithHexes);
-
-
-	SDL_FreeSurface(cellBorder);
-	SDL_FreeSurface(cellShade);
 
 	//TODO: play AI tracks if battle was during AI turn
 	//if (!curInt->makingTurn)
@@ -404,7 +336,7 @@ void CBattleInterface::setPrintCellBorders(bool set)
 	Settings cellBorders = settings.write["battle"]["cellBorders"];
 	cellBorders->Bool() = set;
 
-	redrawBackgroundWithHexes(activeStack);
+	fieldController->redrawBackgroundWithHexes(activeStack);
 	GH.totalRedraw();
 }
 
@@ -413,7 +345,7 @@ void CBattleInterface::setPrintStackRange(bool set)
 	Settings stackRange = settings.write["battle"]["stackRange"];
 	stackRange->Bool() = set;
 
-	redrawBackgroundWithHexes(activeStack);
+	fieldController->redrawBackgroundWithHexes(activeStack);
 	GH.totalRedraw();
 }
 
@@ -445,8 +377,7 @@ void CBattleInterface::activate()
 	if (defendingHero)
 		defendingHero->activate();
 
-	for (auto hex : bfield)
-		hex->activate();
+	fieldController->activate();
 
 	if (settings["battle"]["showQueue"].Bool())
 		queue->activate();
@@ -477,8 +408,7 @@ void CBattleInterface::deactivate()
 	bWait->deactivate();
 	bDefence->deactivate();
 
-	for (auto hex : bfield)
-		hex->deactivate();
+	fieldController->deactivate();
 
 	if (attackingHero)
 		attackingHero->deactivate();
@@ -525,185 +455,9 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key)
 }
 void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 {
-	auto hexItr = std::find_if(bfield.begin(), bfield.end(), [](std::shared_ptr<CClickableHex> hex)
-	{
-		return hex->hovered && hex->strictHovered;
-	});
+	BattleHex selectedHex = fieldController->getHoveredHex();
 
-	handleHex(hexItr == bfield.end() ? -1 : (*hexItr)->myNumber, MOVE);
-}
-
-void CBattleInterface::setBattleCursor(const int myNumber)
-{
-	const CClickableHex & hoveredHex = *bfield[myNumber];
-	CCursorHandler *cursor = CCS->curh;
-
-	const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
-	const double hexMidX = hoveredHex.pos.x + hoveredHex.pos.w/2.0;
-	const double hexMidY = hoveredHex.pos.y + hoveredHex.pos.h/2.0;
-	const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
-	const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
-	const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
-
-	std::vector<int> sectorCursor; // From left to bottom left.
-	sectorCursor.push_back(8);
-	sectorCursor.push_back(9);
-	sectorCursor.push_back(10);
-	sectorCursor.push_back(11);
-	sectorCursor.push_back(12);
-	sectorCursor.push_back(7);
-
-	const bool doubleWide = activeStack->doubleWide();
-	bool aboveAttackable = true, belowAttackable = true;
-
-	// Exclude directions which cannot be attacked from.
-	// Check to the left.
-	if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
-	{
-		sectorCursor[0] = -1;
-	}
-	// Check top left, top right as well as above for 2-hex creatures.
-	if (myNumber/GameConstants::BFIELD_WIDTH == 0)
-	{
-			sectorCursor[1] = -1;
-			sectorCursor[2] = -1;
-			aboveAttackable = false;
-	}
-	else
-	{
-		if (doubleWide)
-		{
-			bool attackRow[4] = {true, true, true, true};
-
-			if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
-				attackRow[0] = false;
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				attackRow[1] = false;
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				attackRow[2] = false;
-			if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
-				attackRow[3] = false;
-
-			if (!(attackRow[0] && attackRow[1]))
-				sectorCursor[1] = -1;
-			if (!(attackRow[1] && attackRow[2]))
-				aboveAttackable = false;
-			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[2] = -1;
-		}
-		else
-		{
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[1] = -1;
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[2] = -1;
-		}
-	}
-	// Check to the right.
-	if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
-	{
-		sectorCursor[3] = -1;
-	}
-	// Check bottom right, bottom left as well as below for 2-hex creatures.
-	if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
-	{
-		sectorCursor[4] = -1;
-		sectorCursor[5] = -1;
-		belowAttackable = false;
-	}
-	else
-	{
-		if (doubleWide)
-		{
-			bool attackRow[4] = {true, true, true, true};
-
-			if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
-				attackRow[0] = false;
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				attackRow[1] = false;
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				attackRow[2] = false;
-			if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
-				attackRow[3] = false;
-
-			if (!(attackRow[0] && attackRow[1]))
-				sectorCursor[5] = -1;
-			if (!(attackRow[1] && attackRow[2]))
-				belowAttackable = false;
-			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[4] = -1;
-		}
-		else
-		{
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[4] = -1;
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[5] = -1;
-		}
-	}
-
-	// Determine index from sector.
-	int cursorIndex;
-	if (doubleWide)
-	{
-		sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
-		sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
-
-		if (sector < 1.5)
-			cursorIndex = static_cast<int>(sector);
-		else if (sector >= 1.5 && sector < 2.5)
-			cursorIndex = 2;
-		else if (sector >= 2.5 && sector < 4.5)
-			cursorIndex = (int) sector + 1;
-		else if (sector >= 4.5 && sector < 5.5)
-			cursorIndex = 6;
-		else
-			cursorIndex = (int) sector + 2;
-	}
-	else
-	{
-		cursorIndex = static_cast<int>(sector);
-	}
-
-	// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
-	if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
-	{
-		logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
-		attackingHex = -1;
-		return;
-	}
-
-	// Find the closest direction attackable, starting with the right one.
-	// FIXME: Is this really how the original H3 client does it?
-	int i = 0;
-	while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
-		i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
-	int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
-	cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
-	switch (index)
-	{
-		case 0:
-			attackingHex = myNumber - 1; //left
-			break;
-		case 1:
-			attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
-			break;
-		case 2:
-			attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
-			break;
-		case 3:
-			attackingHex = myNumber + 1; //right
-			break;
-		case 4:
-			attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
-			break;
-		case 5:
-			attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
-			break;
-	}
-	BattleHex hex(attackingHex);
-	if (!hex.isValid())
-		attackingHex = -1;
+	handleHex(selectedHex, MOVE);
 }
 
 void CBattleInterface::clickRight(tribool down, bool previousState)
@@ -949,7 +703,7 @@ void CBattleInterface::stackRemoved(uint32_t stackID)
 
 	//todo: ensure that ghost stack animation has fadeout effect
 
-	redrawBackgroundWithHexes(activeStack);
+	fieldController->redrawBackgroundWithHexes(activeStack);
 	queue->update();
 }
 
@@ -1077,17 +831,6 @@ void CBattleInterface::sendCommand(BattleAction *& command, const CStack * actor
 	}
 }
 
-bool CBattleInterface::isTileAttackable(const BattleHex & number) const
-{
-	for (auto & elem : occupyableHexes)
-	{
-		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
-			return true;
-	}
-	return false;
-}
-
-
 const CGHeroInstance * CBattleInterface::getActiveHero()
 {
 	const CStack *attacker = activeStack;
@@ -1254,7 +997,7 @@ void CBattleInterface::spellCast(const BattleSpellCast * sc)
 void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
 {
 	if(activeStack != nullptr)
-		redrawBackgroundWithHexes(activeStack);
+		fieldController->redrawBackgroundWithHexes(activeStack);
 }
 
 void CBattleInterface::setHeroAnimation(ui8 side, int phase)
@@ -1491,7 +1234,7 @@ void CBattleInterface::activateStack()
 		return;
 
 	queue->update();
-	redrawBackgroundWithHexes(activeStack);
+	fieldController->redrawBackgroundWithHexes(activeStack);
 
 	//set casting flag to true if creature can use it to not check it every time
 	const auto spellcaster = s->getBonusLocalFirst(Selector::type()(Bonus::SPELLCASTER)),
@@ -1691,7 +1434,7 @@ void CBattleInterface::endAction(const BattleAction* action)
 		bTacticNextStack(stack);
 
 	if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
-		redrawBackgroundWithHexes(activeStack);
+		fieldController->redrawBackgroundWithHexes(activeStack);
 
 	if (activeStack && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
 	{
@@ -1989,10 +1732,10 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			{
 				if(curInt->cb->battleCanAttack(activeStack, shere, myNumber))
 				{
-					if (isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
+					if (fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
 					{
-						setBattleCursor(myNumber); // temporary - needed for following function :(
-						BattleHex attackFromHex = fromWhichHexAttack(myNumber);
+						fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
+						BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
 
 						if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
 							legalAction = true;
@@ -2154,14 +1897,14 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 			case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
 				{
-					setBattleCursor(myNumber); //handle direction of cursor and attackable tile
+					fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
 					setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
 
 					bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
 
 					realizeAction = [=]()
 					{
-						BattleHex attackFromHex = fromWhichHexAttack(myNumber);
+						BattleHex attackFromHex = fieldController->fromWhichHexAttack(myNumber);
 						if(attackFromHex.isValid()) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
 						{
 							auto command = new BattleAction(BattleAction::makeMeleeAttack(activeStack, myNumber, attackFromHex, returnAfterAttack));
@@ -2426,163 +2169,6 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack
 	return isCastingPossible;
 }
 
-BattleHex CBattleInterface::fromWhichHexAttack(BattleHex myNumber)
-{
-	//TODO far too much repeating code
-	BattleHex destHex;
-	switch(CCS->curh->frame)
-	{
-	case 12: //from bottom right
-		{
-			bool doubleWide = activeStack->doubleWide();
-			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
-				(activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if (vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //if we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	case 7: //from bottom left
-		{
-			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
-			if (vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if(vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	case 8: //from left
-		{
-			if(activeStack->doubleWide() && activeStack->side == BattleSide::DEFENDER)
-			{
-				std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack);
-				if (vstd::contains(acc, myNumber))
-					return myNumber - 1;
-				else
-					return myNumber - 2;
-			}
-			else
-			{
-				return myNumber - 1;
-			}
-			break;
-		}
-	case 9: //from top left
-		{
-			destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if(vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //if we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	case 10: //from top right
-		{
-			bool doubleWide = activeStack->doubleWide();
-			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
-				(activeStack->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if(vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //if we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	case 11: //from right
-		{
-			if(activeStack->doubleWide() && activeStack->side == BattleSide::ATTACKER)
-			{
-				std::vector<BattleHex> acc = curInt->cb->battleGetAvailableHexes(activeStack);
-				if(vstd::contains(acc, myNumber))
-					return myNumber + 1;
-				else
-					return myNumber + 2;
-			}
-			else
-			{
-				return myNumber + 1;
-			}
-			break;
-		}
-	case 13: //from bottom
-		{
-			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if(vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //if we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	case 14: //from top
-		{
-			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
-			if (vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(activeStack->side == BattleSide::ATTACKER)
-			{
-				if(vstd::contains(occupyableHexes, destHex+1))
-					return destHex+1;
-			}
-			else //if we are defender
-			{
-				if(vstd::contains(occupyableHexes, destHex-1))
-					return destHex-1;
-			}
-			break;
-		}
-	}
-	return -1;
-}
-
-Rect CBattleInterface::hexPosition(BattleHex hex) const
-{
-	int x = 14 + ((hex.getY())%2==0 ? 22 : 0) + 44*hex.getX() + pos.x;
-	int y = 86 + 42 *hex.getY() + pos.y;
-	int w = cellShade->w;
-	int h = cellShade->h;
-	return Rect(x, y, w, h);
-}
-
 void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
 {
 	obstacleController->obstaclePlaced(oi);
@@ -2662,7 +2248,20 @@ void CBattleInterface::show(SDL_Surface *to)
 
 	++animCount;
 
-	showBackground(to);
+	if (activeStack != nullptr && creAnims[activeStack->ID]->isIdle()) //show everything with range
+	{
+		// FIXME: any *real* reason to keep this separate? Speed difference can't be that big
+		fieldController->showBackgroundImageWithHexes(to);
+	}
+	else
+	{
+		fieldController->showBackgroundImage(to);
+		obstacleController->showAbsoluteObstacles(to);
+		if ( siegeController )
+			siegeController->showAbsoluteObstacles(to);
+	}
+	fieldController->showHighlightedHexes(to);
+
 	showBattlefieldObjects(to);
 	projectilesController->showProjectiles(to);
 
@@ -2684,126 +2283,6 @@ void CBattleInterface::show(SDL_Surface *to)
 	}
 }
 
-void CBattleInterface::showBackground(SDL_Surface *to)
-{
-	if (activeStack != nullptr && creAnims[activeStack->ID]->isIdle()) //show everything with range
-	{
-		// FIXME: any *real* reason to keep this separate? Speed difference can't be that big
-		blitAt(backgroundWithHexes, pos.x, pos.y, to);
-	}
-	else
-	{
-		showBackgroundImage(to);
-		obstacleController->showAbsoluteObstacles(to);
-		if ( siegeController )
-			siegeController->showAbsoluteObstacles(to);
-	}
-	showHighlightedHexes(to);
-}
-
-void CBattleInterface::showBackgroundImage(SDL_Surface *to)
-{
-	blitAt(background, pos.x, pos.y, to);
-	if (settings["battle"]["cellBorders"].Bool())
-	{
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, to, &pos);
-	}
-}
-
-
-void CBattleInterface::showHighlightedHexes(SDL_Surface *to)
-{
-	bool delayedBlit = false; //workaround for blitting enemy stack hex without mouse shadow with stack range on
-	if(activeStack && settings["battle"]["stackRange"].Bool())
-	{
-		std::set<BattleHex> set = curInt->cb->battleGetAttackedHexes(activeStack, currentlyHoveredHex, attackingHex);
-		for(BattleHex hex : set)
-			if(hex != currentlyHoveredHex)
-				showHighlightedHex(to, hex);
-
-		// display the movement shadow of the stack at b (i.e. stack under mouse)
-		const CStack * const shere = curInt->cb->battleGetStackByPos(currentlyHoveredHex, false);
-		if(shere && shere != activeStack && shere->alive())
-		{
-			std::vector<BattleHex> v = curInt->cb->battleGetAvailableHexes(shere, true, nullptr);
-			for(BattleHex hex : v)
-			{
-				if(hex != currentlyHoveredHex)
-					showHighlightedHex(to, hex);
-				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
-			}
-		}
-	}
-
-	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
-
-			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(spellToCast)//hero casts spell
-				{
-					spell = SpellID(spellToCast->actionSubtype).toSpell();
-					caster = getActiveHero();
-				}
-				else if(creatureSpellToCast >= 0 && stackCanCastSpell && creatureCasting)//stack casts spell
-				{
-					spell = SpellID(creatureSpellToCast).toSpell();
-					caster = activeStack;
-					mode = spells::Mode::CREATURE_ACTIVE;
-				}
-
-				if(caster && spell) //when casting spell
-				{
-					// printing shaded hex(es)
-					spells::BattleCast event(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(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"
-				}
-			}
-		}
-	}
-}
-
-void CBattleInterface::showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder)
-{
-	int x = 14 + (hex.getY() % 2 == 0 ? 22 : 0) + 44 *(hex.getX()) + pos.x;
-	int y = 86 + 42 *hex.getY() + pos.y;
-	SDL_Rect temp_rect = genRect (cellShade->h, cellShade->w, x, y);
-	CSDL_Ext::blit8bppAlphaTo24bpp (cellShade, nullptr, to, &temp_rect);
-	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorder, nullptr, to, &temp_rect); //redraw border to make it light green instead of shaded
-}
-
 void CBattleInterface::showBattlefieldObjects(SDL_Surface *to)
 {
 	auto showHexEntry = [&](BattleObjectsByHex::HexData & hex)
@@ -2915,7 +2394,7 @@ void CBattleInterface::showAliveStacks(SDL_Surface *to, std::vector<const CStack
 			const int reverseSideShift = stack->side == BattleSide::ATTACKER ? -1 : 1;
 			const BattleHex nextPos = stack->getPosition() + sideShift;
 			const bool edge = stack->getPosition() % GameConstants::BFIELD_WIDTH == (stack->side == BattleSide::ATTACKER ? GameConstants::BFIELD_WIDTH - 2 : 1);
-			const bool moveInside = !edge && !stackCountOutsideHexes[nextPos];
+			const bool moveInside = !edge && !fieldController->stackCountOutsideHex(nextPos);
 			int xAdd = (stack->side == BattleSide::ATTACKER ? 220 : 202) +
 					   (stack->doubleWide() ? 44 : 0) * sideShift +
 					   (moveInside ? amountNormal->w + 10 : 0) * reverseSideShift;
@@ -3120,44 +2599,3 @@ void CBattleInterface::updateBattleAnimations()
 		animsAreDisplayed.setn(false);
 	}
 }
-
-void CBattleInterface::redrawBackgroundWithHexes(const CStack *activeStack)
-{
-	attackableHexes.clear();
-	if (activeStack)
-		occupyableHexes = curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
-
-	auto fillStackCountOutsideHexes = [&]()
-	{
-		auto accessibility = curInt->cb->getAccesibility();
-
-		for(int i = 0; i < accessibility.size(); i++)
-			stackCountOutsideHexes.at(i) = (accessibility[i] == EAccessibility::ACCESSIBLE);
-	};
-
-	fillStackCountOutsideHexes();
-
-	//prepare background graphic with hexes and shaded hexes
-	blitAt(background, 0, 0, backgroundWithHexes);
-
-	obstacleController->redrawBackgroundWithHexes();
-
-	if (settings["battle"]["stackRange"].Bool())
-	{
-		std::vector<BattleHex> hexesToShade = occupyableHexes;
-		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);
-		}
-	}
-
-	if(settings["battle"]["cellBorders"].Bool())
-		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, nullptr, backgroundWithHexes, nullptr);
-}
-

+ 4 - 22
client/battle/CBattleInterface.h

@@ -61,6 +61,7 @@ class IImage;
 class CBattleProjectileController;
 class CBattleSiegeController;
 class CBattleObstacleController;
+class CBattleFieldController;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -117,7 +118,7 @@ enum class MouseHoveredHexContext
 class CBattleInterface : public WindowBase
 {
 private:
-	SDL_Surface *background, *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral, *cellBorders, *backgroundWithHexes;
+	SDL_Surface *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral;
 
 	std::shared_ptr<CButton> bOptions;
 	std::shared_ptr<CButton> bSurrender;
@@ -147,12 +148,6 @@ private:
 	const CStack *stackToActivate; //when animation is playing, we should wait till the end to make the next stack active; nullptr of none
 	const CStack *selectedStack; //for Teleport / Sacrifice
 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
-	std::vector<BattleHex> occupyableHexes, //hexes available for active stack
-		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
-	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)
-	int attackingHex; //hex from which the stack would perform attack with current cursor
 
 	std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
 	bool tacticsMode;
@@ -186,8 +181,6 @@ private:
 	void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1);
 	void sendCommand(BattleAction *& command, const CStack * actor = nullptr);
 
-	bool isTileAttackable(const BattleHex & number) const; //returns true if tile 'number' is neighboring any tile from active stack's range or is one of these tiles
-
 	std::list<BattleEffect> battleEffects; //different animations to display on the screen like spell effects
 
 	std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
@@ -195,11 +188,6 @@ private:
 	const CGHeroInstance *getActiveHero(); //returns hero that can currently cast a spell
 
 	/** Methods for displaying battle screen */
-	void showBackground(SDL_Surface *to);
-
-	void showBackgroundImage(SDL_Surface *to);
-	void showHighlightedHexes(SDL_Surface *to);
-	void showHighlightedHex(SDL_Surface *to, BattleHex hex, bool darkBorder = false);
 	void showInterface(SDL_Surface *to);
 
 	void showBattlefieldObjects(SDL_Surface *to);
@@ -212,7 +200,6 @@ private:
 	BattleObjectsByHex sortObjectsByHex();
 	void updateBattleAnimations();
 
-	void redrawBackgroundWithHexes(const CStack *activeStack);
 	/** End of battle screen blitting methods */
 
 	void setHeroAnimation(ui8 side, int phase);
@@ -220,6 +207,7 @@ public:
 	std::unique_ptr<CBattleProjectileController> projectilesController;
 	std::unique_ptr<CBattleSiegeController> siegeController;
 	std::unique_ptr<CBattleObstacleController> obstacleController;
+	std::unique_ptr<CBattleFieldController> fieldController;
 
 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
@@ -241,9 +229,6 @@ public:
 	CPlayerInterface *getCurrentPlayerInterface() const;
 	bool shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex);
 
-	std::vector<std::shared_ptr<CClickableHex>> bfield; //11 lines, 17 hexes on each
-	SDL_Surface *cellBorder, *cellShade;
-
 	bool myTurn; //if true, interface is active (commands can be ordered)
 
 	bool moveStarted; //if true, the creature that is already moving is going to make its first step
@@ -310,18 +295,14 @@ public:
 	void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
 
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
-	void setBattleCursor(const int myNumber); //really complex and messy, sets attackingHex
 	void endAction(const BattleAction* action);
 	void hideQueue();
 	void showQueue();
 
-	Rect hexPosition(BattleHex hex) const;
-
 	void handleHex(BattleHex myNumber, int eventType);
 	bool isCastingPossibleHere (const CStack *sactive, const CStack *shere, BattleHex myNumber);
 	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber); //TODO: move to BattleState / callback
 
-	BattleHex fromWhichHexAttack(BattleHex myNumber);
 	void obstaclePlaced(const CObstacleInstance & oi);
 
 	void gateStateChanged(const EGateState state);
@@ -350,4 +331,5 @@ public:
 	friend class CBattleProjectileController;
 	friend class CBattleSiegeController;
 	friend class CBattleObstacleController;
+	friend class CBattleFieldController;
 };

+ 8 - 17
client/battle/CBattleInterfaceClasses.cpp

@@ -12,6 +12,7 @@
 
 #include "CBattleInterface.h"
 #include "CBattleSiegeController.h"
+#include "CBattleFieldController.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -206,11 +207,11 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
 
 	if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
 	{
-		for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
-		{
-			if(myOwner->bfield[it]->hovered && myOwner->bfield[it]->strictHovered)
-				return;
-		}
+		BattleHex hoveredHex = myOwner->fieldController->getHoveredHex();
+		//do nothing when any hex is hovered - hero's animation overlaps battlefield
+		if ( hoveredHex != BattleHex::INVALID )
+			return;
+
 		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
 
 		GH.pushIntT<CSpellWindow>(myHero, myOwner->getCurrentPlayerInterface());
@@ -631,24 +632,14 @@ void CClickableHex::hover(bool on)
 	}
 }
 
-CClickableHex::CClickableHex() : setAlterText(false), myNumber(-1), accessible(true), strictHovered(false), myInterface(nullptr)
+CClickableHex::CClickableHex() : setAlterText(false), myNumber(-1), strictHovered(false), myInterface(nullptr)
 {
 	addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
 }
 
 void CClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 {
-	if(myInterface->cellShade)
-	{
-		if(CSDL_Ext::SDL_GetPixel(myInterface->cellShade, sEvent.x-pos.x, sEvent.y-pos.y) == 0) //hovered pixel is outside hex
-		{
-			strictHovered = false;
-		}
-		else //hovered pixel is inside hex
-		{
-			strictHovered = true;
-		}
-	}
+	strictHovered = myInterface->fieldController->isPixelInHex(Point(sEvent.x-pos.x, sEvent.y-pos.y));
 
 	if(hovered && strictHovered) //print attacked creature to console
 	{

+ 1 - 1
client/battle/CBattleInterfaceClasses.h

@@ -138,7 +138,7 @@ private:
 	bool setAlterText; //if true, this hex has set alternative text in console and will clean it
 public:
 	ui32 myNumber; //number of hex in commonly used format
-	bool accessible; //if true, this hex is accessible for units
+	//bool accessible; //if true, this hex is accessible for units
 	//CStack * ourStack;
 	bool strictHovered; //for determining if hex is hovered by mouse (this is different problem than hex's graphic hovering)
 	CBattleInterface * myInterface; //interface that owns me

+ 4 - 3
client/battle/CBattleObstacleController.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 #include "CBattleObstacleController.h"
 #include "CBattleInterface.h"
+#include "CBattleFieldController.h"
 #include "../CPlayerInterface.h"
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
@@ -194,13 +195,13 @@ Point CBattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> ima
 {
 	int offset = obstacle.getAnimationYOffset(image->height());
 
-	Rect r = owner->hexPosition(obstacle.pos);
+	Rect r = owner->fieldController->hexPosition(obstacle.pos);
 	r.y += 42 - image->height() + offset;
 
 	return r.topLeft();
 }
 
-void CBattleObstacleController::redrawBackgroundWithHexes()
+void CBattleObstacleController::redrawBackgroundWithHexes(SDL_Surface * to)
 {
 	//draw absolute obstacles (cliffs and so on)
 	for(auto & oi : owner->curInt->cb->battleGetAllObstacles())
@@ -209,7 +210,7 @@ void CBattleObstacleController::redrawBackgroundWithHexes()
 		{
 			auto img = getObstacleImage(*oi);
 			if(img)
-				img->draw(owner->backgroundWithHexes, oi->getInfo().width, oi->getInfo().height);
+				img->draw(to, oi->getInfo().width, oi->getInfo().height);
 		}
 	}
 }

+ 1 - 1
client/battle/CBattleObstacleController.h

@@ -37,5 +37,5 @@ public:
 
 	Point getObstaclePosition(std::shared_ptr<IImage> image, const CObstacleInstance & obstacle);
 
-	void redrawBackgroundWithHexes();
+	void redrawBackgroundWithHexes(SDL_Surface * to);
 };

+ 25 - 21
client/battle/CBattleSiegeController.cpp

@@ -30,6 +30,9 @@ CBattleSiegeController::~CBattleSiegeController()
 		if (g != EWallVisual::GATE || (gateState != EGateState::NONE && gateState != EGateState::CLOSED && gateState != EGateState::BLOCKED))
 			SDL_FreeSurface(walls[g]);
 	}
+
+	SDL_FreeSurface(moatSurface);
+	SDL_FreeSurface(mlipSurface);
 }
 
 std::string CBattleSiegeController::getSiegeName(ui16 what) const
@@ -144,31 +147,19 @@ void CBattleSiegeController::printPartOfWall(SDL_Surface *to, int what)
 	}
 }
 
+std::string CBattleSiegeController::getBattleBackgroundName()
+{
+	return getSiegeName(0);
+}
 
 CBattleSiegeController::CBattleSiegeController(CBattleInterface * owner, const CGTownInstance *siegeTown):
 	owner(owner),
 	town(siegeTown)
 {
-	owner->background = BitmapHandler::loadBitmap( getSiegeName(0), false );
-	ui8 siegeLevel = owner->curInt->cb->battleGetSiegeLevel();
-	if (siegeLevel >= 2) //citadel or castle
-	{
-		//print moat/mlip
-		SDL_Surface *moat = BitmapHandler::loadBitmap( getSiegeName(13) ),
-			* mlip = BitmapHandler::loadBitmap( getSiegeName(14) );
+	assert(owner->fieldController.get() == nullptr); // must be created after this
 
-		auto & info = town->town->clientInfo;
-		Point moatPos(info.siegePositions[13].x, info.siegePositions[13].y);
-		Point mlipPos(info.siegePositions[14].x, info.siegePositions[14].y);
-
-		if (moat) //eg. tower has no moat
-			blitAt(moat, moatPos.x,moatPos.y, owner->background);
-		if (mlip) //eg. tower has no mlip
-			blitAt(mlip, mlipPos.x, mlipPos.y, owner->background);
-
-		SDL_FreeSurface(moat);
-		SDL_FreeSurface(mlip);
-	}
+	moatSurface = BitmapHandler::loadBitmap( getSiegeName(13) );
+	mlipSurface = BitmapHandler::loadBitmap( getSiegeName(14) );
 
 	for (int g = 0; g < ARRAY_COUNT(walls); ++g)
 	{
@@ -247,13 +238,26 @@ void CBattleSiegeController::gateStateChanged(const EGateState state)
 
 void CBattleSiegeController::showAbsoluteObstacles(SDL_Surface * to)
 {
-	if (town->hasBuilt(BuildingID::CITADEL))
+	ui8 siegeLevel = owner->curInt->cb->battleGetSiegeLevel();
+	if (siegeLevel >= 2) //citadel or castle
+	{
+		//print moat/mlip
+
+		auto & info = town->town->clientInfo;
+		Point moatPos(info.siegePositions[13].x, info.siegePositions[13].y);
+		Point mlipPos(info.siegePositions[14].x, info.siegePositions[14].y);
+
+		if (moatSurface) //eg. tower has no moat
+			blitAt(moatSurface, moatPos.x + owner->pos.x, moatPos.y  + owner->pos.y, to);
+		if (mlipSurface) //eg. tower has no mlip
+			blitAt(mlipSurface, mlipPos.x + owner->pos.x, mlipPos.y + owner->pos.y, to);
+
 		printPartOfWall(to, EWallVisual::BACKGROUND_MOAT);
+	}
 }
 
 void CBattleSiegeController::sortObjectsByHex(BattleObjectsByHex & sorted)
 {
-
 	sorted.beforeAll.walls.push_back(EWallVisual::BACKGROUND_WALL);
 	sorted.hex[135].walls.push_back(EWallVisual::KEEP);
 	sorted.afterAll.walls.push_back(EWallVisual::BOTTOM_TOWER);

+ 4 - 0
client/battle/CBattleSiegeController.h

@@ -49,6 +49,9 @@ class CBattleSiegeController
 {
 	CBattleInterface * owner;
 
+	SDL_Surface *moatSurface;
+	SDL_Surface *mlipSurface;
+
 	SDL_Surface* walls[18];
 	const CGTownInstance *town; //besieged town
 
@@ -63,6 +66,7 @@ public:
 
 	void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces);
 
+	std::string getBattleBackgroundName();
 	const CCreature *turretCreature();
 	Point turretCreaturePosition( BattleHex position );