Просмотр исходного кода

First working prototype that mimics rangedFullDamageLimit code

Next step is to create more generic functions that can be shared between the 2.
krs 2 лет назад
Родитель
Сommit
33bbbefdeb

+ 0 - 12
Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsGreen.json

@@ -32,15 +32,3 @@
 	]
 }
 
-
-
-
-
-
-
-
-
-
-
-
-

+ 34 - 0
Mods/vcmi/Sprites/battle/rangeHighlights/rangeHighlightsRed.json

@@ -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
+	]
+}
+

BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/empty.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/fullHex.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/left.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/leftHalf.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/top.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeft.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftCorner.png


BIN
Mods/vcmi/Sprites/battle/rangeHighlights/red/topLeftHalfCorner.png


+ 102 - 26
client/battle/BattleFieldController.cpp

@@ -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();
+
+	flipRangedFullDamageLimitImagesIntoPositions(rangedFullDamageLimitImages);
+	flipRangedFullDamageLimitImagesIntoPositions(shootingRangeLimitImages);
 
 	if(!owner.siegeController)
 	{
@@ -469,6 +474,34 @@ std::vector<BattleHex> BattleFieldController::getRangedFullDamageHexes()
 	return rangedFullDamageHexes;
 }
 
+std::vector<BattleHex> BattleFieldController::getSootingRangeHexes()
+{
+	std::vector<BattleHex> sootingRangeHexes; // used for return
+
+	// if not a hovered arcer unit -> return
+	auto hoveredHex = getHoveredHex();
+	const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+
+	if (!settings["battle"]["rangedFullDamageLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
+		return sootingRangeHexes;
+
+	if(!(hoveredStack && hoveredStack->isShooter()))
+		return sootingRangeHexes;
+
+	auto shootingRangeDistance = hoveredStack->getSootingRangeDistance();
+
+	// get only battlefield hexes that are in full range damage distance
+	std::set<BattleHex> shootingRangeLimit; 
+	for(auto i = 0; i < GameConstants::BFIELD_SIZE; i++)
+	{
+		BattleHex hex(i);
+		if(hex.isAvailable() && BattleHex::getDistance(hoveredHex, hex) <= shootingRangeDistance)
+			sootingRangeHexes.push_back(hex);
+	}
+
+	return sootingRangeHexes;
+}
+
 std::vector<BattleHex> BattleFieldController::getRangedFullDamageLimitHexes(std::vector<BattleHex> rangedFullDamageHexes)
 {
 	std::vector<BattleHex> rangedFullDamageLimitHexes; // used for return
@@ -492,14 +525,37 @@ std::vector<BattleHex> BattleFieldController::getRangedFullDamageLimitHexes(std:
 	return rangedFullDamageLimitHexes;
 }
 
-std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangedFullDamageHexes, std::vector<BattleHex> rangedFullDamageLimitHexes)
+std::vector<BattleHex> BattleFieldController::getShootingRangeLimitHexes(std::vector<BattleHex> shootingRangeHexes)
+{
+	std::vector<BattleHex> shootingRangeLimitHexes; // used for return
+
+	// if not a hovered arcer unit -> return
+	auto hoveredHex = getHoveredHex();
+	const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
+
+	if(!(hoveredStack && hoveredStack->isShooter()))
+		return shootingRangeLimitHexes;
+
+	auto shootingRangeDistance = hoveredStack->getSootingRangeDistance();
+
+	// from ranged full damage hexes get only the ones at the limit
+	for(auto & hex : shootingRangeHexes)
+	{
+		if(BattleHex::getDistance(hoveredHex, hex) == shootingRangeDistance)
+			shootingRangeLimitHexes.push_back(hex);
+	}
+
+	return shootingRangeLimitHexes;
+}
+
+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
 		
@@ -513,9 +569,9 @@ std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeigh
 			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 +581,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::calculateRangeHighlightImages(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,30 +597,30 @@ 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::flipRangedFullDamageLimitImagesIntoPositions(std::shared_ptr<CAnimation> images)
 {
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip();
+	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::bottom])->horizontalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip();
 
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
 
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
 
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
-	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
+	images->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
 }
 
 void BattleFieldController::showHighlightedHexes(Canvas & canvas)
@@ -580,7 +636,13 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 	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);
+	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighligts = calculateRangeHighlightImages(rangedFullDamageLimitHexesNeighbourDirections, rangedFullDamageLimitImages);
+	
+	// calculate array with highlight images for shooting range limit
+	std::vector<BattleHex> shootingRangeHexes = getSootingRangeHexes();
+	std::vector<BattleHex> shootingRangeLimitHexes = getShootingRangeLimitHexes(shootingRangeHexes);
+	std::vector<std::vector<BattleHex::EDir>> shootingRangeLimitHexesNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(shootingRangeHexes, shootingRangeLimitHexes);
+	std::vector<std::shared_ptr<IImage>> shootingRangeLimitHexesHighligts = calculateRangeHighlightImages(shootingRangeLimitHexesNeighbourDirections, shootingRangeLimitImages);
 
 	auto const & hoveredMouseHexes = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
 
@@ -599,6 +661,16 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 			isRangedFullDamageLimit = pos != rangedFullDamageLimitHexes.end();
 		}
 
+		// calculate if hex is Shooting Range Limit and its position in highlight array
+		bool isShootingRangeLimit = false;
+		int hexIndexInShootingRangeLimit = 0;
+		if(!shootingRangeLimitHexes.empty())
+		{
+			auto pos = std::find(shootingRangeLimitHexes.begin(), shootingRangeLimitHexes.end(), hex);
+			hexIndexInShootingRangeLimit = std::distance(shootingRangeLimitHexes.begin(), pos);
+			isShootingRangeLimit = pos != shootingRangeLimitHexes.end();
+		}
+
 		if(stackMovement && mouse) // area where hovered stackMovement can move shown with highlight. Because also affected by mouse cursor, shade as well
 		{
 			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
@@ -616,6 +688,10 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 		{
 			showHighlightedHex(canvas, rangedFullDamageLimitHexesHighligts[hexIndexInRangedFullDamageLimit], hex, false);
 		}
+		if(isShootingRangeLimit)
+		{
+			showHighlightedHex(canvas, shootingRangeLimitHexesHighligts[hexIndexInShootingRangeLimit], hex, false);
+		}
 	}
 }
 

+ 8 - 3
client/battle/BattleFieldController.h

@@ -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;
 
@@ -62,19 +63,23 @@ class BattleFieldController : public CIntObject
 	/// get all hexes where a ranged unit can do full damage
 	std::vector<BattleHex> getRangedFullDamageHexes();
 
+	std::vector<BattleHex> getSootingRangeHexes();
+
 	/// get only hexes at the limit of a ranged unit's full damage range
 	std::vector<BattleHex> getRangedFullDamageLimitHexes(std::vector<BattleHex> rangedFullDamageHexes);
 
+	std::vector<BattleHex> getShootingRangeLimitHexes(std::vector<BattleHex> shootingRangeHexes);
+
 	/// 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);
 
 	/// 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>> calculateRangeHighlightImages(std::vector<std::vector<BattleHex::EDir>> hexesNeighbourDirections, std::shared_ptr<CAnimation> limitImages);
 
 	/// to reduce the number of source images used, some images will be used as flipped versions of preloaded ones
-	void flipRangedFullDamageLimitImagesIntoPositions();
+	void flipRangedFullDamageLimitImagesIntoPositions(std::shared_ptr<CAnimation> images);
 
 	void showBackground(Canvas & canvas);
 	void showBackgroundImage(Canvas & canvas);

+ 18 - 0
lib/battle/CUnitState.cpp

@@ -609,6 +609,24 @@ uint8_t CUnitState::getRangedFullDamageDistance() const
 	return rangedFullDamageDistance;
 }
 
+uint8_t CUnitState::getSootingRangeDistance() const
+{
+	if(!isShooter())
+		return 0;
+
+	uint8_t shootingRangeDistance = GameConstants::BATTLE_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)))
+	{
+		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

+ 1 - 0
lib/battle/CUnitState.h

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