Parcourir la source

Merge pull request #3366 from Laserlicht/battle_informations

Battle stacks informations
Ivan Savenko il y a 1 an
Parent
commit
a9005da01e

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

@@ -183,6 +183,7 @@
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d damage",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d will perish",
+	"vcmi.battleWindow.killed" : "Killed",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Apply battle result",
 
 

+ 1 - 0
Mods/vcmi/config/vcmi/german.json

@@ -180,6 +180,7 @@
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d Schaden",
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d Schaden",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d werden verenden",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d werden verenden",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d werden verenden",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d werden verenden",
+	"vcmi.battleWindow.killed" : "Getötet",
 
 
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
 	"vcmi.battleResultsWindow.applyResultsLabel" : "Kampfergebnis übernehmen",
 
 

+ 113 - 0
client/battle/BattleInterfaceClasses.cpp

@@ -447,6 +447,119 @@ void HeroInfoBasicPanel::show(Canvas & to)
 	CIntObject::show(to);
 	CIntObject::show(to);
 }
 }
 
 
+
+StackInfoBasicPanel::StackInfoBasicPanel(const CStack * stack, Point * position, bool initializeBackground)
+	: CIntObject(0)
+{
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	if (position != nullptr)
+		moveTo(*position);
+
+	if(initializeBackground)
+	{
+		background = std::make_shared<CPicture>(ImagePath::builtin("CCRPOP"));
+		background->pos.y += 37;
+		background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
+		background->colorize(stack->getOwner());
+		background2 = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
+		background2->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
+		background2->colorize(stack->getOwner());
+	}
+
+	initializeData(stack);
+}
+
+void StackInfoBasicPanel::initializeData(const CStack * stack)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), stack->creatureId() + 2, 0, 10, 6));
+	labels.push_back(std::make_shared<CLabel>(10 + 58, 6 + 64, FONT_MEDIUM, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, TextOperations::formatMetric(stack->getCount(), 4)));
+
+	auto attack = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getAttack(stack->isShooter())) + "(" + std::to_string(stack->getAttack(stack->isShooter())) + ")";
+	auto defense = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getDefense(stack->isShooter())) + "(" + std::to_string(stack->getDefense(stack->isShooter())) + ")";
+	auto damage = std::to_string(CGI->creatures()->getByIndex(stack->creatureIndex())->getMinDamage(stack->isShooter())) + "-" + std::to_string(stack->getMaxDamage(stack->isShooter()));
+	auto health = CGI->creatures()->getByIndex(stack->creatureIndex())->getMaxHealth();
+	auto morale = stack->moraleVal();
+	auto luck = stack->luckVal();
+
+	auto killed = stack->getKilled();
+	auto healthRemaining = TextOperations::formatMetric(std::max(stack->getAvailableHealth() - (stack->getCount() - 1) * health, (si64)0), 4);
+
+	//primary stats*/
+	labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
+	labels.push_back(std::make_shared<CLabel>(9, 87, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[381] + ":"));
+	labels.push_back(std::make_shared<CLabel>(9, 99, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[386] + ":"));
+	labels.push_back(std::make_shared<CLabel>(9, 111, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[389] + ":"));
+
+	labels.push_back(std::make_shared<CLabel>(69, 87, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, attack));
+	labels.push_back(std::make_shared<CLabel>(69, 99, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, defense));
+	labels.push_back(std::make_shared<CLabel>(69, 111, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, damage));
+	labels.push_back(std::make_shared<CLabel>(69, 123, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(health)));
+
+	//morale+luck
+	labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
+	labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
+
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("IMRL22"), morale + 3, 0, 47, 131));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ILCK22"), luck + 3, 0, 47, 143));
+
+	//extra information
+	labels.push_back(std::make_shared<CLabel>(9, 168, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, VLC->generaltexth->translate("vcmi.battleWindow.killed") + ":"));
+	labels.push_back(std::make_shared<CLabel>(9, 180, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[389] + ":"));
+
+	labels.push_back(std::make_shared<CLabel>(69, 180, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(killed)));
+	labels.push_back(std::make_shared<CLabel>(69, 192, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, healthRemaining));
+
+	//spells
+	static const Point firstPos(15, 206); // position of 1st spell box
+	static const Point offset(0, 38);  // offset of each spell box from previous
+
+	for(int i = 0; i < 3; i++)
+		icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), 78, 0, firstPos.x + offset.x * i, firstPos.y + offset.y * i));
+
+	int printed=0; //how many effect pics have been printed
+	std::vector<SpellID> spells = stack->activeSpells();
+	for(SpellID effect : spells)
+	{
+		//not all effects have graphics (for eg. Acid Breath)
+		//for modded spells iconEffect is added to SpellInt.def
+		const bool hasGraphics = (effect < SpellID::THUNDERBOLT) || (effect >= SpellID::AFTER_LAST);
+
+		if (hasGraphics)
+		{
+			//FIXME: support permanent duration
+			int duration = stack->getBonusLocalFirst(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)))->turnsRemain;
+
+			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
+			if(settings["general"]["enableUiEnhancements"].Bool())
+				labels.push_back(std::make_shared<CLabel>(firstPos.x + offset.x * printed + 46, firstPos.y + offset.y * printed + 36, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(duration)));
+			if(++printed >= 3 || (printed == 2 && spells.size() > 3)) // interface limit reached
+				break;
+		}
+	}
+
+	if(spells.size() == 0)
+		labelsMultiline.push_back(std::make_shared<CMultiLineLabel>(Rect(firstPos.x, firstPos.y, 48, 36), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[674]));
+	if(spells.size() > 3)
+		labelsMultiline.push_back(std::make_shared<CMultiLineLabel>(Rect(firstPos.x + offset.x * 2, firstPos.y + offset.y * 2 - 4, 48, 36), EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, "..."));
+}
+
+void StackInfoBasicPanel::update(const CStack * updatedInfo)
+{
+	icons.clear();
+	labels.clear();
+	labelsMultiline.clear();
+
+	initializeData(updatedInfo);
+}
+
+void StackInfoBasicPanel::show(Canvas & to)
+{
+	showAll(to);
+	CIntObject::show(to);
+}
+
 HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
 HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
 	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("CHRPOP"))
 	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("CHRPOP"))
 {
 {

+ 18 - 0
client/battle/BattleInterfaceClasses.h

@@ -37,6 +37,7 @@ class CFilledTexture;
 class CButton;
 class CButton;
 class CToggleButton;
 class CToggleButton;
 class CLabel;
 class CLabel;
+class CMultiLineLabel;
 class CTextBox;
 class CTextBox;
 class CAnimImage;
 class CAnimImage;
 class TransparentFilledRectangle;
 class TransparentFilledRectangle;
@@ -145,6 +146,23 @@ public:
 	void update(const InfoAboutHero & updatedInfo);
 	void update(const InfoAboutHero & updatedInfo);
 };
 };
 
 
+class StackInfoBasicPanel : public CIntObject
+{
+private:
+	std::shared_ptr<CPicture> background;
+	std::shared_ptr<CPicture> background2;
+	std::vector<std::shared_ptr<CLabel>> labels;
+	std::vector<std::shared_ptr<CMultiLineLabel>> labelsMultiline;
+	std::vector<std::shared_ptr<CAnimImage>> icons;
+public:
+	StackInfoBasicPanel(const CStack * stack, Point * position, bool initializeBackground = true);
+
+	void show(Canvas & to) override;
+
+	void initializeData(const CStack * stack);
+	void update(const CStack * updatedInfo);
+};
+
 class HeroInfoWindow : public CWindowObject
 class HeroInfoWindow : public CWindowObject
 {
 {
 private:
 private:

+ 8 - 0
client/battle/BattleStacksController.cpp

@@ -26,6 +26,7 @@
 #include "../CMusicHandler.h"
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CGameInfo.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../gui/WindowHandler.h"
 #include "../render/Colors.h"
 #include "../render/Colors.h"
 #include "../render/Canvas.h"
 #include "../render/Canvas.h"
 #include "../render/IRenderHandler.h"
 #include "../render/IRenderHandler.h"
@@ -812,6 +813,9 @@ void BattleStacksController::updateHoveredStacks()
 {
 {
 	auto newStacks = selectHoveredStacks();
 	auto newStacks = selectHoveredStacks();
 
 
+	if(newStacks.size() == 0)
+		owner.windowObject->updateStackInfoWindow(nullptr);
+
 	for(const auto * stack : mouseHoveredStacks)
 	for(const auto * stack : mouseHoveredStacks)
 	{
 	{
 		if (vstd::contains(newStacks, stack))
 		if (vstd::contains(newStacks, stack))
@@ -828,11 +832,15 @@ void BattleStacksController::updateHoveredStacks()
 		if (vstd::contains(mouseHoveredStacks, stack))
 		if (vstd::contains(mouseHoveredStacks, stack))
 			continue;
 			continue;
 
 
+		owner.windowObject->updateStackInfoWindow(newStacks.size() == 1 && vstd::find_pos(newStacks, stack) == 0 ? stack : nullptr);
 		stackAnimation[stack->unitId()]->setBorderColor(AnimationControls::getBlueBorder());
 		stackAnimation[stack->unitId()]->setBorderColor(AnimationControls::getBlueBorder());
 		if (stackAnimation[stack->unitId()]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
 		if (stackAnimation[stack->unitId()]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
 			stackAnimation[stack->unitId()]->playOnce(ECreatureAnimType::MOUSEON);
 			stackAnimation[stack->unitId()]->playOnce(ECreatureAnimType::MOUSEON);
 	}
 	}
 
 
+	if(mouseHoveredStacks != newStacks)
+		GH.windows().totalRedraw(); //fix for frozen stack info window and blue border in action bar
+
 	mouseHoveredStacks = newStacks;
 	mouseHoveredStacks = newStacks;
 }
 }
 
 

+ 2 - 1
client/battle/BattleStacksController.h

@@ -94,7 +94,6 @@ class BattleStacksController
 	void tickFrameBattleAnimations(uint32_t msPassed);
 	void tickFrameBattleAnimations(uint32_t msPassed);
 
 
 	void updateBattleAnimations(uint32_t msPassed);
 	void updateBattleAnimations(uint32_t msPassed);
-	void updateHoveredStacks();
 
 
 	std::vector<const CStack *> selectHoveredStacks();
 	std::vector<const CStack *> selectHoveredStacks();
 
 
@@ -127,6 +126,8 @@ public:
 	void showAliveStack(Canvas & canvas, const CStack * stack);
 	void showAliveStack(Canvas & canvas, const CStack * stack);
 	void showStack(Canvas & canvas, const CStack * stack);
 	void showStack(Canvas & canvas, const CStack * stack);
 
 
+	void updateHoveredStacks();
+
 	void collectRenderableObjects(BattleRenderer & renderer);
 	void collectRenderableObjects(BattleRenderer & renderer);
 
 
 	/// Adds new color filter effect targeting stack
 	/// Adds new color filter effect targeting stack

+ 29 - 0
client/battle/BattleWindow.cpp

@@ -287,6 +287,35 @@ void BattleWindow::updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero
 	panelToUpdate->update(hero);
 	panelToUpdate->update(hero);
 }
 }
 
 
+void BattleWindow::updateStackInfoWindow(const CStack * stack)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	bool showInfoWindows = settings["battle"]["stickyHeroInfoWindows"].Bool();
+
+	if(stack && stack->unitSide() == BattleSide::DEFENDER)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x + pos.w + 15, defenderHeroWindow ? defenderHeroWindow->pos.y + 210 : pos.y)
+				: Point(pos.x + pos.w -79, defenderHeroWindow ? defenderHeroWindow->pos.y : pos.y + 135);
+		defenderStackWindow = std::make_shared<StackInfoBasicPanel>(stack, &position);
+		defenderStackWindow->setEnabled(showInfoWindows);
+	}
+	else
+		defenderStackWindow = nullptr;
+	
+	if(stack && stack->unitSide() == BattleSide::ATTACKER)
+	{
+		Point position = (GH.screenDimensions().x >= 1000)
+				? Point(pos.x - 93, attackerHeroWindow ? attackerHeroWindow->pos.y + 210 : pos.y)
+				: Point(pos.x + 1, attackerHeroWindow ? attackerHeroWindow->pos.y : pos.y + 135);
+		attackerStackWindow = std::make_shared<StackInfoBasicPanel>(stack, &position);
+		attackerStackWindow->setEnabled(showInfoWindows);
+	}
+	else
+		attackerStackWindow = nullptr;
+}
+
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
 void BattleWindow::heroManaPointsChanged(const CGHeroInstance * hero)
 {
 {
 	if(hero == owner.attackingHeroInstance || hero == owner.defendingHeroInstance)
 	if(hero == owner.attackingHeroInstance || hero == owner.defendingHeroInstance)

+ 6 - 0
client/battle/BattleWindow.h

@@ -26,6 +26,7 @@ class BattleRenderer;
 class StackQueue;
 class StackQueue;
 class TurnTimerWidget;
 class TurnTimerWidget;
 class HeroInfoBasicPanel;
 class HeroInfoBasicPanel;
+class StackInfoBasicPanel;
 
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
 /// GUI object that handles functionality of panel at the bottom of combat screen
 class BattleWindow : public InterfaceObjectConfigurable
 class BattleWindow : public InterfaceObjectConfigurable
@@ -36,6 +37,8 @@ class BattleWindow : public InterfaceObjectConfigurable
 	std::shared_ptr<BattleConsole> console;
 	std::shared_ptr<BattleConsole> console;
 	std::shared_ptr<HeroInfoBasicPanel> attackerHeroWindow;
 	std::shared_ptr<HeroInfoBasicPanel> attackerHeroWindow;
 	std::shared_ptr<HeroInfoBasicPanel> defenderHeroWindow;
 	std::shared_ptr<HeroInfoBasicPanel> defenderHeroWindow;
+	std::shared_ptr<StackInfoBasicPanel> attackerStackWindow;
+	std::shared_ptr<StackInfoBasicPanel> defenderStackWindow;
 
 
 	std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
 	std::shared_ptr<TurnTimerWidget> attackerTimerWidget;
 	std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
 	std::shared_ptr<TurnTimerWidget> defenderTimerWidget;
@@ -100,6 +103,9 @@ public:
 	/// Refresh sticky variant of hero info window after spellcast, side same as in BattleSpellCast::side
 	/// Refresh sticky variant of hero info window after spellcast, side same as in BattleSpellCast::side
 	void updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero);
 	void updateHeroInfoWindow(uint8_t side, const InfoAboutHero & hero);
 
 
+	/// Refresh sticky variant of hero info window after spellcast, side same as in BattleSpellCast::side
+	void updateStackInfoWindow(const CStack * stack);
+
 	/// Get mouse-hovered battle queue unit ID if any found
 	/// Get mouse-hovered battle queue unit ID if any found
 	std::optional<uint32_t> getQueueHoveredUnitId();
 	std::optional<uint32_t> getQueueHoveredUnitId();
 
 

+ 2 - 0
client/widgets/MiscWidgets.cpp

@@ -611,6 +611,8 @@ void MoraleLuckBox::set(const AFactionMember * node)
 
 
 	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3);
 	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3);
 	image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon
 	image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon
+	if(settings["general"]["enableUiEnhancements"].Bool())
+		label = std::make_shared<CLabel>(small ? 30 : 42, small ? 20 : 38, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(modifierList->totalValue()));
 }
 }
 
 
 MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)
 MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)

+ 1 - 0
client/widgets/MiscWidgets.h

@@ -238,6 +238,7 @@ public:
 class MoraleLuckBox : public LRClickableAreaWTextComp
 class MoraleLuckBox : public LRClickableAreaWTextComp
 {
 {
 	std::shared_ptr<CAnimImage> image;
 	std::shared_ptr<CAnimImage> image;
+	std::shared_ptr<CLabel> label;
 public:
 public:
 	bool morale; //true if morale, false if luck
 	bool morale; //true if morale, false if luck
 	bool small;
 	bool small;

+ 1 - 0
client/windows/CCreatureWindow.cpp

@@ -229,6 +229,7 @@ CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int
 			boost::replace_first(spellText, "%d", std::to_string(duration));
 			boost::replace_first(spellText, "%d", std::to_string(duration));
 
 
 			spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
 			spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
+			labels.push_back(std::make_shared<CLabel>(firstPos.x + offset.x * printed + 46, firstPos.y + offset.y * printed + 36, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(duration)));
 			clickableAreas.push_back(std::make_shared<LRClickableAreaWText>(Rect(firstPos + offset * printed, Point(50, 38)), spellText, spellText));
 			clickableAreas.push_back(std::make_shared<LRClickableAreaWText>(Rect(firstPos + offset * printed, Point(50, 38)), spellText, spellText));
 			if(++printed >= 8) // interface limit reached
 			if(++printed >= 8) // interface limit reached
 				break;
 				break;

+ 1 - 0
client/windows/CCreatureWindow.h

@@ -72,6 +72,7 @@ class CStackWindow : public CWindowObject
 	{
 	{
 		std::vector<std::shared_ptr<CAnimImage>> spellIcons;
 		std::vector<std::shared_ptr<CAnimImage>> spellIcons;
 		std::vector<std::shared_ptr<LRClickableAreaWText>> clickableAreas;
 		std::vector<std::shared_ptr<LRClickableAreaWText>> clickableAreas;
+		std::vector<std::shared_ptr<CLabel>> labels;
 	public:
 	public:
 		ActiveSpellsSection(CStackWindow * owner, int yOffset);
 		ActiveSpellsSection(CStackWindow * owner, int yOffset);
 	};
 	};