2
0

CCreatureWindow.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. /*
  2. * CCreatureWindow.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CCreatureWindow.h"
  12. #include <vcmi/spells/Spell.h>
  13. #include <vcmi/spells/Service.h>
  14. #include "../CPlayerInterface.h"
  15. #include "../render/Canvas.h"
  16. #include "../widgets/Buttons.h"
  17. #include "../widgets/CComponent.h"
  18. #include "../widgets/CComponentHolder.h"
  19. #include "../widgets/GraphicalPrimitiveCanvas.h"
  20. #include "../widgets/Images.h"
  21. #include "../widgets/TextControls.h"
  22. #include "../widgets/ObjectLists.h"
  23. #include "../widgets/GraphicalPrimitiveCanvas.h"
  24. #include "../windows/InfoWindows.h"
  25. #include "../GameEngine.h"
  26. #include "../GameInstance.h"
  27. #include "../gui/Shortcut.h"
  28. #include "../battle/BattleInterface.h"
  29. #include "../../lib/CBonusTypeHandler.h"
  30. #include "../../lib/CStack.h"
  31. #include "../../lib/GameLibrary.h"
  32. #include "../../lib/IGameSettings.h"
  33. #include "../../lib/bonuses/Propagators.h"
  34. #include "../../lib/callback/CCallback.h"
  35. #include "../../lib/entities/artifact/ArtifactUtils.h"
  36. #include "../../lib/entities/hero/CHeroHandler.h"
  37. #include "../../lib/gameState/CGameState.h"
  38. #include "../../lib/gameState/UpgradeInfo.h"
  39. #include "../../lib/networkPacks/ArtifactLocation.h"
  40. #include "../../lib/texts/CGeneralTextHandler.h"
  41. #include "../../lib/texts/TextOperations.h"
  42. #include "../../lib/texts/Languages.h"
  43. class CCreatureArtifactInstance;
  44. class CSelectableSkill;
  45. class UnitView
  46. {
  47. public:
  48. // helper structs
  49. struct CommanderLevelInfo
  50. {
  51. std::vector<ui32> skills;
  52. std::function<void(ui32)> callback;
  53. };
  54. struct StackDismissInfo
  55. {
  56. std::function<void()> callback;
  57. };
  58. struct StackUpgradeInfo
  59. {
  60. StackUpgradeInfo() = delete;
  61. StackUpgradeInfo(const UpgradeInfo & upgradeInfo)
  62. : info(upgradeInfo)
  63. { }
  64. UpgradeInfo info;
  65. std::function<void(CreatureID)> callback;
  66. };
  67. // pointers to permanent objects in game state
  68. const CCreature * creature;
  69. const CCommanderInstance * commander;
  70. const CStackInstance * stackNode;
  71. const CStack * stack;
  72. const CGHeroInstance * owner;
  73. // temporary objects which should be kept as copy if needed
  74. std::optional<CommanderLevelInfo> levelupInfo;
  75. std::optional<StackDismissInfo> dismissInfo;
  76. std::optional<StackUpgradeInfo> upgradeInfo;
  77. // misc fields
  78. unsigned int creatureCount;
  79. bool popupWindow;
  80. UnitView()
  81. : creature(nullptr),
  82. commander(nullptr),
  83. stackNode(nullptr),
  84. stack(nullptr),
  85. owner(nullptr),
  86. creatureCount(0),
  87. popupWindow(false)
  88. {
  89. }
  90. std::string getName() const
  91. {
  92. if(commander)
  93. return commander->getType()->getNameSingularTranslated();
  94. else
  95. return creature->getNamePluralTranslated();
  96. }
  97. private:
  98. };
  99. CCommanderSkillIcon::CCommanderSkillIcon(std::shared_ptr<CIntObject> object_, bool isMasterAbility_, std::function<void()> callback)
  100. : object(),
  101. isMasterAbility(isMasterAbility_),
  102. isSelected(false),
  103. callback(callback)
  104. {
  105. pos = object_->pos;
  106. this->isMasterAbility = isMasterAbility_;
  107. setObject(object_);
  108. }
  109. void CCommanderSkillIcon::setObject(std::shared_ptr<CIntObject> newObject)
  110. {
  111. if(object)
  112. removeChild(object.get());
  113. object = newObject;
  114. addChild(object.get());
  115. object->moveTo(pos.topLeft());
  116. redraw();
  117. }
  118. void CCommanderSkillIcon::clickPressed(const Point & cursorPosition)
  119. {
  120. callback();
  121. isSelected = true;
  122. redraw();
  123. }
  124. void CCommanderSkillIcon::deselect()
  125. {
  126. isSelected = false;
  127. redraw();
  128. }
  129. bool CCommanderSkillIcon::getIsMasterAbility()
  130. {
  131. return isMasterAbility;
  132. }
  133. void CCommanderSkillIcon::show(Canvas &to)
  134. {
  135. CIntObject::show(to);
  136. if(isMasterAbility && isSelected)
  137. to.drawBorder(pos, Colors::YELLOW, 2);
  138. }
  139. static ImagePath skillToFile(int skill, int level, bool selected)
  140. {
  141. // FIXME: is this a correct handling?
  142. // level 0 = skill not present, use image with "no" suffix
  143. // level 1-5 = skill available, mapped to images indexed as 0-4
  144. // selecting skill means that it will appear one level higher (as if already upgraded)
  145. std::string file = "zvs/Lib1.res/_";
  146. switch (skill)
  147. {
  148. case ECommander::ATTACK:
  149. file += "AT";
  150. break;
  151. case ECommander::DEFENSE:
  152. file += "DF";
  153. break;
  154. case ECommander::HEALTH:
  155. file += "HP";
  156. break;
  157. case ECommander::DAMAGE:
  158. file += "DM";
  159. break;
  160. case ECommander::SPEED:
  161. file += "SP";
  162. break;
  163. case ECommander::SPELL_POWER:
  164. file += "MP";
  165. break;
  166. }
  167. std::string suffix;
  168. if (selected)
  169. level++; // UI will display resulting level
  170. if (level == 0)
  171. suffix = "no"; //not available - no number
  172. else
  173. suffix = std::to_string(level-1);
  174. if (selected)
  175. suffix += "="; //level-up highlight
  176. return ImagePath::builtin(file + suffix + ".bmp");
  177. }
  178. CStackWindow::CWindowSection::CWindowSection(CStackWindow * parent, const ImagePath & backgroundPath, int yOffset)
  179. : parent(parent)
  180. {
  181. pos.y += yOffset;
  182. OBJECT_CONSTRUCTION;
  183. if(!backgroundPath.empty())
  184. {
  185. background = std::make_shared<CPicture>(backgroundPath);
  186. pos.w = background->pos.w;
  187. pos.h = background->pos.h;
  188. }
  189. }
  190. CStackWindow::ActiveSpellsSection::ActiveSpellsSection(CStackWindow * owner, int yOffset)
  191. : CWindowSection(owner, ImagePath::builtin("stackWindow/spell-effects"), yOffset)
  192. {
  193. static const Point firstPos(6, 2); // position of 1st spell box
  194. static const Point offset(54, 0); // offset of each spell box from previous
  195. OBJECT_CONSTRUCTION;
  196. const CStack * battleStack = parent->info->stack;
  197. assert(battleStack); // Section should be created only for battles
  198. //spell effects
  199. int printed=0; //how many effect pics have been printed
  200. std::vector<SpellID> spells = battleStack->activeSpells();
  201. for(SpellID effect : spells)
  202. {
  203. const spells::Spell * spell = LIBRARY->spells()->getById(effect);
  204. //not all effects have graphics (for eg. Acid Breath)
  205. //for modded spells iconEffect is added to SpellInt.def
  206. const bool hasGraphics = (effect < SpellID::THUNDERBOLT) || (effect >= SpellID::AFTER_LAST);
  207. if (hasGraphics)
  208. {
  209. auto spellBonuses = battleStack->getBonuses(Selector::source(BonusSource::SPELL_EFFECT, BonusSourceID(effect)));
  210. if (spellBonuses->empty())
  211. throw std::runtime_error("Failed to find effects for spell " + effect.toSpell()->getJsonKey());
  212. int duration = spellBonuses->front()->turnsRemain;
  213. std::string preferredLanguage = LIBRARY->generaltexth->getPreferredLanguage();
  214. MetaString spellText;
  215. spellText.appendTextID(spell->getDescriptionTextID(0)); // TODO: select correct mastery level?
  216. spellText.appendRawString("\n");
  217. spellText.appendTextID(Languages::getPluralFormTextID( preferredLanguage, duration, "vcmi.battleResultsWindow.spellDurationRemaining"));
  218. spellText.replaceNumber(duration);
  219. std::string spellDescription = spellText.toString();
  220. spellIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SpellInt"), effect + 1, 0, firstPos.x + offset.x * printed, firstPos.y + offset.y * printed));
  221. labels.push_back(std::make_shared<CLabel>(firstPos.x + offset.x * printed + 46, firstPos.y + offset.y * printed + 36, EFonts::FONT_TINY, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, std::to_string(duration)));
  222. clickableAreas.push_back(std::make_shared<LRClickableAreaWText>(Rect(firstPos + offset * printed, Point(50, 38)), spellDescription, spellDescription));
  223. if(++printed >= 8) // interface limit reached
  224. break;
  225. }
  226. }
  227. }
  228. CStackWindow::BonusLineSection::BonusLineSection(CStackWindow * owner, size_t lineIndex)
  229. : CWindowSection(owner, ImagePath::builtin("stackWindow/bonus-effects"), 0)
  230. {
  231. OBJECT_CONSTRUCTION;
  232. static const std::array<Point, 2> offset =
  233. {
  234. Point(6, 2),
  235. Point(214, 2)
  236. };
  237. auto drawBonusSource = [this](int leftRight, Point p, BonusInfo & bi)
  238. {
  239. std::map<BonusSource, ColorRGBA> bonusColors = {
  240. {BonusSource::ARTIFACT, Colors::GREEN},
  241. {BonusSource::ARTIFACT_INSTANCE, Colors::GREEN},
  242. {BonusSource::CREATURE_ABILITY, Colors::YELLOW},
  243. {BonusSource::SPELL_EFFECT, Colors::ORANGE},
  244. {BonusSource::SECONDARY_SKILL, Colors::PURPLE},
  245. {BonusSource::HERO_SPECIAL, Colors::PURPLE},
  246. {BonusSource::STACK_EXPERIENCE, Colors::CYAN},
  247. {BonusSource::COMMANDER, Colors::CYAN},
  248. };
  249. std::map<BonusSource, std::string> bonusNames = {
  250. {BonusSource::ARTIFACT, LIBRARY->generaltexth->translate("vcmi.bonusSource.artifact")},
  251. {BonusSource::ARTIFACT_INSTANCE, LIBRARY->generaltexth->translate("vcmi.bonusSource.artifact")},
  252. {BonusSource::CREATURE_ABILITY, LIBRARY->generaltexth->translate("vcmi.bonusSource.creature")},
  253. {BonusSource::SPELL_EFFECT, LIBRARY->generaltexth->translate("vcmi.bonusSource.spell")},
  254. {BonusSource::SECONDARY_SKILL, LIBRARY->generaltexth->translate("vcmi.bonusSource.hero")},
  255. {BonusSource::HERO_SPECIAL, LIBRARY->generaltexth->translate("vcmi.bonusSource.hero")},
  256. {BonusSource::STACK_EXPERIENCE, LIBRARY->generaltexth->translate("vcmi.bonusSource.commander")},
  257. {BonusSource::COMMANDER, LIBRARY->generaltexth->translate("vcmi.bonusSource.commander")},
  258. };
  259. auto c = bonusColors.count(bi.bonusSource) ? bonusColors[bi.bonusSource] : ColorRGBA(192, 192, 192);
  260. std::string t = bonusNames.count(bi.bonusSource) ? bonusNames[bi.bonusSource] : LIBRARY->generaltexth->translate("vcmi.bonusSource.other");
  261. int maxLen = 50;
  262. EFonts f = FONT_TINY;
  263. Point pText = p + Point(4, 38);
  264. // 1px Black border
  265. bonusSource[leftRight].push_back(std::make_shared<CLabel>(pText.x - 1, pText.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen));
  266. bonusSource[leftRight].push_back(std::make_shared<CLabel>(pText.x + 1, pText.y, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen));
  267. bonusSource[leftRight].push_back(std::make_shared<CLabel>(pText.x, pText.y - 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen));
  268. bonusSource[leftRight].push_back(std::make_shared<CLabel>(pText.x, pText.y + 1, f, ETextAlignment::TOPLEFT, Colors::BLACK, t, maxLen));
  269. bonusSource[leftRight].push_back(std::make_shared<CLabel>(pText.x, pText.y, f, ETextAlignment::TOPLEFT, c, t, maxLen));
  270. frame[leftRight] = std::make_shared<GraphicalPrimitiveCanvas>(Rect(p.x, p.y, 52, 52));
  271. frame[leftRight]->addRectangle(Point(0, 0), Point(52, 52), c);
  272. };
  273. for(size_t leftRight : {0, 1})
  274. {
  275. auto position = offset[leftRight];
  276. size_t bonusIndex = lineIndex * 2 + leftRight;
  277. if(parent->activeBonuses.size() > bonusIndex)
  278. {
  279. BonusInfo & bi = parent->activeBonuses[bonusIndex];
  280. if (!bi.imagePath.empty())
  281. icon[leftRight] = std::make_shared<CPicture>(bi.imagePath, position.x, position.y);
  282. description[leftRight] = std::make_shared<CMultiLineLabel>(Rect(position.x + 60, position.y, 137, 50), FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, bi.description);
  283. drawBonusSource(leftRight, Point(position.x - 1, position.y - 1), bi);
  284. }
  285. }
  286. }
  287. CStackWindow::BonusesSection::BonusesSection(CStackWindow * owner, int yOffset, std::optional<size_t> preferredSize):
  288. CWindowSection(owner, {}, yOffset)
  289. {
  290. OBJECT_CONSTRUCTION;
  291. // size of single image for an item
  292. static const int itemHeight = 59;
  293. size_t totalSize = (owner->activeBonuses.size() + 1) / 2;
  294. size_t visibleSize = preferredSize.value_or(std::min<size_t>(3, totalSize));
  295. pos.w = owner->pos.w;
  296. pos.h = itemHeight * (int)visibleSize;
  297. auto onCreate = [=](size_t index) -> std::shared_ptr<CIntObject>
  298. {
  299. return std::make_shared<BonusLineSection>(owner, index);
  300. };
  301. lines = std::make_shared<CListBox>(onCreate, Point(0, 0), Point(0, itemHeight), visibleSize, totalSize, 0, totalSize > 3 ? 1 : 0, Rect(pos.w - 15, 0, pos.h, pos.h));
  302. }
  303. CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
  304. : CWindowSection(owner, ImagePath::builtin("stackWindow/button-panel"), yOffset)
  305. {
  306. OBJECT_CONSTRUCTION;
  307. if(parent->info->dismissInfo && parent->info->dismissInfo->callback)
  308. {
  309. auto onDismiss = [this]()
  310. {
  311. parent->info->dismissInfo->callback();
  312. parent->close();
  313. };
  314. auto onClick = [=] ()
  315. {
  316. GAME->interface()->showYesNoDialog(LIBRARY->generaltexth->allTexts[12], onDismiss, nullptr);
  317. };
  318. dismiss = std::make_shared<CButton>(Point(5, 5),AnimationPath::builtin("IVIEWCR2.DEF"), LIBRARY->generaltexth->zelp[445], onClick, EShortcut::HERO_DISMISS);
  319. }
  320. if(parent->info->upgradeInfo && !parent->info->commander)
  321. {
  322. // used space overlaps with commander switch button
  323. // besides - should commander really be upgradeable?
  324. auto & upgradeInfo = parent->info->upgradeInfo.value();
  325. const size_t buttonsToCreate = std::min<size_t>(upgradeInfo.info.size(), upgrade.size());
  326. for(size_t buttonIndex = 0; buttonIndex < buttonsToCreate; buttonIndex++)
  327. {
  328. TResources totalCost = upgradeInfo.info.getAvailableUpgradeCosts().at(buttonIndex) * parent->info->creatureCount;
  329. auto onUpgrade = [this, upgradeInfo, buttonIndex]()
  330. {
  331. upgradeInfo.callback(upgradeInfo.info.getAvailableUpgrades().at(buttonIndex));
  332. parent->close();
  333. };
  334. auto onClick = [totalCost, onUpgrade]()
  335. {
  336. std::vector<std::shared_ptr<CComponent>> resComps;
  337. for(TResources::nziterator i(totalCost); i.valid(); i++)
  338. {
  339. resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal));
  340. }
  341. if(GAME->interface()->cb->getResourceAmount().canAfford(totalCost))
  342. {
  343. GAME->interface()->showYesNoDialog(LIBRARY->generaltexth->allTexts[207], onUpgrade, nullptr, resComps);
  344. }
  345. else
  346. {
  347. GAME->interface()->showInfoDialog(LIBRARY->generaltexth->allTexts[314], resComps);
  348. }
  349. };
  350. auto upgradeBtn = std::make_shared<CButton>(Point(221 + (int)buttonIndex * 40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), LIBRARY->generaltexth->zelp[446], onClick);
  351. upgradeBtn->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), LIBRARY->creh->objects[upgradeInfo.info.getAvailableUpgrades()[buttonIndex]]->getIconIndex()));
  352. if(buttonsToCreate == 1) // single upgrade available
  353. upgradeBtn->assignedKey = EShortcut::RECRUITMENT_UPGRADE;
  354. upgrade[buttonIndex] = upgradeBtn;
  355. }
  356. }
  357. if(parent->info->commander)
  358. {
  359. for(size_t buttonIndex = 0; buttonIndex < 2; buttonIndex++)
  360. {
  361. std::string btnIDs[2] = { "showSkills", "showBonuses" };
  362. auto onSwitch = [buttonIndex, this]()
  363. {
  364. logAnim->debug("Switch %d->%d", parent->activeTab, buttonIndex);
  365. parent->switchButtons[parent->activeTab]->enable();
  366. parent->commanderTab->setActive(buttonIndex);
  367. parent->switchButtons[buttonIndex]->disable();
  368. parent->redraw(); // FIXME: enable/disable don't redraw screen themselves
  369. };
  370. std::string tooltipText = "vcmi.creatureWindow." + btnIDs[buttonIndex];
  371. parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(302 + (int)buttonIndex*40, 5), AnimationPath::builtin("stackWindow/upgradeButton"), CButton::tooltipLocalized(tooltipText), onSwitch);
  372. parent->switchButtons[buttonIndex]->setOverlay(std::make_shared<CAnimImage>(AnimationPath::builtin("stackWindow/switchModeIcons"), buttonIndex));
  373. }
  374. parent->switchButtons[parent->activeTab]->disable();
  375. }
  376. exit = std::make_shared<CButton>(Point(382, 5), AnimationPath::builtin("hsbtns.def"), LIBRARY->generaltexth->zelp[447], [this](){ parent->close(); }, EShortcut::GLOBAL_RETURN);
  377. }
  378. CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, int yOffset)
  379. : CWindowSection(owner, ImagePath::builtin("stackWindow/commander-bg"), yOffset)
  380. {
  381. OBJECT_CONSTRUCTION;
  382. auto getSkillPos = [](int index)
  383. {
  384. return Point(10 + 80 * (index%3), 20 + 80 * (index/3));
  385. };
  386. auto getSkillImage = [this](int skillIndex)
  387. {
  388. bool selected = ((parent->selectedSkill == skillIndex) && parent->info->levelupInfo );
  389. return skillToFile(skillIndex, parent->info->commander->secondarySkills[skillIndex], selected);
  390. };
  391. auto getSkillDescription = [this](int skillIndex) -> std::string
  392. {
  393. return parent->getCommanderSkillDescription(skillIndex, parent->info->commander->secondarySkills[skillIndex]);
  394. };
  395. for(int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index)
  396. {
  397. Point skillPos = getSkillPos(index);
  398. auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(getSkillImage(index), skillPos.x, skillPos.y), false, [=]()
  399. {
  400. GAME->interface()->showInfoDialog(getSkillDescription(index));
  401. });
  402. icon->text = getSkillDescription(index); //used to handle right click description via LRClickableAreaWText::ClickRight()
  403. if(parent->selectedSkill == index)
  404. parent->selectedIcon = icon;
  405. if(parent->info->levelupInfo && vstd::contains(parent->info->levelupInfo->skills, index)) // can be upgraded - enable selection switch
  406. {
  407. if(parent->selectedSkill == index)
  408. parent->setSelection(index, icon);
  409. icon->callback = [this, index, icon]()
  410. {
  411. parent->setSelection(index, icon);
  412. };
  413. }
  414. skillIcons.push_back(icon);
  415. }
  416. auto getArtifactPos = [](int index)
  417. {
  418. return Point(269 + 52 * (index % 3), 22 + 52 * (index / 3));
  419. };
  420. for(auto equippedArtifact : parent->info->commander->artifactsWorn)
  421. {
  422. Point artPos = getArtifactPos(equippedArtifact.first);
  423. const auto commanderArt = equippedArtifact.second.getArt();
  424. assert(commanderArt);
  425. auto artPlace = std::make_shared<CCommanderArtPlace>(artPos, parent->info->owner, equippedArtifact.first, commanderArt->getTypeId());
  426. artifacts.push_back(artPlace);
  427. }
  428. if(parent->info->levelupInfo)
  429. {
  430. abilitiesBackground = std::make_shared<CPicture>(ImagePath::builtin("stackWindow/commander-abilities.png"));
  431. abilitiesBackground->moveBy(Point(0, pos.h));
  432. size_t abilitiesCount = boost::range::count_if(parent->info->levelupInfo->skills, [](ui32 skillID)
  433. {
  434. return skillID >= 100;
  435. });
  436. auto onCreate = [this](size_t index)->std::shared_ptr<CIntObject>
  437. {
  438. for(auto skillID : parent->info->levelupInfo->skills)
  439. {
  440. if(index == 0 && skillID >= 100)
  441. {
  442. const auto bonuses = LIBRARY->creh->skillRequirements[skillID-100].first;
  443. const CStackInstance * stack = parent->info->commander;
  444. auto icon = std::make_shared<CCommanderSkillIcon>(std::make_shared<CPicture>(stack->bonusToGraphics(bonuses[0])), true, [](){});
  445. icon->callback = [this, skillID, icon]()
  446. {
  447. parent->setSelection(skillID, icon);
  448. };
  449. for(int i = 0; i < bonuses.size(); i++)
  450. {
  451. icon->hoverText += stack->bonusToString(bonuses[i]);
  452. }
  453. return icon;
  454. }
  455. if(skillID >= 100)
  456. index--;
  457. }
  458. return nullptr;
  459. };
  460. abilities = std::make_shared<CListBox>(onCreate, Point(38, 3+pos.h), Point(63, 0), 6, abilitiesCount);
  461. abilities->setRedrawParent(true);
  462. leftBtn = std::make_shared<CButton>(Point(10, pos.h + 6), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [this](){ abilities->moveToPrev(); }, EShortcut::MOVE_LEFT);
  463. rightBtn = std::make_shared<CButton>(Point(411, pos.h + 6), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [this](){ abilities->moveToNext(); }, EShortcut::MOVE_RIGHT);
  464. if(abilitiesCount <= 6)
  465. {
  466. leftBtn->block(true);
  467. rightBtn->block(true);
  468. }
  469. pos.h += abilitiesBackground->pos.h;
  470. }
  471. }
  472. CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool showExp, bool showArt)
  473. : CWindowSection(owner, getBackgroundName(showExp, showArt), yOffset)
  474. {
  475. OBJECT_CONSTRUCTION;
  476. statNames =
  477. {
  478. LIBRARY->generaltexth->primarySkillNames[0], //ATTACK
  479. LIBRARY->generaltexth->primarySkillNames[1],//DEFENCE
  480. LIBRARY->generaltexth->allTexts[198],//SHOTS
  481. LIBRARY->generaltexth->allTexts[199],//DAMAGE
  482. LIBRARY->generaltexth->allTexts[388],//HEALTH
  483. LIBRARY->generaltexth->allTexts[200],//HEALTH_LEFT
  484. LIBRARY->generaltexth->zelp[441].first,//SPEED
  485. LIBRARY->generaltexth->allTexts[399]//MANA
  486. };
  487. statFormats =
  488. {
  489. "%d (%d)",
  490. "%d (%d)",
  491. "%d (%d)",
  492. "%d - %d",
  493. "%d (%d)",
  494. "%d (%d)",
  495. "%d (%d)",
  496. "%d (%d)"
  497. };
  498. animation = std::make_shared<CCreaturePic>(5, 41, parent->info->creature);
  499. animationArea = std::make_shared<LRClickableArea>(Rect(5, 41, 100, 130), nullptr, [&]{
  500. if(!parent->info->creature->getDescriptionTranslated().empty())
  501. CRClickPopup::createAndPush(parent->info->creature->getDescriptionTranslated());
  502. });
  503. if(parent->info->stackNode != nullptr && parent->info->commander == nullptr)
  504. {
  505. //normal stack, not a commander and not non-existing stack (e.g. recruitment dialog)
  506. animation->setAmount(parent->info->creatureCount);
  507. }
  508. name = std::make_shared<CLabel>(215, 13, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, parent->info->getName());
  509. const CStack* battleStack = parent->info->stack;
  510. int dmgMultiply = 1;
  511. if (battleStack != nullptr && battleStack->hasBonusOfType(BonusType::SIEGE_WEAPON))
  512. {
  513. static const auto bonusSelector =
  514. Selector::sourceTypeSel(BonusSource::ARTIFACT).Or(
  515. Selector::sourceTypeSel(BonusSource::HERO_BASE_SKILL)).And(
  516. Selector::typeSubtype(BonusType::PRIMARY_SKILL, BonusSubtypeID(PrimarySkill::ATTACK)));
  517. dmgMultiply += battleStack->valOfBonuses(bonusSelector);
  518. }
  519. icons = std::make_shared<CPicture>(ImagePath::builtin("stackWindow/icons"), 117, 32);
  520. morale = std::make_shared<MoraleLuckBox>(true, Rect(Point(321, 32), Point(42, 42) ));
  521. luck = std::make_shared<MoraleLuckBox>(false, Rect(Point(375, 32), Point(42, 42) ));
  522. if(battleStack != nullptr) // in battle
  523. {
  524. addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(battleStack->isShooter()), battleStack->getAttack(battleStack->isShooter()));
  525. addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(battleStack->isShooter()), battleStack->getDefense(battleStack->isShooter()));
  526. addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(battleStack->isShooter()) * dmgMultiply, battleStack->getMaxDamage(battleStack->isShooter()) * dmgMultiply);
  527. addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), battleStack->getMaxHealth());
  528. addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), battleStack->getMovementRange());
  529. if(battleStack->isShooter())
  530. addStatLabel(EStat::SHOTS, battleStack->shots.total(), battleStack->shots.available());
  531. if(battleStack->isCaster())
  532. addStatLabel(EStat::MANA, battleStack->casts.total(), battleStack->casts.available());
  533. addStatLabel(EStat::HEALTH_LEFT, battleStack->getFirstHPleft());
  534. morale->set(battleStack);
  535. luck->set(battleStack);
  536. }
  537. else
  538. {
  539. const bool shooter = parent->info->stackNode->hasBonusOfType(BonusType::SHOOTER) && parent->info->stackNode->valOfBonuses(BonusType::SHOTS);
  540. const bool caster = parent->info->stackNode->valOfBonuses(BonusType::CASTS);
  541. addStatLabel(EStat::ATTACK, parent->info->creature->getAttack(shooter), parent->info->stackNode->getAttack(shooter));
  542. addStatLabel(EStat::DEFENCE, parent->info->creature->getDefense(shooter), parent->info->stackNode->getDefense(shooter));
  543. addStatLabel(EStat::DAMAGE, parent->info->stackNode->getMinDamage(shooter), parent->info->stackNode->getMaxDamage(shooter));
  544. addStatLabel(EStat::HEALTH, parent->info->creature->getMaxHealth(), parent->info->stackNode->getMaxHealth());
  545. addStatLabel(EStat::SPEED, parent->info->creature->getMovementRange(), parent->info->stackNode->getMovementRange());
  546. if(shooter)
  547. addStatLabel(EStat::SHOTS, parent->info->stackNode->valOfBonuses(BonusType::SHOTS));
  548. if(caster)
  549. addStatLabel(EStat::MANA, parent->info->stackNode->valOfBonuses(BonusType::CASTS));
  550. morale->set(parent->info->stackNode);
  551. luck->set(parent->info->stackNode);
  552. }
  553. if(showExp)
  554. {
  555. const CStackInstance * stack = parent->info->stackNode;
  556. Point pos = showArt ? Point(321, 111) : Point(349, 111);
  557. if(parent->info->commander)
  558. {
  559. const CCommanderInstance * commander = parent->info->commander;
  560. expRankIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 4, 0, pos.x, pos.y);
  561. auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(pos.x, pos.y, 44, 44), ComponentType::EXPERIENCE);
  562. expArea = area;
  563. area->text = LIBRARY->generaltexth->allTexts[2];
  564. area->component.value = commander->getExpRank();
  565. boost::replace_first(area->text, "%d", std::to_string(commander->getExpRank()));
  566. boost::replace_first(area->text, "%d", std::to_string(LIBRARY->heroh->reqExp(commander->getExpRank() + 1)));
  567. boost::replace_first(area->text, "%d", std::to_string(commander->getAverageExperience()));
  568. }
  569. else
  570. {
  571. expRankIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("stackWindow/levels"), stack->getExpRank(), 0, pos.x, pos.y - 2);
  572. expArea = std::make_shared<LRClickableAreaWText>(Rect(pos.x, pos.y, 44, 44));
  573. expArea->text = parent->generateStackExpDescription();
  574. }
  575. expLabel = std::make_shared<CLabel>(
  576. pos.x + 21, pos.y + 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
  577. TextOperations::formatMetric(stack->getAverageExperience(), 6));
  578. }
  579. if(showArt)
  580. {
  581. Point pos = showExp ? Point(373, 109) : Point(347, 109);
  582. // ALARMA: do not refactor this into a separate function
  583. // otherwise, artifact icon is drawn near the hero's portrait
  584. // this is really strange
  585. auto art = parent->info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT);
  586. if(art)
  587. {
  588. parent->stackArtifact = std::make_shared<CArtPlace>(pos, art->getTypeId());
  589. parent->stackArtifact->setShowPopupCallback([](CComponentHolder & artPlace, const Point & cursorPosition)
  590. {
  591. artPlace.LRClickableAreaWTextComp::showPopupWindow(cursorPosition);
  592. });
  593. if(parent->info->owner)
  594. {
  595. parent->stackArtifactButton = std::make_shared<CButton>(
  596. Point(pos.x , pos.y + 47), AnimationPath::builtin("stackWindow/cancelButton"),
  597. CButton::tooltipLocalized("vcmi.creatureWindow.returnArtifact"), [this]()
  598. {
  599. parent->removeStackArtifact(ArtifactPosition::CREATURE_SLOT);
  600. });
  601. }
  602. }
  603. }
  604. }
  605. ImagePath CStackWindow::MainSection::getBackgroundName(bool showExp, bool showArt)
  606. {
  607. if(showExp && showArt)
  608. return ImagePath::builtin("stackWindow/info-panel-2");
  609. else if(showExp || showArt)
  610. return ImagePath::builtin("stackWindow/info-panel-1");
  611. else
  612. return ImagePath::builtin("stackWindow/info-panel-0");
  613. }
  614. void CStackWindow::MainSection::addStatLabel(EStat index, int64_t value1, int64_t value2)
  615. {
  616. const auto title = statNames.at(static_cast<size_t>(index));
  617. stats.push_back(std::make_shared<CLabel>(145, 32 + (int)index*19, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, title));
  618. const bool useRange = value1 != value2;
  619. std::string formatStr = useRange ? statFormats.at(static_cast<size_t>(index)) : "%d";
  620. boost::format fmt(formatStr);
  621. fmt % value1;
  622. if(useRange)
  623. fmt % value2;
  624. stats.push_back(std::make_shared<CLabel>(307, 48 + (int)index*19, FONT_SMALL, ETextAlignment::BOTTOMRIGHT, Colors::WHITE, fmt.str()));
  625. }
  626. void CStackWindow::MainSection::addStatLabel(EStat index, int64_t value)
  627. {
  628. addStatLabel(index, value, value);
  629. }
  630. CStackWindow::CStackWindow(const CStack * stack, bool popup)
  631. : CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
  632. info(std::make_unique<UnitView>())
  633. {
  634. info->stack = stack;
  635. info->stackNode = stack->base;
  636. info->commander = dynamic_cast<const CCommanderInstance*>(stack->base);
  637. info->creature = stack->unitType();
  638. info->creatureCount = stack->getCount();
  639. info->popupWindow = popup;
  640. init();
  641. }
  642. CStackWindow::CStackWindow(const CCreature * creature, bool popup)
  643. : CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
  644. info(std::make_unique<UnitView>())
  645. {
  646. info->creature = creature;
  647. info->popupWindow = popup;
  648. init();
  649. }
  650. CStackWindow::CStackWindow(const CStackInstance * stack, bool popup)
  651. : CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
  652. info(std::make_unique<UnitView>())
  653. {
  654. info->stackNode = stack;
  655. info->creature = stack->getCreature();
  656. info->creatureCount = stack->getCount();
  657. info->popupWindow = popup;
  658. info->owner = dynamic_cast<const CGHeroInstance *> (stack->getArmy());
  659. init();
  660. }
  661. CStackWindow::CStackWindow(const CStackInstance * stack, std::function<void()> dismiss, const UpgradeInfo & upgradeInfo, std::function<void(CreatureID)> callback)
  662. : CWindowObject(BORDERED),
  663. info(std::make_unique<UnitView>())
  664. {
  665. info->stackNode = stack;
  666. info->creature = stack->getCreature();
  667. info->creatureCount = stack->getCount();
  668. if(upgradeInfo.canUpgrade())
  669. {
  670. info->upgradeInfo = std::make_optional(UnitView::StackUpgradeInfo(upgradeInfo));
  671. info->upgradeInfo->callback = callback;
  672. }
  673. info->dismissInfo = std::make_optional(UnitView::StackDismissInfo());
  674. info->dismissInfo->callback = dismiss;
  675. info->owner = dynamic_cast<const CGHeroInstance *> (stack->getArmy());
  676. init();
  677. }
  678. CStackWindow::CStackWindow(const CCommanderInstance * commander, bool popup)
  679. : CWindowObject(BORDERED | (popup ? RCLICK_POPUP : 0)),
  680. info(std::make_unique<UnitView>())
  681. {
  682. info->stackNode = commander;
  683. info->creature = commander->getCreature();
  684. info->commander = commander;
  685. info->creatureCount = 1;
  686. info->popupWindow = popup;
  687. info->owner = dynamic_cast<const CGHeroInstance *> (commander->getArmy());
  688. init();
  689. }
  690. CStackWindow::CStackWindow(const CCommanderInstance * commander, std::vector<ui32> &skills, std::function<void(ui32)> callback)
  691. : CWindowObject(BORDERED),
  692. info(std::make_unique<UnitView>())
  693. {
  694. info->stackNode = commander;
  695. info->creature = commander->getCreature();
  696. info->commander = commander;
  697. info->creatureCount = 1;
  698. info->levelupInfo = std::make_optional(UnitView::CommanderLevelInfo());
  699. info->levelupInfo->skills = skills;
  700. info->levelupInfo->callback = callback;
  701. info->owner = dynamic_cast<const CGHeroInstance *> (commander->getArmy());
  702. init();
  703. }
  704. CStackWindow::~CStackWindow()
  705. {
  706. if(info->levelupInfo && !info->levelupInfo->skills.empty())
  707. info->levelupInfo->callback(vstd::find_pos(info->levelupInfo->skills, selectedSkill));
  708. }
  709. void CStackWindow::init()
  710. {
  711. OBJECT_CONSTRUCTION;
  712. background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
  713. if(!info->stackNode)
  714. {
  715. fakeNode = std::make_unique<CStackInstance>(nullptr, info->creature->getId(), 1, true);
  716. info->stackNode = fakeNode.get();
  717. }
  718. selectedIcon = nullptr;
  719. selectedSkill = -1;
  720. if(info->levelupInfo && !info->levelupInfo->skills.empty())
  721. selectedSkill = info->levelupInfo->skills.front();
  722. activeTab = 0;
  723. initBonusesList();
  724. initSections();
  725. background->pos = pos;
  726. }
  727. void CStackWindow::initBonusesList()
  728. {
  729. BonusList receivedBonuses = *info->stackNode->getBonuses(CSelector(Bonus::Permanent));
  730. BonusList abilities = info->creature->getExportedBonusList();
  731. // remove all bonuses that are not propagated away
  732. // such bonuses should be present in received bonuses, and if not - this means that they are behind inactive limiter, such as inactive stack experience bonuses
  733. abilities.remove_if([](const Bonus* b){ return b->propagator == nullptr;});
  734. const auto & bonusSortingPredicate = [this](const std::shared_ptr<Bonus> & v1, const std::shared_ptr<Bonus> & v2){
  735. if (v1->source != v2->source)
  736. {
  737. int priorityV1 = v1->source == BonusSource::CREATURE_ABILITY ? -1 : static_cast<int>(v1->source);
  738. int priorityV2 = v2->source == BonusSource::CREATURE_ABILITY ? -1 : static_cast<int>(v2->source);
  739. return priorityV1 < priorityV2;
  740. }
  741. else
  742. return info->stackNode->bonusToString(v1) < info->stackNode->bonusToString(v2);
  743. };
  744. receivedBonuses.remove_if([](const Bonus* b)
  745. {
  746. return !LIBRARY->bth->shouldPropagateDescription(b->type);
  747. });
  748. std::vector<BonusList> groupedBonuses;
  749. while (!receivedBonuses.empty())
  750. {
  751. auto currentBonus = receivedBonuses.front();
  752. const auto & sameBonusPredicate = [currentBonus](const std::shared_ptr<Bonus> & b)
  753. {
  754. return currentBonus->type == b->type && currentBonus->subtype == b->subtype;
  755. };
  756. groupedBonuses.emplace_back();
  757. std::copy_if(receivedBonuses.begin(), receivedBonuses.end(), std::back_inserter(groupedBonuses.back()), sameBonusPredicate);
  758. receivedBonuses.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype));
  759. // FIXME: potential edge case: unit has ability that is propagated away (and needs to be displayed), but also receives same bonus from someplace else
  760. abilities.remove_if(Selector::typeSubtype(currentBonus->type, currentBonus->subtype));
  761. }
  762. // Add any remaining abilities of this unit that don't affect it at the moment, such as abilities that are propagated away, e.g. to other side in combat
  763. BonusList visibleBonuses = abilities;
  764. for (auto & group : groupedBonuses)
  765. {
  766. // Try to find the bonus in the group that represents the final effect in the best way.
  767. std::sort(group.begin(), group.end(), bonusSortingPredicate);
  768. BonusList groupIndepMin = group;
  769. BonusList groupIndepMax = group;
  770. BonusList groupNoMinMax = group;
  771. BonusList groupBaseOnly = group;
  772. groupIndepMin.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MIN; });
  773. groupIndepMax.remove_if([](const Bonus * b) { return b->valType != BonusValueType::INDEPENDENT_MAX; });
  774. groupNoMinMax.remove_if([](const Bonus * b) { return b->valType == BonusValueType::INDEPENDENT_MAX || b->valType == BonusValueType::INDEPENDENT_MIN; });
  775. groupBaseOnly.remove_if([](const Bonus * b) { return b->valType != BonusValueType::ADDITIVE_VALUE || b->valType == BonusValueType::BASE_NUMBER; });
  776. int valIndepMin = groupIndepMin.totalValue();
  777. int valIndepMax = groupIndepMax.totalValue();
  778. int valNoMinMax = groupNoMinMax.totalValue();
  779. BonusList usedGroup;
  780. if (!groupIndepMin.empty() && valNoMinMax != valIndepMin)
  781. usedGroup = groupIndepMin; // bonus value was limited due to INDEPENDENT_MIN bonus -> show this bonus
  782. else if (!groupIndepMax.empty() && valNoMinMax != valIndepMax)
  783. usedGroup = groupIndepMax; // bonus value was limited due to INDEPENDENT_MAX bonus -> show this bonus
  784. else if (!groupBaseOnly.empty())
  785. usedGroup = groupNoMinMax; // bonus value is not limited and has bonuses other than percent to base / percent to all - show first non-independent bonus
  786. // It is possible that empty group was selected. For example, there is only INDEPENDENT effect with value of 0, which does not actually has any effect on this unit
  787. // For example, orb of vulnerability on unit without any resistances
  788. if (!usedGroup.empty())
  789. visibleBonuses.push_back(usedGroup.front());
  790. }
  791. std::sort(visibleBonuses.begin(), visibleBonuses.end(), bonusSortingPredicate);
  792. BonusInfo bonusInfo;
  793. for(auto b : visibleBonuses)
  794. {
  795. bonusInfo.description = info->stackNode->bonusToString(b);
  796. bonusInfo.imagePath = info->stackNode->bonusToGraphics(b);
  797. bonusInfo.bonusSource = b->source;
  798. //if it's possible to give any description or image for this kind of bonus
  799. if(!bonusInfo.description.empty() && !b->hidden)
  800. activeBonuses.push_back(bonusInfo);
  801. }
  802. }
  803. void CStackWindow::initSections()
  804. {
  805. OBJECT_CONSTRUCTION;
  806. bool showArt = GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_ARTIFACT) && info->commander == nullptr && info->stackNode;
  807. bool showExp = (GAME->interface()->cb->getSettings().getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE) || info->commander != nullptr) && info->stackNode;
  808. mainSection = std::make_shared<MainSection>(this, pos.h, showExp, showArt);
  809. pos.w = mainSection->pos.w;
  810. pos.h += mainSection->pos.h;
  811. if(info->stack) // in battle
  812. {
  813. activeSpellsSection = std::make_shared<ActiveSpellsSection>(this, pos.h);
  814. pos.h += activeSpellsSection->pos.h;
  815. }
  816. if(info->commander)
  817. {
  818. auto onCreate = [this](size_t index) -> std::shared_ptr<CIntObject>
  819. {
  820. auto obj = switchTab(index);
  821. if(obj)
  822. {
  823. obj->activate();
  824. obj->recActions |= (UPDATE | SHOWALL);
  825. }
  826. return obj;
  827. };
  828. auto deactivateObj = [=](std::shared_ptr<CIntObject> obj)
  829. {
  830. obj->deactivate();
  831. obj->recActions &= ~(UPDATE | SHOWALL);
  832. };
  833. commanderMainSection = std::make_shared<CommanderMainSection>(this, 0);
  834. auto size = std::make_optional<size_t>((info->levelupInfo) ? 4 : 3);
  835. commanderBonusesSection = std::make_shared<BonusesSection>(this, 0, size);
  836. deactivateObj(commanderBonusesSection);
  837. commanderTab = std::make_shared<CTabbedInt>(onCreate, Point(0, pos.h), 0);
  838. pos.h += commanderMainSection->pos.h;
  839. }
  840. if(!info->commander && !activeBonuses.empty())
  841. {
  842. bonusesSection = std::make_shared<BonusesSection>(this, pos.h);
  843. pos.h += bonusesSection->pos.h;
  844. }
  845. if(!info->popupWindow)
  846. {
  847. buttonsSection = std::make_shared<ButtonsSection>(this, pos.h);
  848. pos.h += buttonsSection->pos.h;
  849. //FIXME: add status bar to image?
  850. }
  851. updateShadow();
  852. pos = center(pos);
  853. }
  854. std::string CStackWindow::generateStackExpDescription()
  855. {
  856. const CStackInstance * stack = info->stackNode;
  857. const CCreature * creature = info->creature;
  858. int tier = stack->getType()->getLevel();
  859. int rank = stack->getExpRank();
  860. if (!vstd::iswithin(tier, 1, 7))
  861. tier = 0;
  862. int number;
  863. std::string expText = LIBRARY->generaltexth->translate("vcmi.stackExperience.description");
  864. boost::replace_first(expText, "%s", creature->getNamePluralTranslated());
  865. boost::replace_first(expText, "%s", LIBRARY->generaltexth->translate("vcmi.stackExperience.rank", rank));
  866. boost::replace_first(expText, "%i", std::to_string(rank));
  867. boost::replace_first(expText, "%i", std::to_string(stack->getAverageExperience()));
  868. number = static_cast<int>(LIBRARY->creh->expRanks[tier][rank] - stack->getAverageExperience());
  869. boost::replace_first(expText, "%i", std::to_string(number));
  870. number = LIBRARY->creh->maxExpPerBattle[tier]; //percent
  871. boost::replace_first(expText, "%i%", std::to_string(number));
  872. number *= LIBRARY->creh->expRanks[tier].back() / 100; //actual amount
  873. boost::replace_first(expText, "%i", std::to_string(number));
  874. boost::replace_first(expText, "%i", std::to_string(stack->getCount())); //Number of Creatures in stack
  875. int expmin = std::max(LIBRARY->creh->expRanks[tier][std::max(rank-1, 0)], (ui32)1);
  876. number = stack->getTotalExperience() / expmin - stack->getCount(); //Maximum New Recruits without losing current Rank
  877. boost::replace_first(expText, "%i", std::to_string(number)); //TODO
  878. boost::replace_first(expText, "%.2f", std::to_string(1)); //TODO Experience Multiplier
  879. number = LIBRARY->creh->expAfterUpgrade;
  880. boost::replace_first(expText, "%.2f", std::to_string(number) + "%"); //Upgrade Multiplier
  881. expmin = LIBRARY->creh->expRanks[tier][9];
  882. int expmax = LIBRARY->creh->expRanks[tier][10];
  883. number = expmax - expmin;
  884. boost::replace_first(expText, "%i", std::to_string(number)); //Experience after Rank 10
  885. number = (stack->getCount() * (expmax - expmin)) / expmin;
  886. boost::replace_first(expText, "%i", std::to_string(number)); //Maximum New Recruits to remain at Rank 10 if at Maximum Experience
  887. return expText;
  888. }
  889. std::string CStackWindow::getCommanderSkillDescription(int skillIndex, int skillLevel)
  890. {
  891. constexpr std::array skillNames = {
  892. "attack",
  893. "defence",
  894. "health",
  895. "damage",
  896. "speed",
  897. "magic"
  898. };
  899. std::string textID = TextIdentifier("vcmi", "commander", "skill", skillNames.at(skillIndex), skillLevel).get();
  900. return LIBRARY->generaltexth->translate(textID);
  901. }
  902. void CStackWindow::setSelection(si32 newSkill, std::shared_ptr<CCommanderSkillIcon> newIcon)
  903. {
  904. auto getSkillDescription = [this](int skillIndex, bool selected) -> std::string
  905. {
  906. if(selected)
  907. return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex] + 1); //upgrade description
  908. else
  909. return getCommanderSkillDescription(skillIndex, info->commander->secondarySkills[skillIndex]);
  910. };
  911. auto getSkillImage = [this](int skillIndex)
  912. {
  913. bool selected = ((selectedSkill == skillIndex) && info->levelupInfo );
  914. return skillToFile(skillIndex, info->commander->secondarySkills[skillIndex], selected);
  915. };
  916. OBJECT_CONSTRUCTION;
  917. int oldSelection = selectedSkill; // update selection
  918. selectedSkill = newSkill;
  919. if(selectedIcon && oldSelection < 100) // recreate image on old selection, only for skills
  920. selectedIcon->setObject(std::make_shared<CPicture>(getSkillImage(oldSelection)));
  921. if(selectedIcon)
  922. {
  923. if(!selectedIcon->getIsMasterAbility()) //unlike WoG, in VCMI master skill descriptions are taken from bonus descriptions
  924. {
  925. selectedIcon->text = getSkillDescription(oldSelection, false); //update previously selected icon's message to existing skill level
  926. }
  927. selectedIcon->deselect();
  928. }
  929. selectedIcon = newIcon; // update new selection
  930. if(newSkill < 100)
  931. {
  932. newIcon->setObject(std::make_shared<CPicture>(getSkillImage(newSkill)));
  933. if(!newIcon->getIsMasterAbility())
  934. {
  935. newIcon->text = getSkillDescription(newSkill, true); //update currently selected icon's message to show upgrade description
  936. }
  937. }
  938. }
  939. std::shared_ptr<CIntObject> CStackWindow::switchTab(size_t index)
  940. {
  941. std::shared_ptr<CIntObject> ret;
  942. switch(index)
  943. {
  944. case 0:
  945. {
  946. activeTab = 0;
  947. ret = commanderMainSection;
  948. }
  949. break;
  950. case 1:
  951. {
  952. activeTab = 1;
  953. ret = commanderBonusesSection;
  954. }
  955. break;
  956. default:
  957. break;
  958. }
  959. return ret;
  960. }
  961. void CStackWindow::removeStackArtifact(ArtifactPosition pos)
  962. {
  963. auto art = info->stackNode->getArt(ArtifactPosition::CREATURE_SLOT);
  964. if(!art)
  965. {
  966. logGlobal->error("Attempt to remove missing artifact");
  967. return;
  968. }
  969. const auto slot = ArtifactUtils::getArtBackpackPosition(info->owner, art->getTypeId());
  970. if(slot != ArtifactPosition::PRE_FIRST)
  971. {
  972. auto artLoc = ArtifactLocation(info->owner->id, pos);
  973. artLoc.creature = info->stackNode->getArmy()->findStack(info->stackNode);
  974. GAME->interface()->cb->swapArtifacts(artLoc, ArtifactLocation(info->owner->id, slot));
  975. stackArtifactButton.reset();
  976. stackArtifact.reset();
  977. redraw();
  978. }
  979. }