Ver código fonte

Merge pull request #1874 from krs0/feature/separate_movement_highlight_for_hoverd_units_in_battle

Separate movement highlight for hovered units in battle
Ivan Savenko 2 anos atrás
pai
commit
a97ebc2bf1

+ 1 - 1
AI/BattleAI/BattleAI.cpp

@@ -271,7 +271,7 @@ BattleAction CBattleAI::activeStack( const CStack * stack )
 BattleAction CBattleAI::goTowardsNearest(const CStack * stack, std::vector<BattleHex> hexes) const
 {
 	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, true);
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{

+ 1 - 1
AI/BattleAI/PotentialTargets.cpp

@@ -15,7 +15,7 @@ PotentialTargets::PotentialTargets(const battle::Unit * attacker, const Hypothet
 {
 	auto attackerInfo = state.battleGetUnitByID(attacker->unitId());
 	auto reachability = state.getReachability(attackerInfo);
-	auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo);
+	auto avHexes = state.battleGetAvailableHexes(reachability, attackerInfo, true);
 
 	//FIXME: this should part of battleGetAvailableHexes
 	bool forceTarget = false;

+ 2 - 2
AI/StupidAI/StupidAI.cpp

@@ -120,7 +120,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
 		}
 		else
 		{
-			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack);
+			std::vector<BattleHex> avHexes = cb->battleGetAvailableHexes(stack, true);
 
 			for (BattleHex hex : avHexes)
 			{
@@ -238,7 +238,7 @@ void CStupidAI::print(const std::string &text) const
 BattleAction CStupidAI::goTowards(const CStack * stack, std::vector<BattleHex> hexes) const
 {
 	auto reachability = cb->getReachability(stack);
-	auto avHexes = cb->battleGetAvailableHexes(reachability, stack);
+	auto avHexes = cb->battleGetAvailableHexes(reachability, stack, false);
 
 	if(!avHexes.size() || !hexes.size()) //we are blocked or dest is blocked
 	{

BIN
Mods/vcmi/Data/UnitMaxMovementHighlight.png


BIN
Mods/vcmi/Data/UnitMovementHighlight.png


+ 2 - 0
Mods/vcmi/config/vcmi/english.json

@@ -81,6 +81,8 @@
 	"vcmi.battleOptions.animationsSpeed6.help": "Set animation speed to instantaneous",
 	"vcmi.battleOptions.touchscreenMode.hover": "Touchscreen mode",
 	"vcmi.battleOptions.touchscreenMode.help": "{Touchscreen mode}\n\nIf enabled, second click is required to confirm and execute action. This is more suitable for touchscreen devices.",
+	"vcmi.battleOptions.movementHighlightOnHover.hover": "Movement Highlight on Hover",
+	"vcmi.battleOptions.movementHighlightOnHover.help": "{Movement Highlight on Hover}\n\nHighlight unit's movement range when you hover over it.",
 	"vcmi.battleOptions.skipBattleIntroMusic.hover": "Skip Intro Music",
 	"vcmi.battleOptions.skipBattleIntroMusic.help": "{Skip Intro Music}\n\nAllow actions during the intro music that plays at the beginning of each battle",
 	"vcmi.battleWindow.pressKeyToSkipIntro" : "Press any key to start battle immediately",

+ 1 - 0
Mods/vcmi/mod.json

@@ -50,6 +50,7 @@
 		"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
 		"author" : "Команда VCMI",
 		
+		"skipValidation" : true,
 		"translations" : [
 			"config/vcmi/ukrainian.json"
 		]

+ 2 - 2
client/battle/BattleActionsController.cpp

@@ -623,7 +623,7 @@ void BattleActionsController::actionRealize(PossiblePlayerBattleAction action, B
 		{
 			if(owner.stacksController->getActiveStack()->doubleWide())
 			{
-				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
+				std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack(), true);
 				BattleHex shiftedDest = targetHex.cloneInDirection(owner.stacksController->getActiveStack()->destShiftDir(), false);
 				if(vstd::contains(acc, targetHex))
 					owner.giveCommand(EActionType::WALK, targetHex);
@@ -926,7 +926,7 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell,
 
 bool BattleActionsController::canStackMoveHere(const CStack * stackToMove, BattleHex myNumber) const
 {
-	std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove);
+	std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(stackToMove, false);
 	BattleHex shiftedDest = myNumber.cloneInDirection(stackToMove->destShiftDir(), false);
 
 	if (vstd::contains(acc, myNumber))

+ 90 - 71
client/battle/BattleFieldController.cpp

@@ -44,6 +44,8 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	//preparing cells and hexes
 	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
 	cellShade = IImage::createFromFile("CCELLSHD.BMP");
+	cellUnitMovementHighlight = IImage::createFromFile("UnitMovementHighlight.PNG", EImageBlitMode::COLORKEY);
+	cellUnitMaxMovementHighlight = IImage::createFromFile("UnitMaxMovementHighlight.PNG", EImageBlitMode::COLORKEY);
 
 	if(!owner.siegeController)
 	{
@@ -138,7 +140,6 @@ void BattleFieldController::showBackground(Canvas & canvas)
 		showBackgroundImage(canvas);
 
 	showHighlightedHexes(canvas);
-
 }
 
 void BattleFieldController::showBackgroundImage(Canvas & canvas)
@@ -172,32 +173,34 @@ void BattleFieldController::redrawBackgroundWithHexes()
 {
 	const CStack *activeStack = owner.stacksController->getActiveStack();
 	std::vector<BattleHex> attackableHexes;
-	if (activeStack)
-		occupyableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, &attackableHexes);
+	if(activeStack)
+		occupiableHexes = owner.curInt->cb->battleGetAvailableHexes(activeStack, true, true, &attackableHexes);
 
-	//prepare background graphic with hexes and shaded hexes
+	// prepare background graphic with hexes and shaded hexes
 	backgroundWithHexes->draw(background, Point(0,0));
 	owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes);
-	if ( owner.siegeController )
+	if(owner.siegeController)
 		owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
 
-	if (settings["battle"]["stackRange"].Bool())
+	// show shaded hexes for active's stack valid movement and the hexes that it can attack
+	if(settings["battle"]["stackRange"].Bool())
 	{
-		std::vector<BattleHex> hexesToShade = occupyableHexes;
+		std::vector<BattleHex> hexesToShade = occupiableHexes;
 		hexesToShade.insert(hexesToShade.end(), attackableHexes.begin(), attackableHexes.end());
-		for (BattleHex hex : hexesToShade)
+		for(BattleHex hex : hexesToShade)
 		{
-			backgroundWithHexes->draw(cellShade, hexPositionLocal(hex).topLeft());
+			showHighlightedHex(*backgroundWithHexes, cellShade, hex, false);
 		}
 	}
 
+	// draw cell borders
 	if(settings["battle"]["cellBorders"].Bool())
 	{
-		for (int i=0; i<GameConstants::BFIELD_SIZE; ++i)
+		for(int i=0; i<GameConstants::BFIELD_SIZE; ++i)
 		{
-			if ( i % GameConstants::BFIELD_WIDTH == 0)
+			if(i % GameConstants::BFIELD_WIDTH == 0)
 				continue;
-			if ( i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
+			if(i % GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_WIDTH - 1)
 				continue;
 
 			backgroundWithHexes->draw(cellBorder, hexPositionLocal(i).topLeft());
@@ -205,23 +208,23 @@ void BattleFieldController::redrawBackgroundWithHexes()
 	}
 }
 
-void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
+void BattleFieldController::showHighlightedHex(Canvas & canvas, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder)
 {
 	Point hexPos = hexPositionLocal(hex).topLeft();
 
-	canvas.draw(cellShade, hexPos);
+	canvas.draw(highlight, hexPos);
 	if(!darkBorder && settings["battle"]["cellBorders"].Bool())
 		canvas.draw(cellBorder, hexPos);
 }
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
+std::set<BattleHex> BattleFieldController::getHighlightedHexesForActiveStack()
 {
 	std::set<BattleHex> result;
 
-	if ( !owner.stacksController->getActiveStack())
+	if(!owner.stacksController->getActiveStack())
 		return result;
 
-	if ( !settings["battle"]["stackRange"].Bool())
+	if(!settings["battle"]["stackRange"].Bool())
 		return result;
 
 	auto hoveredHex = getHoveredHex();
@@ -230,18 +233,33 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesStackRange()
 	for(BattleHex hex : set)
 		result.insert(hex);
 
-	// display the movement shadow of stack under mouse
+	return result;
+}
+
+std::set<BattleHex> BattleFieldController::getMovementRangeForHoveredStack()
+{
+	std::set<BattleHex> result;
+
+	if (!owner.stacksController->getActiveStack())
+		return result;
+
+	if (!settings["battle"]["movementHighlightOnHover"].Bool())
+		return result;
+
+	auto hoveredHex = getHoveredHex();
+
+	// add possible movement hexes for stack under mouse
 	const CStack * const hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
-	if(hoveredStack && hoveredStack != owner.stacksController->getActiveStack())
+	if(hoveredStack)
 	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, true, nullptr);
+		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(hoveredStack, false, true, nullptr);
 		for(BattleHex hex : v)
 			result.insert(hex);
 	}
 	return result;
 }
 
-std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
+std::set<BattleHex> BattleFieldController::getHighlightedHexesForSpellRange()
 {
 	std::set<BattleHex> result;
 	auto hoveredHex = getHoveredHex();
@@ -260,9 +278,9 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
 	{
 		// printing shaded hex(es)
 		spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
-		auto shaded = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
+		auto shadedHexes = spell->battleMechanics(&event)->rangeInHexes(hoveredHex);
 
-		for(BattleHex shadedHex : shaded)
+		for(BattleHex shadedHex : shadedHexes)
 		{
 			if((shadedHex.getX() != 0) && (shadedHex.getX() != GameConstants::BFIELD_WIDTH - 1))
 				result.insert(shadedHex);
@@ -276,72 +294,73 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
 	const CStack * stack = owner.stacksController->getActiveStack();
 	auto hoveredHex = getHoveredHex();
 
-	if (stack)
-	{
-		std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(stack, false, nullptr);
+	if(!stack)
+		return {};
 
-		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};
-			}
-		}
+	std::vector<BattleHex> availableHexes = owner.curInt->cb->battleGetAvailableHexes(stack, true, false, nullptr);
 
-		if (vstd::contains(v,hoveredHex))
+	auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+	if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
+	{
+		if(isTileAttackable(hoveredHex))
 		{
-			if (stack->doubleWide())
-				return {hoveredHex, stack->occupiedHex(hoveredHex)};
+			BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
+
+			if(stack->doubleWide())
+				return {attackFromHex, stack->occupiedHex(attackFromHex)};
 			else
-				return {hoveredHex};
+				return {attackFromHex};
 		}
-		if (stack->doubleWide())
+	}
+
+	if(vstd::contains(availableHexes, hoveredHex))
+	{
+		if(stack->doubleWide())
+			return {hoveredHex, stack->occupiedHex(hoveredHex)};
+		else
+			return {hoveredHex};
+	}
+
+	if(stack->doubleWide())
+	{
+		for(auto const & hex : availableHexes)
 		{
-			for (auto const & hex : v)
-			{
-				if (stack->occupiedHex(hex) == hoveredHex)
-					return { hoveredHex, hex };
-			}
+			if(stack->occupiedHex(hex) == hoveredHex)
+				return {hoveredHex, hex};
 		}
 	}
+
 	return {};
 }
 
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 {
-	std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
-	std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
-	std::set<BattleHex> hoveredMove  = getHighlightedHexesMovementTarget();
+	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
+	std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
+	std::set<BattleHex> hoveredMoveHexes  = getHighlightedHexesMovementTarget();
 
-	if (getHoveredHex() == BattleHex::INVALID)
+	if(getHoveredHex() == BattleHex::INVALID)
 		return;
 
-	auto const & hoveredMouse = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpell : hoveredMove;
+	auto const & hoveredMouseHexes = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
 
-	for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
+	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex)
 	{
-		bool stack = hoveredStack.count(b);
-		bool mouse = hoveredMouse.count(b);
+		bool stackMovement = hoveredStackMovementRangeHexes.count(hex);
+		bool mouse = hoveredMouseHexes.count(hex);
 
-		if ( stack && mouse )
+		if(stackMovement && mouse) // area where hovered stackMovement can move shown with highlight. Because also affected by mouse cursor, shade as well
 		{
-			// 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);
+			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
+			showHighlightedHex(canvas, cellShade, hex, true);
 		}
-		if ( !stack && mouse )
+		if(!stackMovement && mouse) // hexes affected only at mouse cursor shown as shaded
 		{
-			showHighlightedHex(canvas, b, true);
+			showHighlightedHex(canvas, cellShade, hex, true);
 		}
-		if ( stack && !mouse )
+		if(stackMovement && !mouse) // hexes where hovered stackMovement can move shown with highlight
 		{
-			showHighlightedHex(canvas, b, false);
+			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
 		}
 	}
 }
@@ -438,18 +457,18 @@ BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber,
 		// |    - -   |   - -    |    - -   |   - 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));
+			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, 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[i] = vstd::contains(occupiableHexes, neighbours[i]) && vstd::contains(occupiableHexes, 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]);
+		attackAvailability[6] = vstd::contains(occupiableHexes, neighbours[0]) && vstd::contains(occupiableHexes, neighbours[1]);
+		attackAvailability[7] = vstd::contains(occupiableHexes, neighbours[3]) && vstd::contains(occupiableHexes, neighbours[4]);
 	}
 	else
 	{
 		for (size_t i = 0; i < 6; ++i)
-			attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]);
+			attackAvailability[i] = vstd::contains(occupiableHexes, neighbours[i]);
 
 		attackAvailability[6] = false;
 		attackAvailability[7] = false;
@@ -561,7 +580,7 @@ BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
 
 bool BattleFieldController::isTileAttackable(const BattleHex & number) const
 {
-	for (auto & elem : occupyableHexes)
+	for (auto & elem : occupiableHexes)
 	{
 		if (BattleHex::mutualPosition(elem, number) != -1 || elem == number)
 			return true;

+ 7 - 4
client/battle/BattleFieldController.h

@@ -32,6 +32,8 @@ class BattleFieldController : public CIntObject
 
 	std::shared_ptr<IImage> background;
 	std::shared_ptr<IImage> cellBorder;
+	std::shared_ptr<IImage> cellUnitMovementHighlight;
+	std::shared_ptr<IImage> cellUnitMaxMovementHighlight;
 	std::shared_ptr<IImage> cellShade;
 
 	/// Canvas that contains background, hex grid (if enabled), absolute obstacles and movement range of active stack
@@ -41,15 +43,16 @@ class BattleFieldController : public CIntObject
 	BattleHex attackingHex;
 
 	/// hexes to which currently active stack can move
-	std::vector<BattleHex> occupyableHexes;
+	std::vector<BattleHex> occupiableHexes;
 
 	/// hexes that when in front of a unit cause it's amount box to move back
 	std::array<bool, GameConstants::BFIELD_SIZE> stackCountOutsideHexes;
 
-	void showHighlightedHex(Canvas & to, BattleHex hex, bool darkBorder);
+	void showHighlightedHex(Canvas & to, std::shared_ptr<IImage> highlight, BattleHex hex, bool darkBorder);
 
-	std::set<BattleHex> getHighlightedHexesStackRange();
-	std::set<BattleHex> getHighlightedHexesSpellRange();
+	std::set<BattleHex> getHighlightedHexesForActiveStack();
+	std::set<BattleHex> getMovementRangeForHoveredStack();
+	std::set<BattleHex> getHighlightedHexesForSpellRange();
 	std::set<BattleHex> getHighlightedHexesMovementTarget();
 
 	void showBackground(Canvas & canvas);

+ 17 - 0
client/battle/BattleWindow.cpp

@@ -198,6 +198,23 @@ void BattleWindow::keyPressed(const SDL_Keycode & key)
 	{
 		owner.actionsController->endCastingSpell();
 	}
+	else if(GH.isKeyboardShiftDown())
+	{
+		// save and activate setting
+		Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
+		movementHighlightOnHoverCache = movementHighlightOnHover->Bool();
+		movementHighlightOnHover->Bool() = true;
+	}
+}
+
+void BattleWindow::keyReleased(const SDL_Keycode & key)
+{
+	if(!GH.isKeyboardShiftDown())
+	{
+		// set back to initial state
+		Settings movementHighlightOnHover = settings.write["battle"]["movementHighlightOnHover"];
+		movementHighlightOnHover->Bool() = movementHighlightOnHoverCache;
+	}
 }
 
 void BattleWindow::clickRight(tribool down, bool previousState)

+ 4 - 0
client/battle/BattleWindow.h

@@ -85,6 +85,7 @@ public:
 	void activate() override;
 	void deactivate() override;
 	void keyPressed(const SDL_Keycode & key) override;
+	void keyReleased(const SDL_Keycode& key) override;
 	void clickRight(tribool down, bool previousState) override;
 	void show(SDL_Surface *to) override;
 	void showAll(SDL_Surface *to) override;
@@ -98,5 +99,8 @@ public:
 	/// Set possible alternative options. If more than 1 - the last will be considered as default option
 	void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
 
+private:
+	/// used to save the state of this setting on toggle.
+	bool movementHighlightOnHoverCache;
 };
 

+ 1 - 1
client/gui/CGuiHandler.cpp

@@ -418,7 +418,7 @@ void CGuiHandler::handleCurrentEvent( SDL_Event & current )
 		for(auto i = miCopy.begin(); i != miCopy.end() && continueEventHandling; i++)
 			if(vstd::contains(keyinterested,*i) && (!keysCaptured || (*i)->captureThisKey(key.keysym.sym)))
 			{
-				if (key.state == SDL_PRESSED)
+				if (key.state == SDL_PRESSED && key.repeat == 0) // function like key_DOWN, and not like a periodic key_Pressed check 
 					(**i).keyPressed(key.keysym.sym);
 				if (key.state == SDL_RELEASED)
 					(**i).keyReleased(key.keysym.sym);

+ 15 - 0
client/windows/settings/BattleOptionsTab.cpp

@@ -34,6 +34,10 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
 	{
 		movementShadowChangedCallback(value, owner);
 	});
+	addCallback("movementHighlightOnHoverChanged", [this, owner](bool value)
+	{
+		movementHighlightOnHoverChangedCallback(value, owner);
+	});
 	addCallback("mouseShadowChanged", [this](bool value)
 	{
 		mouseShadowChangedCallback(value);
@@ -72,6 +76,9 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
 	std::shared_ptr<CToggleButton> movementShadowCheckbox = widget<CToggleButton>("movementShadowCheckbox");
 	movementShadowCheckbox->setSelected(settings["battle"]["stackRange"].Bool());
 
+	std::shared_ptr<CToggleButton> movementHighlightOnHoverCheckbox = widget<CToggleButton>("movementHighlightOnHoverCheckbox");
+	movementHighlightOnHoverCheckbox->setSelected(settings["battle"]["movementHighlightOnHover"].Bool());
+
 	std::shared_ptr<CToggleButton> mouseShadowCheckbox = widget<CToggleButton>("mouseShadowCheckbox");
 	mouseShadowCheckbox->setSelected(settings["battle"]["mouseShadow"].Bool());
 	
@@ -138,6 +145,14 @@ void BattleOptionsTab::movementShadowChangedCallback(bool value, BattleInterface
 		parentBattleInterface->redrawBattlefield();
 }
 
+void BattleOptionsTab::movementHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface)
+{
+	Settings stackRange = settings.write["battle"]["movementHighlightOnHover"];
+	stackRange->Bool() = value;
+	if(parentBattleInterface)
+		parentBattleInterface->redrawBattlefield();
+}
+
 void BattleOptionsTab::mouseShadowChangedCallback(bool value)
 {
 	Settings shadow = settings.write["battle"]["mouseShadow"];

+ 1 - 0
client/windows/settings/BattleOptionsTab.h

@@ -24,6 +24,7 @@ private:
 	std::string getQueueSizeStringFromId(int value) const;
 	void viewGridChangedCallback(bool value, BattleInterface * parentBattleInterface);
 	void movementShadowChangedCallback(bool value, BattleInterface * parentBattleInterface);
+	void movementHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface);
 	void mouseShadowChangedCallback(bool value);
 	void animationSpeedChangedCallback(int value);
 	void showQueueChangedCallback(bool value, BattleInterface * parentBattleInterface);

+ 5 - 1
config/schemas/settings.json

@@ -286,7 +286,7 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"default": {},
-			"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "showQueue", "queueSize", "touchscreenMode" ],
+			"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "showQueue", "queueSize", "touchscreenMode" ],
 			"properties" : {
 				"speedFactor" : {
 					"type" : "number",
@@ -308,6 +308,10 @@
 					"type" : "boolean",
 					"default" : true
 				},
+				"movementHighlightOnHover" : {
+					"type" : "boolean",
+					"default" : true
+				},
 				"showQueue" : {
 					"type" : "boolean",
 					"default" : true

+ 17 - 5
config/widgets/settings/battleOptionsTab.json

@@ -142,13 +142,17 @@
 					"position": {"x": 45, "y": 85}
 				},
 				{
-					"text": "core.genrltxt.406",
+					"text": "vcmi.battleOptions.movementHighlightOnHover.hover",
 					"position": {"x": 45, "y": 115}
 				},
 				{
-					"text": "core.genrltxt.407",
+					"text": "core.genrltxt.406",
 					"position": {"x": 45, "y": 145}
 				},
+				{
+					"text": "core.genrltxt.407",
+					"position": {"x": 45, "y": 175}
+				},
 				{
 					"text": "vcmi.battleOptions.skipBattleIntroMusic.hover",
 					"position": {"x": 45, "y": 175}
@@ -176,26 +180,34 @@
 			"position": {"x": 10, "y": 83},
 			"callback": "movementShadowChanged"
 		},
+		{
+			"name": "movementHighlightOnHoverCheckbox",
+			"type": "toggleButton",
+			"image": "sysopchk.def",
+			"help": "vcmi.battleOptions.movementHighlightOnHover",
+			"position": {"x": 10, "y": 113},
+			"callback": "movementHighlightOnHoverChanged"
+		},
 		{
 			"name": "mouseShadowCheckbox",
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "core.help.429",
-			"position": {"x": 10, "y": 113},
+			"position": {"x": 10, "y": 143},
 			"callback": "mouseShadowChanged"
 		},
 		{
 			"name": "battleFieldCasualtiesPlaceholder",
 			"type": "picture",
 			"image": "settingsWindow/checkBoxEmpty",
-			"position": {"x": 10, "y": 143},
+			"position": {"x": 10, "y": 173},
 		},
 		{
 			"name": "skipBattleIntroMusicCheckbox",
 			"type": "toggleButton",
 			"image": "sysopchk.def",
 			"help": "vcmi.battleOptions.skipBattleIntroMusic",
-			"position": {"x": 10, "y": 173},
+			"position": {"x": 10, "y": 203},
 			"callback": "skipBattleIntroMusicChanged"
 		},
 		{

+ 12 - 15
lib/battle/CBattleInfoCallback.cpp

@@ -538,7 +538,7 @@ void CBattleInfoCallback::battleGetTurnOrder(std::vector<battle::Units> & turns,
 		battleGetTurnOrder(turns, maxUnits, maxTurns, actualTurn + 1, sideThatLastMoved);
 }
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit) const
+std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool isActiveStack) const
 {
 
 	RETURN_IF_NOT_BATTLE(std::vector<BattleHex>());
@@ -547,10 +547,10 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle
 
 	auto reachability = getReachability(unit);
 
-	return battleGetAvailableHexes(reachability, unit);
+	return battleGetAvailableHexes(reachability, unit, isActiveStack);
 }
 
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit) const
+std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool isActiveStack) const
 {
 	std::vector<BattleHex> ret;
 
@@ -560,7 +560,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 
 	auto unitSpeed = unit->Speed(0, true);
 
-	const bool tacticPhase = battleTacticDist() && battleGetTacticsSide() == unit->unitSide();
+	const bool showTacticsRange = battleTacticDist() && battleGetTacticsSide() == unit->unitSide() && isActiveStack;
 
 	for(int i = 0; i < GameConstants::BFIELD_SIZE; ++i)
 	{
@@ -568,15 +568,15 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 		if(!cache.isReachable(i))
 			continue;
 
-		if(tacticPhase)
+		if(showTacticsRange)
 		{
-			//Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
+			// Stack has to perform tactic-phase movement -> can enter any reachable tile within given range
 			if(!isInTacticRange(i))
 				continue;
 		}
 		else
 		{
-			//Not tactics phase -> destination must be reachable and within unit range.
+			// Not tactics phase -> destination must be reachable and within unit range.
 			if(cache.distances[i] > static_cast<int>(unitSpeed))
 				continue;
 		}
@@ -587,10 +587,9 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const Reacha
 	return ret;
 }
 
-
-std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool addOccupiable, std::vector<BattleHex> * attackable) const
+std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle::Unit * unit, bool isActiveStack, bool addOccupiable, std::vector<BattleHex> * attackable) const
 {
-	std::vector<BattleHex> ret = battleGetAvailableHexes(unit);
+	std::vector<BattleHex> ret = battleGetAvailableHexes(unit, isActiveStack);
 
 	if(ret.empty())
 		return ret;
@@ -976,7 +975,6 @@ ReachabilityInfo CBattleInfoCallback::makeBFS(const AccessibilityInfo &accessibi
 	return ret;
 }
 
-
 bool CBattleInfoCallback::isInObstacle(
 	BattleHex hex,
 	const std::set<BattleHex> & obstacles,
@@ -1030,7 +1028,7 @@ std::set<BattleHex> CBattleInfoCallback::getStoppers(BattlePerspective::BattlePe
 std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack(const battle::Unit * closest) const
 {
 	auto reachability = getReachability(closest);
-	auto avHexes = battleGetAvailableHexes(reachability, closest);
+	auto avHexes = battleGetAvailableHexes(reachability, closest, false);
 
 	// I hate std::pairs with their undescriptive member names first / second
 	struct DistStack
@@ -1097,7 +1095,6 @@ BattleHex CBattleInfoCallback::getAvaliableHex(const CreatureID & creID, ui8 sid
 	return BattleHex::getClosestTile(side, pos, occupyable);
 }
 
-
 si8 CBattleInfoCallback::battleGetTacticDist() const
 {
 	RETURN_IF_NOT_BATTLE(0);
@@ -1158,7 +1155,7 @@ ReachabilityInfo CBattleInfoCallback::getFlyingReachability(const ReachabilityIn
 	return ret;
 }
 
-AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const  battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const
+AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes(const battle::Unit* attacker, BattleHex destinationTile, BattleHex attackerPos) const
 {
 	//does not return hex attacked directly
 	AttackableTiles at;
@@ -1242,7 +1239,7 @@ AttackableTiles CBattleInfoCallback::getPotentiallyAttackableHexes (const  battl
 	return at;
 }
 
-AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const  battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
+AttackableTiles CBattleInfoCallback::getPotentiallyShootableHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const
 {
 	//does not return hex attacked directly
 	AttackableTiles at;

+ 3 - 3
lib/battle/CBattleInfoCallback.h

@@ -74,12 +74,12 @@ public:
 	void battleGetTurnOrder(std::vector<battle::Units> & out, const size_t maxUnits, const int maxTurns, const int turn = 0, int8_t lastMoved = -1) const;
 
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position
-	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit, bool addOccupiable, std::vector<BattleHex> * attackable) const;
+	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit, bool isActiveStack, bool addOccupiable, std::vector<BattleHex> * attackable) const;
 
 	///returns reachable hexes (valid movement destinations), DOES contain stack current position (lite version)
-	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit) const;
+	std::vector<BattleHex> battleGetAvailableHexes(const battle::Unit * unit, bool isActiveStack) const;
 
-	std::vector<BattleHex> battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit) const;
+	std::vector<BattleHex> battleGetAvailableHexes(const ReachabilityInfo & cache, const battle::Unit * unit, bool isActiveStack) const;
 
 	int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible
 	ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const;