瀏覽代碼

New creature window made without use of H3 graphics

This is import of ancient unfinished branch from svn. As of now window is largely finished but commander-related elements are still TODO.

Note that I also uploading graphical content - this is intended and necessary for final goal - to make VCMI functional without any additional graphical pack.
Ivan Savenko 11 年之前
父節點
當前提交
7a9547bb44
共有 34 個文件被更改,包括 751 次插入168 次删除
  1. 二進制
      Mods/vcmi/Data/stackWindow/bonus-effects.png
  2. 二進制
      Mods/vcmi/Data/stackWindow/button-panel.png
  3. 二進制
      Mods/vcmi/Data/stackWindow/commander-bg.png
  4. 二進制
      Mods/vcmi/Data/stackWindow/icons.png
  5. 二進制
      Mods/vcmi/Data/stackWindow/info-panel-0.png
  6. 二進制
      Mods/vcmi/Data/stackWindow/info-panel-1.png
  7. 二進制
      Mods/vcmi/Data/stackWindow/info-panel-2.png
  8. 二進制
      Mods/vcmi/Data/stackWindow/spell-effects.png
  9. 二進制
      Mods/vcmi/Sprites/stackWindow/level-0.png
  10. 二進制
      Mods/vcmi/Sprites/stackWindow/level-1.png
  11. 二進制
      Mods/vcmi/Sprites/stackWindow/level-10.png
  12. 二進制
      Mods/vcmi/Sprites/stackWindow/level-2.png
  13. 二進制
      Mods/vcmi/Sprites/stackWindow/level-3.png
  14. 二進制
      Mods/vcmi/Sprites/stackWindow/level-4.png
  15. 二進制
      Mods/vcmi/Sprites/stackWindow/level-5.png
  16. 二進制
      Mods/vcmi/Sprites/stackWindow/level-6.png
  17. 二進制
      Mods/vcmi/Sprites/stackWindow/level-7.png
  18. 二進制
      Mods/vcmi/Sprites/stackWindow/level-8.png
  19. 二進制
      Mods/vcmi/Sprites/stackWindow/level-9.png
  20. 17 0
      Mods/vcmi/Sprites/stackWindow/levels.json
  21. 8 0
      Mods/vcmi/Sprites/stackWindow/switch-mode-button.json
  22. 二進制
      Mods/vcmi/Sprites/stackWindow/switch-mode-normal.png
  23. 二進制
      Mods/vcmi/Sprites/stackWindow/switch-mode-pressed.png
  24. 8 0
      Mods/vcmi/Sprites/stackWindow/switchModeIcons.json
  25. 二進制
      Mods/vcmi/Sprites/stackWindow/upgrade-normal.png
  26. 二進制
      Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png
  27. 8 0
      Mods/vcmi/Sprites/stackWindow/upgradeButton.json
  28. 598 21
      client/CCreatureWindow.cpp
  29. 99 135
      client/CCreatureWindow.h
  30. 1 1
      client/CHeroWindow.cpp
  31. 5 3
      client/CPlayerInterface.cpp
  32. 5 6
      client/GUIClasses.cpp
  33. 1 1
      client/battle/CBattleInterface.cpp
  34. 1 1
      client/battle/CBattleInterfaceClasses.cpp

二進制
Mods/vcmi/Data/stackWindow/bonus-effects.png


二進制
Mods/vcmi/Data/stackWindow/button-panel.png


二進制
Mods/vcmi/Data/stackWindow/commander-bg.png


二進制
Mods/vcmi/Data/stackWindow/icons.png


二進制
Mods/vcmi/Data/stackWindow/info-panel-0.png


二進制
Mods/vcmi/Data/stackWindow/info-panel-1.png


二進制
Mods/vcmi/Data/stackWindow/info-panel-2.png


二進制
Mods/vcmi/Data/stackWindow/spell-effects.png


二進制
Mods/vcmi/Sprites/stackWindow/level-0.png


二進制
Mods/vcmi/Sprites/stackWindow/level-1.png


二進制
Mods/vcmi/Sprites/stackWindow/level-10.png


二進制
Mods/vcmi/Sprites/stackWindow/level-2.png


二進制
Mods/vcmi/Sprites/stackWindow/level-3.png


二進制
Mods/vcmi/Sprites/stackWindow/level-4.png


二進制
Mods/vcmi/Sprites/stackWindow/level-5.png


二進制
Mods/vcmi/Sprites/stackWindow/level-6.png


二進制
Mods/vcmi/Sprites/stackWindow/level-7.png


二進制
Mods/vcmi/Sprites/stackWindow/level-8.png


二進制
Mods/vcmi/Sprites/stackWindow/level-9.png


+ 17 - 0
Mods/vcmi/Sprites/stackWindow/levels.json

@@ -0,0 +1,17 @@
+{
+	"basepath" : "stackWindow/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "level-0.png"},
+		{ "frame" : 1, "file" : "level-1.png"},
+		{ "frame" : 2, "file" : "level-2.png"},
+		{ "frame" : 3, "file" : "level-3.png"},
+		{ "frame" : 4, "file" : "level-4.png"},
+		{ "frame" : 5, "file" : "level-5.png"},
+		{ "frame" : 6, "file" : "level-6.png"},
+		{ "frame" : 7, "file" : "level-7.png"},
+		{ "frame" : 8, "file" : "level-8.png"},
+		{ "frame" : 9, "file" : "level-9.png"},
+		{ "frame" : 10,"file" : "level-10.png"}
+	]
+}

+ 8 - 0
Mods/vcmi/Sprites/stackWindow/switch-mode-button.json

@@ -0,0 +1,8 @@
+{
+	"basepath" : "stackWindow/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "switch-mode-normal.png"},
+		{ "frame" : 0, "file" : "switch-mode-pressed.png"}
+	]
+}

二進制
Mods/vcmi/Sprites/stackWindow/switch-mode-normal.png


二進制
Mods/vcmi/Sprites/stackWindow/switch-mode-pressed.png


+ 8 - 0
Mods/vcmi/Sprites/stackWindow/switchModeIcons.json

@@ -0,0 +1,8 @@
+{
+	"images" :
+	[
+		{ "frame" : 0, "file" : "SECSK32:69"},
+		{ "frame" : 1, "file" : "SECSK32:28"},
+		{ "frame" : 2, "file" : "SECSK32:78"}
+	]
+}

二進制
Mods/vcmi/Sprites/stackWindow/upgrade-normal.png


二進制
Mods/vcmi/Sprites/stackWindow/upgrade-pressed.png


+ 8 - 0
Mods/vcmi/Sprites/stackWindow/upgradeButton.json

@@ -0,0 +1,8 @@
+{
+	"basepath" : "stackWindow/",
+	"images" :
+	[
+		{ "frame" : 0, "file" : "stackWindow/upgrade-normal.png"},
+		{ "frame" : 1, "file" : "stackWindow/upgrade-pressed.png"}
+	]
+}

+ 598 - 21
client/CCreatureWindow.cpp

@@ -1,28 +1,16 @@
 #include "StdInc.h"
 #include "CCreatureWindow.h"
 
-#include "../lib/CCreatureSet.h"
 #include "CGameInfo.h"
-#include "../lib/CGeneralTextHandler.h"
-#include "../lib/BattleState.h"
-#include "../CCallback.h"
-
-#include <SDL.h>
-#include "gui/SDL_Extensions.h"
-#include "CBitmapHandler.h"
-#include "CDefHandler.h"
-#include "Graphics.h"
 #include "CPlayerInterface.h"
-#include "../lib/CConfigHandler.h"
-#include "CAnimation.h"
+#include "GUIClasses.h"
 
-#include "../lib/CGameState.h"
+#include "../CCallback.h"
 #include "../lib/BattleState.h"
-#include "../lib/CSpellHandler.h"
-#include "../lib/CArtHandler.h"
-#include "../lib/NetPacksBase.h" //ArtifactLocation
+#include "../lib/CBonusTypeHandler.h"
+#include "../lib/CGeneralTextHandler.h"
 #include "../lib/CModHandler.h"
-#include "../lib/IBonusTypeHandler.h"
+#include "../lib/CSpellHandler.h"
 
 #include "gui/CGuiHandler.h"
 #include "gui/CIntObjectClasses.h"
@@ -42,6 +30,595 @@ class CSelectableSkill;
  *
  */
 
+namespace
+{
+	namespace EStat
+	{
+		enum EStat
+		{
+			ATTACK,
+			DEFENCE,
+			SHOTS,
+			DAMAGE,
+			HEALTH,
+			HEALTH_LEFT,
+			SPEED,
+			MANA
+		};
+	}
+}
+
+StackWindowInfo::StackWindowInfo():
+	creature(nullptr),
+	commander(nullptr),
+	stackNode(nullptr),
+	owner(nullptr),
+	creatureCount(0),
+	popupWindow(false)
+{
+}
+
+void CStackWindow::CWindowSection::createBackground(std::string path)
+{
+	background = new CPicture("stackWindow/" + path);
+	pos = background->pos;
+}
+
+void CStackWindow::CWindowSection::printStatString(int index, std::string name, std::string value)
+{
+	new CLabel(145, 32 + index*19, FONT_SMALL, TOPLEFT, Colors::WHITE, name);
+	new CLabel(307, 48 + index*19, FONT_SMALL, BOTTOMRIGHT, Colors::WHITE, value);
+}
+
+void CStackWindow::CWindowSection::printStatRange(int index, std::string name, int min, int max)
+{
+	if(min != max)
+		printStatString(index, name, boost::str(boost::format("%d - %d") % min % max));
+	else
+		printStatString(index, name, boost::str(boost::format("%d") % min));
+}
+
+void CStackWindow::CWindowSection::printStatBase(int index, std::string name, int base, int current)
+{
+	if(base != current)
+		printStatString(index, name, boost::str(boost::format("%d (%d)") % base % current));
+	else
+		printStatString(index, name, boost::str(boost::format("%d") % base));
+}
+
+void CStackWindow::CWindowSection::printStat(int index, std::string name, int value)
+{
+	printStatBase(index, name, value, value);
+}
+
+void CStackWindow::CWindowSection::createStackInfo(bool showExp, bool showArt)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	if (showExp && showArt)
+		createBackground("info-panel-2");
+	else if (showExp || showArt)
+		createBackground("info-panel-1");
+	else
+		createBackground("info-panel-0");
+
+	new CCreaturePic(5, 41, parent->info.creature);
+
+	std::string visibleName;
+	if (parent->info.commander != nullptr)
+		visibleName = parent->info.commander->type->nameSing;
+	else
+		visibleName = parent->info.creature->namePl;
+	new CLabel(215, 12, FONT_SMALL, CENTER, Colors::YELLOW, visibleName);
+
+	//TODO
+	int dmgMultiply = 1;
+	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());
+
+	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
+	{
+		if (shooter)
+			printStatBase(EStat::SHOTS, CGI->generaltexth->allTexts[198], battleStack->valOfBonuses(Bonus::SHOTS), battleStack->shots);
+		if (caster)
+			printStatBase(EStat::MANA, CGI->generaltexth->allTexts[399], battleStack->valOfBonuses(Bonus::CASTS), battleStack->casts);
+		printStat(EStat::HEALTH_LEFT, CGI->generaltexth->allTexts[200], battleStack->firstHPleft);
+	}
+	else
+	{
+		if (shooter)
+			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));
+	}
+
+	auto morale = new MoraleLuckBox(true, genRect(42, 42, 321, 110));
+	morale->set(parent->info.stackNode);
+	auto luck = new MoraleLuckBox(false, genRect(42, 42, 375, 110));
+	luck->set(parent->info.stackNode);
+}
+
+void CStackWindow::CWindowSection::createActiveSpells()
+{
+	static const Point firstPos(7 ,4); // position of 1st spell box
+	static const Point offset(54, 0);  // offset of each spell box from previous
+
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	createBackground("spell-effects");
+
+	const CStack * battleStack = dynamic_cast<const CStack*>(parent->info.stackNode);
+
+	assert(battleStack); // Section should be created only for battles
+
+	//spell effects
+	int printed=0; //how many effect pics have been printed
+	std::vector<si32> spells = battleStack->activeSpells();
+	for(si32 effect : spells)
+	{
+		std::string spellText;
+		if (effect < 77) //not all effects have graphics (for eg. Acid Breath)
+		{
+			spellText = CGI->generaltexth->allTexts[610]; //"%s, duration: %d rounds."
+			boost::replace_first (spellText, "%s", CGI->spellh->objects[effect]->name);
+			int duration = battleStack->getBonusLocalFirst(Selector::source(Bonus::SPELL_EFFECT,effect))->turnsRemain;
+			boost::replace_first (spellText, "%d", boost::lexical_cast<std::string>(duration));
+
+			new CAnimImage("SpellInt", effect + 1, 0, firstPos.x + offset.x * printed, firstPos.x + offset.y * printed);
+			new LRClickableAreaWText(Rect(firstPos + offset * printed, Point(50, 38)), spellText, spellText);
+			if (++printed >= 8) // interface limit reached
+				break;
+		}
+	}
+}
+
+void CStackWindow::CWindowSection::createCommanderSection()
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	auto onCreate = [=](size_t index) -> CIntObject *
+	{
+		return parent->switchTab(index);
+	};
+	auto onDestroy = [=](CIntObject * obj)
+	{
+		delete obj;
+	};
+	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)
+{
+		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";
+
+		return file;
+}
+
+void CStackWindow::CWindowSection::createCommander()
+{/*
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	createBackground("commander-bg");
+
+	auto getSkillPos = [&](int index)
+	{
+		return Point(10 + 80 * (index%3), 20 + 80 * (index/3));
+	};
+
+	for (int i = ECommander::ATTACK; i <= ECommander::SPELL_POWER; ++i)
+	{
+		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;
+
+	if (parent->info.levelupInfo)
+	{
+		for (auto option : parent->info.levelupInfo->skills)
+		{
+			if (option < 100)
+			{
+				auto selectableSkill = new CStackWindow::CSelectableSkill();
+
+				if (option == parent->selectedSkill)
+					selectedIcon = selectableSkill;
+
+				selectableSkill->callback = std::bind(parent->info.levelupInfo->callback, option);
+				selectableSkill->pos = Rect(getSkillPos(option), Point(70, 70)); //resize
+			}
+			else
+				createAbilities = true;
+		}
+	}*/
+}
+
+void CStackWindow::CWindowSection::createCommanderAbilities()
+{/*
+	for (auto option : parent->info.levelupInfo->skills)
+	{
+
+	}
+	selectableSkill->pos = Rect (95, 256, 55, 55); //TODO: scroll
+	const Bonus *b = CGI->creh->skillRequirements[option-100].first;
+	bonusItems.push_back (new CBonusItem (genRect(0, 0, 251, 57), stack->bonusToString(b, false), stack->bonusToString(b, true), stack->bonusToGraphics(b)));
+	selectableBonuses.push_back (selectableSkill); //insert these before other bonuses
+*/
+}
+
+void CStackWindow::CWindowSection::createBonuses(boost::optional<size_t> preferredSize)
+{
+	// size of single image for an item
+	static const int itemHeight = 59;
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	size_t totalSize = (parent->activeBonuses.size() + 1) / 2;
+	size_t visibleSize = preferredSize ? preferredSize.get() : std::min<size_t>(3, totalSize);
+
+	pos.w = parent->pos.w;
+	pos.h = itemHeight * visibleSize;
+
+	auto onCreate = [=](size_t index) -> CIntObject *
+	{
+		return parent->createBonusEntry(index);
+	};
+	auto onDestroy = [=](CIntObject * obj)
+	{
+		delete obj;
+	};
+	new CListBox(onCreate, onDestroy, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, 1, Rect(pos.w - 15, 0, pos.h, pos.h));
+}
+
+void CStackWindow::CWindowSection::createButtonPanel()
+{
+	//TODO: localization, place creature icon on button, proper path to animation-button
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	createBackground("button-panel");
+
+	if (parent->info.dismissInfo)
+	{
+		auto onDismiss = [=]()
+		{
+			parent->info.dismissInfo->callback();
+			parent->close();
+		};
+		auto onClick = [=] ()
+		{
+			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[12], onDismiss, 0, false, std::vector<CComponent*>());
+		};
+		new CAdventureMapButton(CGI->generaltexth->zelp[445], onClick, 5, 5,"IVIEWCR2.DEF",SDLK_d);
+	}
+	if (parent->info.upgradeInfo)
+	{
+		// used space overlaps with commander switch button
+		// besides - should commander really be upgradeable?
+		assert(!parent->info.commander);
+
+		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;
+
+			auto onUpgrade = [=]()
+			{
+				upgradeInfo.callback(upgradeInfo.info.newID[i]);
+				parent->close();
+			};
+			auto onClick = [=]()
+			{
+				std::vector<CComponent*> resComps;
+				for(TResources::nziterator i(totalCost); i.valid(); i++)
+				{
+					resComps.push_back(new CComponent(CComponent::resource, i->resType, i->resVal));
+				}
+				LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[207], onUpgrade, nullptr, true, resComps);
+			};
+			auto upgradeBtn = new CAdventureMapButton(CGI->generaltexth->zelp[446], onClick, 221 + i * 40, 5, "stackWindow/upgradeButton", SDLK_1);
+			if (!LOCPLINT->cb->getResourceAmount().canAfford(totalCost))
+				upgradeBtn->block(true);
+//			else
+//				upgradeBtn->addOverlay(new CPicture());
+		}
+	}
+
+	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 = [=]()
+		{
+			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);
+	}
+
+	auto exitBtn = new CAdventureMapButton(CGI->generaltexth->zelp[445], [=]{ parent->close(); }, 382, 5, "hsbtns.def", SDLK_RETURN);
+	exitBtn->assignedKeys.insert(SDLK_ESCAPE);
+}
+
+CStackWindow::CWindowSection::CWindowSection(CStackWindow * parent):
+	parent(parent)
+{
+}
+
+void CStackWindow::CSelectableSkill::clickLeft(tribool down, bool previousState)
+{
+	if (down)
+		callback();
+}
+
+CIntObject * CStackWindow::createBonusEntry(size_t index)
+{
+	auto section = new CWindowSection(this);
+	section->createBonusEntry(index);
+	return section;
+}
+
+void CStackWindow::CWindowSection::createBonusEntry(size_t index)
+{
+	OBJ_CONSTRUCTION_CAPTURING_ALL;
+	createBackground("bonus-effects");
+	createBonusItem(index * 2, Point(6, 4));
+	createBonusItem(index * 2 + 1, Point(214, 4));
+}
+
+void CStackWindow::CWindowSection::createBonusItem(size_t index, Point position)
+{
+	if (parent->activeBonuses.size() > index)
+	{
+		BonusInfo & bi = parent->activeBonuses[index];
+		new CPicture(bi.imagePath, position.x, position.y);
+		new CLabel(position.x + 60, position.y + 2,  FONT_SMALL, TOPLEFT, Colors::WHITE, bi.name);
+		new CLabel(position.x + 60, position.y + 25, FONT_SMALL, TOPLEFT, Colors::WHITE, bi.description);
+	}
+}
+
+CIntObject * CStackWindow::switchTab(size_t index)
+{
+	switch (index)
+	{
+		case 0:
+		{
+			activeTab = 0;
+			auto ret = new CWindowSection(this);
+			ret->createCommanderSection();
+			return ret;
+		}
+		case 1:
+		{
+			activeTab = 1;
+			auto ret = new CWindowSection(this);
+			ret->createBonuses(3);
+			return ret;
+		}
+		case 2:
+		{
+			activeTab = 2;
+			auto ret = new CWindowSection(this);
+			ret->createCommanderAbilities();
+			return ret;
+		}
+		default:
+		{
+			return nullptr;
+		}
+	}
+}
+
+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;
+	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
+	{
+		currentSection = new CWindowSection(this);
+		currentSection->pos.y += pos.h;
+		currentSection->createActiveSpells();
+		pos.h += currentSection->pos.h;
+	}
+	if (info.commander)
+	{
+		currentSection = new CWindowSection(this);
+		currentSection->pos.y += pos.h;
+		currentSection->createCommanderSection();
+		pos.h += currentSection->pos.h;
+	}
+	if (!info.commander && !activeBonuses.empty())
+	{
+		currentSection = new CWindowSection(this);
+		currentSection->pos.y += pos.h;
+		currentSection->createBonuses();
+		pos.h += currentSection->pos.h;
+	}
+
+	if (!info.popupWindow)
+	{
+		currentSection = new CWindowSection(this);
+		currentSection->pos.y += pos.h;
+		currentSection->createButtonPanel();
+		pos.h += currentSection->pos.h;
+	}
+	updateShadow();
+	pos = center(pos);
+}
+
+void CStackWindow::initBonusesList()
+{
+	BonusList output, input;
+	input = *(info.stackNode->getBonuses(Selector::durationType(Bonus::PERMANENT).And(Selector::anyRange())));
+
+	while (!input.empty())
+	{
+		Bonus * b = input.front();
+
+		output.push_back(new Bonus(*b));
+		output.back()->val = input.valOfBonuses(Selector::typeSubtype(b->type, b->subtype)); //merge multiple bonuses into one
+		input.remove_if (Selector::typeSubtype(b->type, b->subtype)); //remove used bonuses
+	}
+
+	BonusInfo bonusInfo;
+	for(Bonus* b : output)
+	{
+		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
+		if (!bonusInfo.name.empty() || !bonusInfo.imagePath.empty())
+			activeBonuses.push_back(bonusInfo);
+	}
+
+	//handle Magic resistance separately :/
+	int magicResistance = info.stackNode->magicResistance();
+
+	if (magicResistance)
+	{
+		BonusInfo bonusInfo;
+		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);
+		activeBonuses.push_back(bonusInfo);
+	}
+}
+
+void CStackWindow::init()
+{
+	selectedIcon = nullptr;
+	selectedSkill = 0;
+	if (info.levelupInfo)
+		selectedSkill = info.levelupInfo->skills.front();
+
+	commanderTab = nullptr;
+	activeTab = 0;
+
+	initBonusesList();
+	initSections();
+}
+
+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;
+	init();
+}
+
+CStackWindow::CStackWindow(const CCreature * creature, bool popup):
+	CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0))
+{
+	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))
+{
+	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)
+{
+	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))
+{
+	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)
+{
+	info.stackNode = commander;
+	info.creature = commander->type;
+	info.commander = commander;
+	info.creatureCount = 1;
+	init();
+}
+
+/*
+
 CCreatureWindow::CCreatureWindow (const CStack &stack, CreWinType Type):
     CWindowObject(PLAYER_COLORED | (Type == OTHER ? RCLICK_POPUP : 0 ) ),
     type(Type)
@@ -458,7 +1035,7 @@ void CCreatureWindow::init(const CStackInstance *Stack, const CBonusSystemNode *
 	//AUIDAT.DEF
 }
 
-void CCreatureWindow::printLine(int nr, const std::string &text, int baseVal, int val/*=-1*/, bool range/*=false*/)
+void CCreatureWindow::printLine(int nr, const std::string &text, int baseVal, int val, bool range)
 {
 	new CLabel(162, 48 + nr*19, FONT_SMALL, TOPLEFT, Colors::WHITE, text);
 
@@ -821,7 +1398,7 @@ CCreInfoWindow::~CCreInfoWindow()
 		delete object;
 }
 
-void CCreInfoWindow::printLine(int position, const std::string &text, int baseVal, int val/*=-1*/, bool range/*=false*/)
+void CCreInfoWindow::printLine(int position, const std::string &text, int baseVal, int val, bool range)
 {
 	infoTexts[position].first = new CLabel(155, 48 + position*19, FONT_SMALL, TOPLEFT, Colors::WHITE, text);
 	std::string valueStr;
@@ -901,8 +1478,7 @@ void CCreInfoWindow::init(const CCreature *creature, const CBonusSystemNode *sta
 	}
 }
 
-CIntObject * createCreWindow(
-	const CStack *s, bool lclick/* = false*/)
+CIntObject * createCreWindow(const CStack *s, bool lclick)
 {
 	auto c = dynamic_cast<const CCommanderInstance *>(s->base);
 	if (c)
@@ -933,3 +1509,4 @@ CIntObject * createCreWindow(const CStackInstance *s, CCreatureWindow::CreWinTyp
 	else
 		return  new CCreatureWindow(*s, type, Upg, Dsm, ui);
 }
+*/

+ 99 - 135
client/CCreatureWindow.h

@@ -1,8 +1,8 @@
 #pragma once
 
-#include "gui/CIntObject.h"
+#include "gui/CIntObjectClasses.h"
 #include "../lib/HeroBonus.h"
-#include "GUIClasses.h"
+#include "../lib/CGameState.h"
 
 /*
  * CCreatureWindow.h, part of VCMI engine
@@ -14,150 +14,114 @@
  *
  */
 
-struct Bonus;
-class CCreature;
-class CStackInstance;
-class CCommanderInstance;
-class CStack;
-struct ArtifactLocation;
-class CCreatureArtifactInstance;
-class CAdventureMapButton;
-class CBonusItem;
-class CGHeroInstance;
-class CComponent;
-class LRClickableAreaWText;
-class MoraleLuckBox;
-class CAdventureMapButton;
-struct UpgradeInfo;
-class CPicture;
-class CCreaturePic;
-class LRClickableAreaWTextComp;
-class CSlider;
-class CLabel;
-class CAnimImage;
-class CSelectableSkill;
-
-// New creature window
-class CCreatureWindow : public CWindowObject, public CArtifactHolder
+struct StackWindowInfo
 {
-public:
-	enum CreWinType {OTHER = 0, BATTLE = 1, ARMY = 2, HERO = 3, COMMANDER = 4, COMMANDER_LEVEL_UP = 5, COMMANDER_BATTLE = 6}; // > 3 are opened permanently
-	//bool active; //TODO: comment me
-	CreWinType type;
-	int bonusRows; //height of skill window
-	ArtifactPosition displayedArtifact;
-
-	std::string count; //creature count in text format
-	const CCreature *c; //related creature
-	const CStackInstance *stack;
-	const CBonusSystemNode *stackNode;
+	// 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 CGHeroInstance *heroOwner;
-	const CArtifactInstance *creatureArtifact; //currently worn artifact
-	std::vector<CComponent*> upgResCost; //cost of upgrade (if not possible then empty)
-	std::vector<CBonusItem*> bonusItems;
-	std::vector<LRClickableAreaWText*> spellEffects;
-
-	CCreaturePic *anim; //related creature's animation
-	MoraleLuckBox *luck, *morale;
-	LRClickableAreaWTextComp * expArea; //displays exp details
-	CSlider * slider; //Abilities
-	CAdventureMapButton *dismiss, *upgrade, *ok;
-	CAdventureMapButton * leftArtRoll, * rightArtRoll; //artifact selection
-	CAdventureMapButton * passArtToHero;
-	CAnimImage * artifactImage;
-	CAnimation * spellEffectsPics; //bitmaps representing spells affecting a stack in battle
-
-	//commander level-up
-	int selectedOption; //index for upgradeOptions
-	std::vector<ui32> upgradeOptions; //value 0-5 - secondary skills, 100+ - special skills
-	std::vector<CSelectableSkill *> selectableSkills, selectableBonuses;
-	std::vector<CPicture *> skillPictures; //secondary skills
-
-	std::string skillToFile(int skill); //return bitmap for secondary skill depending on selection / avaliability
-	void selectSkill (ui32 which);
-	void setArt(const CArtifactInstance *creatureArtifact);
-
-	void artifactRemoved (const ArtifactLocation &artLoc);
-	void artifactMoved (const ArtifactLocation &artLoc, const ArtifactLocation &destLoc);
-	void artifactDisassembled (const ArtifactLocation &artLoc) {return;};
-	void artifactAssembled (const ArtifactLocation &artLoc) {return;};
-
-	std::function<void()> dsm; //dismiss button callback
-	std::function<void()> Upg; //upgrade button callback
-	std::function<void(ui32)> levelUp; //choose commander skill to level up
-
-	CCreatureWindow(const CStack & stack, CreWinType type); //battle c-tor
-	CCreatureWindow (const CStackInstance &stack, CreWinType Type); //pop-up c-tor
-	CCreatureWindow(const CStackInstance &st, CreWinType Type, std::function<void()> Upg, std::function<void()> Dsm, UpgradeInfo *ui); //full garrison window
-	CCreatureWindow(const CCommanderInstance * commander, const CStack * stack = nullptr); //commander window
-	CCreatureWindow(std::vector<ui32> &skills, const CCommanderInstance * commander, std::function<void(ui32)> callback); 
-	CCreatureWindow(CreatureID Cid, CreWinType Type, int creatureCount); //c-tor
-
-	void init(const CStackInstance *stack, const CBonusSystemNode *stackNode, const CGHeroInstance *heroOwner);
-	void showAll(SDL_Surface * to);
-	void show(SDL_Surface * to);
-	void printLine(int nr, const std::string &text, int baseVal, int val=-1, bool range=false);
-	void sliderMoved(int newpos);
-	void close();
-	~CCreatureWindow(); //d-tor
-
-	void recreateSkillList(int pos);
-	void scrollArt(int dir);
-	void passArtifactToHero();
-};
+	const CStackInstance * stackNode;
+	const CGHeroInstance * owner;
 
-class CBonusItem : public LRClickableAreaWTextComp //responsible for displaying creature skill, active or not
-{
-public:
-	std::string name, description;
-	CPicture * bonusGraphics;
-	bool visible;
+	// temporary objects which should be kept as copy if needed
+	boost::optional<CommanderLevelInfo> levelupInfo;
+	boost::optional<StackDismissInfo> dismissInfo;
+	boost::optional<StackUpgradeInfo> upgradeInfo;
 
-	CBonusItem();
-	CBonusItem(const Rect &Pos, const std::string &Name, const std::string &Description, const std::string &graphicsName);
-	~CBonusItem();
+	// misc fields
+	unsigned int creatureCount;
+	bool popupWindow;
 
-	void showAll (SDL_Surface * to);
+	StackWindowInfo();
 };
 
-class CSelectableSkill : public LRClickableAreaWText
+class CStackWindow : public CWindowObject
 {
-public:
-	std::function<void()> callback; //TODO: create more generic clickable class than AdvMapButton?
-
-	virtual void clickLeft(tribool down, bool previousState);
-	virtual void clickRight(tribool down, bool previousState){};
-};
+	struct BonusInfo
+	{
+		std::string name;
+		std::string description;
+		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;
+
+		CPicture * background;
+
+		void createBackground(std::string path);
+		void createBonusItem(size_t index, Point position);
+
+		void printStatString(int index, std::string name, std::string value);
+		void printStatRange(int index, std::string name, int min, int max);
+		void printStatBase(int index, std::string name, int base, int current);
+		void printStat(int index, std::string name, int value);
+	public:
+		void createStackInfo(bool showExp, bool showArt);
+		void createActiveSpells();
+		void createCommanderSection();
+		void createCommander();
+		void createCommanderAbilities();
+		void createBonuses(boost::optional<size_t> size = boost::optional<size_t>());
+		void createBonusEntry(size_t index);
+		void createButtonPanel();
+
+		CWindowSection(CStackWindow * parent);
+	};
+
+	StackWindowInfo info;
+	std::vector<BonusInfo> activeBonuses;
+	size_t activeTab;
+	CTabbedInt * commanderTab;
+
+	CSelectableSkill * selectedIcon;
+	si32 selectedSkill;
+
+	CIntObject * createBonusEntry(size_t index);
+	CIntObject * switchTab(size_t index);
+
+	void initSections();
+	void initBonusesList();
+
+	void init();
 
-/// original creature info window
-class CCreInfoWindow : public CWindowObject
-{
 public:
-	CLabel * creatureCount;
-	CLabel * creatureName;
-	CLabel * abilityText;
-
-	CCreaturePic * animation;
-	std::vector<CComponent *> upgResCost; //cost of upgrade (if not possible then empty)
-	std::vector<CAnimImage *> effects;
-	std::map<size_t, std::pair<CLabel *, CLabel * > > infoTexts;
+	// for battles
+	CStackWindow(const CStack * stack, bool popup);
 
-	MoraleLuckBox * luck, * morale;
+	// for non-existing stacks, e.g. recruit screen
+	CStackWindow(const CCreature * creature, bool popup);
 
-	CAdventureMapButton * dismiss, * upgrade, * ok;
+	// for normal stacks in armies
+	CStackWindow(const CStackInstance * stack, bool popup);
+	CStackWindow(const CStackInstance * stack, std::function<void()> dismiss, const UpgradeInfo & info, std::function<void(CreatureID)> callback);
 
-	CCreInfoWindow(const CStackInstance & st, bool LClicked, std::function<void()> Upg = nullptr, std::function<void()> Dsm = nullptr, UpgradeInfo * ui = nullptr);
-	CCreInfoWindow(const CStack & st, bool LClicked = 0);
-	CCreInfoWindow(int Cid, bool LClicked, int creatureCount);
-	~CCreInfoWindow();
-
-	void init(const CCreature * cre, const CBonusSystemNode * stackNode, const CGHeroInstance * heroOwner, int creatureCount, bool LClicked);
-	void printLine(int nr, const std::string & text, int baseVal, int val = -1, bool range = false);
-
-	void show(SDL_Surface * to);
+	// 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);
 };
-
-CIntObject *createCreWindow(const CStack *s, bool lclick = false);
-CIntObject *createCreWindow(CreatureID Cid, CCreatureWindow::CreWinType Type, int creatureCount);
-CIntObject *createCreWindow(const CStackInstance *s, CCreatureWindow::CreWinType type, std::function<void()> Upg = nullptr, std::function<void()> Dsm = nullptr, UpgradeInfo *ui = nullptr);

+ 1 - 1
client/CHeroWindow.cpp

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

+ 5 - 3
client/CPlayerInterface.cpp

@@ -482,10 +482,12 @@ void CPlayerInterface::commanderGotLevel (const CCommanderInstance * commander,
 	waitWhileDialog();
 	CCS->soundh->playSound(soundBase::heroNewLevel);
 
-	CCreatureWindow * cw = new CCreatureWindow(skills, commander,
-												[=](ui32 selection){ cb->selectionMade(selection, queryID); });
-	GH.pushInt(cw);
+	GH.pushInt(new CStackWindow(commander, skills, [=](ui32 selection)
+	{
+		cb->selectionMade(selection, queryID);
+	}));
 }
+
 void CPlayerInterface::heroInGarrisonChange(const CGTownInstance *town)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;

+ 5 - 6
client/GUIClasses.cpp

@@ -303,7 +303,7 @@ void CGarrisonSlot::clickRight(tribool down, bool previousState)
 {
 	if(down && creature)
 	{
-		GH.pushInt(createCreWindow(myStack, CCreatureWindow::ARMY));
+		GH.pushInt(new CStackWindow(creature, true));
 	}
 }
 void CGarrisonSlot::clickLeft(tribool down, bool previousState)
@@ -320,9 +320,9 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
 
 				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(CreatureID)> upgr = nullptr;
 				std::function<void()> dism = nullptr;
-				if(canUpgrade) upgr = [=] { LOCPLINT->cb->upgradeCreature(getObj(), ID, pom.newID[0]); };
+				if(canUpgrade) upgr = [=] (CreatureID newID) { LOCPLINT->cb->upgradeCreature(getObj(), ID, newID); };
 				if(canDismiss) dism = [=] { LOCPLINT->cb->dismissCreature(getObj(), ID); };
 
 				owner->selectSlot(nullptr);
@@ -333,8 +333,7 @@ void CGarrisonSlot::clickLeft(tribool down, bool previousState)
 
 				redraw();
 				refr = true;
-				CIntObject *creWindow = createCreWindow(myStack, CCreatureWindow::HERO, upgr, dism, &pom);
-				GH.pushInt(creWindow);
+				GH.pushInt(new CStackWindow(myStack, dism, pom, upgr));
 			}
 			else
 			{
@@ -1386,7 +1385,7 @@ void CRecruitmentWindow::CCreatureCard::clickLeft(tribool down, bool previousSta
 void CRecruitmentWindow::CCreatureCard::clickRight(tribool down, bool previousState)
 {
 	if (down)
-		GH.pushInt(createCreWindow(creature->idNumber, CCreatureWindow::OTHER, 0));
+		GH.pushInt(new CStackWindow(creature, true));
 }
 
 void CRecruitmentWindow::CCreatureCard::showAll(SDL_Surface * to)

+ 1 - 1
client/battle/CBattleInterface.cpp

@@ -2432,7 +2432,7 @@ void CBattleInterface::handleHex(BattleHex myNumber, int eventType)
 			{
 				cursorFrame = ECursor::COMBAT_QUERY;
 				consoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
-				realizeAction = [=]{ GH.pushInt(createCreWindow(shere, true)); };
+				realizeAction = [=]{ GH.pushInt(new CStackWindow(shere, false)); };
 				break;
 			}
 		}

+ 1 - 1
client/battle/CBattleInterfaceClasses.cpp

@@ -606,7 +606,7 @@ void CClickableHex::clickRight(tribool down, bool previousState)
 		if(!myst->alive()) return;
 		if(down)
 		{
-			GH.pushInt(createCreWindow(myst));
+			GH.pushInt(new CStackWindow(myst, true));
 		}
 	}
 }