1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-03-05 15:05:40 +02:00
vcmi/client/windows/CHeroWindow.cpp
Konstantin f264c541fb vcmi: skill-agnostic tactics
Tactics is split to 2 bonuses, and it is now possible to
block tactics without having tactics itself. But tactics
for two sides is not implemented, because it is huge rework
and not high priority for me now, I want to do basic
secondary skill rework first.
2023-03-16 16:46:41 +03:00

383 lines
15 KiB
C++

/*
* CHeroWindow.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CHeroWindow.h"
#include "CCreatureWindow.h"
#include "CKingdomInterface.h"
#include "GUIClasses.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/TextAlignment.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/CComponent.h"
#include "../widgets/TextControls.h"
#include "../widgets/Buttons.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../render/CAnimation.h"
#include "../../CCallback.h"
#include "../lib/CArtHandler.h"
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CSkillHandler.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/NetPacksBase.h"
TConstBonusListPtr CHeroWithMaybePickedArtifact::getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root, const std::string & cachingStr) const
{
TBonusListPtr out(new BonusList());
TConstBonusListPtr heroBonuses = hero->getAllBonuses(selector, limit, hero, cachingStr);
TConstBonusListPtr bonusesFromPickedUpArtifact;
std::shared_ptr<CArtifactsOfHero::SCommonPart> cp = cww->getCommonPart();
if(cp && cp->src.art && cp->src.valid() && cp->src.AOH && cp->src.AOH->getHero() == hero)
bonusesFromPickedUpArtifact = cp->src.art->getAllBonuses(selector, limit, hero);
else
bonusesFromPickedUpArtifact = TBonusListPtr(new BonusList());
for(const auto & b : *heroBonuses)
out->push_back(b);
for(const auto & b : *bonusesFromPickedUpArtifact)
*out -= b;
return out;
}
int64_t CHeroWithMaybePickedArtifact::getTreeVersion() const
{
return hero->getTreeVersion(); //this assumes that hero and artifact belongs to main bonus tree
}
CHeroWithMaybePickedArtifact::CHeroWithMaybePickedArtifact(CWindowWithArtifacts * Cww, const CGHeroInstance * Hero)
: hero(Hero), cww(Cww)
{
}
void CHeroSwitcher::clickLeft(tribool down, bool previousState)
{
if(!down)
{
//TODO: do not recreate window
#if 0
owner->update(hero, true);
#else
MAYBE_UNUSED(owner);
const CGHeroInstance * buf = hero;
GH.popInts(1);
GH.pushIntT<CHeroWindow>(buf);
#endif // 0
}
}
CHeroSwitcher::CHeroSwitcher(CHeroWindow * owner_, Point pos_, const CGHeroInstance * hero_)
: CIntObject(LCLICK),
owner(owner_),
hero(hero_)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
pos += pos_;
image = std::make_shared<CAnimImage>("PortraitsSmall", hero->portrait);
pos.w = image->pos.w;
pos.h = image->pos.h;
}
CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
: CStatusbarWindow(PLAYER_COLORED, "HeroScr4"),
heroWArt(this, hero)
{
auto & heroscrn = CGI->generaltexth->heroscrn;
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
curHero = hero;
banner = std::make_shared<CAnimImage>("CREST58", LOCPLINT->playerID.getNum(), 0, 606, 8);
name = std::make_shared<CLabel>(190, 38, EFonts::FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW);
title = std::make_shared<CLabel>(190, 65, EFonts::FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE);
statusbar = CGStatusBar::create(7, 559, "ADROLLVR.bmp", 660);
quitButton = std::make_shared<CButton>(Point(609, 516), "hsbtns.def", CButton::tooltip(heroscrn[17]), [=](){ close(); }, SDLK_RETURN);
quitButton->assignedKeys.insert(SDLK_ESCAPE);
dismissLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[8], Rect(370, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
dismissButton = std::make_shared<CButton>(Point(454, 429), "hsbtns2.def", CButton::tooltip(heroscrn[28]), [=](){ dismissCurrent(); }, SDLK_d);
questlogLabel = std::make_shared<CTextBox>(CGI->generaltexth->jktexts[9], Rect(510, 430, 65, 35), 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
questlogButton = std::make_shared<CButton>(Point(314, 429), "hsbtns4.def", CButton::tooltip(heroscrn[0]), [=](){ LOCPLINT->showQuestLog(); }, SDLK_q);
formations = std::make_shared<CToggleGroup>(0);
formations->addToggle(0, std::make_shared<CToggleButton>(Point(481, 483), "hsbtns6.def", std::make_pair(heroscrn[23], heroscrn[29]), 0, SDLK_t));
formations->addToggle(1, std::make_shared<CToggleButton>(Point(481, 519), "hsbtns7.def", std::make_pair(heroscrn[24], heroscrn[30]), 0, SDLK_l));
if(hero->commander)
{
commanderButton = std::make_shared<CButton>(Point(317, 18), "buttons/commander", CButton::tooltipLocalized("vcmi.heroWindow.openCommander"), [&](){ commanderWindow(); }, SDLK_c);
}
//right list of heroes
for(int i=0; i < std::min(LOCPLINT->cb->howManyHeroes(false), 8); i++)
heroList.push_back(std::make_shared<CHeroSwitcher>(this, Point(612, 87 + i * 54), LOCPLINT->cb->getHeroBySerial(i, false)));
//areas
portraitArea = std::make_shared<LRClickableAreaWText>(Rect(18, 18, 58, 64));
portraitImage = std::make_shared<CAnimImage>("PortraitsLarge", 0, 0, 19, 19);
for(int v = 0; v < GameConstants::PRIMARY_SKILLS; ++v)
{
auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(30 + 70 * v, 109, 42, 64), CComponent::primskill);
area->text = CGI->generaltexth->arraytxt[2+v];
area->type = v;
area->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % CGI->generaltexth->primarySkillNames[v]);
primSkillAreas.push_back(area);
auto value = std::make_shared<CLabel>(53 + 70 * v, 166, FONT_SMALL, ETextAlignment::CENTER);
primSkillValues.push_back(value);
}
auto primSkills = std::make_shared<CAnimation>("PSKIL42");
primSkills->preload();
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 0, 0, 32, 111));
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 1, 0, 102, 111));
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 2, 0, 172, 111));
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 3, 0, 162, 230));
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 4, 0, 20, 230));
primSkillImages.push_back(std::make_shared<CAnimImage>(primSkills, 5, 0, 242, 111));
specImage = std::make_shared<CAnimImage>("UN44", 0, 0, 18, 180);
specArea = std::make_shared<LRClickableAreaWText>(Rect(18, 180, 136, 42), CGI->generaltexth->heroscrn[27]);
specName = std::make_shared<CLabel>(69, 205);
expArea = std::make_shared<LRClickableAreaWText>(Rect(18, 228, 136, 42), CGI->generaltexth->heroscrn[9]);
morale = std::make_shared<MoraleLuckBox>(true, Rect(175, 179, 53, 45));
luck = std::make_shared<MoraleLuckBox>(false, Rect(233, 179, 53, 45));
spellPointsArea = std::make_shared<LRClickableAreaWText>(Rect(162,228, 136, 42), CGI->generaltexth->heroscrn[22]);
expValue = std::make_shared<CLabel>(68, 252);
manaValue = std::make_shared<CLabel>(211, 252);
auto secSkills = std::make_shared<CAnimation>("SECSKILL");
for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
{
Rect r = Rect(i%2 == 0 ? 18 : 162, 276 + 48 * (i/2), 136, 42);
secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, CComponent::secskill));
secSkillImages.push_back(std::make_shared<CAnimImage>(secSkills, 0, 0, r.x, r.y));
int x = (i % 2) ? 212 : 68;
int y = 280 + 48 * (i/2);
secSkillValues.push_back(std::make_shared<CLabel>(x, y, FONT_SMALL, ETextAlignment::TOPLEFT));
secSkillNames.push_back(std::make_shared<CLabel>(x, y+20, FONT_SMALL, ETextAlignment::TOPLEFT));
}
// various texts
labels.push_back(std::make_shared<CLabel>(52, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[1]));
labels.push_back(std::make_shared<CLabel>(123, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[2]));
labels.push_back(std::make_shared<CLabel>(193, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[3]));
labels.push_back(std::make_shared<CLabel>(262, 99, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->jktexts[4]));
labels.push_back(std::make_shared<CLabel>(69, 183, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[5]));
labels.push_back(std::make_shared<CLabel>(69, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[6]));
labels.push_back(std::make_shared<CLabel>(213, 232, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->jktexts[7]));
update(hero);
}
void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
{
auto & heroscrn = CGI->generaltexth->heroscrn;
if(!hero) //something strange... no hero? it shouldn't happen
{
logGlobal->error("Set nullptr hero? no way...");
return;
}
assert(hero == curHero);
name->setText(curHero->getNameTranslated());
title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->type->heroClass->getNameTranslated()).str());
specArea->text = curHero->type->getSpecialtyDescriptionTranslated();
specImage->setFrame(curHero->type->imageIndex);
specName->setText(curHero->type->getSpecialtyNameTranslated());
tacticsButton = std::make_shared<CToggleButton>(Point(539, 483), "hsbtns8.def", std::make_pair(heroscrn[26], heroscrn[31]), 0, SDLK_b);
tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);
dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated()));
portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated());
portraitArea->text = curHero->getBiographyTranslated();
portraitImage->setFrame(curHero->portrait);
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
if(!garr)
{
std::string helpBox = heroscrn[32];
boost::algorithm::replace_first(helpBox, "%s", CGI->generaltexth->allTexts[43]);
garr = std::make_shared<CGarrisonInt>(15, 485, 8, Point(), curHero);
auto split = std::make_shared<CButton>(Point(539, 519), "hsbtns9.def", CButton::tooltip(CGI->generaltexth->allTexts[256], helpBox), [&](){ garr->splitClick(); });
garr->addSplitBtn(split);
}
if(!arts)
{
arts = std::make_shared<CArtifactsOfHero>(Point(-65, -8), true);
arts->setHero(curHero);
addSet(arts);
}
int serial = LOCPLINT->cb->getHeroSerial(curHero, false);
listSelection.reset();
if(serial >= 0)
listSelection = std::make_shared<CPicture>("HPSYYY", 612, 33 + serial * 54);
}
//primary skills support
for(size_t g=0; g<primSkillAreas.size(); ++g)
{
primSkillAreas[g]->bonusValue = heroWArt.getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(g));
primSkillValues[g]->setText(std::to_string(primSkillAreas[g]->bonusValue));
}
//secondary skills support
for(size_t g=0; g< secSkillAreas.size(); ++g)
{
int skill = curHero->secSkills[g].first;
int level = curHero->getSecSkillLevel(SecondarySkill(curHero->secSkills[g].first));
std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated();
std::string skillValue = CGI->generaltexth->levels[level-1];
secSkillAreas[g]->type = skill;
secSkillAreas[g]->bonusValue = level;
secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName);
secSkillImages[g]->setFrame(skill*3 + level + 2);
secSkillNames[g]->setText(skillName);
secSkillValues[g]->setText(skillValue);
}
std::ostringstream expstr;
expstr << curHero->exp;
expValue->setText(expstr.str());
std::ostringstream manastr;
manastr << curHero->mana << '/' << heroWArt.manaLimit();
manaValue->setText(manastr.str());
//printing experience - original format does not support ui64
expArea->text = CGI->generaltexth->allTexts[2];
boost::replace_first(expArea->text, "%d", std::to_string(curHero->level));
boost::replace_first(expArea->text, "%d", std::to_string(CGI->heroh->reqExp(curHero->level+1)));
boost::replace_first(expArea->text, "%d", std::to_string(curHero->exp));
//printing spell points, boost::format can't be used due to locale issues
spellPointsArea->text = CGI->generaltexth->allTexts[205];
boost::replace_first(spellPointsArea->text, "%s", curHero->getNameTranslated());
boost::replace_first(spellPointsArea->text, "%d", std::to_string(curHero->mana));
boost::replace_first(spellPointsArea->text, "%d", std::to_string(heroWArt.manaLimit()));
//if we have exchange window with this curHero open
bool noDismiss=false;
for(auto isa : GH.listInt)
{
if(CExchangeWindow * cew = dynamic_cast<CExchangeWindow*>(isa.get()))
{
for(int g=0; g < cew->heroInst.size(); ++g)
if(cew->heroInst[g] == curHero)
noDismiss = true;
}
if(dynamic_cast<CKingdomInterface*>(isa.get()))
noDismiss = true;
}
//if player only have one hero and no towns
if(!LOCPLINT->cb->howManyTowns() && LOCPLINT->cb->howManyHeroes() == 1)
noDismiss = true;
if(curHero->isMissionCritical())
noDismiss = true;
dismissButton->block(!!curHero->visitedTown || noDismiss);
if(curHero->valOfBonuses(Selector::type()(Bonus::BEFORE_BATTLE_REPOSITION)) == 0)
{
tacticsButton->block(true);
}
else
{
tacticsButton->block(false);
tacticsButton->addCallback([&](bool on){curHero->tacticFormationEnabled = on;});
}
formations->resetCallback();
//setting formations
formations->setSelected(curHero->formation);
formations->addCallback([=](int value){ LOCPLINT->cb->setFormation(curHero, value);});
morale->set(&heroWArt);
luck->set(&heroWArt);
if(redrawNeeded)
redraw();
}
void CHeroWindow::dismissCurrent()
{
CFunctionList<void()> ony = [=](){ close(); };
ony += [=](){ LOCPLINT->cb->dismissHero(curHero); };
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[22], ony, nullptr);
}
void CHeroWindow::commanderWindow()
{
//bool artSelected = false;
std::shared_ptr<CArtifactsOfHero::SCommonPart> commonInfo = getCommonPart();
if(const CArtifactInstance *art = commonInfo->src.art)
{
const CGHeroInstance *srcHero = commonInfo->src.AOH->getHero();
//artSelected = true;
ArtifactPosition freeSlot = art->firstAvailableSlot (curHero->commander);
if(freeSlot < ArtifactPosition::COMMANDER_AFTER_LAST) //we don't want to put it in commander's backpack!
{
ArtifactLocation src(srcHero, commonInfo->src.slotID);
ArtifactLocation dst(curHero->commander.get(), freeSlot);
if(art->canBePutAt(dst, true))
{ //equip clicked stack
if(dst.getArt())
{
LOCPLINT->cb->swapArtifacts (dst, ArtifactLocation(srcHero, dst.getArt()->firstBackpackSlot(srcHero)));
}
LOCPLINT->cb->swapArtifacts(src, dst);
}
}
}
else
{
GH.pushIntT<CStackWindow>(curHero->commander, false);
}
}
void CHeroWindow::updateGarrisons()
{
garr->recreateSlots();
morale->set(&heroWArt);
}