Browse Source

Moved all GUI handling from BattleInterface to BattleControlPanel

Ivan Savenko 3 years ago
parent
commit
c835a84051

+ 4 - 4
client/CPlayerInterface.cpp

@@ -93,7 +93,7 @@ boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
 
 CPlayerInterface * LOCPLINT;
 
-BattleInterface * CPlayerInterface::battleInt;
+std::shared_ptr<BattleInterface> CPlayerInterface::battleInt;
 
 enum  EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
 CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
@@ -836,9 +836,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 		autofightingAI.reset();
 	}
 
-	BattleInterface *b = battleInt;
+	assert(battleInt);
 
-	if(!b)
+	if(!battleInt)
 	{
 		return BattleAction::makeDefend(stack); // probably battle is finished already
 	}
@@ -851,7 +851,7 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 
 	{
 		boost::unique_lock<boost::recursive_mutex> un(*pim);
-		b->stackActivated(stack);
+		battleInt->stackActivated(stack);
 		//Regeneration & mana drain go there
 	}
 	//wait till BattleInterface sets its command

+ 1 - 1
client/CPlayerInterface.h

@@ -84,7 +84,7 @@ public:
 	static const int SAVES_COUNT = 5;
 
 	CCastleInterface * castleInt; //nullptr if castle window isn't opened
-	static BattleInterface * battleInt; //nullptr if no battle
+	static std::shared_ptr<BattleInterface> battleInt; //nullptr if no battle
 	CInGameConsole * cingconsole;
 
 	std::shared_ptr<CCallback> cb; //to communicate with engine

+ 14 - 15
client/Client.cpp

@@ -377,18 +377,18 @@ void CClient::endGame()
 		i.second->finish();
 
 	GH.curInt = nullptr;
-	{
-		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-		logNetwork->info("Ending current game!");
-		removeGUI();
-
-		vstd::clear_pointer(const_cast<CGameInfo *>(CGI)->mh);
-		vstd::clear_pointer(gs);
-
-		logNetwork->info("Deleted mapHandler and gameState.");
-	}
-
-	playerint.clear();
+	{
+		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
+		logNetwork->info("Ending current game!");
+		removeGUI();
+
+		vstd::clear_pointer(const_cast<CGameInfo *>(CGI)->mh);
+		vstd::clear_pointer(gs);
+
+		logNetwork->info("Deleted mapHandler and gameState.");
+	}
+
+	playerint.clear();
 	battleints.clear();
 	battleCallbacks.clear();
 	playerEnvironments.clear();
@@ -586,11 +586,10 @@ void CClient::battleStarted(const BattleInfo * info)
 
 	if(!settings["session"]["headless"].Bool())
 	{
-		Rect battleIntRect((screen->w - 800)/2, (screen->h - 600)/2, 800, 600);
 		if(!!att || !!def)
 		{
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-			GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def);
+			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
 		}
 		else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
 		{
@@ -598,7 +597,7 @@ void CClient::battleStarted(const BattleInfo * info)
 			auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
 			spectratorInt->cb->setBattle(info);
 			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
-			GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def, spectratorInt);
+			CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
 		}
 	}
 

+ 3 - 3
client/battle/BattleActionsController.cpp

@@ -668,9 +668,9 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			}
 
 			if (!currentConsoleMsg.empty())
-				owner.controlPanel->console->clearIfMatching(currentConsoleMsg);
+				GH.statusbar->clearIfMatching(currentConsoleMsg);
 			if (!newConsoleMsg.empty())
-				owner.controlPanel->console->write(newConsoleMsg);
+				GH.statusbar->write(newConsoleMsg);
 
 			currentConsoleMsg = newConsoleMsg;
 		}
@@ -684,7 +684,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
 			realizeAction();
 			if (!secondaryTarget) //do not replace teleport or sacrifice cursor
 				CCS->curh->set(Cursor::Combat::POINTER);
-			owner.controlPanel->console->clear();
+			GH.statusbar->clear();
 		}
 	}
 }

+ 3 - 3
client/battle/BattleAnimationClasses.cpp

@@ -982,9 +982,9 @@ bool PointEffectAnimation::init()
 
 	if (screenFill())
 	{
-		for(int i=0; i * first->width() < owner.pos.w ; ++i)
-			for(int j=0; j * first->height() < owner.pos.h ; ++j)
-				positions.push_back(Point( owner.pos.x + i * first->width(), owner.pos.y + j * first->height()));
+		for(int i=0; i * first->width() < owner.fieldController->pos.w ; ++i)
+			for(int j=0; j * first->height() < owner.fieldController->pos.h ; ++j)
+				positions.push_back(Point( owner.fieldController->pos.x + i * first->width(), owner.fieldController->pos.y + j * first->height()));
 	}
 
 	BattleEffect be;

+ 144 - 33
client/battle/BattleControlPanel.cpp

@@ -12,14 +12,19 @@
 
 #include "BattleInterface.h"
 #include "BattleInterfaceClasses.h"
+#include "BattleFieldController.h"
 #include "BattleStacksController.h"
 #include "BattleActionsController.h"
 
 #include "../CGameInfo.h"
+#include "../CMessage.h"
 #include "../CPlayerInterface.h"
+#include "../CMusicHandler.h"
+#include "../gui/Canvas.h"
 #include "../gui/CCursorHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../windows/CSpellWindow.h"
+#include "../widgets/AdventureMapClasses.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 
@@ -29,71 +34,163 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
 
-BattleControlPanel::BattleControlPanel(BattleInterface & owner, const Point & position):
+BattleControlPanel::BattleControlPanel(BattleInterface & owner):
 	owner(owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	pos += position;
+	pos.w = 800;
+	pos.h = 600;
+	pos = center();
+
+	// create background for panel/ribbon at the bottom
+	menuTactics = std::make_shared<CPicture>("COPLACBR.BMP", 0, 556);
+	menuBattle = std::make_shared<CPicture>("CBAR.BMP", 0, 556);
+	menuTactics->colorize(owner.curInt->playerID);
+	menuBattle->colorize(owner.curInt->playerID);
 
 	//preparing buttons and console
-	bOptions = std::make_shared<CButton>    (Point(  3,  5), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&BattleControlPanel::bOptionsf,this), SDLK_o);
-	bSurrender = std::make_shared<CButton>  (Point( 54,  5), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&BattleControlPanel::bSurrenderf,this), SDLK_s);
-	bFlee = std::make_shared<CButton>       (Point(105,  5), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&BattleControlPanel::bFleef,this), SDLK_r);
-	bAutofight = std::make_shared<CButton>  (Point(158,  5), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&BattleControlPanel::bAutofightf,this), SDLK_a);
-	bSpell = std::make_shared<CButton>      (Point(645,  5), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&BattleControlPanel::bSpellf,this), SDLK_c);
-	bWait = std::make_shared<CButton>       (Point(696,  5), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&BattleControlPanel::bWaitf,this), SDLK_w);
-	bDefence = std::make_shared<CButton>    (Point(747,  5), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&BattleControlPanel::bDefencef,this), SDLK_d);
-	bConsoleUp = std::make_shared<CButton>  (Point(624,  5), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::bConsoleUpf,this), SDLK_UP);
-	bConsoleDown = std::make_shared<CButton>(Point(624, 24), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::bConsoleDownf,this), SDLK_DOWN);
+	bOptions = std::make_shared<CButton>    (Point(  3,  5 + 556), "icm003.def", CGI->generaltexth->zelp[381], std::bind(&BattleControlPanel::bOptionsf,this), SDLK_o);
+	bSurrender = std::make_shared<CButton>  (Point( 54,  5 + 556), "icm001.def", CGI->generaltexth->zelp[379], std::bind(&BattleControlPanel::bSurrenderf,this), SDLK_s);
+	bFlee = std::make_shared<CButton>       (Point(105,  5 + 556), "icm002.def", CGI->generaltexth->zelp[380], std::bind(&BattleControlPanel::bFleef,this), SDLK_r);
+	bAutofight = std::make_shared<CButton>  (Point(158,  5 + 556), "icm004.def", CGI->generaltexth->zelp[382], std::bind(&BattleControlPanel::bAutofightf,this), SDLK_a);
+	bSpell = std::make_shared<CButton>      (Point(645,  5 + 556), "icm005.def", CGI->generaltexth->zelp[385], std::bind(&BattleControlPanel::bSpellf,this), SDLK_c);
+	bWait = std::make_shared<CButton>       (Point(696,  5 + 556), "icm006.def", CGI->generaltexth->zelp[386], std::bind(&BattleControlPanel::bWaitf,this), SDLK_w);
+	bDefence = std::make_shared<CButton>    (Point(747,  5 + 556), "icm007.def", CGI->generaltexth->zelp[387], std::bind(&BattleControlPanel::bDefencef,this), SDLK_d);
+	bConsoleUp = std::make_shared<CButton>  (Point(624,  5 + 556), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::bConsoleUpf,this), SDLK_UP);
+	bConsoleDown = std::make_shared<CButton>(Point(624, 24 + 556), "ComSlide.def", std::make_pair("", ""),     std::bind(&BattleControlPanel::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<BattleConsole>(Rect(211, 4, 406,38));
+	btactNext = std::make_shared<CButton>(Point(213, 4 + 556), "icm011.def", std::make_pair("", ""), [&]() { bTacticNextStack();}, SDLK_SPACE);
+	btactEnd = std::make_shared<CButton>(Point(419,  4 + 556), "icm012.def", std::make_pair("", ""),  [&](){ bTacticPhaseEnd();}, SDLK_RETURN);
+
+	console = std::make_shared<BattleConsole>(menuBattle, Point(211, 4 + 556), Point(211, 4), Point(406,38));
 	GH.statusbar = console;
+	owner.console = console;
+
+	owner.fieldController.reset( new BattleFieldController(owner));
+	owner.fieldController->createHeroes();
+
+	//create stack queue and adjust our own position
+	bool embedQueue;
+	std::string queueSize = settings["battle"]["queueSize"].String();
+
+	if(queueSize == "auto")
+		embedQueue = screen->h < 700;
+	else
+		embedQueue = screen->h < 700 || queueSize == "small";
+
+	queue = std::make_shared<StackQueue>(embedQueue, owner);
+	if(!embedQueue && settings["battle"]["showQueue"].Bool())
+	{
+		//re-center, taking into account stack queue position
+		pos.y -= queue->pos.h;
+		pos.h += queue->pos.h;
+		pos = center();
+	}
 
 	if ( owner.tacticsMode )
 		tacticPhaseStarted();
 	else
 		tacticPhaseEnded();
+
+	addUsedEvents(RCLICK | KEYBOARD);
 }
 
-void BattleControlPanel::show(SDL_Surface * to)
+void BattleControlPanel::hideQueue()
 {
-	//show menu before all other elements to keep it in background
-	menu->show(to);
-	CIntObject::show(to);
+	Settings showQueue = settings.write["battle"]["showQueue"];
+	showQueue->Bool() = false;
+
+	queue->disable();
+
+	if (!queue->embedded)
+	{
+		moveBy(Point(0, -queue->pos.h / 2));
+		GH.totalRedraw();
+	}
 }
 
-void BattleControlPanel::showAll(SDL_Surface * to)
+void BattleControlPanel::showQueue()
 {
-	//show menu before all other elements to keep it in background
-	menu->showAll(to);
-	CIntObject::showAll(to);
+	Settings showQueue = settings.write["battle"]["showQueue"];
+	showQueue->Bool() = true;
+
+	queue->enable();
+
+	if (!queue->embedded)
+	{
+		moveBy(Point(0, +queue->pos.h / 2));
+		GH.totalRedraw();
+	}
+}
+
+void BattleControlPanel::updateQueue()
+{
+	queue->update();
+}
+
+void BattleControlPanel::activate()
+{
+	CIntObject::activate();
+	LOCPLINT->cingconsole->activate();
+}
+
+void BattleControlPanel::deactivate()
+{
+	CIntObject::deactivate();
+	LOCPLINT->cingconsole->deactivate();
+}
+
+void BattleControlPanel::keyPressed(const SDL_KeyboardEvent & key)
+{
+	if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
+	{
+		if(settings["battle"]["showQueue"].Bool()) //hide queue
+			hideQueue();
+		else
+			showQueue();
+
+	}
+	else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
+	{
+		owner.actionsController->enterCreatureCastingMode();
+	}
+	else if(key.keysym.sym == SDLK_ESCAPE)
+	{
+		if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true)
+			CCS->soundh->stopSound(owner.battleIntroSoundChannel);
+		else
+			owner.actionsController->endCastingSpell();
+	}
 }
 
+void BattleControlPanel::clickRight(tribool down, bool previousState)
+{
+	if (!down)
+		owner.actionsController->endCastingSpell();
+}
 
 void BattleControlPanel::tacticPhaseStarted()
 {
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	menuBattle->disable();
+	console->disable();
 
-	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);
-	menu->recActions &= ~(SHOWALL | UPDATE);
+	menuTactics->enable();
+	btactNext->enable();
+	btactEnd->enable();
 }
+
 void BattleControlPanel::tacticPhaseEnded()
 {
-	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-
-	btactNext.reset();
-	btactEnd.reset();
+	menuBattle->enable();
+	console->enable();
 
-	menu = std::make_shared<CPicture>("CBAR.BMP", 0, 0);
-	menu->colorize(owner.curInt->playerID);
-	menu->recActions &= ~(SHOWALL | UPDATE);
+	menuTactics->disable();
+	btactNext->disable();
+	btactEnd->disable();
 }
 
 void BattleControlPanel::bOptionsf()
@@ -326,3 +423,17 @@ void BattleControlPanel::blockUI(bool on)
 	bWait->block(on || owner.tacticsMode || !canWait);
 	bDefence->block(on || owner.tacticsMode);
 }
+
+void BattleControlPanel::showAll(SDL_Surface *to)
+{
+	CIntObject::showAll(to);
+
+	if (screen->w != 800 || screen->h !=600)
+		CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
+}
+
+void BattleControlPanel::show(SDL_Surface *to)
+{
+	CIntObject::show(to);
+	LOCPLINT->cingconsole->show(to);
+}

+ 26 - 7
client/battle/BattleControlPanel.h

@@ -19,13 +19,20 @@ VCMI_LIB_NAMESPACE_END
 class CButton;
 class BattleInterface;
 class BattleConsole;
+class BattleRenderer;
+class StackQueue;
 
 /// GUI object that handles functionality of panel at the bottom of combat screen
-class BattleControlPanel : public CIntObject
+class BattleControlPanel : public WindowBase
 {
 	BattleInterface & owner;
 
-	std::shared_ptr<CPicture> menu;
+	std::shared_ptr<StackQueue> queue;
+
+	std::shared_ptr<BattleConsole> console;
+
+	std::shared_ptr<CPicture> menuTactics;
+	std::shared_ptr<CPicture> menuBattle;
 
 	std::shared_ptr<CButton> bOptions;
 	std::shared_ptr<CButton> bSurrender;
@@ -56,21 +63,33 @@ class BattleControlPanel : public CIntObject
 	void reallyFlee();
 	void reallySurrender();
 
+	/// Toggle StackQueue visibility
+	void hideQueue();
+	void showQueue();
+
 public:
-	std::shared_ptr<BattleConsole> console;
+	BattleControlPanel(BattleInterface & owner );
+
+	/// Closes window once battle finished (explicit declaration to move into public visibility)
+	using WindowBase::close;
 
 	/// block all UI elements when player is not allowed to act, e.g. during enemy turn
 	void blockUI(bool on);
 
-	void show(SDL_Surface * to) override;
-	void showAll(SDL_Surface * to) override;
+	/// Refresh queue after turn order changes
+	void updateQueue();
+
+	void activate() override;
+	void deactivate() override;
+	void keyPressed(const SDL_KeyboardEvent & key) override;
+	void clickRight(tribool down, bool previousState) override;
+	void show(SDL_Surface *to) override;
+	void showAll(SDL_Surface *to) override;
 
 	/// Toggle UI to displaying tactics phase
 	void tacticPhaseStarted();
 
 	/// Toggle UI to displaying battle log in place of tactics UI
 	void tacticPhaseEnded();
-
-	BattleControlPanel(BattleInterface & owner, const Point & position);
 };
 

+ 3 - 3
client/battle/BattleEffectsController.cpp

@@ -79,7 +79,7 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 			std::string hlp = CGI->generaltexth->allTexts[33];
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
 			displayEffect(EBattleEffect::GOOD_MORALE, soundBase::GOODMRLE, stack->getPosition());
-			owner.controlPanel->console->addText(hlp);
+			owner.appendBattleLog(hlp);
 			break;
 		}
 		default:
@@ -97,10 +97,10 @@ void BattleEffectsController::startAction(const BattleAction* action)
 	switch(action->actionType)
 	{
 	case EActionType::WAIT:
-		owner.controlPanel->console->addText(stack->formatGeneralMessage(136));
+		owner.appendBattleLog(stack->formatGeneralMessage(136));
 		break;
 	case EActionType::BAD_MORALE:
-		owner.controlPanel->console->addText(stack->formatGeneralMessage(-34));
+		owner.appendBattleLog(stack->formatGeneralMessage(-34));
 		displayEffect(EBattleEffect::BAD_MORALE, soundBase::BADMRLE, stack->getPosition());
 		break;
 	}

+ 50 - 6
client/battle/BattleFieldController.cpp

@@ -22,6 +22,7 @@
 
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../widgets/AdventureMapClasses.h"
 #include "../gui/CAnimation.h"
 #include "../gui/Canvas.h"
 #include "../gui/CGuiHandler.h"
@@ -37,8 +38,7 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	owner(owner)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	pos.w = owner.pos.w;
-	pos.h = owner.pos.h;
+	strongInterest = true;
 
 	//preparing cells and hexes
 	cellBorder = IImage::createFromFile("CCELLGRD.BMP");
@@ -58,6 +58,8 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 		std::string backgroundName = owner.siegeController->getBattleBackgroundName();
 		background = IImage::createFromFile(backgroundName);
 	}
+	pos.w = background->width();
+	pos.h = background->height();
 
 	//preparing graphic with cell borders
 	cellBorders = std::make_unique<Canvas>(Point(background->width(), background->height()));
@@ -86,8 +88,31 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	auto accessibility = owner.curInt->cb->getAccesibility();
 	for(int i = 0; i < accessibility.size(); i++)
 		stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
+
+	addUsedEvents(MOVE);
+	LOCPLINT->cingconsole->pos = this->pos;
+}
+
+void BattleFieldController::createHeroes()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+
+	// create heroes as part of our constructor for correct positioning inside battlefield
+	if(owner.attackingHeroInstance)
+		owner.attackingHero = std::make_shared<BattleHero>(owner, owner.attackingHeroInstance, false);
+
+	if(owner.defendingHeroInstance)
+		owner.defendingHero = std::make_shared<BattleHero>(owner, owner.defendingHeroInstance, true);
+}
+
+void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event)
+{
+	BattleHex selectedHex = getHoveredHex();
+
+	owner.actionsController->handleHex(selectedHex, MOVE);
 }
 
+
 void BattleFieldController::renderBattlefield(Canvas & canvas)
 {
 	showBackground(canvas);
@@ -112,19 +137,19 @@ void BattleFieldController::showBackground(Canvas & canvas)
 
 void BattleFieldController::showBackgroundImage(Canvas & canvas)
 {
-	canvas.draw(background, owner.pos.topLeft());
+	canvas.draw(background, pos.topLeft());
 
 	owner.obstacleController->showAbsoluteObstacles(canvas, pos.topLeft());
 	if ( owner.siegeController )
 		owner.siegeController->showAbsoluteObstacles(canvas, pos.topLeft());
 
 	if (settings["battle"]["cellBorders"].Bool())
-		canvas.draw(*cellBorders, owner.pos.topLeft());
+		canvas.draw(*cellBorders, pos.topLeft());
 }
 
 void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
 {
-	canvas.draw(*backgroundWithHexes.get(), owner.pos.topLeft());
+	canvas.draw(*backgroundWithHexes.get(), pos.topLeft());
 }
 
 void BattleFieldController::redrawBackgroundWithHexes()
@@ -318,7 +343,7 @@ Rect BattleFieldController::hexPositionLocal(BattleHex hex) const
 
 Rect BattleFieldController::hexPositionAbsolute(BattleHex hex) const
 {
-	return hexPositionLocal(hex) + owner.pos.topLeft();
+	return hexPositionLocal(hex) + pos.topLeft();
 }
 
 bool BattleFieldController::isPixelInHex(Point const & position)
@@ -512,3 +537,22 @@ bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const
 {
 	return stackCountOutsideHexes[number];
 }
+
+void BattleFieldController::showAll(SDL_Surface * to)
+{
+	show(to);
+}
+
+void BattleFieldController::show(SDL_Surface * to)
+{
+	owner.stacksController->update();
+	owner.obstacleController->update();
+
+	SDL_Rect buf;
+
+	Canvas canvas(to);
+	SDL_GetClipRect(to, &buf);
+	SDL_SetClipRect(to, &pos);
+	renderBattlefield(canvas);
+	SDL_SetClipRect(to, &buf); //restoring previous clip_rect
+}

+ 8 - 0
client/battle/BattleFieldController.h

@@ -22,6 +22,7 @@ struct Rect;
 struct Point;
 
 class ClickableHex;
+class BattleHero;
 class Canvas;
 class IImage;
 class BattleInterface;
@@ -64,9 +65,16 @@ class BattleFieldController : public CIntObject
 	void showHighlightedHexes(Canvas & canvas);
 
 	BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
+
+	void mouseMoved(const SDL_MouseMotionEvent &event) override;
+	void showAll(SDL_Surface * to) override;
+	void show(SDL_Surface * to) override;
+
 public:
 	BattleFieldController(BattleInterface & owner);
 
+	void createHeroes();
+
 	void redrawBackgroundWithHexes();
 	void renderBattlefield(Canvas & canvas);
 

+ 17 - 235
client/battle/BattleInterface.cpp

@@ -46,13 +46,11 @@ CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
 
 BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
 		const CGHeroInstance *hero1, const CGHeroInstance *hero2,
-		const SDL_Rect & myRect,
 		std::shared_ptr<CPlayerInterface> att,
 		std::shared_ptr<CPlayerInterface> defen,
 		std::shared_ptr<CPlayerInterface> spectatorInt)
 	: attackingHeroInstance(hero1)
 	, defendingHeroInstance(hero2)
-	, animCount(0)
 	, attackerInt(att)
 	, defenderInt(defen)
 	, curInt(att)
@@ -60,8 +58,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	, moveSoundHander(-1)
 	, bresult(nullptr)
 {
-	OBJ_CONSTRUCTION;
-
 	for ( auto & event : animationEvents)
 		event.setn(false);
 
@@ -75,8 +71,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 		curInt = defenderInt;
 	}
 
-	pos = myRect;
-	strongInterest = true;
 	givenCommand.setn(nullptr);
 
 	//hot-seat -> check tactics for both players (defender may be local human)
@@ -88,26 +82,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	//if we found interface of player with tactics, then enter tactics mode
 	tacticsMode = static_cast<bool>(tacticianInterface);
 
-	//create stack queue
-	bool embedQueue;
-	std::string queueSize = settings["battle"]["queueSize"].String();
-
-	if(queueSize == "auto")
-		embedQueue = screen->h < 700;
-	else
-		embedQueue = screen->h < 700 || queueSize == "small";
-
-	queue = std::make_shared<StackQueue>(embedQueue, *this);
-	if(!embedQueue)
-	{
-		if (settings["battle"]["showQueue"].Bool())
-			pos.y += queue->pos.h / 2; //center whole window
-
-		queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
-	}
-
-	CPlayerInterface::battleInt = this;
-
 	//initializing armies
 	this->army1 = army1;
 	this->army2 = army2;
@@ -116,19 +90,11 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 	if(town && town->hasFort())
 		siegeController.reset(new BattleSiegeController(*this, town));
 
-	controlPanel = std::make_shared<BattleControlPanel>(*this, Point(0, 556));
+	windowObject = std::make_shared<BattleControlPanel>(*this);
 	projectilesController.reset(new BattleProjectileController(*this));
-	fieldController.reset( new BattleFieldController(*this));
 	stacksController.reset( new BattleStacksController(*this));
 	actionsController.reset( new BattleActionsController(*this));
 	effectsController.reset(new BattleEffectsController(*this));
-
-	if(hero1) // attacking hero
-		attackingHero = std::make_shared<BattleHero>(*this, hero1, false);
-
-	if(hero2) // defending hero
-		defendingHero = std::make_shared<BattleHero>(*this, hero2, true);
-
 	obstacleController.reset(new BattleObstacleController(*this));
 
 	if(tacticsMode)
@@ -150,10 +116,9 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
 
 	CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
 
-	addUsedEvents(RCLICK | MOVE | KEYBOARD);
-	queue->update();
-
-	controlPanel->blockUI(true);
+	GH.pushInt(windowObject);
+	windowObject->blockUI(true);
+	windowObject->updateQueue();
 }
 
 BattleInterface::~BattleInterface()
@@ -161,10 +126,6 @@ BattleInterface::~BattleInterface()
 	CPlayerInterface::battleInt = nullptr;
 	givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
 
-	assert(!active);
-	if (active) //dirty fix for #485
-		deactivate();
-
 	if (adventureInt && adventureInt->selection)
 	{
 		//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
@@ -202,84 +163,6 @@ void BattleInterface::setPrintMouseShadow(bool set)
 	shadow->Bool() = set;
 }
 
-void BattleInterface::activate()
-{
-	controlPanel->activate();
-
-	if (curInt->isAutoFightOn)
-		return;
-
-	CIntObject::activate();
-
-	if (attackingHero)
-		attackingHero->activate();
-	if (defendingHero)
-		defendingHero->activate();
-
-	fieldController->activate();
-
-	if (settings["battle"]["showQueue"].Bool())
-		queue->activate();
-
-	LOCPLINT->cingconsole->activate();
-}
-
-void BattleInterface::deactivate()
-{
-	controlPanel->deactivate();
-	CIntObject::deactivate();
-
-	fieldController->deactivate();
-
-	if (attackingHero)
-		attackingHero->deactivate();
-	if (defendingHero)
-		defendingHero->deactivate();
-	if (settings["battle"]["showQueue"].Bool())
-		queue->deactivate();
-
-	LOCPLINT->cingconsole->deactivate();
-}
-
-void BattleInterface::keyPressed(const SDL_KeyboardEvent & key)
-{
-	if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
-	{
-		if(settings["battle"]["showQueue"].Bool()) //hide queue
-			hideQueue();
-		else
-			showQueue();
-
-	}
-	else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
-	{
-		actionsController->enterCreatureCastingMode();
-	}
-	else if(key.keysym.sym == SDLK_ESCAPE)
-	{
-		if(getAnimationCondition(EAnimationEvents::OPENING) == true)
-			CCS->soundh->stopSound(battleIntroSoundChannel);
-		else
-			actionsController->endCastingSpell();
-	}
-}
-void BattleInterface::mouseMoved(const SDL_MouseMotionEvent &event)
-{
-	BattleHex selectedHex = fieldController->getHoveredHex();
-
-	actionsController->handleHex(selectedHex, MOVE);
-
-	controlPanel->mouseMoved(event);
-}
-
-void BattleInterface::clickRight(tribool down, bool previousState)
-{
-	if (!down)
-	{
-		actionsController->endCastingSpell();
-	}
-}
-
 void BattleInterface::stackReset(const CStack * stack)
 {
 	stacksController->stackReset(stack);
@@ -294,7 +177,7 @@ void BattleInterface::stackRemoved(uint32_t stackID)
 {
 	stacksController->stackRemoved(stackID);
 	fieldController->redrawBackgroundWithHexes();
-	queue->update();
+	windowObject->updateQueue();
 }
 
 void BattleInterface::stackActivated(const CStack *stack)
@@ -346,7 +229,7 @@ void BattleInterface::newRoundFirst( int round )
 
 void BattleInterface::newRound(int number)
 {
-	controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
+	console->addText(CGI->generaltexth->allTexts[412]);
 }
 
 void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
@@ -409,11 +292,6 @@ const CGHeroInstance * BattleInterface::getActiveHero()
 	return defendingHeroInstance;
 }
 
-void BattleInterface::hexLclicked(int whichOne)
-{
-	actionsController->handleHex(whichOne, LCLICK);
-}
-
 void BattleInterface::stackIsCatapulting(const CatapultAttack & ca)
 {
 	if (siegeController)
@@ -440,7 +318,7 @@ void BattleInterface::displayBattleFinished()
 	CCS->curh->set(Cursor::Map::POINTER);
 	if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
 	{
-		close();
+		windowObject->close();
 		return;
 	}
 
@@ -451,7 +329,7 @@ void BattleInterface::displayBattleFinished()
 
 void BattleInterface::spellCast(const BattleSpellCast * sc)
 {
-	controlPanel->blockUI(true);
+	windowObject->blockUI(true);
 
 	const SpellID spellID = sc->spellID;
 	const CSpell * spell = spellID.toSpell();
@@ -541,8 +419,8 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	//mana absorption
 	if (sc->manaGained > 0)
 	{
-		Point leftHero = Point(15, 30) + pos;
-		Point rightHero = Point(755, 30) + pos;
+		Point leftHero = Point(15, 30) + fieldController->pos;
+		Point rightHero = Point(755, 30) + fieldController->pos;
 		bool side = sc->side;
 
 		executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
@@ -578,8 +456,7 @@ void BattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog
 	{
 		std::string formatted = line.toString();
 		boost::algorithm::trim(formatted);
-		if(!controlPanel->console->addText(formatted))
-			logGlobal->warn("Too long battle log line");
+		appendBattleLog(formatted);
 	}
 }
 
@@ -671,7 +548,7 @@ void BattleInterface::activateStack()
 		return;
 
 	myTurn = true;
-	queue->update();
+	windowObject->updateQueue();
 	fieldController->redrawBackgroundWithHexes();
 	actionsController->activateStack();
 	GH.fakeMouseMove();
@@ -682,8 +559,7 @@ void BattleInterface::endAction(const BattleAction* action)
 	const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
 
 	stacksController->endAction(action);
-
-	queue->update();
+	windowObject->updateQueue();
 
 	//stack ended movement in tactics phase -> select the next one
 	if (tacticsMode)
@@ -694,39 +570,16 @@ void BattleInterface::endAction(const BattleAction* action)
 		fieldController->redrawBackgroundWithHexes();
 }
 
-void BattleInterface::hideQueue()
+void BattleInterface::appendBattleLog(const std::string & newEntry)
 {
-	Settings showQueue = settings.write["battle"]["showQueue"];
-	showQueue->Bool() = false;
-
-	queue->deactivate();
-
-	if (!queue->embedded)
-	{
-		moveBy(Point(0, -queue->pos.h / 2));
-		GH.totalRedraw();
-	}
-}
-
-void BattleInterface::showQueue()
-{
-	Settings showQueue = settings.write["battle"]["showQueue"];
-	showQueue->Bool() = true;
-
-	queue->activate();
-
-	if (!queue->embedded)
-	{
-		moveBy(Point(0, +queue->pos.h / 2));
-		GH.totalRedraw();
-	}
+	console->addText(newEntry);
 }
 
 void BattleInterface::startAction(const BattleAction* action)
 {
 	if(action->actionType == EActionType::END_TACTIC_PHASE)
 	{
-		controlPanel->tacticPhaseEnded();
+		windowObject->tacticPhaseEnded();
 		return;
 	}
 
@@ -734,7 +587,7 @@ void BattleInterface::startAction(const BattleAction* action)
 
 	if (stack)
 	{
-		queue->update();
+		windowObject->updateQueue();
 	}
 	else
 	{
@@ -743,8 +596,6 @@ void BattleInterface::startAction(const BattleAction* action)
 
 	stacksController->startAction(action);
 
-	redraw(); // redraw after deactivation, including proper handling of hovered hexes
-
 	if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
 		return;
 
@@ -859,75 +710,6 @@ void BattleInterface::requestAutofightingAIToTakeAction()
 	aiThread.detach();
 }
 
-void BattleInterface::showAll(SDL_Surface *to)
-{
-	show(to);
-}
-
-void BattleInterface::show(SDL_Surface *to)
-{
-	Canvas canvas(to);
-	assert(to);
-
-	SDL_Rect buf;
-	SDL_GetClipRect(to, &buf);
-	SDL_SetClipRect(to, &pos);
-
-	++animCount;
-
-	fieldController->renderBattlefield(canvas);
-
-	stacksController->update();
-
-	SDL_SetClipRect(to, &buf); //restoring previous clip_rect
-
-	showInterface(to);
-}
-
-void BattleInterface::collectRenderableObjects(BattleRenderer & renderer)
-{
-	if (attackingHero)
-	{
-		renderer.insert(EBattleFieldLayer::HEROES, BattleHex(0),[this](BattleRenderer::RendererRef canvas)
-		{
-			attackingHero->render(canvas);
-		});
-	}
-	if (defendingHero)
-	{
-		renderer.insert(EBattleFieldLayer::HEROES, BattleHex(GameConstants::BFIELD_WIDTH-1),[this](BattleRenderer::RendererRef canvas)
-		{
-			defendingHero->render(canvas);
-		});
-	}
-}
-
-void BattleInterface::showInterface(SDL_Surface * to)
-{
-	//showing in-game console
-	LOCPLINT->cingconsole->show(to);
-	controlPanel->showAll(to);
-
-	Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
-
-	if (settings["battle"]["showQueue"].Bool())
-	{
-		if (!queue->embedded)
-		{
-			posWithQueue.y -= queue->pos.h;
-			posWithQueue.h += queue->pos.h;
-		}
-
-		queue->showAll(to);
-	}
-
-	//printing border around interface
-	if (screen->w != 800 || screen->h !=600)
-	{
-		CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
-	}
-}
-
 void BattleInterface::castThisSpell(SpellID spellID)
 {
 	actionsController->castThisSpell(spellID);

+ 23 - 29
client/battle/BattleInterface.h

@@ -52,6 +52,7 @@ class BattleControlPanel;
 class BattleStacksController;
 class BattleActionsController;
 class BattleEffectsController;
+class BattleConsole;
 
 /// Small struct which contains information about the id of the attacked stack, the damage dealt,...
 struct StackAttackedInfo
@@ -86,25 +87,26 @@ struct StackAttackInfo
 	bool lifeDrain;
 };
 
-
-/// Big class which handles the overall battle interface actions and it is also responsible for
-/// drawing everything correctly.
-class BattleInterface : public WindowBase
+/// Main class for battles, responsible for relaying information from server to various battle entities
+class BattleInterface
 {
 private:
-	std::shared_ptr<BattleHero> attackingHero;
-	std::shared_ptr<BattleHero> defendingHero;
-	std::shared_ptr<StackQueue> queue;
-	std::shared_ptr<BattleControlPanel> controlPanel;
+	std::shared_ptr<BattleControlPanel> windowObject;
+	std::shared_ptr<BattleConsole> console;
 
 	std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
-	std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
+
+	std::shared_ptr<CPlayerInterface> attackerInt; // attacker interface, not null if attacker is human in our vcmiclient
+	std::shared_ptr<CPlayerInterface> defenderInt; // defender interface, not null if attacker is human in our vcmiclient
+
 	std::shared_ptr<CPlayerInterface> curInt; //current player interface
 
-	const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
-	const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
+	 //copy of initial armies (for result window)
+	const CCreatureSet *army1;
+	const CCreatureSet *army2;
 
-	ui8 animCount;
+	const CGHeroInstance *attackingHeroInstance;
+	const CGHeroInstance *defendingHeroInstance;
 
 	bool tacticsMode;
 	int battleIntroSoundChannel; //required as variable for disabling it via ESC key
@@ -138,6 +140,8 @@ private:
 
 	void executeSpellCast(); //called when a hero casts a spell
 
+	void appendBattleLog(const std::string & newEntry);
+
 public:
 	std::unique_ptr<BattleProjectileController> projectilesController;
 	std::unique_ptr<BattleSiegeController> siegeController;
@@ -147,6 +151,9 @@ public:
 	std::unique_ptr<BattleActionsController> actionsController;
 	std::unique_ptr<BattleEffectsController> effectsController;
 
+	std::shared_ptr<BattleHero> attackingHero;
+	std::shared_ptr<BattleHero> defendingHero;
+
 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
 
 	bool myTurn; //if true, interface is active (commands can be ordered)
@@ -154,8 +161,8 @@ public:
 
 	const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
 
-	BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
-	virtual ~BattleInterface();
+	BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
+	~BattleInterface();
 
 	void setPrintCellBorders(bool set); //if true, cell borders will be printed
 	void setPrintStackRange(bool set); //if true,range of active stack will be printed
@@ -179,18 +186,6 @@ public:
 	/// adds action that will be executed one selected condition reached targeted state
 	void executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action);
 
-	//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
-	void activate() override;
-	void deactivate() override;
-	void keyPressed(const SDL_KeyboardEvent & key) override;
-	void mouseMoved(const SDL_MouseMotionEvent &sEvent) override;
-	void clickRight(tribool down, bool previousState) override;
-
-	void show(SDL_Surface *to) override;
-	void showAll(SDL_Surface *to) override;
-
-	void collectRenderableObjects(BattleRenderer & renderer);
-
 	//call-ins
 	void startAction(const BattleAction* action);
 	void stackReset(const CStack * stack);
@@ -202,7 +197,6 @@ public:
 	void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
 	void newRoundFirst( int round );
 	void newRound(int number); //caled when round is ended; number is the number of round
-	void hexLclicked(int whichOne); //hex only call-in
 	void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
 	void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
 	void displayBattleFinished(); //displays battle result
@@ -218,8 +212,6 @@ public:
 	void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
 
 	void endAction(const BattleAction* action);
-	void hideQueue();
-	void showQueue();
 
 	void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
 
@@ -252,4 +244,6 @@ public:
 	friend class BattleStacksController;
 	friend class BattleActionsController;
 	friend class BattleEffectsController;
+	friend class BattleRenderer;
+	friend class CInGameConsole;
 };

+ 45 - 17
client/battle/BattleInterfaceClasses.cpp

@@ -12,6 +12,7 @@
 
 #include "BattleInterface.h"
 #include "BattleActionsController.h"
+#include "BattleRenderer.h"
 #include "BattleSiegeController.h"
 #include "BattleFieldController.h"
 #include "BattleStacksController.h"
@@ -48,6 +49,8 @@
 
 void BattleConsole::showAll(SDL_Surface * to)
 {
+	CIntObject::showAll(to);
+
 	Point consolePos(pos.x + 10,      pos.y + 19);
 	Point textPos   (pos.x + pos.w/2, pos.y + 19);
 
@@ -91,27 +94,33 @@ bool BattleConsole::addText(const std::string & text)
 
 	logEntries.push_back( text.substr(firstInToken, text.size()) );
 	scrollPosition = (int)logEntries.size()-1;
+	redraw();
 	return true;
 }
 void BattleConsole::scrollUp(ui32 by)
 {
 	if(scrollPosition > static_cast<int>(by))
 		scrollPosition -= by;
+	redraw();
 }
 
 void BattleConsole::scrollDown(ui32 by)
 {
 	if(scrollPosition + by < logEntries.size())
 		scrollPosition += by;
+	redraw();
 }
 
-BattleConsole::BattleConsole(const Rect & position)
+BattleConsole::BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size)
 	: scrollPosition(-1)
 	, enteringText(false)
 {
-	pos += position.topLeft();
-	pos.w = position.w;
-	pos.h = position.h;
+	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pos += objectPos;
+	pos.w = size.x;
+	pos.h = size.y;
+
+	background = std::make_shared<CPicture>(backgroundSource->getSurface(), Rect(imagePos, size), 0, 0 );
 }
 
 BattleConsole::~BattleConsole()
@@ -141,11 +150,13 @@ void BattleConsole::setEnteredText(const std::string & text)
 {
 	assert(enteringText == true);
 	consoleText = text;
+	redraw();
 }
 
 void BattleConsole::write(const std::string & Text)
 {
 	hoverText = Text;
+	redraw();
 }
 
 void BattleConsole::clearIfMatching(const std::string & Text)
@@ -159,6 +170,11 @@ void BattleConsole::clear()
 	write({});
 }
 
+const CGHeroInstance * BattleHero::instance()
+{
+	return hero;
+}
+
 void BattleHero::render(Canvas & canvas)
 {
 	size_t groupIndex = static_cast<size_t>(phase);
@@ -206,6 +222,16 @@ float BattleHero::getFrame() const
 	return currentFrame;
 }
 
+void BattleHero::collectRenderableObjects(BattleRenderer & renderer)
+{
+	auto hex = defender ? BattleHex(GameConstants::BFIELD_WIDTH-1) : BattleHex(0);
+
+	renderer.insert(EBattleFieldLayer::HEROES, hex, [this](BattleRenderer::RendererRef canvas)
+	{
+		render(canvas);
+	});
+}
+
 void BattleHero::onPhaseFinished(const std::function<void()> & callback)
 {
 	phaseFinishedCallback = callback;
@@ -257,8 +283,8 @@ void BattleHero::clickRight(tribool down, bool previousState)
 		return;
 
 	Point windowPosition;
-	windowPosition.x = (!defender) ? owner.pos.topLeft().x + 1 : owner.pos.topRight().x - 79;
-	windowPosition.y = owner.pos.y + 135;
+	windowPosition.x = (!defender) ? owner.fieldController->pos.topLeft().x + 1 : owner.fieldController->pos.topRight().x - 79;
+	windowPosition.y = owner.fieldController->pos.y + 135;
 
 	InfoAboutHero targetHero;
 	if(down && (owner.myTurn || settings["session"]["spectate"].Bool()))
@@ -304,8 +330,8 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 
 	pos.w = 64;
 	pos.h = 136;
-	pos.x = owner.pos.x + (defender ? (owner.pos.w - pos.w) : 0);
-	pos.y = owner.pos.y;
+	pos.x = owner.fieldController->pos.x + (defender ? (owner.fieldController->pos.w - pos.w) : 0);
+	pos.y = owner.fieldController->pos.y;
 
 	if(defender)
 		animation->verticalFlip();
@@ -627,7 +653,7 @@ void ClickableHex::hover(bool on)
 	//Hoverable::hover(on);
 	if(!on && setAlterText)
 	{
-		myInterface->controlPanel->console->clear();
+		GH.statusbar->clear();
 		setAlterText = false;
 	}
 }
@@ -651,13 +677,13 @@ void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
 			MetaString text;
 			text.addTxt(MetaString::GENERAL_TXT, 220);
 			attackedStack->addNameReplacement(text);
-			myInterface->controlPanel->console->write(text.toString());
+			GH.statusbar->write(text.toString());
 			setAlterText = true;
 		}
 	}
 	else if(setAlterText)
 	{
-		myInterface->controlPanel->console->clear();
+		GH.statusbar->clear();
 		setAlterText = false;
 	}
 }
@@ -666,7 +692,7 @@ void ClickableHex::clickLeft(tribool down, bool previousState)
 {
 	if(!down && hovered && strictHovered) //we've been really clicked!
 	{
-		myInterface->hexLclicked(myNumber);
+		myInterface->actionsController->handleHex(myNumber, LCLICK);
 	}
 }
 
@@ -690,10 +716,10 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	if(embedded)
 	{
-		pos.w = QUEUE_SIZE * 37;
-		pos.h = 46;
-		pos.x = screen->w/2 - pos.w/2;
-		pos.y = (screen->h - 600)/2 + 10;
+		pos.w = QUEUE_SIZE * 41;
+		pos.h = 49;
+		pos.x += parent->pos.w/2 - pos.w/2;
+		pos.y += 10;
 
 		icons = std::make_shared<CAnimation>("CPRSMALL");
 		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
@@ -702,6 +728,8 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	{
 		pos.w = 800;
 		pos.h = 85;
+		pos.x += 0;
+		pos.y -= pos.h;
 
 		background = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, 0, pos.w, pos.h));
 
@@ -716,7 +744,7 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 	for (int i = 0; i < stackBoxes.size(); i++)
 	{
 		stackBoxes[i] = std::make_shared<StackBox>(this);
-		stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80) * i, 0));
+		stackBoxes[i]->moveBy(Point(1 + (embedded ? 41 : 80) * i, 0));
 	}
 }
 

+ 9 - 2
client/battle/BattleInterfaceClasses.h

@@ -40,11 +40,14 @@ class CLabel;
 class CTextBox;
 class CAnimImage;
 class CPlayerInterface;
+class BattleRenderer;
 
 /// Class which shows the console at the bottom of the battle screen and manages the text of the console
 class BattleConsole : public CIntObject, public IStatusBar
 {
 private:
+	std::shared_ptr<CPicture> background;
+
 	/// List of all texts added during battle, essentially - log of entire battle
 	std::vector< std::string > logEntries;
 
@@ -60,7 +63,7 @@ private:
 	/// if true then we are currently entering console text
 	bool enteringText;
 public:
-	BattleConsole(const Rect & position);
+	BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
 	~BattleConsole();
 	void showAll(SDL_Surface * to) override;
 
@@ -98,10 +101,14 @@ class BattleHero : public CIntObject
 
 	void switchToNextPhase();
 
-public:
 	void render(Canvas & canvas); //prints next frame of animation to to
+public:
+	const CGHeroInstance * instance();
+
 	void setPhase(EHeroAnimType newPhase); //sets phase of hero animation
 
+	void collectRenderableObjects(BattleRenderer & renderer);
+
 	float getFrame() const;
 	void onPhaseFinished(const std::function<void()> &);
 

+ 11 - 3
client/battle/BattleObstacleController.cpp

@@ -15,17 +15,20 @@
 #include "BattleAnimationClasses.h"
 #include "BattleStacksController.h"
 #include "BattleRenderer.h"
+#include "CreatureAnimation.h"
 
 #include "../CPlayerInterface.h"
 #include "../gui/CAnimation.h"
 #include "../gui/Canvas.h"
+#include "../gui/CGuiHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/ObstacleHandler.h"
 
 BattleObstacleController::BattleObstacleController(BattleInterface & owner):
-	owner(owner)
+	owner(owner),
+	timePassed(0.f)
 {
 	auto obst = owner.curInt->cb->battleGetAllObstacles();
 	for(auto & elem : obst)
@@ -136,9 +139,14 @@ void BattleObstacleController::collectRenderableObjects(BattleRenderer & rendere
 	}
 }
 
+void BattleObstacleController::update()
+{
+	timePassed += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
+}
+
 std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
 {
-	int frameIndex = (owner.animCount+1) *25 / owner.getAnimSpeed();
+	int framesCount = timePassed * AnimationControls::getObstaclesSpeed();
 	std::shared_ptr<CAnimation> animation;
 
 	// obstacle is not loaded yet, don't show anything
@@ -150,7 +158,7 @@ std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstac
 
 	if(animation)
 	{
-		frameIndex %= animation->size(0);
+		int frameIndex = framesCount % animation->size(0);
 		return animation->getImage(frameIndex, 0);
 	}
 	return nullptr;

+ 6 - 0
client/battle/BattleObstacleController.h

@@ -29,6 +29,9 @@ class BattleObstacleController
 {
 	BattleInterface & owner;
 
+	/// total time, in seconds, since start of battle. Used for animating obstacles
+	float timePassed;
+
 	/// cached animations of all obstacles in current battle
 	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
 
@@ -43,6 +46,9 @@ class BattleObstacleController
 public:
 	BattleObstacleController(BattleInterface & owner);
 
+	/// called every frame
+	void update();
+
 	/// call-in from network pack, add newly placed obstacles with any required animations
 	void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);
 

+ 6 - 1
client/battle/BattleRenderer.cpp

@@ -11,19 +11,24 @@
 #include "BattleRenderer.h"
 
 #include "BattleInterface.h"
+#include "BattleInterfaceClasses.h"
 #include "BattleEffectsController.h"
+#include "BattleControlPanel.h"
 #include "BattleSiegeController.h"
 #include "BattleStacksController.h"
 #include "BattleObstacleController.h"
 
 void BattleRenderer::collectObjects()
 {
-	owner.collectRenderableObjects(*this);
 	owner.effectsController->collectRenderableObjects(*this);
 	owner.obstacleController->collectRenderableObjects(*this);
 	owner.stacksController->collectRenderableObjects(*this);
 	if (owner.siegeController)
 		owner.siegeController->collectRenderableObjects(*this);
+	if (owner.defendingHero)
+		owner.defendingHero->collectRenderableObjects(*this);
+	if (owner.attackingHero)
+		owner.attackingHero->collectRenderableObjects(*this);
 }
 
 void BattleRenderer::sortObjects()

+ 3 - 3
client/battle/BattleSiegeController.cpp

@@ -205,7 +205,7 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con
 
 	if (posID != 0)
 	{
-		Point result = owner.pos.topLeft();
+		Point result = owner.fieldController->pos.topLeft();
 		result.x += town->town->clientInfo.siegePositions[posID].x;
 		result.y += town->town->clientInfo.siegePositions[posID].y;
 		return result;
@@ -301,11 +301,11 @@ void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
 				owner.stacksController->showStack(canvas, getTurretStack(wallPiece));
 			});
 			renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
-				showWallPiece(canvas, wallPiece, owner.pos.topLeft());
+				showWallPiece(canvas, wallPiece, owner.fieldController->pos.topLeft());
 			});
 		}
 		renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
-			showWallPiece(canvas, wallPiece, owner.pos.topLeft());
+			showWallPiece(canvas, wallPiece, owner.fieldController->pos.topLeft());
 		});
 
 

+ 6 - 6
client/battle/BattleStacksController.cpp

@@ -240,7 +240,7 @@ void BattleStacksController::setActiveStack(const CStack *stack)
 	if (activeStack) // update UI
 		stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
 
-	owner.controlPanel->blockUI(activeStack == nullptr);
+	owner.windowObject->blockUI(activeStack == nullptr);
 }
 
 bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
@@ -558,7 +558,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	if(info.lucky)
 	{
 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
-			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-45));
+			owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
 			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, attacker->getPosition());
 		});
 	}
@@ -566,7 +566,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	if(info.unlucky)
 	{
 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
-			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(-44));
+			owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
 			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, attacker->getPosition());
 		});
 	}
@@ -574,7 +574,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	if(info.deathBlow)
 	{
 		owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
-			owner.controlPanel->console->addText(info.attacker->formatGeneralMessage(365));
+			owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
 			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, soundBase::deathBlow, defender->getPosition());
 		});
 
@@ -682,7 +682,7 @@ void BattleStacksController::endAction(const BattleAction* action)
 	assert(owner.getAnimationCondition(EAnimationEvents::HIT) == false);
 	assert(owner.getAnimationCondition(EAnimationEvents::PROJECTILES) == false);
 
-	owner.controlPanel->blockUI(activeStack == nullptr);
+	owner.windowObject->blockUI(activeStack == nullptr);
 	removeExpiredColorFilters();
 }
 
@@ -802,7 +802,7 @@ Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CSta
 		}
 	}
 	//returning
-	return ret + owner.pos.topLeft();
+	return ret + owner.fieldController->pos.topLeft();
 }
 
 void BattleStacksController::setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell * source, bool persistent)

+ 5 - 0
client/battle/CreatureAnimation.cpp

@@ -145,6 +145,11 @@ float AnimationControls::getFadeInDuration()
 	return 1.0f / settings["battle"]["animationSpeed"].Float();
 }
 
+float AnimationControls::getObstaclesSpeed()
+{
+	return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 30);
+}
+
 ECreatureAnimType::Type CreatureAnimation::getType() const
 {
 	return type;

+ 3 - 0
client/battle/CreatureAnimation.h

@@ -49,6 +49,9 @@ namespace AnimationControls
 
 	/// Returns total time for full fade-in effect on newly summoned creatures, in seconds
 	float getFadeInDuration();
+
+	/// Returns animation speed for obstacles, in frames per second
+	float getObstaclesSpeed();
 }
 
 /// Class which manages animations of creatures/units inside battles

+ 2 - 5
client/widgets/AdventureMapClasses.cpp

@@ -974,11 +974,8 @@ void CInGameConsole::show(SDL_Surface * to)
 	boost::unique_lock<boost::mutex> lock(texts_mx);
 	for(auto it = texts.begin(); it != texts.end(); ++it, ++number)
 	{
-		Point leftBottomCorner(0, screen->h);
-		if(LOCPLINT->battleInt)
-		{
-			leftBottomCorner = LOCPLINT->battleInt->pos.bottomLeft();
-		}
+		Point leftBottomCorner(0, pos.h);
+
 		graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN,
 			Point(leftBottomCorner.x + 50, leftBottomCorner.y - (int)texts.size() * 20 - 80 + number*20));
 

+ 1 - 1
client/widgets/CArtifactHolder.cpp

@@ -126,7 +126,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
 	if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH)
 	{
 		if(ourArt->artType->id == ArtifactID::SPELLBOOK)
-			GH.pushIntT<CSpellWindow>(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt);
+			GH.pushIntT<CSpellWindow>(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt.get());
 	}
 
 	if (!down && previousState)

+ 3 - 0
client/windows/CAdvmapInterface.cpp

@@ -941,7 +941,10 @@ void CAdvMapInt::activate()
 	GH.statusbar = statusbar;
 	
 	if(LOCPLINT)
+	{
 		LOCPLINT->cingconsole->activate();
+		LOCPLINT->cingconsole->pos = this->pos;
+	}
 	
 	if(!duringAITurn)
 	{