Explorar o código

Separated battle control panel into new class, refactoring of
CGStatusBar to allow shared API with battle console

Ivan Savenko %!s(int64=2) %!d(string=hai) anos
pai
achega
7a6ad671ab

+ 2 - 0
client/CMakeLists.txt

@@ -3,6 +3,7 @@ set(client_SRCS
 		../CCallback.cpp
 
 		battle/CBattleAnimations.cpp
+		battle/CBattleControlPanel.cpp
 		battle/CBattleInterfaceClasses.cpp
 		battle/CBattleInterface.cpp
 		battle/CBattleFieldController.cpp
@@ -80,6 +81,7 @@ set(client_HEADERS
 		StdInc.h
 
 		battle/CBattleAnimations.h
+		battle/CBattleControlPanel.h
 		battle/CBattleInterfaceClasses.h
 		battle/CBattleInterface.h
 		battle/CBattleFieldController.h

+ 4 - 3
client/CPlayerInterface.cpp

@@ -15,6 +15,7 @@
 #include "battle/CBattleInterface.h"
 #include "battle/CBattleFieldController.h"
 #include "battle/CBattleInterfaceClasses.h"
+#include "battle/CBattleControlPanel.h"
 #include "../CCallback.h"
 #include "windows/CCastleInterface.h"
 #include "gui/CCursorHandler.h"
@@ -1028,19 +1029,19 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
 
 	if(ba->lucky()) //lucky hit
 	{
-		battleInt->console->addText(attacker->formatGeneralMessage(-45));
+		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-45));
 		battleInt->displayEffect(18, attacker->getPosition());
 		CCS->soundh->playSound(soundBase::GOODLUCK);
 	}
 	if(ba->unlucky()) //unlucky hit
 	{
-		battleInt->console->addText(attacker->formatGeneralMessage(-44));
+		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-44));
 		battleInt->displayEffect(48, attacker->getPosition());
 		CCS->soundh->playSound(soundBase::BADLUCK);
 	}
 	if(ba->deathBlow())
 	{
-		battleInt->console->addText(attacker->formatGeneralMessage(365));
+		battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(365));
 		for(auto & elem : ba->bsa)
 		{
 			const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);

+ 304 - 0
client/battle/CBattleControlPanel.cpp

@@ -0,0 +1,304 @@
+/*
+ * CBattleControlPanel.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "CBattleControlPanel.h"
+#include "CBattleInterface.h"
+#include "CBattleInterfaceClasses.h"
+#include "../widgets/Buttons.h"
+#include "../CGameInfo.h"
+#include "../CBitmapHandler.h"
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+#include "../CPlayerInterface.h"
+#include "../../CCallback.h"
+#include "../gui/CCursorHandler.h"
+#include "../gui/CGuiHandler.h"
+#include "../windows/CSpellWindow.h"
+#include "../../lib/CStack.h"
+#include "../../lib/CConfigHandler.h"
+
+CBattleControlPanel::CBattleControlPanel(CBattleInterface * owner):
+	owner(owner)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	//preparing buttons and console
+	bOptions = std::make_shared<CButton>    (Point(  3,  5), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&CBattleControlPanel::bOptionsf,this), SDLK_o);
+	bSurrender = std::make_shared<CButton>  (Point( 54,  5), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&CBattleControlPanel::bSurrenderf,this), SDLK_s);
+	bFlee = std::make_shared<CButton>       (Point(105,  5), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&CBattleControlPanel::bFleef,this), SDLK_r);
+	bAutofight = std::make_shared<CButton>  (Point(157,  5), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&CBattleControlPanel::bAutofightf,this), SDLK_a);
+	bSpell = std::make_shared<CButton>      (Point(645,  5), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&CBattleControlPanel::bSpellf,this), SDLK_c);
+	bWait = std::make_shared<CButton>       (Point(696,  5), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&CBattleControlPanel::bWaitf,this), SDLK_w);
+	bDefence = std::make_shared<CButton>    (Point(747,  5), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&CBattleControlPanel::bDefencef,this), SDLK_d);
+	bConsoleUp = std::make_shared<CButton>  (Point(624,  5), "ComSlide.def", std::make_pair("", ""),     std::bind(&CBattleControlPanel::bConsoleUpf,this), SDLK_UP);
+	bConsoleDown = std::make_shared<CButton>(Point(624, 24), "ComSlide.def", std::make_pair("", ""),     std::bind(&CBattleControlPanel::bConsoleDownf,this), SDLK_DOWN);
+
+	bDefence->assignedKeys.insert(SDLK_SPACE);
+	bConsoleUp->setImageOrder(0, 1, 0, 0);
+	bConsoleDown->setImageOrder(2, 3, 2, 2);
+
+	console = std::make_shared<CBattleConsole>(Rect(211, 4, 406,38));
+	GH.statusbar = console;
+
+	if ( owner->tacticsMode )
+		tacticPhaseStarted();
+	else
+		tacticPhaseEnded();
+}
+
+void CBattleControlPanel::tacticPhaseStarted()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	btactNext = std::make_shared<CButton>(Point(213, 4), "icm011.def", std::make_pair("", ""), [&]() { bTacticNextStack();}, SDLK_SPACE);
+	btactEnd = std::make_shared<CButton>(Point(419,  4), "icm012.def", std::make_pair("", ""),  [&](){ bTacticPhaseEnd();}, SDLK_RETURN);
+	menu = std::make_shared<CPicture>("COPLACBR.BMP", 0, 0);
+	menu->colorize(owner->curInt->playerID);
+}
+void CBattleControlPanel::tacticPhaseEnded()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
+	menu->colorize(owner->curInt->playerID);
+}
+
+void CBattleControlPanel::bOptionsf()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+
+	Rect tempRect = genRect(431, 481, 160, 84);
+	tempRect += pos.topLeft();
+	GH.pushIntT<CBattleOptionsWindow>(tempRect, owner);
+}
+
+void CBattleControlPanel::bSurrenderf()
+{
+	if(owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	int cost = owner->curInt->cb->battleGetSurrenderCost();
+	if(cost >= 0)
+	{
+		std::string enemyHeroName = owner->curInt->cb->battleGetEnemyHero().name;
+		if(enemyHeroName.empty())
+		{
+			logGlobal->warn("Surrender performed without enemy hero, should not happen!");
+			enemyHeroName = "#ENEMY#";
+		}
+
+		std::string surrenderMessage = boost::str(boost::format(CGI->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
+		owner->curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
+	}
+}
+
+void CBattleControlPanel::bFleef()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	if ( owner->curInt->cb->battleCanFlee() )
+	{
+		CFunctionList<void()> ony = std::bind(&CBattleControlPanel::reallyFlee,this);
+		owner->curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
+	}
+	else
+	{
+		std::vector<std::shared_ptr<CComponent>> comps;
+		std::string heroName;
+		//calculating fleeing hero's name
+		if (owner->attackingHeroInstance)
+			if (owner->attackingHeroInstance->tempOwner == owner->curInt->cb->getMyColor())
+				heroName = owner->attackingHeroInstance->name;
+		if (owner->defendingHeroInstance)
+			if (owner->defendingHeroInstance->tempOwner == owner->curInt->cb->getMyColor())
+				heroName = owner->defendingHeroInstance->name;
+		//calculating text
+		auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present.  %s can not retreat!
+
+		//printing message
+		owner->curInt->showInfoDialog(boost::to_string(txt), comps);
+	}
+}
+
+void CBattleControlPanel::reallyFlee()
+{
+	owner->giveCommand(EActionType::RETREAT);
+	CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+}
+
+void CBattleControlPanel::reallySurrender()
+{
+	if (owner->curInt->cb->getResourceAmount(Res::GOLD) < owner->curInt->cb->battleGetSurrenderCost())
+	{
+		owner->curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
+	}
+	else
+	{
+		owner->giveCommand(EActionType::SURRENDER);
+		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
+	}
+}
+
+void CBattleControlPanel::bAutofightf()
+{
+	if(owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	//Stop auto-fight mode
+	if(owner->curInt->isAutoFightOn)
+	{
+		assert(owner->curInt->autofightingAI);
+		owner->curInt->isAutoFightOn = false;
+		logGlobal->trace("Stopping the autofight...");
+	}
+	else if(!owner->curInt->autofightingAI)
+	{
+		owner->curInt->isAutoFightOn = true;
+		blockUI(true);
+
+		auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
+		ai->init(owner->curInt->env, owner->curInt->cb);
+		ai->battleStart(owner->army1, owner->army2, int3(0,0,0), owner->attackingHeroInstance, owner->defendingHeroInstance, owner->curInt->cb->battleGetMySide());
+		owner->curInt->autofightingAI = ai;
+		owner->curInt->cb->registerBattleInterface(ai);
+
+		owner->requestAutofightingAIToTakeAction();
+	}
+}
+
+void CBattleControlPanel::bSpellf()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	if (!owner->myTurn)
+		return;
+
+	auto myHero = owner->currentHero();
+	if(!myHero)
+		return;
+
+	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
+
+	ESpellCastProblem::ESpellCastProblem spellCastProblem = owner->curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
+
+	if(spellCastProblem == ESpellCastProblem::OK)
+	{
+		GH.pushIntT<CSpellWindow>(myHero, owner->curInt.get());
+	}
+	else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
+	{
+		//TODO: move to spell mechanics, add more information to spell cast problem
+		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
+		auto blockingBonus = owner->currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
+		if (!blockingBonus)
+			return;
+
+		if (blockingBonus->source == Bonus::ARTIFACT)
+		{
+			const int32_t artID = blockingBonus->sid;
+			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
+			//TODO check who *really* is source of bonus
+			std::string heroName = myHero->hasArt(artID) ? myHero->name : owner->enemyHero().name;
+
+			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
+			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
+										% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
+		}
+	}
+}
+
+void CBattleControlPanel::bWaitf()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	if (owner->activeStack != nullptr)
+		owner->giveCommand(EActionType::WAIT);
+}
+
+void CBattleControlPanel::bDefencef()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	if (owner->activeStack != nullptr)
+		owner->giveCommand(EActionType::DEFEND);
+}
+
+void CBattleControlPanel::bConsoleUpf()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	console->scrollUp();
+}
+
+void CBattleControlPanel::bConsoleDownf()
+{
+	if (owner->spellDestSelectMode) //we are casting a spell
+		return;
+
+	console->scrollDown();
+}
+
+void CBattleControlPanel::bTacticNextStack()
+{
+	owner->tacticNextStack(nullptr);
+}
+
+void CBattleControlPanel::bTacticPhaseEnd()
+{
+	owner->tacticPhaseEnd();
+}
+
+void CBattleControlPanel::blockUI(bool on)
+{
+	bool canCastSpells = false;
+	auto hero = owner->curInt->cb->battleGetMyHero();
+
+	if(hero)
+	{
+		ESpellCastProblem::ESpellCastProblem spellcastingProblem = owner->curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
+
+		//if magic is blocked, we leave button active, so the message can be displayed after button click
+		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
+	}
+
+	bool canWait = owner->activeStack ? !owner->activeStack->waitedThisTurn : false;
+
+	bOptions->block(on);
+	bFlee->block(on || !owner->curInt->cb->battleCanFlee());
+	bSurrender->block(on || owner->curInt->cb->battleGetSurrenderCost() < 0);
+
+	// block only if during enemy turn and auto-fight is off
+	// otherwise - crash on accessing non-exisiting active stack
+	bAutofight->block(!owner->curInt->isAutoFightOn && !owner->activeStack);
+
+	if (owner->tacticsMode && btactEnd && btactNext)
+	{
+		btactNext->block(on);
+		btactEnd->block(on);
+	}
+	else
+	{
+		bConsoleUp->block(on);
+		bConsoleDown->block(on);
+	}
+
+
+	bSpell->block(on || owner->tacticsMode || !canCastSpells);
+	bWait->block(on || owner->tacticsMode || !canWait);
+	bDefence->block(on || owner->tacticsMode);
+}

+ 64 - 0
client/battle/CBattleControlPanel.h

@@ -0,0 +1,64 @@
+/*
+ * CBattleControlPanel.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../gui/CIntObject.h"
+
+class CStack;
+class CButton;
+class CBattleInterface;
+class CBattleConsole;
+
+class CBattleControlPanel : public CIntObject
+{
+
+	CBattleInterface * owner;
+
+	std::shared_ptr<CPicture> menu;
+
+	std::shared_ptr<CButton> bOptions;
+	std::shared_ptr<CButton> bSurrender;
+	std::shared_ptr<CButton> bFlee;
+	std::shared_ptr<CButton> bAutofight;
+	std::shared_ptr<CButton> bSpell;
+	std::shared_ptr<CButton> bWait;
+	std::shared_ptr<CButton> bDefence;
+	std::shared_ptr<CButton> bConsoleUp;
+	std::shared_ptr<CButton> bConsoleDown;
+	std::shared_ptr<CButton> btactNext;
+	std::shared_ptr<CButton> btactEnd;
+
+	//button handle funcs:
+	void bOptionsf();
+	void bSurrenderf();
+	void bFleef();
+	void reallyFlee(); //performs fleeing without asking player
+	void reallySurrender(); //performs surrendering without asking player
+	void bAutofightf();
+	void bSpellf();
+	void bWaitf();
+	void bDefencef();
+	void bConsoleUpf();
+	void bConsoleDownf();
+	void bTacticNextStack();
+	void bTacticPhaseEnd();
+
+public:
+	std::shared_ptr<CBattleConsole> console;
+
+	// block all UI elements, e.g. during enemy turn
+	// unlike activate/deactivate this method will correctly grey-out all elements
+	void blockUI(bool on);
+
+	void tacticPhaseStarted();
+	void tacticPhaseEnded();
+
+	CBattleControlPanel(CBattleInterface * owner);
+};

+ 26 - 343
client/battle/CBattleInterface.cpp

@@ -17,6 +17,7 @@
 #include "CBattleObstacleController.h"
 #include "CBattleSiegeController.h"
 #include "CBattleFieldController.h"
+#include "CBattleControlPanel.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -200,37 +201,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	CSDL_Ext::alphaTransform(amountEffNeutral);
 	transformPalette(amountEffNeutral, 1.00, 1.00, 0.18);
 
-	//preparing buttons and console
-	bOptions = std::make_shared<CButton>(Point(  3, 561), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&CBattleInterface::bOptionsf,this), SDLK_o);
-	bSurrender = std::make_shared<CButton>(Point( 54, 561), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&CBattleInterface::bSurrenderf,this), SDLK_s);
-	bFlee = std::make_shared<CButton>(Point(105, 561), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&CBattleInterface::bFleef,this), SDLK_r);
-	bAutofight = std::make_shared<CButton>(Point(157, 561), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&CBattleInterface::bAutofightf,this), SDLK_a);
-	bSpell = std::make_shared<CButton>(Point(645, 561), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&CBattleInterface::bSpellf,this), SDLK_c);
-	bWait = std::make_shared<CButton>(Point(696, 561), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&CBattleInterface::bWaitf,this), SDLK_w);
-	bDefence = std::make_shared<CButton>(Point(747, 561), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&CBattleInterface::bDefencef,this), SDLK_d);
-	bDefence->assignedKeys.insert(SDLK_SPACE);
-	bConsoleUp = std::make_shared<CButton>(Point(624, 561), "ComSlide.def", std::make_pair("", ""), std::bind(&CBattleInterface::bConsoleUpf,this), SDLK_UP);
-	bConsoleDown = std::make_shared<CButton>(Point(624, 580), "ComSlide.def", std::make_pair("", ""), std::bind(&CBattleInterface::bConsoleDownf,this), SDLK_DOWN);
-	bConsoleUp->setImageOrder(0, 1, 0, 0);
-	bConsoleDown->setImageOrder(2, 3, 2, 2);
-
-	console = std::make_shared<CBattleConsole>();
-	console->pos.x += 211;
-	console->pos.y += 560;
-	console->pos.w = 406;
-	console->pos.h = 38;
-	if(tacticsMode)
-	{
-		btactNext = std::make_shared<CButton>(Point(213, 560), "icm011.def", std::make_pair("", ""), [&](){ bTacticNextStack(nullptr);}, SDLK_SPACE);
-		btactEnd = std::make_shared<CButton>(Point(419, 560), "icm012.def", std::make_pair("", ""), [&](){ bEndTacticPhase();}, SDLK_RETURN);
-		menu = BitmapHandler::loadBitmap("COPLACBR.BMP");
-	}
-	else
-	{
-		menu = BitmapHandler::loadBitmap("CBAR.BMP");
-	}
-	graphics->blueToPlayersAdv(menu, curInt->playerID);
-
 	//loading hero animations
 	if(hero1) // attacking hero
 	{
@@ -281,7 +251,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	obstacleController.reset(new CBattleObstacleController(this));
 
 	if(tacticsMode)
-		bTacticNextStack();
+		tacticNextStack(nullptr);
 
 	CCS->musich->stopMusic();
 	battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
@@ -291,7 +261,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 		{
 			CCS->musich->playMusicFromSet("battle", true, true);
 			battleActionsStarted = true;
-			blockUI(settings["session"]["spectate"].Bool());
+			controlPanel->blockUI(settings["session"]["spectate"].Bool());
 			battleIntroSoundChannel = -1;
 		}
 	};
@@ -301,7 +271,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 	currentAction = PossiblePlayerBattleAction::INVALID;
 	selectedAction = PossiblePlayerBattleAction::INVALID;
 	addUsedEvents(RCLICK | MOVE | KEYBOARD);
-	blockUI(true);
+	controlPanel->blockUI(true);
 }
 
 CBattleInterface::~CBattleInterface()
@@ -313,7 +283,6 @@ CBattleInterface::~CBattleInterface()
 	{
 		deactivate();
 	}
-	SDL_FreeSurface(menu);
 	SDL_FreeSurface(amountNormal);
 	SDL_FreeSurface(amountNegative);
 	SDL_FreeSurface(amountPositive);
@@ -357,20 +326,12 @@ void CBattleInterface::setPrintMouseShadow(bool set)
 
 void CBattleInterface::activate()
 {
+	controlPanel->activate();
+
 	if (curInt->isAutoFightOn)
-	{
-		bAutofight->activate();
 		return;
-	}
 
 	CIntObject::activate();
-	bOptions->activate();
-	bSurrender->activate();
-	bFlee->activate();
-	bAutofight->activate();
-	bSpell->activate();
-	bWait->activate();
-	bDefence->activate();
 
 	if (attackingHero)
 		attackingHero->activate();
@@ -382,32 +343,14 @@ void CBattleInterface::activate()
 	if (settings["battle"]["showQueue"].Bool())
 		queue->activate();
 
-	if (tacticsMode)
-	{
-		btactNext->activate();
-		btactEnd->activate();
-	}
-	else
-	{
-		bConsoleUp->activate();
-		bConsoleDown->activate();
-	}
-
 	LOCPLINT->cingconsole->activate();
 }
 
 void CBattleInterface::deactivate()
 {
+	controlPanel->deactivate();
 	CIntObject::deactivate();
 
-	bOptions->deactivate();
-	bSurrender->deactivate();
-	bFlee->deactivate();
-	bAutofight->deactivate();
-	bSpell->deactivate();
-	bWait->deactivate();
-	bDefence->deactivate();
-
 	fieldController->deactivate();
 
 	if (attackingHero)
@@ -417,17 +360,6 @@ void CBattleInterface::deactivate()
 	if (settings["battle"]["showQueue"].Bool())
 		queue->deactivate();
 
-	if (tacticsMode)
-	{
-		btactNext->deactivate();
-		btactEnd->deactivate();
-	}
-	else
-	{
-		bConsoleUp->deactivate();
-		bConsoleDown->deactivate();
-	}
-
 	LOCPLINT->cingconsole->deactivate();
 }
 
@@ -468,189 +400,6 @@ void CBattleInterface::clickRight(tribool down, bool previousState)
 	}
 }
 
-void CBattleInterface::bOptionsf()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
-
-	Rect tempRect = genRect(431, 481, 160, 84);
-	tempRect += pos.topLeft();
-	GH.pushIntT<CBattleOptionsWindow>(tempRect, this);
-}
-
-void CBattleInterface::bSurrenderf()
-{
-	if(spellDestSelectMode) //we are casting a spell
-		return;
-
-	int cost = curInt->cb->battleGetSurrenderCost();
-	if(cost >= 0)
-	{
-		std::string enemyHeroName = curInt->cb->battleGetEnemyHero().name;
-		if(enemyHeroName.empty())
-		{
-			logGlobal->warn("Surrender performed without enemy hero, should not happen!");
-			enemyHeroName = "#ENEMY#";
-		}
-
-		std::string surrenderMessage = boost::str(boost::format(CGI->generaltexth->allTexts[32]) % enemyHeroName % cost); //%s states: "I will accept your surrender and grant you and your troops safe passage for the price of %d gold."
-		curInt->showYesNoDialog(surrenderMessage, [this](){ reallySurrender(); }, nullptr);
-	}
-}
-
-void CBattleInterface::bFleef()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	if ( curInt->cb->battleCanFlee() )
-	{
-		CFunctionList<void()> ony = std::bind(&CBattleInterface::reallyFlee,this);
-		curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
-	}
-	else
-	{
-		std::vector<std::shared_ptr<CComponent>> comps;
-		std::string heroName;
-		//calculating fleeing hero's name
-		if (attackingHeroInstance)
-			if (attackingHeroInstance->tempOwner == curInt->cb->getMyColor())
-				heroName = attackingHeroInstance->name;
-		if (defendingHeroInstance)
-			if (defendingHeroInstance->tempOwner == curInt->cb->getMyColor())
-				heroName = defendingHeroInstance->name;
-		//calculating text
-		auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present.  %s can not retreat!
-
-		//printing message
-		curInt->showInfoDialog(boost::to_string(txt), comps);
-	}
-}
-
-void CBattleInterface::reallyFlee()
-{
-	giveCommand(EActionType::RETREAT);
-	CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-}
-
-void CBattleInterface::reallySurrender()
-{
-	if (curInt->cb->getResourceAmount(Res::GOLD) < curInt->cb->battleGetSurrenderCost())
-	{
-		curInt->showInfoDialog(CGI->generaltexth->allTexts[29]); //You don't have enough gold!
-	}
-	else
-	{
-		giveCommand(EActionType::SURRENDER);
-		CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
-	}
-}
-
-void CBattleInterface::bAutofightf()
-{
-	if(spellDestSelectMode) //we are casting a spell
-		return;
-
-	//Stop auto-fight mode
-	if(curInt->isAutoFightOn)
-	{
-		assert(curInt->autofightingAI);
-		curInt->isAutoFightOn = false;
-		logGlobal->trace("Stopping the autofight...");
-	}
-	else if(!curInt->autofightingAI)
-	{
-		curInt->isAutoFightOn = true;
-		blockUI(true);
-
-		auto ai = CDynLibHandler::getNewBattleAI(settings["server"]["friendlyAI"].String());
-		ai->init(curInt->env, curInt->cb);
-		ai->battleStart(army1, army2, int3(0,0,0), attackingHeroInstance, defendingHeroInstance, curInt->cb->battleGetMySide());
-		curInt->autofightingAI = ai;
-		curInt->cb->registerBattleInterface(ai);
-
-		requestAutofightingAIToTakeAction();
-	}
-}
-
-void CBattleInterface::bSpellf()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	if (!myTurn)
-		return;
-
-	auto myHero = currentHero();
-	if(!myHero)
-		return;
-
-	CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
-
-	ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
-
-	if(spellCastProblem == ESpellCastProblem::OK)
-	{
-		GH.pushIntT<CSpellWindow>(myHero, curInt.get());
-	}
-	else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
-	{
-		//TODO: move to spell mechanics, add more information to spell cast problem
-		//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
-		auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type()(Bonus::BLOCK_ALL_MAGIC));
-		if (!blockingBonus)
-			return;
-
-		if (blockingBonus->source == Bonus::ARTIFACT)
-		{
-			const int32_t artID = blockingBonus->sid;
-			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
-			//TODO check who *really* is source of bonus
-			std::string heroName = myHero->hasArt(artID) ? myHero->name : enemyHero().name;
-
-			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
-			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
-										% heroName % CGI->artifacts()->getByIndex(artID)->getName()));
-		}
-	}
-}
-
-void CBattleInterface::bWaitf()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	if (activeStack != nullptr)
-		giveCommand(EActionType::WAIT);
-}
-
-void CBattleInterface::bDefencef()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	if (activeStack != nullptr)
-		giveCommand(EActionType::DEFEND);
-}
-
-void CBattleInterface::bConsoleUpf()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	console->scrollUp();
-}
-
-void CBattleInterface::bConsoleDownf()
-{
-	if (spellDestSelectMode) //we are casting a spell
-		return;
-
-	console->scrollDown();
-}
-
 void CBattleInterface::unitAdded(const CStack * stack)
 {
 	creDir[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
@@ -784,7 +533,7 @@ void CBattleInterface::newRoundFirst( int round )
 
 void CBattleInterface::newRound(int number)
 {
-	console->addText(CGI->generaltexth->allTexts[412]);
+	controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
 }
 
 void CBattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
@@ -1050,7 +799,7 @@ void CBattleInterface::displayBattleLog(const std::vector<MetaString> & battleLo
 	{
 		std::string formatted = line.toString();
 		boost::algorithm::trim(formatted);
-		if(!console->addText(formatted))
+		if(!controlPanel->console->addText(formatted))
 			logGlobal->warn("Too long battle log line");
 	}
 }
@@ -1143,7 +892,7 @@ void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
 			displayEffect(20,stack->getPosition());
 			CCS->soundh->playSound(soundBase::GOODMRLE);
-			console->addText(hlp);
+			controlPanel->console->addText(hlp);
 			break;
 		}
 		default:
@@ -1194,7 +943,7 @@ void CBattleInterface::setActiveStack(const CStack *stack)
 	if (activeStack) // update UI
 		creAnims[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
 
-	blockUI(activeStack == nullptr);
+	controlPanel->blockUI(activeStack == nullptr);
 }
 
 void CBattleInterface::setHoveredStack(const CStack *stack)
@@ -1431,7 +1180,7 @@ void CBattleInterface::endAction(const BattleAction* action)
 	queue->update();
 
 	if (tacticsMode) //stack ended movement in tactics phase -> select the next one
-		bTacticNextStack(stack);
+		tacticNextStack(stack);
 
 	if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
 		fieldController->redrawBackgroundWithHexes(activeStack);
@@ -1439,12 +1188,12 @@ void CBattleInterface::endAction(const BattleAction* action)
 	if (activeStack && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
 	{
 		logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
-		blockUI(false);
+		controlPanel->blockUI(false);
 	}
 	else
 	{
 		// block UI if no active stack (e.g. enemy turn);
-		blockUI(activeStack == nullptr);
+		controlPanel->blockUI(activeStack == nullptr);
 	}
 }
 
@@ -1476,58 +1225,15 @@ void CBattleInterface::showQueue()
 	}
 }
 
-void CBattleInterface::blockUI(bool on)
-{
-	bool canCastSpells = false;
-	auto hero = curInt->cb->battleGetMyHero();
-
-	if(hero)
-	{
-		ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, spells::Mode::HERO);
-
-		//if magic is blocked, we leave button active, so the message can be displayed after button click
-		canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
-	}
-
-	bool canWait = activeStack ? !activeStack->waitedThisTurn : false;
-
-	bOptions->block(on);
-	bFlee->block(on || !curInt->cb->battleCanFlee());
-	bSurrender->block(on || curInt->cb->battleGetSurrenderCost() < 0);
-
-	// block only if during enemy turn and auto-fight is off
-	// otherwise - crash on accessing non-exisiting active stack
-	bAutofight->block(!curInt->isAutoFightOn && !activeStack);
-
-	if (tacticsMode && btactEnd && btactNext)
-	{
-		btactNext->block(on);
-		btactEnd->block(on);
-	}
-	else
-	{
-		bConsoleUp->block(on);
-		bConsoleDown->block(on);
-	}
-
-
-	bSpell->block(on || tacticsMode || !canCastSpells);
-	bWait->block(on || tacticsMode || !canWait);
-	bDefence->block(on || tacticsMode);
-}
-
 void CBattleInterface::startAction(const BattleAction* action)
 {
 	//setActiveStack(nullptr);
 	setHoveredStack(nullptr);
-	blockUI(true);
+	controlPanel->blockUI(true);
 
 	if(action->actionType == EActionType::END_TACTIC_PHASE)
 	{
-		SDL_FreeSurface(menu);
-		menu = BitmapHandler::loadBitmap("CBAR.bmp");
-
-		graphics->blueToPlayersAdv(menu, curInt->playerID);
+		controlPanel->tacticPhaseEnded();
 		return;
 	}
 
@@ -1586,7 +1292,7 @@ void CBattleInterface::startAction(const BattleAction* action)
 	}
 
 	if(txtid != 0)
-		console->addText(stack->formatGeneralMessage(txtid));
+		controlPanel->console->addText(stack->formatGeneralMessage(txtid));
 
 	//displaying special abilities
 	switch(action->actionType)
@@ -1604,10 +1310,10 @@ void CBattleInterface::waitForAnims()
 	animsAreDisplayed.waitWhileTrue();
 }
 
-void CBattleInterface::bEndTacticPhase()
+void CBattleInterface::tacticPhaseEnd()
 {
 	setActiveStack(nullptr);
-	blockUI(true);
+	controlPanel->blockUI(true);
 	tacticsMode = false;
 }
 
@@ -1616,7 +1322,7 @@ static bool immobile(const CStack *s)
 	return !s->Speed(0, true); //should bound stacks be immobile?
 }
 
-void CBattleInterface::bTacticNextStack(const CStack * current)
+void CBattleInterface::tacticNextStack(const CStack * current)
 {
 	if (!current)
 		current = activeStack;
@@ -1628,7 +1334,7 @@ void CBattleInterface::bTacticNextStack(const CStack * current)
 	vstd::erase_if (stacksOfMine, &immobile);
 	if (stacksOfMine.empty())
 	{
-		bEndTacticPhase();
+		tacticPhaseEnd();
 		return;
 	}
 
@@ -2099,8 +1805,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 		{
 			if (setCursor)
 				CCS->curh->changeGraphic(cursorType, cursorFrame);
-			this->console->alterText(consoleMsg);
-			this->console->whoSetAlter = 0;
+			controlPanel->console->write(consoleMsg);
 		}
 		if (eventType == LCLICK && realizeAction)
 		{
@@ -2112,7 +1817,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			realizeAction();
 			if (!secondaryTarget) //do not replace teleport or sacrifice cursor
 				CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
-			this->console->alterText("");
+			controlPanel->console->clear();
 		}
 	}
 }
@@ -2215,7 +1920,7 @@ void CBattleInterface::requestAutofightingAIToTakeAction()
 				//TODO implement the possibility that the AI will be triggered for further actions
 				//TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
 				setActiveStack(nullptr);
-				blockUI(true);
+				controlPanel->blockUI(true);
 				tacticsMode = false;
 			}
 			else
@@ -2443,31 +2148,9 @@ void CBattleInterface::showBattleEffects(SDL_Surface *to, const std::vector<cons
 
 void CBattleInterface::showInterface(SDL_Surface *to)
 {
-	blitAt(menu, pos.x, 556 + pos.y, to);
-
-	if (tacticsMode)
-	{
-		btactNext->showAll(to);
-		btactEnd->showAll(to);
-	}
-	else
-	{
-		console->showAll(to);
-		bConsoleUp->showAll(to);
-		bConsoleDown->showAll(to);
-	}
-
-	//showing buttons
-	bOptions->showAll(to);
-	bSurrender->showAll(to);
-	bFlee->showAll(to);
-	bAutofight->showAll(to);
-	bSpell->showAll(to);
-	bWait->showAll(to);
-	bDefence->showAll(to);
-
 	//showing in-game console
 	LOCPLINT->cingconsole->show(to);
+	controlPanel->show(to);
 
 	Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
 
@@ -2594,7 +2277,7 @@ void CBattleInterface::updateBattleAnimations()
 	if (preSize > 0 && pendingAnims.empty())
 	{
 		//anims ended
-		blockUI(activeStack == nullptr);
+		controlPanel->blockUI(activeStack == nullptr);
 
 		animsAreDisplayed.setn(false);
 	}

+ 8 - 34
client/battle/CBattleInterface.h

@@ -62,6 +62,7 @@ class CBattleProjectileController;
 class CBattleSiegeController;
 class CBattleObstacleController;
 class CBattleFieldController;
+class CBattleControlPanel;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -118,24 +119,12 @@ enum class MouseHoveredHexContext
 class CBattleInterface : public WindowBase
 {
 private:
-	SDL_Surface *menu, *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral;
-
-	std::shared_ptr<CButton> bOptions;
-	std::shared_ptr<CButton> bSurrender;
-	std::shared_ptr<CButton> bFlee;
-	std::shared_ptr<CButton> bAutofight;
-	std::shared_ptr<CButton> bSpell;
-	std::shared_ptr<CButton> bWait;
-	std::shared_ptr<CButton> bDefence;
-	std::shared_ptr<CButton> bConsoleUp;
-	std::shared_ptr<CButton> bConsoleDown;
-	std::shared_ptr<CButton> btactNext;
-	std::shared_ptr<CButton> btactEnd;
-
-	std::shared_ptr<CBattleConsole> console;
+	SDL_Surface *amountNormal, *amountNegative, *amountPositive, *amountEffNeutral;
+
 	std::shared_ptr<CBattleHero> attackingHero;
 	std::shared_ptr<CBattleHero> defendingHero;
 	std::shared_ptr<CStackQueue> queue;
+	std::shared_ptr<CBattleControlPanel> controlPanel;
 
 	const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
 	const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
@@ -236,25 +225,9 @@ public:
 
 	const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
 
-	// block all UI elements, e.g. during enemy turn
-	// unlike activate/deactivate this method will correctly grey-out all elements
-	void blockUI(bool on);
-
-	//button handle funcs:
-	void bOptionsf();
-	void bSurrenderf();
-	void bFleef();
-	void reallyFlee(); //performs fleeing without asking player
-	void reallySurrender(); //performs surrendering without asking player
-	void bAutofightf();
-	void bSpellf();
-	void bWaitf();
-	void bDefencef();
-	void bConsoleUpf();
-	void bConsoleDownf();
-	void bTacticNextStack(const CStack *current = nullptr);
-	void bEndTacticPhase();
-	//end of button handle funcs
+	void tacticNextStack(const CStack *current);
+	void tacticPhaseEnd();
+
 	//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
 	void activate() override;
 	void deactivate() override;
@@ -332,4 +305,5 @@ public:
 	friend class CBattleSiegeController;
 	friend class CBattleObstacleController;
 	friend class CBattleFieldController;
+	friend class CBattleControlPanel;
 };

+ 26 - 32
client/battle/CBattleInterfaceClasses.cpp

@@ -13,6 +13,7 @@
 #include "CBattleInterface.h"
 #include "CBattleSiegeController.h"
 #include "CBattleFieldController.h"
+#include "CBattleControlPanel.h"
 
 #include "../CBitmapHandler.h"
 #include "../CGameInfo.h"
@@ -50,10 +51,6 @@ void CBattleConsole::showAll(SDL_Surface * to)
 	{
 		graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(ingcAlter, pos.w, FONT_SMALL), Colors::WHITE, textPos);
 	}
-	else if(alterTxt.size())
-	{
-		graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(alterTxt, pos.w, FONT_SMALL), Colors::WHITE, textPos);
-	}
 	else if(texts.size())
 	{
 		if(texts.size()==1)
@@ -88,46 +85,43 @@ bool CBattleConsole::addText(const std::string & text)
 	lastShown = (int)texts.size()-1;
 	return true;
 }
+void CBattleConsole::scrollUp(ui32 by)
+{
+	if(lastShown > static_cast<int>(by))
+		lastShown -= by;
+}
 
-void CBattleConsole::alterText(const std::string &text)
+void CBattleConsole::scrollDown(ui32 by)
 {
-	//char buf[500];
-	//sprintf(buf, text.c_str());
-	//alterTxt = buf;
-	alterTxt = text;
+	if(lastShown + by < texts.size())
+		lastShown += by;
 }
 
-void CBattleConsole::eraseText(ui32 pos)
+CBattleConsole::CBattleConsole(const Rect & position) : lastShown(-1)
 {
-	if(pos < texts.size())
-	{
-		texts.erase(texts.begin() + pos);
-		if(lastShown == texts.size())
-			--lastShown;
-	}
+	pos = position;
 }
 
-void CBattleConsole::changeTextAt(const std::string & text, ui32 pos)
+void CBattleConsole::clearMatching(const std::string & Text)
 {
-	if(pos >= texts.size()) //no such pos
-		return;
-	texts[pos] = text;
+	if (ingcAlter == Text)
+		clear();
 }
 
-void CBattleConsole::scrollUp(ui32 by)
+void CBattleConsole::clear()
 {
-	if(lastShown > static_cast<int>(by))
-		lastShown -= by;
+	ingcAlter.clear();
 }
 
-void CBattleConsole::scrollDown(ui32 by)
+void CBattleConsole::write(const std::string & Text)
 {
-	if(lastShown + by < texts.size())
-		lastShown += by;
+	ingcAlter = Text;
 }
 
-CBattleConsole::CBattleConsole() : lastShown(-1), alterTxt(""), whoSetAlter(0)
-{}
+void CBattleConsole::lock(bool shouldLock)
+{
+	// no-op?
+}
 
 void CBattleHero::show(SDL_Surface * to)
 {
@@ -627,7 +621,7 @@ void CClickableHex::hover(bool on)
 	//Hoverable::hover(on);
 	if(!on && setAlterText)
 	{
-		myInterface->console->alterTxt = std::string();
+		myInterface->controlPanel->console->clear();
 		setAlterText = false;
 	}
 }
@@ -644,20 +638,20 @@ void CClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 	if(hovered && strictHovered) //print attacked creature to console
 	{
 		const CStack * attackedStack = myInterface->getCurrentPlayerInterface()->cb->battleGetStackByPos(myNumber);
-		if(myInterface->console->alterTxt.size() == 0 &&attackedStack != nullptr &&
+		if( attackedStack != nullptr &&
 			attackedStack->owner != myInterface->getCurrentPlayerInterface()->playerID &&
 			attackedStack->alive())
 		{
 			MetaString text;
 			text.addTxt(MetaString::GENERAL_TXT, 220);
 			attackedStack->addNameReplacement(text);
-			myInterface->console->alterTxt = text.toString();
+			myInterface->controlPanel->console->write(text.toString());
 			setAlterText = true;
 		}
 	}
 	else if(setAlterText)
 	{
-		myInterface->console->alterTxt = std::string();
+		myInterface->controlPanel->console->clear();
 		setAlterText = false;
 	}
 }

+ 11 - 8
client/battle/CBattleInterfaceClasses.h

@@ -39,23 +39,26 @@ class CAnimImage;
 class CPlayerInterface;
 
 /// Class which shows the console at the bottom of the battle screen and manages the text of the console
-class CBattleConsole : public CIntObject
+class CBattleConsole : public CIntObject, public IStatusBar
 {
 private:
 	std::vector< std::string > texts; //a place where texts are stored
 	int lastShown; //last shown line of text
-public:
-	std::string alterTxt; //if it's not empty, this text is displayed
+
 	std::string ingcAlter; //alternative text set by in-game console - very important!
-	int whoSetAlter; //who set alter text; 0 - battle interface or none, 1 - button
-	CBattleConsole();
+public:
+	CBattleConsole(const Rect & position);
 	void showAll(SDL_Surface * to = 0) override;
+
 	bool addText(const std::string &text); //adds text at the last position; returns false if failed (e.g. text longer than 70 characters)
-	void alterText(const std::string &text); //place string at alterTxt
-	void eraseText(ui32 pos); //erases added text at position pos
-	void changeTextAt(const std::string &text, ui32 pos); //if we have more than pos texts, pos-th is changed to given one
 	void scrollUp(ui32 by = 1); //scrolls console up by 'by' positions
 	void scrollDown(ui32 by = 1); //scrolls console up by 'by' positions
+
+	void clearMatching(const std::string & Text) override;
+	void clear() override;
+	void write(const std::string & Text) override;
+	void lock(bool shouldLock) override;
+
 };
 
 /// Hero battle animation

+ 2 - 2
client/gui/CGuiHandler.h

@@ -20,7 +20,7 @@ template <typename T> struct CondSh;
 VCMI_LIB_NAMESPACE_END
 
 class CFramerateManager;
-class CGStatusBar;
+class IStatusBar;
 class CIntObject;
 class IUpdateable;
 class IShowActivatable;
@@ -65,7 +65,7 @@ class CGuiHandler
 public:
 	CFramerateManager * mainFPSmng; //to keep const framerate
 	std::list<std::shared_ptr<IShowActivatable>> listInt; //list of interfaces - front=foreground; back = background (includes adventure map, window interfaces, all kind of active dialogs, and so on)
-	std::shared_ptr<CGStatusBar> statusbar;
+	std::shared_ptr<IStatusBar> statusbar;
 
 private:
 	std::vector<std::shared_ptr<IShowActivatable>> disposed;

+ 3 - 0
client/gui/CIntObject.cpp

@@ -373,3 +373,6 @@ void WindowBase::close()
 		logGlobal->error("Only top interface must be closed");
 	GH.popInts(1);
 }
+
+IStatusBar::~IStatusBar()
+{}

+ 10 - 0
client/gui/CIntObject.h

@@ -217,3 +217,13 @@ public:
 protected:
 	void close();
 };
+
+class IStatusBar
+{
+public:
+	virtual ~IStatusBar();
+	virtual void clear() = 0;
+	virtual void clearMatching(const std::string & Text) = 0;
+	virtual void write(const std::string & Text) = 0;
+	virtual void lock(bool shouldLock) = 0;
+};

+ 22 - 33
client/widgets/AdventureMapClasses.cpp

@@ -87,7 +87,7 @@ void CList::CListItem::clickLeft(tribool down, bool previousState)
 void CList::CListItem::hover(bool on)
 {
 	if (on)
-		GH.statusbar->setText(getHoverText());
+		GH.statusbar->write(getHoverText());
 	else
 		GH.statusbar->clear();
 }
@@ -572,7 +572,7 @@ void CMinimap::clickRight(tribool down, bool previousState)
 void CMinimap::hover(bool on)
 {
 	if(on)
-		GH.statusbar->setText(CGI->generaltexth->zelp[291].first);
+		GH.statusbar->write(CGI->generaltexth->zelp[291].first);
 	else
 		GH.statusbar->clear();
 }
@@ -876,7 +876,7 @@ void CInfoBar::clickRight(tribool down, bool previousState)
 void CInfoBar::hover(bool on)
 {
 	if(on)
-		GH.statusbar->setText(CGI->generaltexth->zelp[292].first);
+		GH.statusbar->write(CGI->generaltexth->zelp[292].first);
 	else
 		GH.statusbar->clear();
 }
@@ -1129,20 +1129,17 @@ void CInGameConsole::textEdited(const SDL_TextEditingEvent & event)
 
 void CInGameConsole::startEnteringText()
 {
-	CSDL_Ext::startTextInput(&GH.statusbar->pos);
+	auto * statusBar = dynamic_cast<CGStatusBar*>(GH.statusbar.get());
 
-	enteredText = "_";
-	if(GH.topInt() == adventureInt)
+	if (statusBar)
 	{
-		GH.statusbar->alignment = TOPLEFT;
-		GH.statusbar->setText(enteredText);
+		CSDL_Ext::startTextInput(&statusBar->pos);
 
-		//Prevent changes to the text from mouse interaction with the adventure map
-		GH.statusbar->lock(true);
-	}
-	else if(LOCPLINT->battleInt)
-	{
-		LOCPLINT->battleInt->console->ingcAlter = enteredText;
+		enteredText = "_";
+
+		statusBar->alignment = TOPLEFT;
+		statusBar->write(enteredText);
+		statusBar->lock(true);
 	}
 }
 
@@ -1159,31 +1156,23 @@ void CInGameConsole::endEnteringText(bool printEnteredText)
 		//print(txt);
 	}
 	enteredText.clear();
-	if(GH.topInt() == adventureInt)
-	{
-		GH.statusbar->alignment = CENTER;
-		GH.statusbar->lock(false);
-		GH.statusbar->clear();
-	}
-	else if(LOCPLINT->battleInt)
+
+	auto * statusBar = dynamic_cast<CGStatusBar*>(GH.statusbar.get());
+
+	if(statusBar)
 	{
-		LOCPLINT->battleInt->console->ingcAlter = "";
+		statusBar->alignment = CENTER;
 	}
+	GH.statusbar->lock(false);
+	GH.statusbar->clear();
 }
 
 void CInGameConsole::refreshEnteredText()
 {
-	if(GH.topInt() == adventureInt)
-	{
-		GH.statusbar->lock(false);
-		GH.statusbar->clear();
-		GH.statusbar->setText(enteredText);
-		GH.statusbar->lock(true);
-	}
-	else if(LOCPLINT->battleInt)
-	{
-		LOCPLINT->battleInt->console->ingcAlter = enteredText;
-	}
+	GH.statusbar->lock(false);
+	GH.statusbar->clear();
+	GH.statusbar->write(enteredText);
+	GH.statusbar->lock(true);
 }
 
 CAdvMapPanel::CAdvMapPanel(SDL_Surface * bg, Point position)

+ 5 - 21
client/widgets/Buttons.cpp

@@ -207,26 +207,10 @@ void CButton::hover (bool on)
 
 	if(!name.empty() && !isBlocked()) //if there is no name, there is nothing to display also
 	{
-		if (LOCPLINT && LOCPLINT->battleInt) //for battle buttons
-		{
-			if(on && LOCPLINT->battleInt->console->alterTxt == "")
-			{
-				LOCPLINT->battleInt->console->alterTxt = name;
-				LOCPLINT->battleInt->console->whoSetAlter = 1;
-			}
-			else if (LOCPLINT->battleInt->console->alterTxt == name)
-			{
-				LOCPLINT->battleInt->console->alterTxt = "";
-				LOCPLINT->battleInt->console->whoSetAlter = 0;
-			}
-		}
-		else if(GH.statusbar) //for other buttons
-		{
-			if (on)
-				GH.statusbar->setText(name);
-			else if ( GH.statusbar->getText()==(name) )
-				GH.statusbar->clear();
-		}
+		if (on)
+			GH.statusbar->write(name);
+		else
+			GH.statusbar->clearMatching(name);
 	}
 }
 
@@ -530,7 +514,7 @@ void CVolumeSlider::clickRight(tribool down, bool previousState)
 		if(!helpBox.empty())
 			CRClickPopup::createAndPush(helpBox);
 		if(GH.statusbar)
-			GH.statusbar->setText(helpBox);
+			GH.statusbar->write(helpBox);
 	}
 }
 

+ 1 - 1
client/widgets/CGarrisonInt.cpp

@@ -118,7 +118,7 @@ void CGarrisonSlot::hover (bool on)
 				temp = CGI->generaltexth->tcommands[11]; //Empty
 			}
 		}
-		GH.statusbar->setText(temp);
+		GH.statusbar->write(temp);
 	}
 	else
 	{

+ 4 - 4
client/widgets/MiscWidgets.cpp

@@ -34,9 +34,9 @@
 void CHoverableArea::hover (bool on)
 {
 	if (on)
-		GH.statusbar->setText(hoverText);
-	else if (GH.statusbar->getText()==hoverText)
-		GH.statusbar->clear();
+		GH.statusbar->write(hoverText);
+	else
+		GH.statusbar->clearMatching(hoverText);
 }
 
 CHoverableArea::CHoverableArea()
@@ -150,7 +150,7 @@ void CHeroArea::clickRight(tribool down, bool previousState)
 void CHeroArea::hover(bool on)
 {
 	if (on && hero)
-		GH.statusbar->setText(hero->getObjectName());
+		GH.statusbar->write(hero->getObjectName());
 	else
 		GH.statusbar->clear();
 }

+ 7 - 1
client/widgets/TextControls.cpp

@@ -339,12 +339,18 @@ void CTextBox::setText(const std::string & text)
 	}
 }
 
-void CGStatusBar::setText(const std::string & Text)
+void CGStatusBar::write(const std::string & Text)
 {
 	if(!textLock)
 		CLabel::setText(Text);
 }
 
+void CGStatusBar::clearMatching(const std::string & Text)
+{
+	if (getText() == Text)
+		clear();
+}
+
 void CGStatusBar::clear()
 {
 	setText("");

+ 6 - 4
client/widgets/TextControls.h

@@ -114,7 +114,7 @@ public:
 };
 
 /// Status bar which is shown at the bottom of the in-game screens
-class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusBar>
+class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusBar>, public IStatusBar
 {
 	bool textLock; //Used for blocking changes to the text
 	void init();
@@ -132,12 +132,14 @@ public:
 		ret->init();
 		return ret;
 	}
-	void clear();//clears statusbar and refreshes
-	void setText(const std::string & Text) override; //prints text and refreshes statusbar
+
+	void clearMatching(const std::string & Text) override;
+	void clear() override;//clears statusbar and refreshes
+	void write(const std::string & Text) override; //prints text and refreshes statusbar
 
 	void show(SDL_Surface * to) override; //shows statusbar (with current text)
 
-	void lock(bool shouldLock); //If true, current text cannot be changed until lock(false) is called
+	void lock(bool shouldLock) override; //If true, current text cannot be changed until lock(false) is called
 };
 
 class CFocusable;

+ 10 - 10
client/windows/CCastleInterface.cpp

@@ -261,7 +261,7 @@ void CBuildingRect::mouseMoved (const SDL_MouseMotionEvent & sEvent)
 			  || (*parent->selectedBuilding)<(*this)) //or we are on top
 			{
 				parent->selectedBuilding = this;
-				GH.statusbar->setText(getSubtitle());
+				GH.statusbar->write(getSubtitle());
 			}
 		}
 	}
@@ -379,7 +379,7 @@ void CHeroGSlot::hover(bool on)
 		}
 	}
 	if(temp.size())
-		GH.statusbar->setText(temp);
+		GH.statusbar->write(temp);
 }
 
 void CHeroGSlot::clickLeft(tribool down, bool previousState)
@@ -1031,11 +1031,11 @@ void CCreaInfo::hover(bool on)
 
 	if(on)
 	{
-		GH.statusbar->setText(message);
+		GH.statusbar->write(message);
 	}
-	else if (message == GH.statusbar->getText())
+	else
 	{
-		GH.statusbar->clear();
+		GH.statusbar->clearMatching(message);
 	}
 }
 
@@ -1105,7 +1105,7 @@ void CTownInfo::hover(bool on)
 	if(on)
 	{
 		if(building )
-			GH.statusbar->setText(building->Name());
+			GH.statusbar->write(building->Name());
 	}
 	else
 	{
@@ -1326,7 +1326,7 @@ void CHallInterface::CBuildingBox::hover(bool on)
 		else
 			toPrint = CGI->generaltexth->hcommands[state];
 		boost::algorithm::replace_first(toPrint,"%s",building->Name());
-		GH.statusbar->setText(toPrint);
+		GH.statusbar->write(toPrint);
 	}
 	else
 	{
@@ -1515,7 +1515,7 @@ void LabeledValue::hover(bool on)
 {
 	if(on)
 	{
-		GH.statusbar->setText(hoverText);
+		GH.statusbar->write(hoverText);
 	}
 	else
 	{
@@ -1683,7 +1683,7 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
 void CFortScreen::RecruitArea::hover(bool on)
 {
 	if(on)
-		GH.statusbar->setText(hoverText);
+		GH.statusbar->write(hoverText);
 	else
 		GH.statusbar->clear();
 }
@@ -1774,7 +1774,7 @@ void CMageGuildScreen::Scroll::clickRight(tribool down, bool previousState)
 void CMageGuildScreen::Scroll::hover(bool on)
 {
 	if(on)
-		GH.statusbar->setText(spell->name);
+		GH.statusbar->write(spell->name);
 	else
 		GH.statusbar->clear();
 

+ 3 - 3
client/windows/CTradeWindow.cpp

@@ -244,13 +244,13 @@ void CTradeWindow::CTradeableItem::hover(bool on)
 	{
 	case CREATURE:
 	case CREATURE_PLACEHOLDER:
-		GH.statusbar->setText(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
+		GH.statusbar->write(boost::str(boost::format(CGI->generaltexth->allTexts[481]) % CGI->creh->objects[id]->namePl));
 		break;
 	case ARTIFACT_PLACEHOLDER:
 		if(id < 0)
-			GH.statusbar->setText(CGI->generaltexth->zelp[582].first);
+			GH.statusbar->write(CGI->generaltexth->zelp[582].first);
 		else
-			GH.statusbar->setText(CGI->artifacts()->getByIndex(id)->getName());
+			GH.statusbar->write(CGI->artifacts()->getByIndex(id)->getName());
 		break;
 	}
 }

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -799,7 +799,7 @@ void CTavernWindow::HeroPortrait::hover(bool on)
 {
 	//Hoverable::hover(on);
 	if(on)
-		GH.statusbar->setText(hoverName);
+		GH.statusbar->write(hoverName);
 	else
 		GH.statusbar->clear();
 }
@@ -1550,7 +1550,7 @@ void CUniversityWindow::CItem::clickRight(tribool down, bool previousState)
 void CUniversityWindow::CItem::hover(bool on)
 {
 	if(on)
-		GH.statusbar->setText(CGI->skillh->skillName(ID));
+		GH.statusbar->write(CGI->skillh->skillName(ID));
 	else
 		GH.statusbar->clear();
 }