Browse Source

Leveling up commander skills seems to be working. Optimized some png's to decrease size

Ivan Savenko 11 years ago
parent
commit
b58bbcbdd8

BIN
Mods/vcmi/Data/stackWindow/commander-bg.png


BIN
Mods/vcmi/Data/stackWindow/info-panel-0.png


BIN
Mods/vcmi/Data/stackWindow/info-panel-1.png


BIN
Mods/vcmi/Data/stackWindow/info-panel-2.png


BIN
Mods/vcmi/Data/stackWindow/spell-effects.png


+ 1 - 1
Mods/vcmi/Sprites/stackWindow/switch-mode-button.json → Mods/vcmi/Sprites/stackWindow/switchModeButton.json

@@ -3,6 +3,6 @@
 	"images" :
 	[
 		{ "frame" : 0, "file" : "switch-mode-normal.png"},
-		{ "frame" : 0, "file" : "switch-mode-pressed.png"}
+		{ "frame" : 1, "file" : "switch-mode-pressed.png"}
 	]
 }

+ 239 - 143
client/CCreatureWindow.cpp

@@ -30,6 +30,42 @@ class CSelectableSkill;
  *
  */
 
+struct StackWindowInfo
+{
+	// helper structs
+	struct CommanderLevelInfo
+	{
+		std::vector<ui32> skills;
+		std::function<void(ui32)> callback;
+	};
+	struct StackDismissInfo
+	{
+		std::function<void()> callback;
+	};
+	struct StackUpgradeInfo
+	{
+		UpgradeInfo info;
+		std::function<void(CreatureID)> callback;
+	};
+
+	// pointers to permament objects in game state
+	const CCreature * creature;
+	const CCommanderInstance * commander;
+	const CStackInstance * stackNode;
+	const CGHeroInstance * owner;
+
+	// temporary objects which should be kept as copy if needed
+	boost::optional<CommanderLevelInfo> levelupInfo;
+	boost::optional<StackDismissInfo> dismissInfo;
+	boost::optional<StackUpgradeInfo> upgradeInfo;
+
+	// misc fields
+	unsigned int creatureCount;
+	bool popupWindow;
+
+	StackWindowInfo();
+};
+
 namespace
 {
 	namespace EStat
@@ -101,29 +137,29 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
 	else
 		createBackground("info-panel-0");
 
-	new CCreaturePic(5, 41, parent->info.creature);
+	new CCreaturePic(5, 41, parent->info->creature);
 
 	std::string visibleName;
-	if (parent->info.commander != nullptr)
-		visibleName = parent->info.commander->type->nameSing;
+	if (parent->info->commander != nullptr)
+		visibleName = parent->info->commander->type->nameSing;
 	else
-		visibleName = parent->info.creature->namePl;
+		visibleName = parent->info->creature->namePl;
 	new CLabel(215, 12, FONT_SMALL, CENTER, Colors::YELLOW, visibleName);
 
 	int dmgMultiply = 1;
-	if(parent->info.owner && parent->info.stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
-		dmgMultiply += parent->info.owner->Attack();
+	if(parent->info->owner && parent->info->stackNode->hasBonusOfType(Bonus::SIEGE_WEAPON))
+		dmgMultiply += parent->info->owner->Attack();
 
 	new CPicture("stackWindow/icons", 117, 32);
-	printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info.creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), parent->info.stackNode->Attack());
-	printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info.creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), parent->info.stackNode->Defense());
-	printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info.stackNode->getMinDamage() * dmgMultiply, parent->info.stackNode->getMaxDamage() * dmgMultiply);
-	printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info.creature->valOfBonuses(Bonus::STACK_HEALTH), parent->info.stackNode->valOfBonuses(Bonus::STACK_HEALTH));
-	printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info.creature->Speed(), parent->info.stackNode->Speed());
+	printStatBase(EStat::ATTACK, CGI->generaltexth->primarySkillNames[0], parent->info->creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), parent->info->stackNode->Attack());
+	printStatBase(EStat::DEFENCE, CGI->generaltexth->primarySkillNames[1], parent->info->creature->valOfBonuses(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), parent->info->stackNode->Defense());
+	printStatRange(EStat::DAMAGE, CGI->generaltexth->allTexts[199], parent->info->stackNode->getMinDamage() * dmgMultiply, parent->info->stackNode->getMaxDamage() * dmgMultiply);
+	printStatBase(EStat::HEALTH, CGI->generaltexth->allTexts[388], parent->info->creature->valOfBonuses(Bonus::STACK_HEALTH), parent->info->stackNode->valOfBonuses(Bonus::STACK_HEALTH));
+	printStatBase(EStat::SPEED, CGI->generaltexth->zelp[441].first, parent->info->creature->Speed(), parent->info->stackNode->Speed());
 
-	const CStack * battleStack = dynamic_cast<const CStack*>(parent->info.stackNode);
-	bool shooter = parent->info.stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info.stackNode->valOfBonuses(Bonus::SHOTS);
-	bool caster  = parent->info.stackNode->valOfBonuses(Bonus::CASTS);
+	const CStack * battleStack = dynamic_cast<const CStack*>(parent->info->stackNode);
+	bool shooter = parent->info->stackNode->hasBonusOfType(Bonus::SHOOTER) && parent->info->stackNode->valOfBonuses(Bonus::SHOTS);
+	bool caster  = parent->info->stackNode->valOfBonuses(Bonus::CASTS);
 
 	if (battleStack != nullptr) // in battle
 	{
@@ -136,15 +172,15 @@ void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
 	else
 	{
 		if (shooter)
-			printStat(EStat::SHOTS, CGI->generaltexth->allTexts[198], parent->info.stackNode->valOfBonuses(Bonus::SHOTS));
+			printStat(EStat::SHOTS, CGI->generaltexth->allTexts[198], parent->info->stackNode->valOfBonuses(Bonus::SHOTS));
 		if (caster)
-			printStat(EStat::MANA, CGI->generaltexth->allTexts[399], parent->info.stackNode->valOfBonuses(Bonus::CASTS));
+			printStat(EStat::MANA, CGI->generaltexth->allTexts[399], parent->info->stackNode->valOfBonuses(Bonus::CASTS));
 	}
 
 	auto morale = new MoraleLuckBox(true, genRect(42, 42, 321, 110));
-	morale->set(parent->info.stackNode);
+	morale->set(parent->info->stackNode);
 	auto luck = new MoraleLuckBox(false, genRect(42, 42, 375, 110));
-	luck->set(parent->info.stackNode);
+	luck->set(parent->info->stackNode);
 }
 
 void CStackWindow::CWindowSection::createActiveSpells()
@@ -155,7 +191,7 @@ void CStackWindow::CWindowSection::createActiveSpells()
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	createBackground("spell-effects");
 
-	const CStack * battleStack = dynamic_cast<const CStack*>(parent->info.stackNode);
+	const CStack * battleStack = dynamic_cast<const CStack*>(parent->info->stackNode);
 
 	assert(battleStack); // Section should be created only for battles
 
@@ -191,48 +227,54 @@ void CStackWindow::CWindowSection::createCommanderSection()
 	{
 		delete obj;
 	};
-	new CTabbedInt(onCreate, onDestroy, Point(0,0), 0);
+	parent->commanderTab = new CTabbedInt(onCreate, onDestroy, Point(0,0), 0);
 	pos.w = parent->pos.w;
 	pos.h = 177; //fixed height
 }
 
-static std::string skillToFile (int skill, int level, bool canUpgrade, bool selected)
+static std::string skillToFile (int skill, int level, bool selected)
 {
-		std::string file = "zvs/Lib1.res/_";
-		switch (skill)
-		{
-			case ECommander::ATTACK:
-				file += "AT";
-				break;
-			case ECommander::DEFENSE:
-				file += "DF";
-				break;
-			case ECommander::HEALTH:
-				file += "HP";
-				break;
-			case ECommander::DAMAGE:
-				file += "DM";
-				break;
-			case ECommander::SPEED:
-				file += "SP";
-				break;
-			case ECommander::SPELL_POWER:
-				file += "MP";
-				break;
-		}
-		std::string sufix = boost::lexical_cast<std::string>((int)level);
-		if (selected)
-			sufix += "="; //level-up highlight
-		else if (canUpgrade && level == 0)
-			sufix = "no"; //not avaliable - no number
-
-		file += sufix + ".bmp";
+	// FIXME: is this a correct hadling?
+	// level 0 = skill not present, use image with "no" suffix
+	// level 1-5 = skill available, mapped to images indexed as 0-4
+	// selecting skill means that it will appear one level higher (as if alredy upgraded)
+	std::string file = "zvs/Lib1.res/_";
+	switch (skill)
+	{
+		case ECommander::ATTACK:
+			file += "AT";
+			break;
+		case ECommander::DEFENSE:
+			file += "DF";
+			break;
+		case ECommander::HEALTH:
+			file += "HP";
+			break;
+		case ECommander::DAMAGE:
+			file += "DM";
+			break;
+		case ECommander::SPEED:
+			file += "SP";
+			break;
+		case ECommander::SPELL_POWER:
+			file += "MP";
+			break;
+	}
+	std::string sufix;
+	if (selected)
+		level++; // UI will display resulting level
+	if (level == 0)
+		sufix = "no"; //not avaliable - no number
+	else
+		sufix = boost::lexical_cast<std::string>(level-1);
+	if (selected)
+		sufix += "="; //level-up highlight
 
-		return file;
+	return file + sufix + ".bmp";
 }
 
 void CStackWindow::CWindowSection::createCommander()
-{/*
+{
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	createBackground("commander-bg");
 
@@ -241,45 +283,47 @@ void CStackWindow::CWindowSection::createCommander()
 		return Point(10 + 80 * (index%3), 20 + 80 * (index/3));
 	};
 
-	for (int i = ECommander::ATTACK; i <= ECommander::SPELL_POWER; ++i)
+	auto getSkillImage = [this](int skillIndex) -> std::string
 	{
-		bool haveSkill = parent->info.commander->secondarySkills[i] != 0;
-		bool canLevel  = parent->info.levelupInfo && vstd::contains(parent->info.levelupInfo->skills, i);
-
-		Point skillPos = getSkillPos(i);
-		if (canLevel)
-			new CPicture(skillToFile(i, parent->info.commander->secondarySkills[i], true,  false), skillPos.x, skillPos.y);
-		if (haveSkill && !canLevel)
-			new CPicture(skillToFile(i, parent->info.commander->secondarySkills[i], false, false), skillPos.x, skillPos.y);
-	}
-
-	bool createAbilities = false;
+		bool selected = ((parent->selectedSkill == skillIndex) && parent->info->levelupInfo );
+		return skillToFile(skillIndex, parent->info->commander->secondarySkills[skillIndex], selected);
+	};
 
-	if (parent->info.levelupInfo)
+	for (int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index)
 	{
-		for (auto option : parent->info.levelupInfo->skills)
+		Point skillPos = getSkillPos(index);
+
+		auto icon = new CClickableObject(new CPicture(getSkillImage(index), skillPos.x, skillPos.y), [=]{});
+
+		if (parent->selectedSkill == index)
+			parent->selectedIcon = icon;
+
+		if (parent->info->levelupInfo && vstd::contains(parent->info->levelupInfo->skills, index)) // can be upgraded - enable selection switch
 		{
-			if (option < 100)
+			icon->callback = [=]
 			{
-				auto selectableSkill = new CStackWindow::CSelectableSkill();
+				OBJ_CONSTRUCTION_CAPTURING_ALL;
+				int oldSelection = parent->selectedSkill; // update selection
+				parent->selectedSkill = index;
 
-				if (option == parent->selectedSkill)
-					selectedIcon = selectableSkill;
+				if (parent->selectedIcon) // recreate image on old selection
+					parent->selectedIcon->setObject(new CPicture(getSkillImage(oldSelection)));
 
-				selectableSkill->callback = std::bind(parent->info.levelupInfo->callback, option);
-				selectableSkill->pos = Rect(getSkillPos(option), Point(70, 70)); //resize
-			}
-			else
-				createAbilities = true;
+				parent->selectedIcon = icon; // update new selection
+				icon->setObject(new CPicture(getSkillImage(index)));
+			};
 		}
-	}*/
+	}
 }
 
 void CStackWindow::CWindowSection::createCommanderAbilities()
 {/*
-	for (auto option : parent->info.levelupInfo->skills)
+	for (auto option : parent->info->levelupInfo->skills)
 	{
+		if (option >= 100) // this is an ability
+		{
 
+		}
 	}
 	selectableSkill->pos = Rect (95, 256, 55, 55); //TODO: scroll
 	const Bonus *b = CGI->creh->skillRequirements[option-100].first;
@@ -316,11 +360,11 @@ void CStackWindow::CWindowSection::createButtonPanel()
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	createBackground("button-panel");
 
-	if (parent->info.dismissInfo)
+	if (parent->info->dismissInfo)
 	{
 		auto onDismiss = [=]()
 		{
-			parent->info.dismissInfo->callback();
+			parent->info->dismissInfo->callback();
 			parent->close();
 		};
 		auto onClick = [=] ()
@@ -329,18 +373,18 @@ void CStackWindow::CWindowSection::createButtonPanel()
 		};
 		new CAdventureMapButton(CGI->generaltexth->zelp[445], onClick, 5, 5,"IVIEWCR2.DEF",SDLK_d);
 	}
-	if (parent->info.upgradeInfo)
+	if (parent->info->upgradeInfo)
 	{
 		// used space overlaps with commander switch button
 		// besides - should commander really be upgradeable?
-		assert(!parent->info.commander);
+		assert(!parent->info->commander);
 
-		StackWindowInfo::StackUpgradeInfo & upgradeInfo = parent->info.upgradeInfo.get();
+		StackWindowInfo::StackUpgradeInfo & upgradeInfo = parent->info->upgradeInfo.get();
 		size_t buttonsToCreate = std::min<size_t>(upgradeInfo.info.newID.size(), 3); // no more than 3 windows on UI - space limit
 
 		for (size_t i=0; i<buttonsToCreate; i++)
 		{
-			TResources totalCost = upgradeInfo.info.cost[i] * parent->info.creatureCount;
+			TResources totalCost = upgradeInfo.info.cost[i] * parent->info->creatureCount;
 
 			auto onUpgrade = [=]()
 			{
@@ -365,17 +409,35 @@ void CStackWindow::CWindowSection::createButtonPanel()
 		}
 	}
 
-	if (parent->info.commander)
+	if (parent->info->commander)
 	{
-		//TODO: replace with 3 buttons, using upgrade button as base + sec skill image:
-		// 0) Switch to commander skills: Basic Offence
-		// 1) Switch to upgradable skills: Advanced mysticism (level-up only)
-		// 2) Switch to bonuses view: Basic Sorcery
-		auto onSwitch = [=]()
+		bool createAbilitiesTab = false;
+		if (parent->info->levelupInfo)
 		{
-			parent->commanderTab->setActive(parent->activeTab == 0 ? 1 : 0);
-		};
-		new CAdventureMapButton(std::make_pair("switch to commander", "help box"), onSwitch, 280, 5, "stackWindow/commanderToggle", SDLK_TAB);
+			for (auto option : parent->info->levelupInfo->skills)
+			{
+				if (option >= 100)
+					createAbilitiesTab = true;
+			}
+		}
+
+		for (size_t i=0; i<3; i++)
+		{
+			auto onSwitch = [&, i]()
+			{
+				parent->switchButtons[parent->activeTab]->enable();
+				parent->commanderTab->setActive(i);
+				parent->switchButtons[i]->disable();
+				parent->redraw(); // FIXME: enable/disable don't redraw screen themselves
+			};
+
+			parent->switchButtons[i] = new CAdventureMapButton(std::make_pair("",""), onSwitch, 262 + i*40, 5, "stackWindow/upgradeButton", SDLK_1 + i);
+			parent->switchButtons[i]->addOverlay(new CAnimImage("stackWindow/switchModeIcons", i));
+		}
+
+		parent->switchButtons[parent->activeTab]->disable();
+		if (!createAbilitiesTab)
+			parent->switchButtons[2]->disable();
 	}
 
 	auto exitBtn = new CAdventureMapButton(CGI->generaltexth->zelp[445], [=]{ parent->close(); }, 382, 5, "hsbtns.def", SDLK_RETURN);
@@ -387,7 +449,24 @@ CStackWindow::CWindowSection::CWindowSection(CStackWindow * parent):
 {
 }
 
-void CStackWindow::CSelectableSkill::clickLeft(tribool down, bool previousState)
+CClickableObject::CClickableObject(CIntObject *object, std::function<void()> callback):
+	object(nullptr),
+	callback(callback)
+{
+	pos = object->pos;
+	setObject(object);
+}
+
+void CClickableObject::setObject(CIntObject *newObject)
+{
+	delete object;
+	object = newObject;
+	addChild(object);
+	object->moveTo(pos.topLeft());
+	redraw();
+}
+
+void CClickableObject::clickLeft(tribool down, bool previousState)
 {
 	if (down)
 		callback();
@@ -427,7 +506,7 @@ CIntObject * CStackWindow::switchTab(size_t index)
 		{
 			activeTab = 0;
 			auto ret = new CWindowSection(this);
-			ret->createCommanderSection();
+			ret->createCommander();
 			return ret;
 		}
 		case 1:
@@ -456,28 +535,28 @@ void CStackWindow::initSections()
 	OBJ_CONSTRUCTION_CAPTURING_ALL;
 	CWindowSection * currentSection;
 
-	bool showArt = CGI->modh->modules.STACK_ARTIFACT && info.commander == nullptr;
-	bool showExp = CGI->modh->modules.STACK_EXP || info.commander != nullptr;
+	bool showArt = CGI->modh->modules.STACK_ARTIFACT && info->commander == nullptr;
+	bool showExp = CGI->modh->modules.STACK_EXP || info->commander != nullptr;
 	currentSection = new CWindowSection(this);
 	currentSection->createStackInfo(showExp, showArt);
 	pos.w = currentSection->pos.w;
 	pos.h += currentSection->pos.h;
 
-	if (dynamic_cast<const CStack*>(info.stackNode)) // in battle
+	if (dynamic_cast<const CStack*>(info->stackNode)) // in battle
 	{
 		currentSection = new CWindowSection(this);
 		currentSection->pos.y += pos.h;
 		currentSection->createActiveSpells();
 		pos.h += currentSection->pos.h;
 	}
-	if (info.commander)
+	if (info->commander)
 	{
 		currentSection = new CWindowSection(this);
 		currentSection->pos.y += pos.h;
 		currentSection->createCommanderSection();
 		pos.h += currentSection->pos.h;
 	}
-	if (!info.commander && !activeBonuses.empty())
+	if (!info->commander && !activeBonuses.empty())
 	{
 		currentSection = new CWindowSection(this);
 		currentSection->pos.y += pos.h;
@@ -485,12 +564,13 @@ void CStackWindow::initSections()
 		pos.h += currentSection->pos.h;
 	}
 
-	if (!info.popupWindow)
+	if (!info->popupWindow)
 	{
 		currentSection = new CWindowSection(this);
 		currentSection->pos.y += pos.h;
 		currentSection->createButtonPanel();
 		pos.h += currentSection->pos.h;
+		//FIXME: add status bar to image?
 	}
 	updateShadow();
 	pos = center(pos);
@@ -499,7 +579,7 @@ void CStackWindow::initSections()
 void CStackWindow::initBonusesList()
 {
 	BonusList output, input;
-	input = *(info.stackNode->getBonuses(Selector::durationType(Bonus::PERMANENT).And(Selector::anyRange())));
+	input = *(info->stackNode->getBonuses(Selector::durationType(Bonus::PERMANENT).And(Selector::anyRange())));
 
 	while (!input.empty())
 	{
@@ -513,8 +593,8 @@ void CStackWindow::initBonusesList()
 	BonusInfo bonusInfo;
 	for(Bonus* b : output)
 	{
-		bonusInfo.name = info.stackNode->bonusToString(b, false);
-		bonusInfo.imagePath = info.stackNode->bonusToGraphics(b);
+		bonusInfo.name = info->stackNode->bonusToString(b, false);
+		bonusInfo.imagePath = info->stackNode->bonusToGraphics(b);
 
 		//if it's possible to give any description or image for this kind of bonus
 		//TODO: figure out why half of bonuses don't have proper description
@@ -523,7 +603,7 @@ void CStackWindow::initBonusesList()
 	}
 
 	//handle Magic resistance separately :/
-	int magicResistance = info.stackNode->magicResistance();
+	int magicResistance = info->stackNode->magicResistance();
 
 	if (magicResistance)
 	{
@@ -531,9 +611,9 @@ void CStackWindow::initBonusesList()
 		Bonus b;
 		b.type = Bonus::MAGIC_RESISTANCE;
 
-		bonusInfo.name = VLC->getBth()->bonusToString(&b, info.stackNode, false);
-		bonusInfo.description = VLC->getBth()->bonusToString(&b, info.stackNode, true);
-		bonusInfo.imagePath = info.stackNode->bonusToGraphics(&b);
+		bonusInfo.name = VLC->getBth()->bonusToString(&b, info->stackNode, false);
+		bonusInfo.description = VLC->getBth()->bonusToString(&b, info->stackNode, true);
+		bonusInfo.imagePath = info->stackNode->bonusToGraphics(&b);
 		activeBonuses.push_back(bonusInfo);
 	}
 }
@@ -542,8 +622,8 @@ void CStackWindow::init()
 {
 	selectedIcon = nullptr;
 	selectedSkill = 0;
-	if (info.levelupInfo)
-		selectedSkill = info.levelupInfo->skills.front();
+	if (info->levelupInfo)
+		selectedSkill = info->levelupInfo->skills.front();
 
 	commanderTab = nullptr;
 	activeTab = 0;
@@ -555,64 +635,80 @@ void CStackWindow::init()
 CStackWindow::CStackWindow(const CStack * stack, bool popup):
 	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0))
 {
-	info.stackNode = stack->base;
-	info.creature = stack->type;
-	info.creatureCount = stack->count;
-	info.popupWindow = popup;
+	info->stackNode = stack->base;
+	info->creature = stack->type;
+	info->creatureCount = stack->count;
+	info->popupWindow = popup;
 	init();
 }
 
 CStackWindow::CStackWindow(const CCreature * creature, bool popup):
-	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0))
+	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
+	info(new StackWindowInfo())
 {
-	info.stackNode = new CStackInstance(creature, 1); // FIXME: free data
-	info.creature = creature;
-	info.popupWindow = popup;
+	info->stackNode = new CStackInstance(creature, 1); // FIXME: free data
+	info->creature = creature;
+	info->popupWindow = popup;
 	init();
 }
 
 CStackWindow::CStackWindow(const CStackInstance * stack, bool popup):
-	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0))
+	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
+	info(new StackWindowInfo())
 {
-	info.stackNode = stack;
-	info.creature = stack->type;
-	info.creatureCount = stack->count;
-	info.popupWindow = popup;
+	info->stackNode = stack;
+	info->creature = stack->type;
+	info->creatureCount = stack->count;
+	info->popupWindow = popup;
 	init();
 }
 
 CStackWindow::CStackWindow(const CStackInstance * stack, std::function<void()> dismiss, const UpgradeInfo & upgradeInfo, std::function<void(CreatureID)> callback):
-	CWindowObject(BORDERED)
+	CWindowObject(BORDERED),
+	info(new StackWindowInfo())
 {
-	info.stackNode = stack;
-	info.creature = stack->type;
-	info.creatureCount = stack->count;
-
-	info.upgradeInfo = StackWindowInfo::StackUpgradeInfo();
-	info.dismissInfo = StackWindowInfo::StackDismissInfo();
-	info.upgradeInfo->info = upgradeInfo;
-	info.upgradeInfo->callback = callback;
-	info.dismissInfo->callback = dismiss;
+	info->stackNode = stack;
+	info->creature = stack->type;
+	info->creatureCount = stack->count;
+
+	info->upgradeInfo = StackWindowInfo::StackUpgradeInfo();
+	info->dismissInfo = StackWindowInfo::StackDismissInfo();
+	info->upgradeInfo->info = upgradeInfo;
+	info->upgradeInfo->callback = callback;
+	info->dismissInfo->callback = dismiss;
 	init();
 }
 
 CStackWindow::CStackWindow(const CCommanderInstance * commander, bool popup):
-	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0))
+	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
+	info(new StackWindowInfo())
 {
-	info.stackNode = commander;
-	info.creature = commander->type;
-	info.commander = commander;
-	info.creatureCount = 1;
-	info.popupWindow = popup;
+	info->stackNode = commander;
+	info->creature = commander->type;
+	info->commander = commander;
+	info->creatureCount = 1;
+	info->popupWindow = popup;
 	init();
 }
 
 CStackWindow::CStackWindow(const CCommanderInstance * commander, std::vector<ui32> &skills, std::function<void(ui32)> callback):
-	CWindowObject(BORDERED)
+	CWindowObject(BORDERED),
+	info(new StackWindowInfo())
 {
-	info.stackNode = commander;
-	info.creature = commander->type;
-	info.commander = commander;
-	info.creatureCount = 1;
+	info->stackNode = commander;
+	info->creature = commander->type;
+	info->commander = commander;
+	info->creatureCount = 1;
+	info->levelupInfo = StackWindowInfo::CommanderLevelInfo();
+	info->levelupInfo->skills = skills;
+	info->levelupInfo->callback = callback;
 	init();
 }
+
+CStackWindow::~CStackWindow()
+{
+	if (info->levelupInfo)
+	{
+		info->levelupInfo->callback(selectedSkill);
+	}
+}

+ 20 - 42
client/CCreatureWindow.h

@@ -2,7 +2,6 @@
 
 #include "gui/CIntObjectClasses.h"
 #include "../lib/HeroBonus.h"
-#include "../lib/CGameState.h"
 
 /*
  * CCreatureWindow.h, part of VCMI engine
@@ -14,40 +13,24 @@
  *
  */
 
-struct StackWindowInfo
-{
-	// helper structs
-	struct CommanderLevelInfo
-	{
-		std::vector<ui32> skills;
-		std::function<void(ui32)> callback;
-	};
-	struct StackDismissInfo
-	{
-		std::function<void()> callback;
-	};
-	struct StackUpgradeInfo
-	{
-		UpgradeInfo info;
-		std::function<void(CreatureID)> callback;
-	};
+class StackWindowInfo;
+class CCommanderInstance;
+class CStackInstance;
+class CStack;
+struct UpgradeInfo;
 
-	// pointers to permament objects in game state
-	const CCreature * creature;
-	const CCommanderInstance * commander;
-	const CStackInstance * stackNode;
-	const CGHeroInstance * owner;
+class CClickableObject : public LRClickableAreaWText
+{
+	CIntObject * object; // passive object that will be used to determine clickable area
+public:
+	CClickableObject(CIntObject * object, std::function<void()> callback);
 
-	// temporary objects which should be kept as copy if needed
-	boost::optional<CommanderLevelInfo> levelupInfo;
-	boost::optional<StackDismissInfo> dismissInfo;
-	boost::optional<StackUpgradeInfo> upgradeInfo;
+	std::function<void()> callback; //TODO: create more generic clickable class than AdvMapButton?
 
-	// misc fields
-	unsigned int creatureCount;
-	bool popupWindow;
+	void clickLeft(tribool down, bool previousState);
+	void clickRight(tribool down, bool previousState){};
 
-	StackWindowInfo();
+	void setObject(CIntObject * object);
 };
 
 class CStackWindow : public CWindowObject
@@ -59,15 +42,6 @@ class CStackWindow : public CWindowObject
 		std::string imagePath;
 	};
 
-	class CSelectableSkill : public LRClickableAreaWText
-	{
-	public:
-		std::function<void()> callback; //TODO: create more generic clickable class than AdvMapButton?
-
-		void clickLeft(tribool down, bool previousState);
-		void clickRight(tribool down, bool previousState){};
-	};
-
 	class CWindowSection : public CIntObject
 	{
 		CStackWindow * parent;
@@ -94,12 +68,14 @@ class CStackWindow : public CWindowObject
 		CWindowSection(CStackWindow * parent);
 	};
 
-	StackWindowInfo info;
+	std::unique_ptr<StackWindowInfo> info;
 	std::vector<BonusInfo> activeBonuses;
 	size_t activeTab;
 	CTabbedInt * commanderTab;
 
-	CSelectableSkill * selectedIcon;
+	std::map<int, CAdventureMapButton *> switchButtons;
+
+	CClickableObject * selectedIcon;
 	si32 selectedSkill;
 
 	CIntObject * createBonusEntry(size_t index);
@@ -124,4 +100,6 @@ public:
 	// for commanders & commander level-up dialog
 	CStackWindow(const CCommanderInstance * commander, bool popup);
 	CStackWindow(const CCommanderInstance * commander, std::vector<ui32> &skills, std::function<void(ui32)> callback);
+
+	~CStackWindow();
 };

+ 1 - 1
client/CHeroWindow.cpp

@@ -328,7 +328,7 @@ void CHeroWindow::commanderWindow()
 		}
 	}
 	else
-		GH.pushInt(new CStackWindow(curHero->commander, true));
+		GH.pushInt(new CStackWindow(curHero->commander, false));
 
 }