浏览代码

Reducing size of GUIClasses. Moved Garrison Int into a separate file.

Ivan Savenko 11 年之前
父节点
当前提交
09bedf9aa9
共有 6 个文件被更改,包括 631 次插入594 次删除
  1. 1 0
      client/CMakeLists.txt
  2. 0 490
      client/GUIClasses.cpp
  3. 1 104
      client/GUIClasses.h
  4. 501 0
      client/gui/CGarrisonInt.cpp
  5. 123 0
      client/gui/CGarrisonInt.h
  6. 5 0
      client/gui/CIntObject.cpp

+ 1 - 0
client/CMakeLists.txt

@@ -16,6 +16,7 @@ set(client_SRCS
 
 		gui/CArtifactHolder.cpp
 		gui/CComponent.cpp
+		gui/CGarrisonInt.cpp
 		gui/CGuiHandler.cpp
 		gui/CIntObject.cpp
 		gui/CIntObjectClasses.cpp

+ 0 - 490
client/GUIClasses.cpp

@@ -259,454 +259,6 @@ CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town):
 	init(InfoAboutTown(town, true));
 }
 
-void CGarrisonSlot::setHighlight(bool on)
-{
-	if (on)
-		selectionImage->enable(); //show
-	else
-		selectionImage->disable(); //hide
-}
-
-void CGarrisonSlot::hover (bool on)
-{
-	////Hoverable::hover(on);
-	if(on)
-	{
-		std::string temp;
-		if(creature)
-		{
-			if(owner->getSelection())
-			{
-				if(owner->getSelection() == this)
-				{
-					temp = CGI->generaltexth->tcommands[4]; //View %s
-					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
-				}
-				else if (owner->getSelection()->creature == creature)
-				{
-					temp = CGI->generaltexth->tcommands[2]; //Combine %s armies
-					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
-				}
-				else if (owner->getSelection()->creature)
-				{
-					temp = CGI->generaltexth->tcommands[7]; //Exchange %s with %s
-					boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing);
-					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
-				}
-				else
-				{
-                    logGlobal->warnStream() << "Warning - shouldn't be - highlighted void slot "<<owner->getSelection();
-                    logGlobal->warnStream() << "Highlighted set to nullptr";
-					owner->selectSlot(nullptr);
-				}
-			}
-			else
-			{
-				if(upg)
-				{
-					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
-				}
-				else if(owner->armedObjs[0] && owner->armedObjs[0]->ID == Obj::TOWN)
-				{
-					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
-				}
-				else
-				{
-					temp = CGI->generaltexth->allTexts[481]; //Select %s
-				}
-				boost::algorithm::replace_first(temp,"%s",creature->nameSing);
-			};
-		}
-		else
-		{
-			if(owner->getSelection())
-			{
-				const CArmedInstance *highl = owner->getSelection()->getObj();
-				if(  highl->needsLastStack()		//we are moving stack from hero's
-				  && highl->stacksCount() == 1	//it's only stack
-				  && owner->getSelection()->upg != upg	//we're moving it to the other garrison
-				  )
-				{
-					temp = CGI->generaltexth->tcommands[5]; //Cannot move last army to garrison
-				}
-				else
-				{
-					temp = CGI->generaltexth->tcommands[6]; //Move %s
-					boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing);
-				}
-			}
-			else
-			{
-				temp = CGI->generaltexth->tcommands[11]; //Empty
-			}
-		}
-		GH.statusbar->setText(temp);
-	}
-	else
-	{
-		GH.statusbar->clear();
-	}
-}
-
-const CArmedInstance * CGarrisonSlot::getObj() const
-{
-	return 	(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]);
-}
-
-bool CGarrisonSlot::our() const
-{
-	return 	upg?(owner->owned[1]):(owner->owned[0]);
-}
-
-void CGarrisonSlot::clickRight(tribool down, bool previousState)
-{
-	if(down && creature)
-	{
-		GH.pushInt(createCreWindow(myStack, CCreatureWindow::ARMY));
-	}
-}
-void CGarrisonSlot::clickLeft(tribool down, bool previousState)
-{
-	if(down)
-	{
-		bool refr = false;
-		if(owner->getSelection())
-		{
-			if(owner->getSelection() == this) //view info
-			{
-				UpgradeInfo pom;
-				LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom);
-
-				bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible
-				bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1  || !getObj()->needsLastStack());
-				std::function<void()> upgr = nullptr;
-				std::function<void()> dism = nullptr;
-				if(canUpgrade) upgr = [=] { LOCPLINT->cb->upgradeCreature(getObj(), ID, pom.newID[0]); };
-				if(canDismiss) dism = [=] { LOCPLINT->cb->dismissCreature(getObj(), ID); };
-
-				owner->selectSlot(nullptr);
-				owner->setSplittingMode(false);
-
-				for(auto & elem : owner->splitButtons)
-					elem->block(true);
-
-				redraw();
-				refr = true;
-				CIntObject *creWindow = createCreWindow(myStack, CCreatureWindow::HERO, upgr, dism, &pom);
-				GH.pushInt(creWindow);
-			}
-			else
-			{
-				// Only allow certain moves if troops aren't removable or not ours.
-				if (  ( owner->getSelection()->our()//our creature is selected
-				     || owner->getSelection()->creature == creature )//or we are rebalancing army
-				   && ( owner->removableUnits
-				     || (upg == 0 &&  ( owner->getSelection()->upg == 1 && !creature ) )
-					 || (upg == 1 &&    owner->getSelection()->upg == 1 ) ) )
-				{
-					//we want to split
-					if((owner->getSplittingMode() || LOCPLINT->shiftPressed())
-						&& (!creature
-							|| (creature == owner->getSelection()->creature)))
-					{
-						owner->p2 = ID; //store the second stack pos
-						owner->pb = upg;//store the second stack owner (up or down army)
-						owner->setSplittingMode(false);
-
-						int minLeft=0, minRight=0;
-
-						if(upg != owner->getSelection()->upg) //not splitting within same army
-						{
-							if(owner->getSelection()->getObj()->stacksCount() == 1 //we're splitting away the last stack
-								&& owner->getSelection()->getObj()->needsLastStack() )
-							{
-								minLeft = 1;
-							}
-							if(getObj()->stacksCount() == 1 //destination army can't be emptied, unless we're rebalancing two stacks of same creature
-								&& owner->getSelection()->creature == creature
-								&& getObj()->needsLastStack() )
-							{
-								minRight = 1;
-							}
-						}
-
-						int countLeft = owner->getSelection()->myStack ? owner->getSelection()->myStack->count : 0;
-						int countRight = myStack ? myStack->count : 0;
-
-						GH.pushInt(new CSplitWindow(owner->getSelection()->creature, boost::bind(&CGarrisonInt::splitStacks, owner, _1, _2),
-						                            minLeft, minRight, countLeft, countRight));
-						refr = true;
-					}
-					else if(creature != owner->getSelection()->creature) //swap
-					{
-						LOCPLINT->cb->swapCreatures(
-							(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
-							(!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
-							ID,owner->getSelection()->ID);
-					}
-					else //merge
-					{
-						LOCPLINT->cb->mergeStacks(
-							(!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
-							(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
-							owner->getSelection()->ID,ID);
-					}
-				}
-				else // Highlight
-				{
-					if(creature)
-						owner->selectSlot(this);
-					redraw();
-					refr = true;
-				}
-			}
-		}
-		else //highlight or drop artifact
-		{
-			bool artSelected = false;
-			if (CWindowWithArtifacts* chw = dynamic_cast<CWindowWithArtifacts*>(GH.topInt())) //dirty solution
-			{
-				const CArtifactsOfHero::SCommonPart *commonInfo = chw->artSets.front()->commonInfo;
-				if (const CArtifactInstance *art = commonInfo->src.art)
-				{
-					const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero();
-					artSelected = true;
-					ArtifactLocation src(srcHero, commonInfo->src.slotID);
-					ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT);
-					if (art->canBePutAt(dst, true))
-					{	//equip clicked stack
-						if(dst.getArt())
-						{
-							//creature can wear only one active artifact
-							//if we are placing a new one, the old one will be returned to the hero's backpack
-							LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero, dst.getArt()->firstBackpackSlot(srcHero)));
-						}
-						LOCPLINT->cb->swapArtifacts(src, dst);
-					}
-				}
-			}
-			if (!artSelected && creature)
-			{
-				owner->selectSlot(this);
-				if(creature)
-				{
-					for(auto & elem : owner->splitButtons)
-						elem->block(false);
-				}
-			}
-			redraw();
-			refr = true;
-		}
-		if(refr) {hover(false);	hover(true); } //to refresh statusbar
-	}
-}
-
-void CGarrisonSlot::update()
-{
-	if (getObj() != nullptr)
-	{
-		addUsedEvents(LCLICK | RCLICK | HOVER);
-		myStack = getObj()->getStackPtr(ID);
-		creature = myStack ? myStack->type : nullptr;
-	}
-	else
-	{
-		removeUsedEvents(LCLICK | RCLICK | HOVER);
-		myStack = nullptr;
-		creature = nullptr;
-	}
-
-	if (creature)
-	{
-		creatureImage->enable();
-		creatureImage->setFrame(creature->iconIndex);
-
-		stackCount->enable();
-		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
-	}
-	else
-	{
-		creatureImage->disable();
-		stackCount->disable();
-	}
-}
-
-CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg, const CStackInstance * Creature):
-    ID(IID),
-    owner(Owner),
-    myStack(Creature),
-    creature(Creature ? Creature->type : nullptr),
-    upg(Upg)
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-	if (getObj())
-		addUsedEvents(LCLICK | RCLICK | HOVER);
-	pos.x += x;
-	pos.y += y;
-
-	std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
-
-	creatureImage = new CAnimImage(imgName, creature ? creature->iconIndex : 0);
-	if (!creature)
-		creatureImage->disable();
-
-	selectionImage = new CAnimImage(imgName, 1);
-	selectionImage->disable();
-
-	if(Owner->smallIcons)
-	{
-		pos.w = 32;
-		pos.h = 32;
-	}
-	else
-	{
-		pos.w = 58;
-		pos.h = 64;
-	}
-
-	stackCount = new CLabel(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE);
-	if (!creature)
-		stackCount->disable();
-	else
-		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
-}
-
-void CGarrisonInt::addSplitBtn(CAdventureMapButton * button)
-{
-	addChild(button);
-	button->recActions = defActions;
-	splitButtons.push_back(button);
-	button->block(getSelection() == nullptr);
-}
-
-void CGarrisonInt::createSet(std::vector<CGarrisonSlot*> &ret, const CCreatureSet * set, int posX, int posY, int distance, int Upg )
-{
-	ret.resize(7);
-
-	if (set)
-	{
-		for(auto & elem : set->Slots())
-		{
-			ret[elem.first.getNum()] = new CGarrisonSlot(this, posX + (elem.first.getNum()*distance), posY, elem.first, Upg, elem.second);
-		}
-	}
-
-	for(int i=0; i<ret.size(); i++)
-		if(!ret[i])
-			ret[i] = new CGarrisonSlot(this, posX + (i*distance), posY, SlotID(i), Upg, nullptr);
-
-	if (twoRows)
-		for (int i=4; i<ret.size(); i++)
-		{
-			ret[i]->pos.x -= 126;
-			ret[i]->pos.y += 37;
-		};
-}
-
-void CGarrisonInt::createSlots()
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	int width = smallIcons? 32 : 58;
-
-	createSet(slotsUp, armedObjs[0], 0, 0, width+interx, 0);
-	createSet(slotsDown, armedObjs[1], garOffset.x, garOffset.y, width+interx, 1);
-}
-
-void CGarrisonInt::recreateSlots()
-{
-	selectSlot(nullptr);
-	setSplittingMode(false);
-
-	for(auto & elem : splitButtons)
-		elem->block(true);
-
-
-	for(CGarrisonSlot * slot : slotsUp)
-		slot->update();
-
-	for(CGarrisonSlot * slot : slotsDown)
-		slot->update();
-}
-
-void CGarrisonInt::splitClick()
-{
-	if(!getSelection())
-		return;
-	setSplittingMode(!getSplittingMode());
-	redraw();
-}
-void CGarrisonInt::splitStacks(int, int amountRight)
-{
-	LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight);
-}
-
-CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point &garsOffset,
-                           SDL_Surface *pomsur, const Point& SurOffset,
-                           const CArmedInstance *s1, const CArmedInstance *s2,
-                           bool _removableUnits, bool smallImgs, bool _twoRows ) :
-    highlighted(nullptr),
-    inSplittingMode(false),
-    interx(inx),
-    garOffset(garsOffset),
-    smallIcons(smallImgs),
-    removableUnits(_removableUnits),
-    twoRows(_twoRows)
-{
-	setArmy(s1, false);
-	setArmy(s2, true);
-	pos.x += x;
-	pos.y += y;
-	createSlots();
-}
-
-const CGarrisonSlot * CGarrisonInt::getSelection()
-{
-	return highlighted;
-}
-
-void CGarrisonInt::selectSlot(CGarrisonSlot *slot)
-{
-	if (slot != highlighted)
-	{
-		if (highlighted)
-			highlighted->setHighlight(false);
-
-		highlighted = slot;
-		for (auto button : splitButtons)
-			button->block(highlighted == nullptr);
-
-		if (highlighted)
-			highlighted->setHighlight(true);
-	}
-}
-
-void CGarrisonInt::setSplittingMode(bool on)
-{
-	assert(on == false || highlighted != nullptr); //can't be in splitting mode without selection
-
-	if (inSplittingMode || on)
-	{
-		for(CGarrisonSlot * slot : slotsUp)
-			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
-
-		for(CGarrisonSlot * slot : slotsDown)
-			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
-		inSplittingMode = on;
-	}
-}
-
-bool CGarrisonInt::getSplittingMode()
-{
-	return inSplittingMode;
-}
-
-void CGarrisonInt::setArmy(const CArmedInstance *army, bool bottomGarrison)
-{
-	owned[bottomGarrison] =  army ? (army->tempOwner == LOCPLINT->playerID || army->tempOwner == PlayerColor::UNFLAGGABLE) : false;
-	armedObjs[bottomGarrison] = army;
-}
-
 CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo &comps, const TButtonsInfo &Buttons, bool delComps)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
@@ -3668,48 +3220,6 @@ CInGameConsole::CInGameConsole() : prevEntDisp(-1), defaultTimeout(10000), maxDi
 	#endif
 }
 
-CGarrisonWindow::CGarrisonWindow( const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits ):
-    CWindowObject(PLAYER_COLORED, "GARRISON")
-{
-	OBJ_CONSTRUCTION_CAPTURING_ALL;
-
-	garr = new CGarrisonInt(92, 127, 4, Point(0,96), background->bg, Point(93,127), up, down, removableUnits);
-	{
-		CAdventureMapButton *split = new CAdventureMapButton(CGI->generaltexth->tcommands[3],"",boost::bind(&CGarrisonInt::splitClick,garr),88,314,"IDV6432.DEF");
-		removeChild(split);
-		garr->addSplitBtn(split);
-	}
-	quit = new CAdventureMapButton(CGI->generaltexth->tcommands[8],"",boost::bind(&CGarrisonWindow::close,this),399,314,"IOK6432.DEF",SDLK_RETURN);
-
-	std::string titleText;
-	if (garr->armedObjs[1]->tempOwner == garr->armedObjs[0]->tempOwner)
-		titleText = CGI->generaltexth->allTexts[709];
-	else
-	{
-		titleText = CGI->generaltexth->allTexts[35];
-		boost::algorithm::replace_first(titleText, "%s", garr->armedObjs[0]->Slots().begin()->second->type->namePl);
-	}
-	new CLabel(275, 30, FONT_BIG, CENTER, Colors::YELLOW, titleText);
-
-	new CAnimImage("CREST58", garr->armedObjs[0]->getOwner().getNum(), 0, 28, 124);
-	new CAnimImage("PortraitsLarge", dynamic_cast<const CGHeroInstance*>(garr->armedObjs[1])->portrait, 0, 29, 222);
-}
-
-
-IShowActivatable::IShowActivatable()
-{
-	type = 0;
-}
-
-CGarrisonHolder::CGarrisonHolder()
-{
-}
-
-void CWindowWithGarrison::updateGarrisons()
-{
-	garr->recreateSlots();
-}
-
 void LRClickableAreaWTextComp::clickLeft(tribool down, bool previousState)
 {
 	if((!down) && previousState)

+ 1 - 104
client/GUIClasses.h

@@ -10,6 +10,7 @@
 #include "../lib/GameConstants.h"
 #include "gui/CArtifactHolder.h"
 #include "gui/CComponent.h"
+#include "gui/CGarrisonInt.h"
 
 #ifdef max
 #undef max
@@ -215,87 +216,6 @@ public:
 	CTownTooltip(Point pos, const CGTownInstance * town);
 };
 
-///////////////////////////////////////////////////////////////////////////////
-
-class CGarrisonInt;
-
-/// A single garrison slot which holds one creature of a specific amount
-class CGarrisonSlot : public CIntObject
-{
-	SlotID ID; //for identification
-	CGarrisonInt *owner;
-	const CStackInstance *myStack; //nullptr if slot is empty
-	const CCreature *creature;
-	int upg; //0 - up garrison, 1 - down garrison
-
-	CAnimImage * creatureImage;
-	CAnimImage * selectionImage; // image for selection, not always visible
-	CLabel * stackCount;
-
-	void setHighlight(bool on);
-public:
-	virtual void hover (bool on); //call-in
-	const CArmedInstance * getObj() const;
-	bool our() const;
-	void clickRight(tribool down, bool previousState);
-	void clickLeft(tribool down, bool previousState);
-	void update();
-	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg=0, const CStackInstance * Creature=nullptr);
-
-	friend class CGarrisonInt;
-};
-
-/// Class which manages slots of upper and lower garrison, splitting of units
-class CGarrisonInt :public CIntObject
-{
-	CGarrisonSlot *highlighted; //chosen slot. Should be changed only via selectSlot
-	bool inSplittingMode;
-
-public:
-	void selectSlot(CGarrisonSlot * slot); //null = deselect
-	const CGarrisonSlot * getSelection();
-
-	void setSplittingMode(bool on);
-	bool getSplittingMode();
-
-	int interx; //space between slots
-	Point garOffset; //offset between garrisons (not used if only one hero)
-	std::vector<CAdventureMapButton *> splitButtons; //may be empty if no buttons
-
-	SlotID p2; //TODO: comment me
-	int	shiftPos;//1st slot of the second row, set shiftPoint for effect
-	bool pb,
-	     smallIcons, //true - 32x32 imgs, false - 58x64
-	     removableUnits,//player can remove units from up
-	     twoRows,//slots will be placed in 2 rows
-		 owned[2];//player owns up or down army [0] upper, [1] lower
-
-// 	const CCreatureSet *set1; //top set of creatures
-// 	const CCreatureSet *set2; //bottom set of creatures
-
-	std::vector<CGarrisonSlot*> slotsUp, slotsDown; //slots of upper and lower garrison
-	const CArmedInstance *armedObjs[2]; //[0] is upper, [1] is down
-	//const CArmedInstance *oup, *odown; //upper and lower garrisons (heroes or towns)
-
-	void setArmy(const CArmedInstance *army, bool bottomGarrison);
-	void addSplitBtn(CAdventureMapButton * button);
-	void createSet(std::vector<CGarrisonSlot*> &ret, const CCreatureSet * set, int posX, int distance, int posY, int Upg );
-
-	void createSlots();
-	void recreateSlots();
-
-	void splitClick(); //handles click on split button
-	void splitStacks(int amountLeft, int amountRight); //TODO: comment me
-	//x, y - position;
-	//inx - distance between slots;
-	//pomsur, SurOffset - UNUSED
-	//s1, s2 - top and bottom armies;
-	//removableUnits - you can take units from top;
-	//smallImgs - units images size 64x58 or 32x32;
-	//twoRows - display slots in 2 row (1st row = 4 slots, 2nd = 3 slots)
-	CGarrisonInt(int x, int y, int inx, const Point &garsOffset, SDL_Surface *pomsur, const Point &SurOffset, const CArmedInstance *s1, const CArmedInstance *s2=nullptr, bool _removableUnits = true, bool smallImgs = false, bool _twoRows=false); //c-tor
-};
-
 /// draws picture with creature on background, use Animated=true to get animation
 class CCreaturePic : public CIntObject
 {
@@ -766,29 +686,6 @@ public:
 	LRClickableAreaOpenTown();
 };
 
-class CGarrisonHolder
-{
-public:
-	CGarrisonHolder();
-	virtual void updateGarrisons()=0;
-};
-
-class CWindowWithGarrison : public virtual CGarrisonHolder
-{
-public:
-	CGarrisonInt *garr;
-	virtual void updateGarrisons();
-};
-
-/// Garrison window where you can take creatures out of the hero to place it on the garrison
-class CGarrisonWindow : public CWindowObject, public CWindowWithGarrison
-{
-public:
-	CAdventureMapButton * quit;
-
-	CGarrisonWindow(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits); //c-tor
-};
-
 class CExchangeWindow : public CWindowObject, public CWindowWithGarrison, public CWindowWithArtifacts
 {
 	CGStatusBar * ourBar; //internal statusbar

+ 501 - 0
client/gui/CGarrisonInt.cpp

@@ -0,0 +1,501 @@
+#include "StdInc.h"
+#include "CGarrisonInt.h"
+
+#include "CGuiHandler.h"
+
+#include "../CAnimation.h"
+#include "../CCreatureWindow.h"
+#include "../CGameInfo.h"
+#include "../CPlayerInterface.h"
+
+#include "../../CCallback.h"
+
+#include "../../lib/CGeneralTextHandler.h"
+#include "../../lib/CCreatureHandler.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
+
+#include "../../lib/CGameState.h"
+
+void CGarrisonSlot::setHighlight(bool on)
+{
+	if (on)
+		selectionImage->enable(); //show
+	else
+		selectionImage->disable(); //hide
+}
+
+void CGarrisonSlot::hover (bool on)
+{
+	////Hoverable::hover(on);
+	if(on)
+	{
+		std::string temp;
+		if(creature)
+		{
+			if(owner->getSelection())
+			{
+				if(owner->getSelection() == this)
+				{
+					temp = CGI->generaltexth->tcommands[4]; //View %s
+					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
+				}
+				else if (owner->getSelection()->creature == creature)
+				{
+					temp = CGI->generaltexth->tcommands[2]; //Combine %s armies
+					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
+				}
+				else if (owner->getSelection()->creature)
+				{
+					temp = CGI->generaltexth->tcommands[7]; //Exchange %s with %s
+					boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing);
+					boost::algorithm::replace_first(temp,"%s",creature->nameSing);
+				}
+				else
+				{
+                    logGlobal->warnStream() << "Warning - shouldn't be - highlighted void slot "<<owner->getSelection();
+                    logGlobal->warnStream() << "Highlighted set to nullptr";
+					owner->selectSlot(nullptr);
+				}
+			}
+			else
+			{
+				if(upg)
+				{
+					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
+				}
+				else if(owner->armedObjs[0] && owner->armedObjs[0]->ID == Obj::TOWN)
+				{
+					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
+				}
+				else
+				{
+					temp = CGI->generaltexth->allTexts[481]; //Select %s
+				}
+				boost::algorithm::replace_first(temp,"%s",creature->nameSing);
+			};
+		}
+		else
+		{
+			if(owner->getSelection())
+			{
+				const CArmedInstance *highl = owner->getSelection()->getObj();
+				if(  highl->needsLastStack()		//we are moving stack from hero's
+				  && highl->stacksCount() == 1	//it's only stack
+				  && owner->getSelection()->upg != upg	//we're moving it to the other garrison
+				  )
+				{
+					temp = CGI->generaltexth->tcommands[5]; //Cannot move last army to garrison
+				}
+				else
+				{
+					temp = CGI->generaltexth->tcommands[6]; //Move %s
+					boost::algorithm::replace_first(temp,"%s",owner->getSelection()->creature->nameSing);
+				}
+			}
+			else
+			{
+				temp = CGI->generaltexth->tcommands[11]; //Empty
+			}
+		}
+		GH.statusbar->setText(temp);
+	}
+	else
+	{
+		GH.statusbar->clear();
+	}
+}
+
+const CArmedInstance * CGarrisonSlot::getObj() const
+{
+	return 	(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]);
+}
+
+bool CGarrisonSlot::our() const
+{
+	return 	upg?(owner->owned[1]):(owner->owned[0]);
+}
+
+void CGarrisonSlot::clickRight(tribool down, bool previousState)
+{
+	if(down && creature)
+	{
+		GH.pushInt(createCreWindow(myStack, CCreatureWindow::ARMY));
+	}
+}
+void CGarrisonSlot::clickLeft(tribool down, bool previousState)
+{
+	if(down)
+	{
+		bool refr = false;
+		if(owner->getSelection())
+		{
+			if(owner->getSelection() == this) //view info
+			{
+				UpgradeInfo pom;
+				LOCPLINT->cb->getUpgradeInfo(getObj(), ID, pom);
+
+				bool canUpgrade = getObj()->tempOwner == LOCPLINT->playerID && pom.oldID>=0; //upgrade is possible
+				bool canDismiss = getObj()->tempOwner == LOCPLINT->playerID && (getObj()->stacksCount()>1  || !getObj()->needsLastStack());
+				std::function<void()> upgr = nullptr;
+				std::function<void()> dism = nullptr;
+				if(canUpgrade) upgr = [=] { LOCPLINT->cb->upgradeCreature(getObj(), ID, pom.newID[0]); };
+				if(canDismiss) dism = [=] { LOCPLINT->cb->dismissCreature(getObj(), ID); };
+
+				owner->selectSlot(nullptr);
+				owner->setSplittingMode(false);
+
+				for(auto & elem : owner->splitButtons)
+					elem->block(true);
+
+				redraw();
+				refr = true;
+				CIntObject *creWindow = createCreWindow(myStack, CCreatureWindow::HERO, upgr, dism, &pom);
+				GH.pushInt(creWindow);
+			}
+			else
+			{
+				// Only allow certain moves if troops aren't removable or not ours.
+				if (  ( owner->getSelection()->our()//our creature is selected
+				     || owner->getSelection()->creature == creature )//or we are rebalancing army
+				   && ( owner->removableUnits
+				     || (upg == 0 &&  ( owner->getSelection()->upg == 1 && !creature ) )
+					 || (upg == 1 &&    owner->getSelection()->upg == 1 ) ) )
+				{
+					//we want to split
+					if((owner->getSplittingMode() || LOCPLINT->shiftPressed())
+						&& (!creature
+							|| (creature == owner->getSelection()->creature)))
+					{
+						owner->p2 = ID; //store the second stack pos
+						owner->pb = upg;//store the second stack owner (up or down army)
+						owner->setSplittingMode(false);
+
+						int minLeft=0, minRight=0;
+
+						if(upg != owner->getSelection()->upg) //not splitting within same army
+						{
+							if(owner->getSelection()->getObj()->stacksCount() == 1 //we're splitting away the last stack
+								&& owner->getSelection()->getObj()->needsLastStack() )
+							{
+								minLeft = 1;
+							}
+							if(getObj()->stacksCount() == 1 //destination army can't be emptied, unless we're rebalancing two stacks of same creature
+								&& owner->getSelection()->creature == creature
+								&& getObj()->needsLastStack() )
+							{
+								minRight = 1;
+							}
+						}
+
+						int countLeft = owner->getSelection()->myStack ? owner->getSelection()->myStack->count : 0;
+						int countRight = myStack ? myStack->count : 0;
+
+						GH.pushInt(new CSplitWindow(owner->getSelection()->creature, boost::bind(&CGarrisonInt::splitStacks, owner, _1, _2),
+						                            minLeft, minRight, countLeft, countRight));
+						refr = true;
+					}
+					else if(creature != owner->getSelection()->creature) //swap
+					{
+						LOCPLINT->cb->swapCreatures(
+							(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
+							(!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
+							ID,owner->getSelection()->ID);
+					}
+					else //merge
+					{
+						LOCPLINT->cb->mergeStacks(
+							(!owner->getSelection()->upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
+							(!upg)?(owner->armedObjs[0]):(owner->armedObjs[1]),
+							owner->getSelection()->ID,ID);
+					}
+				}
+				else // Highlight
+				{
+					if(creature)
+						owner->selectSlot(this);
+					redraw();
+					refr = true;
+				}
+			}
+		}
+		else //highlight or drop artifact
+		{
+			bool artSelected = false;
+			if (CWindowWithArtifacts* chw = dynamic_cast<CWindowWithArtifacts*>(GH.topInt())) //dirty solution
+			{
+				const CArtifactsOfHero::SCommonPart *commonInfo = chw->artSets.front()->commonInfo;
+				if (const CArtifactInstance *art = commonInfo->src.art)
+				{
+					const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero();
+					artSelected = true;
+					ArtifactLocation src(srcHero, commonInfo->src.slotID);
+					ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT);
+					if (art->canBePutAt(dst, true))
+					{	//equip clicked stack
+						if(dst.getArt())
+						{
+							//creature can wear only one active artifact
+							//if we are placing a new one, the old one will be returned to the hero's backpack
+							LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero, dst.getArt()->firstBackpackSlot(srcHero)));
+						}
+						LOCPLINT->cb->swapArtifacts(src, dst);
+					}
+				}
+			}
+			if (!artSelected && creature)
+			{
+				owner->selectSlot(this);
+				if(creature)
+				{
+					for(auto & elem : owner->splitButtons)
+						elem->block(false);
+				}
+			}
+			redraw();
+			refr = true;
+		}
+		if(refr) {hover(false);	hover(true); } //to refresh statusbar
+	}
+}
+
+void CGarrisonSlot::update()
+{
+	if (getObj() != nullptr)
+	{
+		addUsedEvents(LCLICK | RCLICK | HOVER);
+		myStack = getObj()->getStackPtr(ID);
+		creature = myStack ? myStack->type : nullptr;
+	}
+	else
+	{
+		removeUsedEvents(LCLICK | RCLICK | HOVER);
+		myStack = nullptr;
+		creature = nullptr;
+	}
+
+	if (creature)
+	{
+		creatureImage->enable();
+		creatureImage->setFrame(creature->iconIndex);
+
+		stackCount->enable();
+		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
+	}
+	else
+	{
+		creatureImage->disable();
+		stackCount->disable();
+	}
+}
+
+CGarrisonSlot::CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg, const CStackInstance * Creature):
+    ID(IID),
+    owner(Owner),
+    myStack(Creature),
+    creature(Creature ? Creature->type : nullptr),
+    upg(Upg)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	if (getObj())
+		addUsedEvents(LCLICK | RCLICK | HOVER);
+	pos.x += x;
+	pos.y += y;
+
+	std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
+
+	creatureImage = new CAnimImage(imgName, creature ? creature->iconIndex : 0);
+	if (!creature)
+		creatureImage->disable();
+
+	selectionImage = new CAnimImage(imgName, 1);
+	selectionImage->disable();
+
+	if(Owner->smallIcons)
+	{
+		pos.w = 32;
+		pos.h = 32;
+	}
+	else
+	{
+		pos.w = 58;
+		pos.h = 64;
+	}
+
+	stackCount = new CLabel(pos.w, pos.h, owner->smallIcons ? FONT_TINY : FONT_MEDIUM, BOTTOMRIGHT, Colors::WHITE);
+	if (!creature)
+		stackCount->disable();
+	else
+		stackCount->setText(boost::lexical_cast<std::string>(myStack->count));
+}
+
+void CGarrisonInt::addSplitBtn(CAdventureMapButton * button)
+{
+	addChild(button);
+	button->recActions = defActions;
+	splitButtons.push_back(button);
+	button->block(getSelection() == nullptr);
+}
+
+void CGarrisonInt::createSet(std::vector<CGarrisonSlot*> &ret, const CCreatureSet * set, int posX, int posY, int distance, int Upg )
+{
+	ret.resize(7);
+
+	if (set)
+	{
+		for(auto & elem : set->Slots())
+		{
+			ret[elem.first.getNum()] = new CGarrisonSlot(this, posX + (elem.first.getNum()*distance), posY, elem.first, Upg, elem.second);
+		}
+	}
+
+	for(int i=0; i<ret.size(); i++)
+		if(!ret[i])
+			ret[i] = new CGarrisonSlot(this, posX + (i*distance), posY, SlotID(i), Upg, nullptr);
+
+	if (twoRows)
+		for (int i=4; i<ret.size(); i++)
+		{
+			ret[i]->pos.x -= 126;
+			ret[i]->pos.y += 37;
+		};
+}
+
+void CGarrisonInt::createSlots()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
+	int width = smallIcons? 32 : 58;
+
+	createSet(slotsUp, armedObjs[0], 0, 0, width+interx, 0);
+	createSet(slotsDown, armedObjs[1], garOffset.x, garOffset.y, width+interx, 1);
+}
+
+void CGarrisonInt::recreateSlots()
+{
+	selectSlot(nullptr);
+	setSplittingMode(false);
+
+	for(auto & elem : splitButtons)
+		elem->block(true);
+
+
+	for(CGarrisonSlot * slot : slotsUp)
+		slot->update();
+
+	for(CGarrisonSlot * slot : slotsDown)
+		slot->update();
+}
+
+void CGarrisonInt::splitClick()
+{
+	if(!getSelection())
+		return;
+	setSplittingMode(!getSplittingMode());
+	redraw();
+}
+void CGarrisonInt::splitStacks(int, int amountRight)
+{
+	LOCPLINT->cb->splitStack(armedObjs[getSelection()->upg], armedObjs[pb], getSelection()->ID, p2, amountRight);
+}
+
+CGarrisonInt::CGarrisonInt(int x, int y, int inx, const Point &garsOffset,
+                           SDL_Surface *pomsur, const Point& SurOffset,
+                           const CArmedInstance *s1, const CArmedInstance *s2,
+                           bool _removableUnits, bool smallImgs, bool _twoRows ) :
+    highlighted(nullptr),
+    inSplittingMode(false),
+    interx(inx),
+    garOffset(garsOffset),
+    smallIcons(smallImgs),
+    removableUnits(_removableUnits),
+    twoRows(_twoRows)
+{
+	setArmy(s1, false);
+	setArmy(s2, true);
+	pos.x += x;
+	pos.y += y;
+	createSlots();
+}
+
+const CGarrisonSlot * CGarrisonInt::getSelection()
+{
+	return highlighted;
+}
+
+void CGarrisonInt::selectSlot(CGarrisonSlot *slot)
+{
+	if (slot != highlighted)
+	{
+		if (highlighted)
+			highlighted->setHighlight(false);
+
+		highlighted = slot;
+		for (auto button : splitButtons)
+			button->block(highlighted == nullptr);
+
+		if (highlighted)
+			highlighted->setHighlight(true);
+	}
+}
+
+void CGarrisonInt::setSplittingMode(bool on)
+{
+	assert(on == false || highlighted != nullptr); //can't be in splitting mode without selection
+
+	if (inSplittingMode || on)
+	{
+		for(CGarrisonSlot * slot : slotsUp)
+			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
+
+		for(CGarrisonSlot * slot : slotsDown)
+			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
+		inSplittingMode = on;
+	}
+}
+
+bool CGarrisonInt::getSplittingMode()
+{
+	return inSplittingMode;
+}
+
+void CGarrisonInt::setArmy(const CArmedInstance *army, bool bottomGarrison)
+{
+	owned[bottomGarrison] =  army ? (army->tempOwner == LOCPLINT->playerID || army->tempOwner == PlayerColor::UNFLAGGABLE) : false;
+	armedObjs[bottomGarrison] = army;
+}
+
+CGarrisonWindow::CGarrisonWindow( const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits ):
+	CWindowObject(PLAYER_COLORED, "GARRISON")
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+
+	garr = new CGarrisonInt(92, 127, 4, Point(0,96), background->bg, Point(93,127), up, down, removableUnits);
+	{
+		CAdventureMapButton *split = new CAdventureMapButton(CGI->generaltexth->tcommands[3],"",boost::bind(&CGarrisonInt::splitClick,garr),88,314,"IDV6432.DEF");
+		removeChild(split);
+		garr->addSplitBtn(split);
+	}
+	quit = new CAdventureMapButton(CGI->generaltexth->tcommands[8],"",boost::bind(&CGarrisonWindow::close,this),399,314,"IOK6432.DEF",SDLK_RETURN);
+
+	std::string titleText;
+	if (garr->armedObjs[1]->tempOwner == garr->armedObjs[0]->tempOwner)
+		titleText = CGI->generaltexth->allTexts[709];
+	else
+	{
+		titleText = CGI->generaltexth->allTexts[35];
+		boost::algorithm::replace_first(titleText, "%s", garr->armedObjs[0]->Slots().begin()->second->type->namePl);
+	}
+	new CLabel(275, 30, FONT_BIG, CENTER, Colors::YELLOW, titleText);
+
+	new CAnimImage("CREST58", garr->armedObjs[0]->getOwner().getNum(), 0, 28, 124);
+	new CAnimImage("PortraitsLarge", dynamic_cast<const CGHeroInstance*>(garr->armedObjs[1])->portrait, 0, 29, 222);
+}
+
+CGarrisonHolder::CGarrisonHolder()
+{
+}
+
+void CWindowWithGarrison::updateGarrisons()
+{
+	garr->recreateSlots();
+}

+ 123 - 0
client/gui/CGarrisonInt.h

@@ -0,0 +1,123 @@
+#pragma once
+
+#include "CIntObject.h"
+#include "CIntObjectClasses.h"
+
+/*
+ * CGarrisonInt.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
+ *
+ */
+
+class CGarrisonInt;
+class CAdventureMapButton;
+class CArmedInstance;
+class CAnimImage;
+class CCreatureSet;
+class CGarrisonSlot;
+class CStackInstance;
+class CLabel;
+
+/// A single garrison slot which holds one creature of a specific amount
+class CGarrisonSlot : public CIntObject
+{
+	SlotID ID; //for identification
+	CGarrisonInt *owner;
+	const CStackInstance *myStack; //nullptr if slot is empty
+	const CCreature *creature;
+	int upg; //0 - up garrison, 1 - down garrison
+
+	CAnimImage * creatureImage;
+	CAnimImage * selectionImage; // image for selection, not always visible
+	CLabel * stackCount;
+
+	void setHighlight(bool on);
+public:
+	virtual void hover (bool on); //call-in
+	const CArmedInstance * getObj() const;
+	bool our() const;
+	void clickRight(tribool down, bool previousState);
+	void clickLeft(tribool down, bool previousState);
+	void update();
+	CGarrisonSlot(CGarrisonInt *Owner, int x, int y, SlotID IID, int Upg=0, const CStackInstance * Creature=nullptr);
+
+	friend class CGarrisonInt;
+};
+
+/// Class which manages slots of upper and lower garrison, splitting of units
+class CGarrisonInt :public CIntObject
+{
+	CGarrisonSlot *highlighted; //chosen slot. Should be changed only via selectSlot
+	bool inSplittingMode;
+
+public:
+	void selectSlot(CGarrisonSlot * slot); //null = deselect
+	const CGarrisonSlot * getSelection();
+
+	void setSplittingMode(bool on);
+	bool getSplittingMode();
+
+	int interx; //space between slots
+	Point garOffset; //offset between garrisons (not used if only one hero)
+	std::vector<CAdventureMapButton *> splitButtons; //may be empty if no buttons
+
+	SlotID p2; //TODO: comment me
+	int	shiftPos;//1st slot of the second row, set shiftPoint for effect
+	bool pb,
+		 smallIcons, //true - 32x32 imgs, false - 58x64
+		 removableUnits,//player can remove units from up
+		 twoRows,//slots will be placed in 2 rows
+		 owned[2];//player owns up or down army [0] upper, [1] lower
+
+// 	const CCreatureSet *set1; //top set of creatures
+// 	const CCreatureSet *set2; //bottom set of creatures
+
+	std::vector<CGarrisonSlot*> slotsUp, slotsDown; //slots of upper and lower garrison
+	const CArmedInstance *armedObjs[2]; //[0] is upper, [1] is down
+	//const CArmedInstance *oup, *odown; //upper and lower garrisons (heroes or towns)
+
+	void setArmy(const CArmedInstance *army, bool bottomGarrison);
+	void addSplitBtn(CAdventureMapButton * button);
+	void createSet(std::vector<CGarrisonSlot*> &ret, const CCreatureSet * set, int posX, int distance, int posY, int Upg );
+
+	void createSlots();
+	void recreateSlots();
+
+	void splitClick(); //handles click on split button
+	void splitStacks(int amountLeft, int amountRight); //TODO: comment me
+	//x, y - position;
+	//inx - distance between slots;
+	//pomsur, SurOffset - UNUSED
+	//s1, s2 - top and bottom armies;
+	//removableUnits - you can take units from top;
+	//smallImgs - units images size 64x58 or 32x32;
+	//twoRows - display slots in 2 row (1st row = 4 slots, 2nd = 3 slots)
+	CGarrisonInt(int x, int y, int inx, const Point &garsOffset, SDL_Surface *pomsur, const Point &SurOffset, const CArmedInstance *s1, const CArmedInstance *s2=nullptr, bool _removableUnits = true, bool smallImgs = false, bool _twoRows=false); //c-tor
+};
+
+class CGarrisonHolder
+{
+public:
+	CGarrisonHolder();
+	virtual void updateGarrisons()=0;
+};
+
+class CWindowWithGarrison : public virtual CGarrisonHolder
+{
+public:
+	CGarrisonInt *garr;
+	virtual void updateGarrisons();
+};
+
+/// Garrison window where you can take creatures out of the hero to place it on the garrison
+class CGarrisonWindow : public CWindowObject, public CWindowWithGarrison
+{
+public:
+	CAdventureMapButton * quit;
+
+	CGarrisonWindow(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits); //c-tor
+};

+ 5 - 0
client/gui/CIntObject.cpp

@@ -4,6 +4,11 @@
 #include "SDL_Extensions.h"
 #include "../CMessage.h"
 
+IShowActivatable::IShowActivatable()
+{
+	type = 0;
+}
+
 void ILockedUpdatable::runLocked(std::function<void(IUpdateable*)> cb)
 {
 	boost::unique_lock<boost::recursive_mutex> lock(updateGuard);