Browse Source

Refactoring of setBattleCursor & fromWhichHexAttack methods

Ivan Savenko 2 years ago
parent
commit
a57eec23e6

+ 1 - 2
client/battle/BattleActionsController.cpp

@@ -298,7 +298,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 				{
 					if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
 					{
-						owner.fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
 						BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
 
 						if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
@@ -461,7 +460,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			case PossiblePlayerBattleAction::WALK_AND_ATTACK:
 			case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
 				{
-					owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
+					owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor
 					setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
 
 					bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;

+ 151 - 302
client/battle/BattleFieldController.cpp

@@ -34,8 +34,7 @@
 #include "../../lib/spells/ISpellMechanics.h"
 
 BattleFieldController::BattleFieldController(BattleInterface & owner):
-	owner(owner),
-	attackingHex(BattleHex::INVALID)
+	owner(owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	pos.w = owner.pos.w;
@@ -239,35 +238,43 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
 std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
 {
 	const CStack * stack = owner.stacksController->getActiveStack();
-	std::set<BattleHex> result;
 	auto hoveredHex = getHoveredHex();
 
 	if (stack)
 	{
 		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(stack, false, nullptr);
 
+		auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+		if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
+		{
+			if (isTileAttackable(hoveredHex))
+			{
+				BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
+
+				if (stack->doubleWide())
+					return {attackFromHex, stack->occupiedHex(attackFromHex)};
+				else
+					return {attackFromHex};
+			}
+		}
+
 		if (vstd::contains(v,hoveredHex))
 		{
-			result.insert(hoveredHex);
 			if (stack->doubleWide())
-				result.insert(stack->occupiedHex(hoveredHex));
+				return {hoveredHex, stack->occupiedHex(hoveredHex)};
+			else
+				return {hoveredHex};
 		}
-		else
+		if (stack->doubleWide())
 		{
-			if (stack->doubleWide())
+			for (auto const & hex : v)
 			{
-				for (auto const & hex : v)
-				{
-					if (stack->occupiedHex(hex) == hoveredHex)
-					{
-						result.insert(hoveredHex);
-						result.insert(hex);
-					}
-				}
+				if (stack->occupiedHex(hex) == hoveredHex)
+					return { hoveredHex, hex };
 			}
 		}
 	}
-	return result;
+	return {};
 }
 
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
@@ -330,323 +337,165 @@ BattleHex BattleFieldController::getHoveredHex()
 
 void BattleFieldController::setBattleCursor(BattleHex myNumber)
 {
-	Rect hoveredHexPos = hexPositionAbsolute(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->position().y, cursor->position().y - 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<Cursor::Combat> sectorCursor; // From left to bottom left.
-	sectorCursor.push_back(Cursor::Combat::HIT_EAST);
-	sectorCursor.push_back(Cursor::Combat::HIT_SOUTHEAST);
-	sectorCursor.push_back(Cursor::Combat::HIT_SOUTHWEST);
-	sectorCursor.push_back(Cursor::Combat::HIT_WEST);
-	sectorCursor.push_back(Cursor::Combat::HIT_NORTHWEST);
-	sectorCursor.push_back(Cursor::Combat::HIT_NORTHEAST);
+	Point cursorPos = CCS->curh->position();
+
+	std::vector<Cursor::Combat> sectorCursor = {
+		Cursor::Combat::HIT_SOUTHEAST,
+		Cursor::Combat::HIT_SOUTHWEST,
+		Cursor::Combat::HIT_WEST,
+		Cursor::Combat::HIT_NORTHWEST,
+		Cursor::Combat::HIT_NORTHEAST,
+		Cursor::Combat::HIT_EAST,
+		Cursor::Combat::HIT_SOUTH,
+		Cursor::Combat::HIT_NORTH,
+	};
+
+	auto direction = static_cast<size_t>(selectAttackDirection(myNumber, cursorPos));
+
+	assert(direction != -1);
+	if (direction != -1)
+		CCS->curh->set(sectorCursor[direction]);
+}
 
+BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber, const Point & cursorPos)
+{
 	const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
-	bool aboveAttackable = true, belowAttackable = true;
+	auto neighbours = myNumber.allNeighbouringTiles();
+	//   0 1
+	//  5 x 2
+	//   4 3
 
-	// Exclude directions which cannot be attacked from.
-	// Check to the left.
-	if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
-	{
-		sectorCursor[0] = Cursor::Combat::INVALID;
-	}
-	// Check top left, top right as well as above for 2-hex creatures.
-	if (myNumber/GameConstants::BFIELD_WIDTH == 0)
-	{
-			sectorCursor[1] = Cursor::Combat::INVALID;
-			sectorCursor[2] = Cursor::Combat::INVALID;
-			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] = Cursor::Combat::INVALID;
-			if (!(attackRow[1] && attackRow[2]))
-				aboveAttackable = false;
-			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[2] = Cursor::Combat::INVALID;
-		}
-		else
-		{
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[1] = Cursor::Combat::INVALID;
-			if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[2] = Cursor::Combat::INVALID;
-		}
-	}
-	// Check to the right.
-	if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
-	{
-		sectorCursor[3] = Cursor::Combat::INVALID;
-	}
-	// Check bottom right, bottom left as well as below for 2-hex creatures.
-	if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
-	{
-		sectorCursor[4] = Cursor::Combat::INVALID;
-		sectorCursor[5] = Cursor::Combat::INVALID;
-		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] = Cursor::Combat::INVALID;
-			if (!(attackRow[1] && attackRow[2]))
-				belowAttackable = false;
-			if (!(attackRow[2] && attackRow[3]))
-				sectorCursor[4] = Cursor::Combat::INVALID;
-		}
-		else
-		{
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
-				sectorCursor[4] = Cursor::Combat::INVALID;
-			if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
-				sectorCursor[5] = Cursor::Combat::INVALID;
-		}
-	}
+	// if true - our current stack can move into this hex (and attack)
+	std::array<bool, 8> attackAvailability;
 
-	// Determine index from sector.
-	int cursorIndex;
 	if (doubleWide)
 	{
-		sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? Cursor::Combat::HIT_NORTH : Cursor::Combat::INVALID);
-		sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? Cursor::Combat::HIT_SOUTH : Cursor::Combat::INVALID);
-
-		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;
+		// For double-hexes we need to ensure that both hexes needed for this direction are occupyable:
+		// |    -0-   |   -1-    |    -2-   |   -3-    |    -4-   |   -5-    |    -6-   |   -7-
+		// |  o o -   |   - o o  |    - -   |   - -    |    - -   |   - -    |    o o   |   - -
+		// |   - x -  |  - x -   |   - x o o|  - x -   |   - x -  |o o x -   |   - x -  |  - x -
+		// |    - -   |   - -    |    - -   |   - o o  |  o o -   |   - -    |    - -   |   o o
+
+		for (size_t i : { 1, 2, 3})
+			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
+
+		for (size_t i : { 4, 5, 0})
+			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
+
+		attackAvailability[6] = vstd::contains(occupyableHexes, neighbours[0]) && vstd::contains(occupyableHexes, neighbours[1]);
+		attackAvailability[7] = vstd::contains(occupyableHexes, neighbours[3]) && vstd::contains(occupyableHexes, neighbours[4]);
 	}
 	else
 	{
-		cursorIndex = static_cast<int>(sector);
-	}
+		for (size_t i = 0; i < 6; ++i)
+			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]);
 
-	// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
-	if (!vstd::contains_if (sectorCursor, [](Cursor::Combat sc) { return sc != Cursor::Combat::INVALID; }))
-	{
-		logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
-		attackingHex = -1;
-		return;
+		attackAvailability[6] = false;
+		attackAvailability[7] = false;
 	}
 
-	// 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()] == Cursor::Combat::INVALID) //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->set(sectorCursor[index]);
-	switch (index)
+	// Zero available tiles to attack from
+	if ( vstd::find(attackAvailability, true) == attackAvailability.end())
 	{
-		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;
+		logGlobal->error("Error: cannot find a hex to attack hex %d from!", myNumber);
+		return BattleHex::NONE;
 	}
-	BattleHex hex(attackingHex);
-	if (!hex.isValid())
-		attackingHex = -1;
+
+	// For each valid direction, select position to test against
+	std::array<Point, 8> testPoint;
+
+	for (size_t i = 0; i < 6; ++i)
+		if (attackAvailability[i])
+			testPoint[i] = hexPositionAbsolute(neighbours[i]).center();
+
+	// For bottom/top directions select central point, but move it a bit away from true center to reduce zones allocated to them
+	if (attackAvailability[6])
+		testPoint[6] = (hexPositionAbsolute(neighbours[0]).center() + hexPositionAbsolute(neighbours[1]).center()) / 2 + Point(0, -5);
+
+	if (attackAvailability[7])
+		testPoint[7] = (hexPositionAbsolute(neighbours[3]).center() + hexPositionAbsolute(neighbours[4]).center()) / 2 + Point(0,  5);
+
+	// Compute distance between tested position & cursor position and pick nearest
+	std::array<int, 8> distance2;
+
+	for (size_t i = 0; i < 8; ++i)
+		if (attackAvailability[i])
+			distance2[i] = (testPoint[i].y - cursorPos.y)*(testPoint[i].y - cursorPos.y) + (testPoint[i].x - cursorPos.x)*(testPoint[i].x - cursorPos.x);
+
+	size_t nearest = -1;
+	for (size_t i = 0; i < 8; ++i)
+		if (attackAvailability[i] && (nearest == -1 || distance2[i] < distance2[nearest]) )
+			nearest = i;
+
+	assert(nearest != -1);
+	return BattleHex::EDir(nearest);
 }
 
-BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
+BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 {
-	//TODO far too much repeating code
-	BattleHex destHex;
-	switch(CCS->curh->get<Cursor::Combat>())
+	BattleHex::EDir direction = selectAttackDirection(attackTarget, CCS->curh->position());
+
+	const CStack * attacker = owner.stacksController->getActiveStack();
+
+	assert(direction != BattleHex::NONE);
+	assert(attacker);
+
+	if (!attacker->doubleWide())
 	{
-	case Cursor::Combat::HIT_NORTHWEST: //from bottom right
-		{
-			bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
-			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
-				(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(owner.stacksController->getActiveStack()->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 Cursor::Combat::HIT_NORTHEAST: //from bottom left
+		assert(direction != BattleHex::BOTTOM);
+		assert(direction != BattleHex::TOP);
+		return attackTarget.cloneInDirection(direction);
+	}
+	else
+	{
+		// We need to find position of right hex of double-hex creature (or left for defending side)
+		// | TOP_LEFT |TOP_RIGHT |   RIGHT  |BOTTOM_RIGHT|BOTTOM_LEFT|  LEFT    |    TOP   |BOTTOM
+		// |  o o -   |   - o o  |    - -   |   - -      |    - -    |   - -    |    o o   |   - -
+		// |   - x -  |  - x -   |   - x o o|  - x -     |   - x -   |o o x -   |   - x -  |  - x -
+		// |    - -   |   - -    |    - -   |   - o o    |  o o -    |   - -    |    - -   |   o o
+
+		switch (direction)
 		{
-			destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
-			if (vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(owner.stacksController->getActiveStack()->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 Cursor::Combat::HIT_EAST: //from left
+		case BattleHex::TOP_LEFT:
+		case BattleHex::LEFT:
+		case BattleHex::BOTTOM_LEFT:
 		{
-			if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::DEFENDER)
-			{
-				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
-				if (vstd::contains(acc, myNumber))
-					return myNumber - 1;
-				else
-					return myNumber - 2;
-			}
+			if ( attacker->side == BattleSide::ATTACKER )
+				return attackTarget.cloneInDirection(direction);
 			else
-			{
-				return myNumber - 1;
-			}
-			break;
-		}
-	case Cursor::Combat::HIT_SOUTHEAST: //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.stacksController->getActiveStack()->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 attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::LEFT);
 		}
-	case Cursor::Combat::HIT_SOUTHWEST: //from top right
+
+		case BattleHex::TOP_RIGHT:
+		case BattleHex::RIGHT:
+		case BattleHex::BOTTOM_RIGHT:
 		{
-			bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
-			destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
-				(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
-			if(vstd::contains(occupyableHexes, destHex))
-				return destHex;
-			else if(owner.stacksController->getActiveStack()->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;
+			if ( attacker->side == BattleSide::ATTACKER )
+				return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::RIGHT);
+			else
+				return attackTarget.cloneInDirection(direction);
 		}
-	case Cursor::Combat::HIT_WEST: //from right
+
+		case BattleHex::TOP:
 		{
-			if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
-			{
-				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
-				if(vstd::contains(acc, myNumber))
-					return myNumber + 1;
-				else
-					return myNumber + 2;
-			}
+			if ( attacker->side == BattleSide::ATTACKER )
+				return attackTarget.cloneInDirection(BattleHex::TOP_RIGHT);
 			else
-			{
-				return myNumber + 1;
-			}
-			break;
+				return attackTarget.cloneInDirection(BattleHex::TOP_LEFT);
 		}
-	case Cursor::Combat::HIT_NORTH: //from bottom
+
+		case BattleHex::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.stacksController->getActiveStack()->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;
+			if ( attacker->side == BattleSide::ATTACKER )
+				return attackTarget.cloneInDirection(BattleHex::BOTTOM_RIGHT);
+			else
+				return attackTarget.cloneInDirection(BattleHex::BOTTOM_LEFT);
 		}
-	case Cursor::Combat::HIT_SOUTH: //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.stacksController->getActiveStack()->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;
+		default:
+			assert(0);
+			return attackTarget.cloneInDirection(BattleHex::LEFT);
 		}
 	}
-	return BattleHex::INVALID;
 }
 
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const

+ 1 - 0
client/battle/BattleFieldController.h

@@ -63,6 +63,7 @@ class BattleFieldController : public CIntObject
 	void showBackgroundImageWithHexes(Canvas & canvas);
 	void showHighlightedHexes(Canvas & canvas);
 
+	BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
 public:
 	BattleFieldController(BattleInterface & owner);
 

+ 4 - 0
client/battle/BattleStacksController.cpp

@@ -864,6 +864,10 @@ void BattleStacksController::updateHoveredStacks()
 
 std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
 {
+	// only allow during our turn - do not try to highlight creatures while they are in the middle of actions
+	if (!activeStack)
+		return {};
+
 	auto hoveredHex = owner.fieldController->getHoveredHex();
 
 	if (!hoveredHex.isValid())

+ 4 - 0
lib/battle/BattleHex.h

@@ -55,6 +55,10 @@ struct DLL_LINKAGE BattleHex //TODO: decide if this should be changed to class f
 		BOTTOM_RIGHT,
 		BOTTOM_LEFT,
 		LEFT,
+
+		//Note: unused by BattleHex class, used by other code
+		TOP,
+		BOTTOM
 	};
 
 	BattleHex();

+ 0 - 3
lib/battle/CBattleInfoCallback.cpp

@@ -1373,9 +1373,6 @@ ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityIn
 AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const  battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const
 {
 	//does not return hex attacked directly
-	//TODO: apply rotation to two-hex attackers
-	bool isAttacker = attacker->unitSide() == BattleSide::ATTACKER;
-
 	AttackableTiles at;
 	RETURN_IF_NOT_BATTLE(at);