1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Merge pull request #2240 from krs0/feature/show_max_range_limit_for_ranged_units

Show max range limit for ranged units
This commit is contained in:
Ivan Savenko 2023-06-25 17:39:31 +03:00 committed by GitHub
commit b93b8f45db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 204 additions and 115 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

View File

@ -109,8 +109,8 @@
"vcmi.battleOptions.animationsSpeed6.help": "Set animation speed to instantaneous",
"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.rangedFullDamageLimitHighlightOnHover.hover": "Ranged Full Damage Limit Highlight",
"vcmi.battleOptions.rangedFullDamageLimitHighlightOnHover.help": "{Ranged Full Damage Limit on Hover}\n\nHighlight ranged unit's full damage range limit when you hover over it.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Show range limits for shooters",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Show range limits for shooters on Hover}\n\nShow shooter's range limits 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",

View File

@ -92,8 +92,8 @@
"vcmi.battleOptions.animationsSpeed6.help": "Встановити миттєву швидкість анімації",
"vcmi.battleOptions.movementHighlightOnHover.hover": "Підсвічувати зону руху істоти",
"vcmi.battleOptions.movementHighlightOnHover.help": "{Підсвічувати зону руху істоти}\n\nПідсвічувати можливу зону руху істоти при наведенні курсора миші на неї",
"vcmi.battleOptions.rangedFullDamageLimitHighlightOnHover.hover": "Межа повного шкоди пострілом",
"vcmi.battleOptions.rangedFullDamageLimitHighlightOnHover.help": "{Межа повного шкоди пострілом}\n\nПідсвічувати зону у якій створіння може завдавати максимальної шкоди пострілом при наведенні на неї курсору миші.",
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Межа повного шкоди пострілом",
"vcmi.battleOptions.rangeLimitHighlightOnHover.help": "{Межа повного шкоди пострілом}\n\nПідсвічувати зону у якій створіння може завдавати максимальної шкоди пострілом при наведенні на неї курсору миші.",
"vcmi.battleOptions.skipBattleIntroMusic.hover": "Пропускати вступну музику",
"vcmi.battleOptions.skipBattleIntroMusic.help": "{Пропускати вступну музику}\n\n Пропускати коротку музику, яка грає на початку кожної битви перед початком дії. Також можна пропустити, натиснувши клавішу ESC.",

View File

@ -128,11 +128,16 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
attackCursors = std::make_shared<CAnimation>("CRCOMBAT");
attackCursors->preload();
rangedFullDamageLimitImages = std::make_unique<CAnimation>("battle/rangeHighlights/rangeHighlightsGreen.json");
initializeHexEdgeMaskToFrameIndex();
rangedFullDamageLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsGreen.json");
rangedFullDamageLimitImages->preload();
initializeHexEdgeMaskToFrameIndex();
flipRangedFullDamageLimitImagesIntoPositions();
shootingRangeLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsRed.json");
shootingRangeLimitImages->preload();
flipRangeLimitImagesIntoPositions(rangedFullDamageLimitImages);
flipRangeLimitImagesIntoPositions(shootingRangeLimitImages);
if(!owner.siegeController)
{
@ -441,65 +446,62 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
return {};
}
std::vector<BattleHex> BattleFieldController::getRangedFullDamageHexes()
// Range limit highlight helpers
std::vector<BattleHex> BattleFieldController::getRangeHexes(BattleHex sourceHex, uint8_t distance)
{
std::vector<BattleHex> rangedFullDamageHexes; // used for return
std::vector<BattleHex> rangeHexes;
// if not a hovered arcer unit -> return
auto hoveredHex = getHoveredHex();
const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if (!settings["battle"]["rangeLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
return rangeHexes;
if (!settings["battle"]["rangedFullDamageLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
return rangedFullDamageHexes;
if(!(hoveredStack && hoveredStack->isShooter()))
return rangedFullDamageHexes;
auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
// get only battlefield hexes that are in full range damage distance
std::set<BattleHex> fullRangeLimit;
// get only battlefield hexes that are within the given distance
for(auto i = 0; i < GameConstants::BFIELD_SIZE; i++)
{
BattleHex hex(i);
if(hex.isAvailable() && BattleHex::getDistance(hoveredHex, hex) <= rangedFullDamageDistance)
rangedFullDamageHexes.push_back(hex);
if(hex.isAvailable() && BattleHex::getDistance(sourceHex, hex) <= distance)
rangeHexes.push_back(hex);
}
return rangedFullDamageHexes;
return rangeHexes;
}
std::vector<BattleHex> BattleFieldController::getRangedFullDamageLimitHexes(std::vector<BattleHex> rangedFullDamageHexes)
std::vector<BattleHex> BattleFieldController::getRangeLimitHexes(BattleHex hoveredHex, std::vector<BattleHex> rangeHexes, uint8_t distanceToLimit)
{
std::vector<BattleHex> rangedFullDamageLimitHexes; // used for return
std::vector<BattleHex> rangeLimitHexes;
// if not a hovered arcer unit -> return
auto hoveredHex = getHoveredHex();
const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if(!(hoveredStack && hoveredStack->isShooter()))
return rangedFullDamageLimitHexes;
auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
// from ranged full damage hexes get only the ones at the limit
for(auto & hex : rangedFullDamageHexes)
// from range hexes get only the ones at the limit
for(auto & hex : rangeHexes)
{
if(BattleHex::getDistance(hoveredHex, hex) == rangedFullDamageDistance)
rangedFullDamageLimitHexes.push_back(hex);
if(BattleHex::getDistance(hoveredHex, hex) == distanceToLimit)
rangeLimitHexes.push_back(hex);
}
return rangedFullDamageLimitHexes;
return rangeLimitHexes;
}
std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangedFullDamageHexes, std::vector<BattleHex> rangedFullDamageLimitHexes)
bool BattleFieldController::IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit)
{
bool hexInRangeLimit = false;
if(!rangeLimitHexes.empty())
{
auto pos = std::find(rangeLimitHexes.begin(), rangeLimitHexes.end(), hex);
*hexIndexInRangeLimit = std::distance(rangeLimitHexes.begin(), pos);
hexInRangeLimit = pos != rangeLimitHexes.end();
}
return hexInRangeLimit;
}
std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> wholeRangeHexes, std::vector<BattleHex> rangeLimitHexes)
{
std::vector<std::vector<BattleHex::EDir>> output;
if(rangedFullDamageHexes.empty())
if(wholeRangeHexes.empty())
return output;
for(auto & hex : rangedFullDamageLimitHexes)
for(auto & hex : rangeLimitHexes)
{
// get all neighbours and their directions
@ -507,15 +509,15 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
std::vector<BattleHex::EDir> outsideNeighbourDirections;
// for each neighbour add to output only the valid ones and only that are not found in rangedFullDamageHexes
// for each neighbour add to output only the valid ones and only that are not found in range Hexes
for(auto direction = 0; direction < 6; direction++)
{
if(!neighbouringTiles[direction].isAvailable())
continue;
auto it = std::find(rangedFullDamageHexes.begin(), rangedFullDamageHexes.end(), neighbouringTiles[direction]);
auto it = std::find(wholeRangeHexes.begin(), wholeRangeHexes.end(), neighbouringTiles[direction]);
if(it == rangedFullDamageHexes.end())
if(it == wholeRangeHexes.end())
outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction
}
@ -525,14 +527,14 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
return output;
}
std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangedFullDamageHighlightImages(std::vector<std::vector<BattleHex::EDir>> rangedFullDamageLimitHexesNeighbourDirections)
std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages)
{
std::vector<std::shared_ptr<IImage>> output; // if no image is to be shown an empty image is still added to help with traverssing the range
if(rangedFullDamageLimitHexesNeighbourDirections.empty())
if(hexesNeighbourDirections.empty())
return output;
for(auto & directions : rangedFullDamageLimitHexesNeighbourDirections)
for(auto & directions : hexesNeighbourDirections)
{
std::bitset<6> mask;
@ -541,46 +543,69 @@ std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangedFullD
mask.set(direction);
uint8_t imageKey = static_cast<uint8_t>(mask.to_ulong());
output.push_back(rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[imageKey]));
output.push_back(limitImages->getImage(hexEdgeMaskToFrameIndex[imageKey]));
}
return output;
}
void BattleFieldController::flipRangedFullDamageLimitImagesIntoPositions()
void BattleFieldController::calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighligts)
{
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip();
std::vector<BattleHex> rangeHexes = getRangeHexes(hoveredHex, distance);
rangeLimitHexes = getRangeLimitHexes(hoveredHex, rangeHexes, distance);
std::vector<std::vector<BattleHex::EDir>> rangeLimitNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangeHexes, rangeLimitHexes);
rangeLimitHexesHighligts = calculateRangeLimitHighlightImages(rangeLimitNeighbourDirections, rangeLimitImages);
}
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip();
void BattleFieldController::flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images)
{
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
}
void BattleFieldController::showHighlightedHexes(Canvas & canvas)
{
std::vector<BattleHex> rangedFullDamageLimitHexes;
std::vector<BattleHex> shootingRangeLimitHexes;
std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighligts;
std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighligts;
std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
std::set<BattleHex> hoveredSpellHexes = getHighlightedHexesForSpellRange();
std::set<BattleHex> hoveredMoveHexes = getHighlightedHexesForMovementTarget();
if(getHoveredHex() == BattleHex::INVALID)
BattleHex hoveredHex = getHoveredHex();
if(hoveredHex == BattleHex::INVALID)
return;
// calculate array with highlight images for ranged full damage limit
std::vector<BattleHex> rangedFullDamageHexes = getRangedFullDamageHexes();
std::vector<BattleHex> rangedFullDamageLimitHexes = getRangedFullDamageLimitHexes(rangedFullDamageHexes);
std::vector<std::vector<BattleHex::EDir>> rangedFullDamageLimitHexesNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangedFullDamageHexes, rangedFullDamageLimitHexes);
std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighligts = calculateRangedFullDamageHighlightImages(rangedFullDamageLimitHexesNeighbourDirections);
const CStack * hoveredStack = getHoveredStack();
// skip range limit calculations if unit hovered is not a shooter
if(hoveredStack && hoveredStack->isShooter())
{
// calculate array with highlight images for ranged full damage limit
auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
calculateRangeLimitAndHighlightImages(rangedFullDamageDistance, rangedFullDamageLimitImages, rangedFullDamageLimitHexes, rangedFullDamageLimitHexesHighligts);
// calculate array with highlight images for shooting range limit
auto shootingRangeDistance = hoveredStack->getShootingRangeDistance();
calculateRangeLimitAndHighlightImages(shootingRangeDistance, shootingRangeLimitImages, shootingRangeLimitHexes, shootingRangeLimitHexesHighligts);
}
auto const & hoveredMouseHexes = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
@ -590,14 +615,12 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
bool mouse = hoveredMouseHexes.count(hex);
// calculate if hex is Ranged Full Damage Limit and its position in highlight array
bool isRangedFullDamageLimit = false;
int hexIndexInRangedFullDamageLimit = 0;
if(!rangedFullDamageLimitHexes.empty())
{
auto pos = std::find(rangedFullDamageLimitHexes.begin(), rangedFullDamageLimitHexes.end(), hex);
hexIndexInRangedFullDamageLimit = std::distance(rangedFullDamageLimitHexes.begin(), pos);
isRangedFullDamageLimit = pos != rangedFullDamageLimitHexes.end();
}
bool hexInRangedFullDamageLimit = IsHexInRangeLimit(hex, rangedFullDamageLimitHexes, &hexIndexInRangedFullDamageLimit);
// calculate if hex is Shooting Range Limit and its position in highlight array
int hexIndexInShootingRangeLimit = 0;
bool hexInShootingRangeLimit = IsHexInRangeLimit(hex, shootingRangeLimitHexes, &hexIndexInShootingRangeLimit);
if(stackMovement && mouse) // area where hovered stackMovement can move shown with highlight. Because also affected by mouse cursor, shade as well
{
@ -612,10 +635,14 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
{
showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
}
if(isRangedFullDamageLimit)
if(hexInRangedFullDamageLimit)
{
showHighlightedHex(canvas, rangedFullDamageLimitHexesHighligts[hexIndexInRangedFullDamageLimit], hex, false);
}
if(hexInShootingRangeLimit)
{
showHighlightedHex(canvas, shootingRangeLimitHexesHighligts[hexIndexInShootingRangeLimit], hex, false);
}
}
}
@ -643,6 +670,14 @@ BattleHex BattleFieldController::getHoveredHex()
return hoveredHex;
}
const CStack* BattleFieldController::getHoveredStack()
{
auto hoveredHex = getHoveredHex();
const CStack* hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
return hoveredStack;
}
BattleHex BattleFieldController::getHexAtPosition(Point hoverPos)
{
if (owner.attackingHero)

View File

@ -33,7 +33,8 @@ class BattleFieldController : public CIntObject
std::shared_ptr<IImage> cellUnitMovementHighlight;
std::shared_ptr<IImage> cellUnitMaxMovementHighlight;
std::shared_ptr<IImage> cellShade;
std::unique_ptr<CAnimation> rangedFullDamageLimitImages;
std::shared_ptr<CAnimation> rangedFullDamageLimitImages;
std::shared_ptr<CAnimation> shootingRangeLimitImages;
std::shared_ptr<CAnimation> attackCursors;
@ -59,22 +60,30 @@ class BattleFieldController : public CIntObject
std::set<BattleHex> getHighlightedHexesForSpellRange();
std::set<BattleHex> getHighlightedHexesForMovementTarget();
/// get all hexes where a ranged unit can do full damage
std::vector<BattleHex> getRangedFullDamageHexes();
// Range limit highlight helpers
/// get only hexes at the limit of a ranged unit's full damage range
std::vector<BattleHex> getRangedFullDamageLimitHexes(std::vector<BattleHex> rangedFullDamageHexes);
/// get all hexes within a certain distance of given hex
std::vector<BattleHex> getRangeHexes(BattleHex sourceHex, uint8_t distance);
/// get only hexes at the limit of a range
std::vector<BattleHex> getRangeLimitHexes(BattleHex hoveredHex, std::vector<BattleHex> hexRange, uint8_t distanceToLimit);
/// calculate if a hex is in range limit and return its index in range
bool IsHexInRangeLimit(BattleHex hex, std::vector<BattleHex> & rangeLimitHexes, int * hexIndexInRangeLimit);
/// get an array that has for each hex in range, an aray with all directions where an ouside neighbour hex exists
std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangedFullDamageHexes, std::vector<BattleHex> rangedFullDamageLimitHexes);
std::vector<std::vector<BattleHex::EDir>> getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangeHexes, std::vector<BattleHex> rangeLimitHexes);
/// calculates what image to use as range limit, depending on the direction of neighbors
/// a mask is used internally to mark the directions of all neighbours
/// based on this mask the corresponding image is selected
std::vector<std::shared_ptr<IImage>> calculateRangedFullDamageHighlightImages(std::vector<std::vector<BattleHex::EDir>> fullRangeLimitHexesNeighbourDirections);
std::vector<std::shared_ptr<IImage>> calculateRangeLimitHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
/// calculates all hexes for a range limit and what images to be shown as highlight for each of the hexes
void calculateRangeLimitAndHighlightImages(uint8_t distance, std::shared_ptr<CAnimation> rangeLimitImages, std::vector<BattleHex> & rangeLimitHexes, std::vector<std::shared_ptr<IImage>> & rangeLimitHexesHighligts);
/// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones
void flipRangedFullDamageLimitImagesIntoPositions();
void flipRangeLimitImagesIntoPositions(std::shared_ptr<CAnimation> images);
void showBackground(Canvas & canvas);
void showBackgroundImage(Canvas & canvas);
@ -118,6 +127,9 @@ public:
/// Returns ID of currently hovered hex or BattleHex::INVALID if none
BattleHex getHoveredHex();
/// Returns the currently hovered stack
const CStack* getHoveredStack();
/// returns true if selected tile can be attacked in melee by current stack
bool isTileAttackable(const BattleHex & number) const;

View File

@ -36,9 +36,9 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
{
movementHighlightOnHoverChangedCallback(value, owner);
});
addCallback("rangedFullDamageLimitHighlightOnHoverChanged", [this, owner](bool value)
addCallback("rangeLimitHighlightOnHoverChanged", [this, owner](bool value)
{
rangedFullDamageLimitHighlightOnHoverChangedCallback(value, owner);
rangeLimitHighlightOnHoverChangedCallback(value, owner);
});
addCallback("mouseShadowChanged", [this](bool value)
{
@ -77,8 +77,8 @@ BattleOptionsTab::BattleOptionsTab(BattleInterface * owner)
std::shared_ptr<CToggleButton> movementHighlightOnHoverCheckbox = widget<CToggleButton>("movementHighlightOnHoverCheckbox");
movementHighlightOnHoverCheckbox->setSelected(settings["battle"]["movementHighlightOnHover"].Bool());
std::shared_ptr<CToggleButton> rangedFullDamageLimitHighlightOnHoverCheckbox = widget<CToggleButton>("rangedFullDamageLimitHighlightOnHoverCheckbox");
rangedFullDamageLimitHighlightOnHoverCheckbox->setSelected(settings["battle"]["rangedFullDamageLimitHighlightOnHover"].Bool());
std::shared_ptr<CToggleButton> rangeLimitHighlightOnHoverCheckbox = widget<CToggleButton>("rangeLimitHighlightOnHoverCheckbox");
rangeLimitHighlightOnHoverCheckbox->setSelected(settings["battle"]["rangeLimitHighlightOnHover"].Bool());
std::shared_ptr<CToggleButton> mouseShadowCheckbox = widget<CToggleButton>("mouseShadowCheckbox");
mouseShadowCheckbox->setSelected(settings["battle"]["mouseShadow"].Bool());
@ -156,9 +156,9 @@ void BattleOptionsTab::movementHighlightOnHoverChangedCallback(bool value, Battl
parentBattleInterface->redrawBattlefield();
}
void BattleOptionsTab::rangedFullDamageLimitHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface)
void BattleOptionsTab::rangeLimitHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface)
{
Settings stackRange = settings.write["battle"]["rangedFullDamageLimitHighlightOnHover"];
Settings stackRange = settings.write["battle"]["rangeLimitHighlightOnHover"];
stackRange->Bool() = value;
if(parentBattleInterface)
parentBattleInterface->redrawBattlefield();

View File

@ -25,7 +25,7 @@ private:
void viewGridChangedCallback(bool value, BattleInterface * parentBattleInterface);
void movementShadowChangedCallback(bool value, BattleInterface * parentBattleInterface);
void movementHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface);
void rangedFullDamageLimitHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface);
void rangeLimitHighlightOnHoverChangedCallback(bool value, BattleInterface * parentBattleInterface);
void mouseShadowChangedCallback(bool value);
void animationSpeedChangedCallback(int value);
void showQueueChangedCallback(bool value, BattleInterface * parentBattleInterface);

View File

@ -293,7 +293,7 @@
"type" : "object",
"additionalProperties" : false,
"default" : {},
"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangedFullDamageLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize" ],
"required" : [ "speedFactor", "mouseShadow", "cellBorders", "stackRange", "movementHighlightOnHover", "rangeLimitHighlightOnHover", "showQueue", "swipeAttackDistance", "queueSize" ],
"properties" : {
"speedFactor" : {
"type" : "number",
@ -315,7 +315,7 @@
"type" : "boolean",
"default" : true
},
"rangedFullDamageLimitHighlightOnHover" : {
"rangeLimitHighlightOnHover" : {
"type" : "boolean",
"default" : false
},

View File

@ -108,7 +108,7 @@
"text": "vcmi.battleOptions.movementHighlightOnHover.hover",
},
{
"text": "vcmi.battleOptions.rangedFullDamageLimitHighlightOnHover.hover",
"text": "vcmi.battleOptions.rangeLimitHighlightOnHover.hover",
},
{
"text": "core.genrltxt.406",
@ -140,9 +140,9 @@
"callback": "movementHighlightOnHoverChanged"
},
{
"name": "rangedFullDamageLimitHighlightOnHoverCheckbox",
"help": "vcmi.battleOptions.rangedFullDamageLimitHighlightOnHover",
"callback": "rangedFullDamageLimitHighlightOnHoverChanged"
"name": "rangeLimitHighlightOnHoverCheckbox",
"help": "vcmi.battleOptions.rangeLimitHighlightOnHover",
"callback": "rangeLimitHighlightOnHoverChanged"
},
{
"name": "mouseShadowCheckbox",

View File

@ -56,7 +56,8 @@ namespace GameConstants
constexpr int HERO_GOLD_COST = 2500;
constexpr int SPELLBOOK_GOLD_COST = 500;
constexpr int SKILL_GOLD_COST = 2000;
constexpr int BATTLE_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
constexpr int BATTLE_SHOOTING_PENALTY_DISTANCE = 10; //if the distance is > than this, then shooting stack has distance penalty
constexpr int BATTLE_SHOOTING_RANGE_DISTANCE = std::numeric_limits<uint8_t>::max(); // used when shooting stack has no shooting range limit
constexpr int ARMY_SIZE = 7;
constexpr int SKILL_PER_HERO = 8;
constexpr ui32 HERO_HIGH_LEVEL = 10; // affects primary skill upgrade order

View File

@ -1456,7 +1456,7 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter,
if(const auto * target = battleGetUnitByPos(destHex, true))
{
//If any hex of target creature is within range, there is no penalty
int range = GameConstants::BATTLE_PENALTY_DISTANCE;
int range = GameConstants::BATTLE_SHOOTING_PENALTY_DISTANCE;
auto bonus = shooter->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE));
if(bonus != nullptr && bonus->additionalInfo != CAddInfo::NONE)
@ -1467,7 +1467,7 @@ bool CBattleInfoCallback::battleHasDistancePenalty(const IBonusBearer * shooter,
}
else
{
if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_PENALTY_DISTANCE)
if(BattleHex::getDistance(shooterPosition, destHex) <= GameConstants::BATTLE_SHOOTING_PENALTY_DISTANCE)
return false;
}

View File

@ -596,7 +596,7 @@ uint8_t CUnitState::getRangedFullDamageDistance() const
if(!isShooter())
return 0;
uint8_t rangedFullDamageDistance = GameConstants::BATTLE_PENALTY_DISTANCE;
uint8_t rangedFullDamageDistance = GameConstants::BATTLE_SHOOTING_PENALTY_DISTANCE;
// overwrite full ranged damage distance with the value set in Additional info field of LIMITED_SHOOTING_RANGE bonus
if(this->hasBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE)))
@ -609,6 +609,24 @@ uint8_t CUnitState::getRangedFullDamageDistance() const
return rangedFullDamageDistance;
}
uint8_t CUnitState::getShootingRangeDistance() const
{
if(!isShooter())
return 0;
uint8_t shootingRangeDistance = GameConstants::BATTLE_SHOOTING_RANGE_DISTANCE;
// overwrite full ranged damage distance with the value set in Additional info field of LIMITED_SHOOTING_RANGE bonus
if(this->hasBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE)))
{
auto bonus = this->getBonus(Selector::type()(BonusType::LIMITED_SHOOTING_RANGE));
if(bonus != nullptr)
shootingRangeDistance = bonus->val;
}
return shootingRangeDistance;
}
bool CUnitState::canMove(int turn) const
{
return alive() && !hasBonus(Selector::type()(BonusType::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature

View File

@ -222,6 +222,7 @@ public:
void setPosition(BattleHex hex) override;
int32_t getInitiative(int turn = 0) const override;
uint8_t getRangedFullDamageDistance() const;
uint8_t getShootingRangeDistance() const;
bool canMove(int turn = 0) const override;
bool defended(int turn = 0) const override;