2023-10-19 16:19:09 +02:00
/*
* CCreatureWindow . 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 "CCreatureWindow.h"
# include <vcmi/spells/Spell.h>
# include <vcmi/spells/Service.h>
# include "../CGameInfo.h"
# include "../CPlayerInterface.h"
# include "../render/Canvas.h"
# include "../widgets/Buttons.h"
# include "../widgets/CComponent.h"
2024-10-20 15:27:21 +03:00
# include "../widgets/CComponentHolder.h"
2023-10-19 16:19:09 +02:00
# include "../widgets/Images.h"
# include "../widgets/TextControls.h"
# include "../widgets/ObjectLists.h"
2024-11-01 19:02:51 +01:00
# include "../widgets/GraphicalPrimitiveCanvas.h"
2024-04-12 23:35:39 +02:00
# include "../windows/InfoWindows.h"
2023-10-19 16:19:09 +02:00
# include "../gui/CGuiHandler.h"
# include "../gui/Shortcut.h"
2024-10-29 20:44:31 +01:00
# include "../battle/BattleInterface.h"
2023-10-19 16:19:09 +02:00
# include "../../CCallback.h"
# include "../../lib/ArtifactUtils.h"
# include "../../lib/CStack.h"
# include "../../lib/CBonusTypeHandler.h"
2024-08-31 11:00:36 +00:00
# include "../../lib/IGameSettings.h"
2024-10-11 16:30:16 +00:00
# include "../../lib/entities/hero/CHeroHandler.h"
2023-10-19 16:19:09 +02:00
# include "../../lib/gameState/CGameState.h"
2024-12-19 11:29:57 +01:00
# include "../../lib/gameState/UpgradeInfo.h"
2023-10-23 16:38:05 +03:00
# include "../../lib/networkPacks/ArtifactLocation.h"
2024-07-20 12:55:17 +00:00
# include "../../lib/texts/CGeneralTextHandler.h"
# include "../../lib/texts/TextOperations.h"
2024-12-22 21:23:31 +01:00
# include "../../lib/bonuses/Propagators.h"
2023-10-19 16:19:09 +02:00
class CCreatureArtifactInstance ;
class CSelectableSkill ;
class UnitView
{
public :
// helper structs
struct CommanderLevelInfo
{
std : : vector < ui32 > skills ;
std : : function < void ( ui32 ) > callback ;
} ;
struct StackDismissInfo
{
std : : function < void ( ) > callback ;
} ;
struct StackUpgradeInfo
{
2024-11-19 10:49:14 +01:00
StackUpgradeInfo ( ) = delete ;
2024-12-16 16:05:46 +01:00
StackUpgradeInfo ( const UpgradeInfo & upgradeInfo )
: info ( upgradeInfo )
2024-11-19 10:49:14 +01:00
{ }
2023-10-19 16:19:09 +02:00
UpgradeInfo info ;
std : : function < void ( CreatureID ) > callback ;
} ;
2024-06-24 03:23:26 +02:00
// pointers to permanent objects in game state
2023-10-19 16:19:09 +02:00
const CCreature * creature ;
const CCommanderInstance * commander ;
const CStackInstance * stackNode ;
const CStack * stack ;
const CGHeroInstance * owner ;
// temporary objects which should be kept as copy if needed
std : : optional < CommanderLevelInfo > levelupInfo ;
std : : optional < StackDismissInfo > dismissInfo ;
std : : optional < StackUpgradeInfo > upgradeInfo ;
// misc fields
unsigned int creatureCount ;
bool popupWindow ;
UnitView ( )
: creature ( nullptr ) ,
commander ( nullptr ) ,
stackNode ( nullptr ) ,
stack ( nullptr ) ,
owner ( nullptr ) ,
creatureCount ( 0 ) ,
popupWindow ( false )
{
}
std : : string getName ( ) const
{
if ( commander )
2024-10-12 16:02:35 +00:00
return commander - > getType ( ) - > getNameSingularTranslated ( ) ;
2023-10-19 16:19:09 +02:00
else
return creature - > getNamePluralTranslated ( ) ;
}
private :
} ;
CCommanderSkillIcon : : CCommanderSkillIcon ( std : : shared_ptr < CIntObject > object_ , bool isMasterAbility_ , std : : function < void ( ) > callback )
: object ( ) ,
isMasterAbility ( isMasterAbility_ ) ,
isSelected ( false ) ,
callback ( callback )
{
pos = object_ - > pos ;
this - > isMasterAbility = isMasterAbility_ ;
setObject ( object_ ) ;
}
void CCommanderSkillIcon : : setObject ( std : : shared_ptr < CIntObject > newObject )
{
if ( object )
removeChild ( object . get ( ) ) ;
object = newObject ;
addChild ( object . get ( ) ) ;
object - > moveTo ( pos . topLeft ( ) ) ;
redraw ( ) ;
}
void CCommanderSkillIcon : : clickPressed ( const Point & cursorPosition )
{
callback ( ) ;
isSelected = true ;
}
void CCommanderSkillIcon : : deselect ( )
{
isSelected = false ;
}
bool CCommanderSkillIcon : : getIsMasterAbility ( )
{
return isMasterAbility ;
}
void CCommanderSkillIcon : : show ( Canvas & to )
{
CIntObject : : show ( to ) ;
if ( isMasterAbility & & isSelected )
to . drawBorder ( pos , Colors : : YELLOW , 2 ) ;
}
static ImagePath skillToFile ( int skill , int level , bool selected )
{
2024-06-24 03:23:26 +02:00
// FIXME: is this a correct handling?
2023-10-19 16:19:09 +02:00
// level 0 = skill not present, use image with "no" suffix
// level 1-5 = skill available, mapped to images indexed as 0-4
2024-06-24 03:23:26 +02:00
// selecting skill means that it will appear one level higher (as if already upgraded)
2023-10-19 16:19:09 +02:00
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 ;
}
2024-06-24 03:23:26 +02:00
std : : string suffix ;
2023-10-19 16:19:09 +02:00
if ( selected )
level + + ; // UI will display resulting level
if ( level = = 0 )
2024-06-24 03:23:26 +02:00
suffix = " no " ; //not available - no number
2023-10-19 16:19:09 +02:00
else
2024-06-24 03:23:26 +02:00
suffix = std : : to_string ( level - 1 ) ;
2023-10-19 16:19:09 +02:00
if ( selected )
2024-06-24 03:23:26 +02:00
suffix + = " = " ; //level-up highlight
2023-10-19 16:19:09 +02:00
2024-06-24 03:23:26 +02:00
return ImagePath : : builtin ( file + suffix + " .bmp " ) ;
2023-10-19 16:19:09 +02:00
}
CStackWindow : : CWindowSection : : CWindowSection ( CStackWindow * parent , const ImagePath & backgroundPath , int yOffset )
: parent ( parent )
{
pos . y + = yOffset ;
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
if ( ! backgroundPath . empty ( ) )
{
background = std : : make_shared < CPicture > ( backgroundPath ) ;
pos . w = background - > pos . w ;
pos . h = background - > pos . h ;
}
}
CStackWindow : : ActiveSpellsSection : : ActiveSpellsSection ( CStackWindow * owner , int yOffset )
: CWindowSection ( owner , ImagePath : : builtin ( " stackWindow/spell-effects " ) , yOffset )
{
static const Point firstPos ( 6 , 2 ) ; // position of 1st spell box
static const Point offset ( 54 , 0 ) ; // offset of each spell box from previous
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
const CStack * battleStack = parent - > info - > stack ;
assert ( battleStack ) ; // Section should be created only for battles
//spell effects
int printed = 0 ; //how many effect pics have been printed
2023-10-22 18:36:41 +03:00
std : : vector < SpellID > spells = battleStack - > activeSpells ( ) ;
for ( SpellID effect : spells )
2023-10-19 16:19:09 +02:00
{
2023-10-22 18:36:41 +03:00
const spells : : Spell * spell = CGI - > spells ( ) - > getById ( effect ) ;
2023-10-19 16:19:09 +02:00
std : : string spellText ;
//not all effects have graphics (for eg. Acid Breath)
//for modded spells iconEffect is added to SpellInt.def
const bool hasGraphics = ( effect < SpellID : : THUNDERBOLT ) | | ( effect > = SpellID : : AFTER_LAST ) ;
if ( hasGraphics )
{
spellText = CGI - > generaltexth - > allTexts [ 610 ] ; //"%s, duration: %d rounds."
boost : : replace_first ( spellText , " %s " , spell - > getNameTranslated ( ) ) ;
//FIXME: support permanent duration
2024-01-16 18:14:40 +02:00
int duration = battleStack - > getFirstBonus ( Selector : : source ( BonusSource : : SPELL_EFFECT , BonusSourceID ( effect ) ) ) - > turnsRemain ;
2023-10-19 16:19:09 +02:00
boost : : replace_first ( spellText , " %d " , std : : to_string ( duration ) ) ;
spellIcons . push_back ( std : : make_shared < CAnimImage > ( AnimationPath : : builtin ( " SpellInt " ) , effect + 1 , 0 , firstPos . x + offset . x * printed , firstPos . y + offset . y * printed ) ) ;
2023-12-23 16:07:12 +01:00
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 ) ) ) ;
2023-10-19 16:19:09 +02:00
clickableAreas . push_back ( std : : make_shared < LRClickableAreaWText > ( Rect ( firstPos + offset * printed , Point ( 50 , 38 ) ) , spellText , spellText ) ) ;
if ( + + printed > = 8 ) // interface limit reached
break ;
}
}
}
CStackWindow : : BonusLineSection : : BonusLineSection ( CStackWindow * owner , size_t lineIndex )
: CWindowSection ( owner , ImagePath : : builtin ( " stackWindow/bonus-effects " ) , 0 )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
static const std : : array < Point , 2 > offset =
{
Point ( 6 , 4 ) ,
Point ( 214 , 4 )
} ;
2024-11-02 14:51:33 +01:00
auto drawBonusSource = [ this ] ( int leftRight , Point p , BonusInfo & bi )
{
std : : map < BonusSource , ColorRGBA > bonusColors = {
{ BonusSource : : ARTIFACT , Colors : : GREEN } ,
{ BonusSource : : ARTIFACT_INSTANCE , Colors : : GREEN } ,
{ BonusSource : : CREATURE_ABILITY , Colors : : YELLOW } ,
{ BonusSource : : SPELL_EFFECT , Colors : : ORANGE } ,
{ BonusSource : : SECONDARY_SKILL , Colors : : PURPLE } ,
{ BonusSource : : HERO_SPECIAL , Colors : : PURPLE } ,
{ BonusSource : : STACK_EXPERIENCE , Colors : : CYAN } ,
{ BonusSource : : COMMANDER , Colors : : CYAN } ,
} ;
std : : map < BonusSource , std : : string > bonusNames = {
{ BonusSource : : ARTIFACT , CGI - > generaltexth - > translate ( " vcmi.bonusSource.artifact " ) } ,
{ BonusSource : : ARTIFACT_INSTANCE , CGI - > generaltexth - > translate ( " vcmi.bonusSource.artifact " ) } ,
{ BonusSource : : CREATURE_ABILITY , CGI - > generaltexth - > translate ( " vcmi.bonusSource.creature " ) } ,
{ BonusSource : : SPELL_EFFECT , CGI - > generaltexth - > translate ( " vcmi.bonusSource.spell " ) } ,
{ BonusSource : : SECONDARY_SKILL , CGI - > generaltexth - > translate ( " vcmi.bonusSource.hero " ) } ,
{ BonusSource : : HERO_SPECIAL , CGI - > generaltexth - > translate ( " vcmi.bonusSource.hero " ) } ,
{ BonusSource : : STACK_EXPERIENCE , CGI - > generaltexth - > translate ( " vcmi.bonusSource.commander " ) } ,
{ BonusSource : : COMMANDER , CGI - > generaltexth - > translate ( " vcmi.bonusSource.commander " ) } ,
} ;
auto c = bonusColors . count ( bi . bonusSource ) ? bonusColors [ bi . bonusSource ] : ColorRGBA ( 192 , 192 , 192 ) ;
std : : string t = bonusNames . count ( bi . bonusSource ) ? bonusNames [ bi . bonusSource ] : CGI - > generaltexth - > translate ( " vcmi.bonusSource.other " ) ;
2024-11-02 14:37:21 +01:00
int maxLen = 50 ;
EFonts f = FONT_TINY ;
2024-11-30 00:41:09 +01:00
Point pText = p + Point ( 4 , 38 ) ;
2024-11-02 14:37:21 +01:00
// 1px Black border
2024-11-02 14:51:33 +01:00
bonusSource [ leftRight ] . push_back ( std : : make_shared < CLabel > ( pText . x - 1 , pText . y , f , ETextAlignment : : TOPLEFT , Colors : : BLACK , t , maxLen ) ) ;
bonusSource [ leftRight ] . push_back ( std : : make_shared < CLabel > ( pText . x + 1 , pText . y , f , ETextAlignment : : TOPLEFT , Colors : : BLACK , t , maxLen ) ) ;
bonusSource [ leftRight ] . push_back ( std : : make_shared < CLabel > ( pText . x , pText . y - 1 , f , ETextAlignment : : TOPLEFT , Colors : : BLACK , t , maxLen ) ) ;
bonusSource [ leftRight ] . push_back ( std : : make_shared < CLabel > ( pText . x , pText . y + 1 , f , ETextAlignment : : TOPLEFT , Colors : : BLACK , t , maxLen ) ) ;
bonusSource [ leftRight ] . push_back ( std : : make_shared < CLabel > ( pText . x , pText . y , f , ETextAlignment : : TOPLEFT , c , t , maxLen ) ) ;
frame [ leftRight ] = std : : make_shared < GraphicalPrimitiveCanvas > ( Rect ( p . x , p . y , 52 , 52 ) ) ;
frame [ leftRight ] - > addRectangle ( Point ( 0 , 0 ) , Point ( 52 , 52 ) , c ) ;
2024-11-02 14:37:21 +01:00
} ;
2023-10-19 16:19:09 +02:00
for ( size_t leftRight : { 0 , 1 } )
{
auto position = offset [ leftRight ] ;
size_t bonusIndex = lineIndex * 2 + leftRight ;
if ( parent - > activeBonuses . size ( ) > bonusIndex )
{
BonusInfo & bi = parent - > activeBonuses [ bonusIndex ] ;
icon [ leftRight ] = std : : make_shared < CPicture > ( bi . imagePath , position . x , position . y ) ;
2024-11-02 14:37:21 +01:00
name [ leftRight ] = std : : make_shared < CLabel > ( position . x + 60 , position . y + 2 , FONT_TINY , ETextAlignment : : TOPLEFT , Colors : : WHITE , bi . name , 137 ) ;
2024-11-02 15:19:39 +01:00
description [ leftRight ] = std : : make_shared < CMultiLineLabel > ( Rect ( position . x + 60 , position . y + 20 , 137 , 26 ) , FONT_TINY , ETextAlignment : : TOPLEFT , Colors : : WHITE , bi . description ) ;
2024-11-02 14:51:33 +01:00
drawBonusSource ( leftRight , Point ( position . x - 1 , position . y - 1 ) , bi ) ;
2023-10-19 16:19:09 +02:00
}
}
}
CStackWindow : : BonusesSection : : BonusesSection ( CStackWindow * owner , int yOffset , std : : optional < size_t > preferredSize ) :
CWindowSection ( owner , { } , yOffset )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
// size of single image for an item
static const int itemHeight = 59 ;
size_t totalSize = ( owner - > activeBonuses . size ( ) + 1 ) / 2 ;
size_t visibleSize = preferredSize . value_or ( std : : min < size_t > ( 3 , totalSize ) ) ;
pos . w = owner - > pos . w ;
pos . h = itemHeight * ( int ) visibleSize ;
auto onCreate = [ = ] ( size_t index ) - > std : : shared_ptr < CIntObject >
{
return std : : make_shared < BonusLineSection > ( owner , index ) ;
} ;
2024-11-01 19:02:51 +01:00
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 ) ) ;
2023-10-19 16:19:09 +02:00
}
CStackWindow : : ButtonsSection : : ButtonsSection ( CStackWindow * owner , int yOffset )
: CWindowSection ( owner , ImagePath : : builtin ( " stackWindow/button-panel " ) , yOffset )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
if ( parent - > info - > dismissInfo & & parent - > info - > dismissInfo - > callback )
{
auto onDismiss = [ = ] ( )
{
parent - > info - > dismissInfo - > callback ( ) ;
parent - > close ( ) ;
} ;
auto onClick = [ = ] ( )
{
LOCPLINT - > showYesNoDialog ( CGI - > generaltexth - > allTexts [ 12 ] , onDismiss , nullptr ) ;
} ;
dismiss = std : : make_shared < CButton > ( Point ( 5 , 5 ) , AnimationPath : : builtin ( " IVIEWCR2.DEF " ) , CGI - > generaltexth - > zelp [ 445 ] , onClick , EShortcut : : HERO_DISMISS ) ;
}
if ( parent - > info - > upgradeInfo & & ! parent - > info - > commander )
{
// used space overlaps with commander switch button
// besides - should commander really be upgradeable?
auto & upgradeInfo = parent - > info - > upgradeInfo . value ( ) ;
2024-11-19 10:49:14 +01:00
const size_t buttonsToCreate = std : : min < size_t > ( upgradeInfo . info . size ( ) , upgrade . size ( ) ) ;
2023-10-19 16:19:09 +02:00
for ( size_t buttonIndex = 0 ; buttonIndex < buttonsToCreate ; buttonIndex + + )
{
2024-12-19 11:29:57 +01:00
TResources totalCost = upgradeInfo . info . getAvailableUpgradeCosts ( ) . at ( buttonIndex ) * parent - > info - > creatureCount ;
2023-10-19 16:19:09 +02:00
auto onUpgrade = [ = ] ( )
{
2024-11-19 10:49:14 +01:00
upgradeInfo . callback ( upgradeInfo . info . getAvailableUpgrades ( ) . at ( buttonIndex ) ) ;
2023-10-19 16:19:09 +02:00
parent - > close ( ) ;
} ;
auto onClick = [ = ] ( )
{
std : : vector < std : : shared_ptr < CComponent > > resComps ;
for ( TResources : : nziterator i ( totalCost ) ; i . valid ( ) ; i + + )
{
2023-10-31 11:09:56 +02:00
resComps . push_back ( std : : make_shared < CComponent > ( ComponentType : : RESOURCE , i - > resType , i - > resVal ) ) ;
2023-10-19 16:19:09 +02:00
}
if ( LOCPLINT - > cb - > getResourceAmount ( ) . canAfford ( totalCost ) )
{
LOCPLINT - > showYesNoDialog ( CGI - > generaltexth - > allTexts [ 207 ] , onUpgrade , nullptr , resComps ) ;
}
else
{
LOCPLINT - > showInfoDialog ( CGI - > generaltexth - > allTexts [ 314 ] , resComps ) ;
}
} ;
auto upgradeBtn = std : : make_shared < CButton > ( Point ( 221 + ( int ) buttonIndex * 40 , 5 ) , AnimationPath : : builtin ( " stackWindow/upgradeButton " ) , CGI - > generaltexth - > zelp [ 446 ] , onClick ) ;
2024-11-19 10:49:14 +01:00
upgradeBtn - > setOverlay ( std : : make_shared < CAnimImage > ( AnimationPath : : builtin ( " CPRSMALL " ) , VLC - > creh - > objects [ upgradeInfo . info . getAvailableUpgrades ( ) [ buttonIndex ] ] - > getIconIndex ( ) ) ) ;
2023-10-19 16:19:09 +02:00
2024-06-24 03:23:26 +02:00
if ( buttonsToCreate = = 1 ) // single upgrade available
2023-10-19 16:19:09 +02:00
upgradeBtn - > assignedKey = EShortcut : : RECRUITMENT_UPGRADE ;
upgrade [ buttonIndex ] = upgradeBtn ;
}
}
if ( parent - > info - > commander )
{
for ( size_t buttonIndex = 0 ; buttonIndex < 2 ; buttonIndex + + )
{
std : : string btnIDs [ 2 ] = { " showSkills " , " showBonuses " } ;
auto onSwitch = [ buttonIndex , this ] ( )
{
logAnim - > debug ( " Switch %d->%d " , parent - > activeTab , buttonIndex ) ;
parent - > switchButtons [ parent - > activeTab ] - > enable ( ) ;
parent - > commanderTab - > setActive ( buttonIndex ) ;
parent - > switchButtons [ buttonIndex ] - > disable ( ) ;
parent - > redraw ( ) ; // FIXME: enable/disable don't redraw screen themselves
} ;
std : : string tooltipText = " vcmi.creatureWindow. " + btnIDs [ buttonIndex ] ;
parent - > switchButtons [ buttonIndex ] = std : : make_shared < CButton > ( Point ( 302 + ( int ) buttonIndex * 40 , 5 ) , AnimationPath : : builtin ( " stackWindow/upgradeButton " ) , CButton : : tooltipLocalized ( tooltipText ) , onSwitch ) ;
2024-02-27 22:19:09 +02:00
parent - > switchButtons [ buttonIndex ] - > setOverlay ( std : : make_shared < CAnimImage > ( AnimationPath : : builtin ( " stackWindow/switchModeIcons " ) , buttonIndex ) ) ;
2023-10-19 16:19:09 +02:00
}
parent - > switchButtons [ parent - > activeTab ] - > disable ( ) ;
}
exit = std : : make_shared < CButton > ( Point ( 382 , 5 ) , AnimationPath : : builtin ( " hsbtns.def " ) , CGI - > generaltexth - > zelp [ 447 ] , [ = ] ( ) { parent - > close ( ) ; } , EShortcut : : GLOBAL_RETURN ) ;
}
CStackWindow : : CommanderMainSection : : CommanderMainSection ( CStackWindow * owner , int yOffset )
: CWindowSection ( owner , ImagePath : : builtin ( " stackWindow/commander-bg " ) , yOffset )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
auto getSkillPos = [ ] ( int index )
{
return Point ( 10 + 80 * ( index % 3 ) , 20 + 80 * ( index / 3 ) ) ;
} ;
auto getSkillImage = [ this ] ( int skillIndex )
{
bool selected = ( ( parent - > selectedSkill = = skillIndex ) & & parent - > info - > levelupInfo ) ;
return skillToFile ( skillIndex , parent - > info - > commander - > secondarySkills [ skillIndex ] , selected ) ;
} ;
auto getSkillDescription = [ this ] ( int skillIndex ) - > std : : string
{
2024-10-26 14:21:05 +00:00
return parent - > getCommanderSkillDescription ( skillIndex , parent - > info - > commander - > secondarySkills [ skillIndex ] ) ;
2023-10-19 16:19:09 +02:00
} ;
for ( int index = ECommander : : ATTACK ; index < = ECommander : : SPELL_POWER ; + + index )
{
Point skillPos = getSkillPos ( index ) ;
auto icon = std : : make_shared < CCommanderSkillIcon > ( std : : make_shared < CPicture > ( getSkillImage ( index ) , skillPos . x , skillPos . y ) , false , [ = ] ( )
{
LOCPLINT - > showInfoDialog ( getSkillDescription ( index ) ) ;
} ) ;
icon - > text = getSkillDescription ( index ) ; //used to handle right click description via LRClickableAreaWText::ClickRight()
if ( parent - > selectedSkill = = index )
parent - > selectedIcon = icon ;
if ( parent - > info - > levelupInfo & & vstd : : contains ( parent - > info - > levelupInfo - > skills , index ) ) // can be upgraded - enable selection switch
{
if ( parent - > selectedSkill = = index )
parent - > setSelection ( index , icon ) ;
icon - > callback = [ = ] ( )
{
parent - > setSelection ( index , icon ) ;
} ;
}
skillIcons . push_back ( icon ) ;
}
auto getArtifactPos = [ ] ( int index )
{
return Point ( 269 + 47 * ( index % 3 ) , 22 + 47 * ( index / 3 ) ) ;
} ;
for ( auto equippedArtifact : parent - > info - > commander - > artifactsWorn )
{
Point artPos = getArtifactPos ( equippedArtifact . first ) ;
2024-10-19 16:25:26 +03:00
const auto commanderArt = equippedArtifact . second . artifact ;
assert ( commanderArt ) ;
auto artPlace = std : : make_shared < CCommanderArtPlace > ( artPos , parent - > info - > owner , equippedArtifact . first , commanderArt - > getTypeId ( ) ) ;
2023-10-19 16:19:09 +02:00
artifacts . push_back ( artPlace ) ;
}
if ( parent - > info - > levelupInfo )
{
abilitiesBackground = std : : make_shared < CPicture > ( ImagePath : : builtin ( " stackWindow/commander-abilities.png " ) ) ;
abilitiesBackground - > moveBy ( Point ( 0 , pos . h ) ) ;
size_t abilitiesCount = boost : : range : : count_if ( parent - > info - > levelupInfo - > skills , [ ] ( ui32 skillID )
{
return skillID > = 100 ;
} ) ;
auto onCreate = [ = ] ( size_t index ) - > std : : shared_ptr < CIntObject >
{
for ( auto skillID : parent - > info - > levelupInfo - > skills )
{
if ( index = = 0 & & skillID > = 100 )
{
const auto bonus = CGI - > creh - > skillRequirements [ skillID - 100 ] . first ;
const CStackInstance * stack = parent - > info - > commander ;
auto icon = std : : make_shared < CCommanderSkillIcon > ( std : : make_shared < CPicture > ( stack - > bonusToGraphics ( bonus ) ) , true , [ ] ( ) { } ) ;
icon - > callback = [ = ] ( )
{
parent - > setSelection ( skillID , icon ) ;
} ;
icon - > text = stack - > bonusToString ( bonus , true ) ;
icon - > hoverText = stack - > bonusToString ( bonus , false ) ;
return icon ;
}
if ( skillID > = 100 )
index - - ;
}
return nullptr ;
} ;
abilities = std : : make_shared < CListBox > ( onCreate , Point ( 38 , 3 + pos . h ) , Point ( 63 , 0 ) , 6 , abilitiesCount ) ;
abilities - > setRedrawParent ( true ) ;
leftBtn = std : : make_shared < CButton > ( Point ( 10 , pos . h + 6 ) , AnimationPath : : builtin ( " hsbtns3.def " ) , CButton : : tooltip ( ) , [ = ] ( ) { abilities - > moveToPrev ( ) ; } , EShortcut : : MOVE_LEFT ) ;
rightBtn = std : : make_shared < CButton > ( Point ( 411 , pos . h + 6 ) , AnimationPath : : builtin ( " hsbtns5.def " ) , CButton : : tooltip ( ) , [ = ] ( ) { abilities - > moveToNext ( ) ; } , EShortcut : : MOVE_RIGHT ) ;
if ( abilitiesCount < = 6 )
{
leftBtn - > block ( true ) ;
rightBtn - > block ( true ) ;
}
pos . h + = abilitiesBackground - > pos . h ;
}
}
CStackWindow : : MainSection : : MainSection ( CStackWindow * owner , int yOffset , bool showExp , bool showArt )
: CWindowSection ( owner , getBackgroundName ( showExp , showArt ) , yOffset )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
statNames =
{
CGI - > generaltexth - > primarySkillNames [ 0 ] , //ATTACK
CGI - > generaltexth - > primarySkillNames [ 1 ] , //DEFENCE
CGI - > generaltexth - > allTexts [ 198 ] , //SHOTS
CGI - > generaltexth - > allTexts [ 199 ] , //DAMAGE
CGI - > generaltexth - > allTexts [ 388 ] , //HEALTH
CGI - > generaltexth - > allTexts [ 200 ] , //HEALTH_LEFT
CGI - > generaltexth - > zelp [ 441 ] . first , //SPEED
CGI - > generaltexth - > allTexts [ 399 ] //MANA
} ;
statFormats =
{
" %d (%d) " ,
" %d (%d) " ,
" %d (%d) " ,
" %d - %d " ,
" %d (%d) " ,
" %d (%d) " ,
" %d (%d) " ,
" %d (%d) "
} ;
animation = std : : make_shared < CCreaturePic > ( 5 , 41 , parent - > info - > creature ) ;
2024-04-12 23:35:39 +02:00
animationArea = std : : make_shared < LRClickableArea > ( Rect ( 5 , 41 , 100 , 130 ) , nullptr , [ & ] {
2024-04-27 18:41:21 +02:00
if ( ! parent - > info - > creature - > getDescriptionTranslated ( ) . empty ( ) )
CRClickPopup : : createAndPush ( parent - > info - > creature - > getDescriptionTranslated ( ) ) ;
2024-04-12 23:35:39 +02:00
} ) ;
2023-10-19 16:19:09 +02:00
2024-10-29 20:44:31 +01:00
2023-10-19 16:19:09 +02:00
if ( parent - > info - > stackNode ! = nullptr & & parent - > info - > commander = = nullptr )
{
//normal stack, not a commander and not non-existing stack (e.g. recruitment dialog)
animation - > setAmount ( parent - > info - > creatureCount ) ;
}
2024-11-01 19:02:51 +01:00
name = std : : make_shared < CLabel > ( 215 , 13 , FONT_SMALL , ETextAlignment : : CENTER , Colors : : YELLOW , parent - > info - > getName ( ) ) ;
2023-10-19 16:19:09 +02:00
2024-10-29 20:44:31 +01:00
const BattleInterface * battleInterface = LOCPLINT - > battleInt . get ( ) ;
const CStack * battleStack = parent - > info - > stack ;
2023-10-19 16:19:09 +02:00
2024-10-29 20:44:31 +01:00
int dmgMultiply = 1 ;
if ( battleInterface & & battleInterface - > getBattle ( ) ! = nullptr & & battleStack - > hasBonusOfType ( BonusType : : SIEGE_WEAPON ) )
{
// Determine the relevant hero based on the unit side
const auto hero = ( battleStack - > unitSide ( ) = = BattleSide : : ATTACKER )
? battleInterface - > attackingHeroInstance
: battleInterface - > defendingHeroInstance ;
2023-10-19 16:19:09 +02:00
2024-10-30 18:48:09 +01:00
dmgMultiply + = hero - > getPrimSkillLevel ( PrimarySkill : : ATTACK ) ;
2024-10-29 20:44:31 +01:00
}
2023-10-19 16:19:09 +02:00
icons = std : : make_shared < CPicture > ( ImagePath : : builtin ( " stackWindow/icons " ) , 117 , 32 ) ;
morale = std : : make_shared < MoraleLuckBox > ( true , Rect ( Point ( 321 , 110 ) , Point ( 42 , 42 ) ) ) ;
luck = std : : make_shared < MoraleLuckBox > ( false , Rect ( Point ( 375 , 110 ) , Point ( 42 , 42 ) ) ) ;
if ( battleStack ! = nullptr ) // in battle
{
addStatLabel ( EStat : : ATTACK , parent - > info - > creature - > getAttack ( battleStack - > isShooter ( ) ) , battleStack - > getAttack ( battleStack - > isShooter ( ) ) ) ;
addStatLabel ( EStat : : DEFENCE , parent - > info - > creature - > getDefense ( battleStack - > isShooter ( ) ) , battleStack - > getDefense ( battleStack - > isShooter ( ) ) ) ;
addStatLabel ( EStat : : DAMAGE , parent - > info - > stackNode - > getMinDamage ( battleStack - > isShooter ( ) ) * dmgMultiply , battleStack - > getMaxDamage ( battleStack - > isShooter ( ) ) * dmgMultiply ) ;
addStatLabel ( EStat : : HEALTH , parent - > info - > creature - > getMaxHealth ( ) , battleStack - > getMaxHealth ( ) ) ;
2024-01-14 17:14:36 +02:00
addStatLabel ( EStat : : SPEED , parent - > info - > creature - > getMovementRange ( ) , battleStack - > getMovementRange ( ) ) ;
2023-10-19 16:19:09 +02:00
if ( battleStack - > isShooter ( ) )
addStatLabel ( EStat : : SHOTS , battleStack - > shots . total ( ) , battleStack - > shots . available ( ) ) ;
if ( battleStack - > isCaster ( ) )
addStatLabel ( EStat : : MANA , battleStack - > casts . total ( ) , battleStack - > casts . available ( ) ) ;
addStatLabel ( EStat : : HEALTH_LEFT , battleStack - > getFirstHPleft ( ) ) ;
morale - > set ( battleStack ) ;
luck - > set ( battleStack ) ;
}
else
{
const bool shooter = parent - > info - > stackNode - > hasBonusOfType ( BonusType : : SHOOTER ) & & parent - > info - > stackNode - > valOfBonuses ( BonusType : : SHOTS ) ;
const bool caster = parent - > info - > stackNode - > valOfBonuses ( BonusType : : CASTS ) ;
addStatLabel ( EStat : : ATTACK , parent - > info - > creature - > getAttack ( shooter ) , parent - > info - > stackNode - > getAttack ( shooter ) ) ;
addStatLabel ( EStat : : DEFENCE , parent - > info - > creature - > getDefense ( shooter ) , parent - > info - > stackNode - > getDefense ( shooter ) ) ;
2024-10-29 20:44:31 +01:00
addStatLabel ( EStat : : DAMAGE , parent - > info - > stackNode - > getMinDamage ( shooter ) , parent - > info - > stackNode - > getMaxDamage ( shooter ) ) ;
2023-10-19 16:19:09 +02:00
addStatLabel ( EStat : : HEALTH , parent - > info - > creature - > getMaxHealth ( ) , parent - > info - > stackNode - > getMaxHealth ( ) ) ;
2024-01-14 17:14:36 +02:00
addStatLabel ( EStat : : SPEED , parent - > info - > creature - > getMovementRange ( ) , parent - > info - > stackNode - > getMovementRange ( ) ) ;
2023-10-19 16:19:09 +02:00
if ( shooter )
addStatLabel ( EStat : : SHOTS , parent - > info - > stackNode - > valOfBonuses ( BonusType : : SHOTS ) ) ;
if ( caster )
addStatLabel ( EStat : : MANA , parent - > info - > stackNode - > valOfBonuses ( BonusType : : CASTS ) ) ;
morale - > set ( parent - > info - > stackNode ) ;
luck - > set ( parent - > info - > stackNode ) ;
}
if ( showExp )
{
const CStackInstance * stack = parent - > info - > stackNode ;
Point pos = showArt ? Point ( 321 , 32 ) : Point ( 347 , 32 ) ;
if ( parent - > info - > commander )
{
const CCommanderInstance * commander = parent - > info - > commander ;
expRankIcon = std : : make_shared < CAnimImage > ( AnimationPath : : builtin ( " PSKIL42 " ) , 4 , 0 , pos . x , pos . y ) ;
2023-10-31 11:09:56 +02:00
auto area = std : : make_shared < LRClickableAreaWTextComp > ( Rect ( pos . x , pos . y , 44 , 44 ) , ComponentType : : EXPERIENCE ) ;
2023-10-19 16:19:09 +02:00
expArea = area ;
area - > text = CGI - > generaltexth - > allTexts [ 2 ] ;
2023-10-31 11:09:56 +02:00
area - > component . value = commander - > getExpRank ( ) ;
2023-10-19 16:19:09 +02:00
boost : : replace_first ( area - > text , " %d " , std : : to_string ( commander - > getExpRank ( ) ) ) ;
boost : : replace_first ( area - > text , " %d " , std : : to_string ( CGI - > heroh - > reqExp ( commander - > getExpRank ( ) + 1 ) ) ) ;
boost : : replace_first ( area - > text , " %d " , std : : to_string ( commander - > experience ) ) ;
}
else
{
expRankIcon = std : : make_shared < CAnimImage > ( AnimationPath : : builtin ( " stackWindow/levels " ) , stack - > getExpRank ( ) , 0 , pos . x , pos . y ) ;
expArea = std : : make_shared < LRClickableAreaWText > ( Rect ( pos . x , pos . y , 44 , 44 ) ) ;
expArea - > text = parent - > generateStackExpDescription ( ) ;
}
expLabel = std : : make_shared < CLabel > (
pos . x + 21 , pos . y + 52 , FONT_SMALL , ETextAlignment : : CENTER , Colors : : WHITE ,
TextOperations : : formatMetric ( stack - > experience , 6 ) ) ;
}
if ( showArt )
{
Point pos = showExp ? Point ( 375 , 32 ) : Point ( 347 , 32 ) ;
// ALARMA: do not refactor this into a separate function
// otherwise, artifact icon is drawn near the hero's portrait
// this is really strange
auto art = parent - > info - > stackNode - > getArt ( ArtifactPosition : : CREATURE_SLOT ) ;
if ( art )
{
2024-10-19 16:25:26 +03:00
parent - > stackArtifact = std : : make_shared < CArtPlace > ( pos , art - > getTypeId ( ) ) ;
2024-10-20 15:27:21 +03:00
parent - > stackArtifact - > setShowPopupCallback ( [ ] ( CComponentHolder & artPlace , const Point & cursorPosition )
2024-10-19 16:25:26 +03:00
{
artPlace . LRClickableAreaWTextComp : : showPopupWindow ( cursorPosition ) ;
} ) ;
2023-10-19 16:19:09 +02:00
if ( parent - > info - > owner )
{
parent - > stackArtifactButton = std : : make_shared < CButton > (
Point ( pos . x - 2 , pos . y + 46 ) , AnimationPath : : builtin ( " stackWindow/cancelButton " ) ,
CButton : : tooltipLocalized ( " vcmi.creatureWindow.returnArtifact " ) , [ = ] ( )
{
parent - > removeStackArtifact ( ArtifactPosition : : CREATURE_SLOT ) ;
} ) ;
}
}
}
}
ImagePath CStackWindow : : MainSection : : getBackgroundName ( bool showExp , bool showArt )
{
if ( showExp & & showArt )
return ImagePath : : builtin ( " stackWindow/info-panel-2 " ) ;
else if ( showExp | | showArt )
return ImagePath : : builtin ( " stackWindow/info-panel-1 " ) ;
else
return ImagePath : : builtin ( " stackWindow/info-panel-0 " ) ;
}
void CStackWindow : : MainSection : : addStatLabel ( EStat index , int64_t value1 , int64_t value2 )
{
const auto title = statNames . at ( static_cast < size_t > ( index ) ) ;
stats . push_back ( std : : make_shared < CLabel > ( 145 , 32 + ( int ) index * 19 , FONT_SMALL , ETextAlignment : : TOPLEFT , Colors : : WHITE , title ) ) ;
const bool useRange = value1 ! = value2 ;
std : : string formatStr = useRange ? statFormats . at ( static_cast < size_t > ( index ) ) : " %d " ;
boost : : format fmt ( formatStr ) ;
fmt % value1 ;
if ( useRange )
fmt % value2 ;
stats . push_back ( std : : make_shared < CLabel > ( 307 , 48 + ( int ) index * 19 , FONT_SMALL , ETextAlignment : : BOTTOMRIGHT , Colors : : WHITE , fmt . str ( ) ) ) ;
}
void CStackWindow : : MainSection : : addStatLabel ( EStat index , int64_t value )
{
addStatLabel ( index , value , value ) ;
}
CStackWindow : : CStackWindow ( const CStack * stack , bool popup )
: CWindowObject ( BORDERED | ( popup ? RCLICK_POPUP : 0 ) ) ,
info ( new UnitView ( ) )
{
info - > stack = stack ;
info - > stackNode = stack - > base ;
2024-01-07 12:45:02 +02:00
info - > commander = dynamic_cast < const CCommanderInstance * > ( stack - > base ) ;
2023-10-19 16:19:09 +02:00
info - > creature = stack - > unitType ( ) ;
info - > creatureCount = stack - > getCount ( ) ;
info - > popupWindow = popup ;
init ( ) ;
}
CStackWindow : : CStackWindow ( const CCreature * creature , bool popup )
: CWindowObject ( BORDERED | ( popup ? RCLICK_POPUP : 0 ) ) ,
info ( new UnitView ( ) )
{
info - > creature = creature ;
info - > popupWindow = popup ;
init ( ) ;
}
CStackWindow : : CStackWindow ( const CStackInstance * stack , bool popup )
: CWindowObject ( BORDERED | ( popup ? RCLICK_POPUP : 0 ) ) ,
info ( new UnitView ( ) )
{
info - > stackNode = stack ;
2024-10-12 16:02:35 +00:00
info - > creature = stack - > getCreature ( ) ;
2023-10-19 16:19:09 +02:00
info - > creatureCount = stack - > count ;
info - > popupWindow = popup ;
info - > owner = dynamic_cast < const CGHeroInstance * > ( stack - > armyObj ) ;
init ( ) ;
}
2024-12-16 16:05:46 +01:00
CStackWindow : : CStackWindow ( const CStackInstance * stack , std : : function < void ( ) > dismiss , const UpgradeInfo & upgradeInfo , std : : function < void ( CreatureID ) > callback )
2023-10-19 16:19:09 +02:00
: CWindowObject ( BORDERED ) ,
info ( new UnitView ( ) )
{
info - > stackNode = stack ;
2024-10-12 16:02:35 +00:00
info - > creature = stack - > getCreature ( ) ;
2023-10-19 16:19:09 +02:00
info - > creatureCount = stack - > count ;
2024-12-16 16:05:46 +01:00
info - > upgradeInfo = std : : make_optional ( UnitView : : StackUpgradeInfo ( upgradeInfo ) ) ;
2023-10-19 16:19:09 +02:00
info - > dismissInfo = std : : make_optional ( UnitView : : StackDismissInfo ( ) ) ;
info - > upgradeInfo - > callback = callback ;
info - > dismissInfo - > callback = dismiss ;
info - > owner = dynamic_cast < const CGHeroInstance * > ( stack - > armyObj ) ;
init ( ) ;
}
CStackWindow : : CStackWindow ( const CCommanderInstance * commander , bool popup )
: CWindowObject ( BORDERED | ( popup ? RCLICK_POPUP : 0 ) ) ,
info ( new UnitView ( ) )
{
info - > stackNode = commander ;
2024-10-12 16:02:35 +00:00
info - > creature = commander - > getCreature ( ) ;
2023-10-19 16:19:09 +02:00
info - > commander = commander ;
info - > creatureCount = 1 ;
info - > popupWindow = popup ;
info - > owner = dynamic_cast < const CGHeroInstance * > ( commander - > armyObj ) ;
init ( ) ;
}
CStackWindow : : CStackWindow ( const CCommanderInstance * commander , std : : vector < ui32 > & skills , std : : function < void ( ui32 ) > callback )
: CWindowObject ( BORDERED ) ,
info ( new UnitView ( ) )
{
info - > stackNode = commander ;
2024-10-12 16:02:35 +00:00
info - > creature = commander - > getCreature ( ) ;
2023-10-19 16:19:09 +02:00
info - > commander = commander ;
info - > creatureCount = 1 ;
info - > levelupInfo = std : : make_optional ( UnitView : : CommanderLevelInfo ( ) ) ;
info - > levelupInfo - > skills = skills ;
info - > levelupInfo - > callback = callback ;
info - > owner = dynamic_cast < const CGHeroInstance * > ( commander - > armyObj ) ;
init ( ) ;
}
CStackWindow : : ~ CStackWindow ( )
{
if ( info - > levelupInfo & & ! info - > levelupInfo - > skills . empty ( ) )
info - > levelupInfo - > callback ( vstd : : find_pos ( info - > levelupInfo - > skills , selectedSkill ) ) ;
}
void CStackWindow : : init ( )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
if ( ! info - > stackNode )
info - > stackNode = new CStackInstance ( info - > creature , 1 , true ) ; // FIXME: free data
selectedIcon = nullptr ;
selectedSkill = - 1 ;
if ( info - > levelupInfo & & ! info - > levelupInfo - > skills . empty ( ) )
selectedSkill = info - > levelupInfo - > skills . front ( ) ;
activeTab = 0 ;
initBonusesList ( ) ;
initSections ( ) ;
}
void CStackWindow : : initBonusesList ( )
{
2025-01-18 19:45:34 +00:00
auto inputPtr = info - > stackNode - > getBonuses ( CSelector ( Bonus : : Permanent ) , Selector : : all ) ;
2024-01-09 22:38:54 +00:00
BonusList output ;
2025-01-18 19:45:34 +00:00
BonusList input = * inputPtr ;
2024-11-02 15:46:20 +01:00
std : : sort ( input . begin ( ) , input . end ( ) , [ this ] ( std : : shared_ptr < Bonus > v1 , std : : shared_ptr < Bonus > & v2 ) {
2024-11-13 20:03:20 +01:00
if ( v1 - > source ! = v2 - > source )
return v1 - > source = = BonusSource : : CREATURE_ABILITY | | ( v1 - > source < v2 - > source ) ;
else
return info - > stackNode - > bonusToString ( v1 , false ) < info - > stackNode - > bonusToString ( v2 , false ) ;
2024-11-02 15:46:20 +01:00
} ) ;
2023-10-19 16:19:09 +02:00
while ( ! input . empty ( ) )
{
auto b = input . front ( ) ;
output . push_back ( std : : make_shared < 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 ( auto b : output )
{
bonusInfo . name = info - > stackNode - > bonusToString ( b , false ) ;
bonusInfo . description = info - > stackNode - > bonusToString ( b , true ) ;
bonusInfo . imagePath = info - > stackNode - > bonusToGraphics ( b ) ;
2024-11-02 13:05:10 +01:00
bonusInfo . bonusSource = b - > source ;
2023-10-19 16:19:09 +02:00
2024-12-22 21:23:31 +01:00
if ( b - > sid . getNum ( ) ! = info - > stackNode - > getId ( ) & & b - > propagator & & b - > propagator - > getPropagatorType ( ) = = CBonusSystemNode : : HERO ) // Shows bonus with "propagator":"HERO" only at creature with bonus
continue ;
2023-10-19 16:19:09 +02:00
//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 ) ;
}
}
void CStackWindow : : initSections ( )
{
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
2024-08-31 11:00:36 +00:00
bool showArt = LOCPLINT - > cb - > getSettings ( ) . getBoolean ( EGameSettings : : MODULE_STACK_ARTIFACT ) & & info - > commander = = nullptr & & info - > stackNode ;
bool showExp = ( LOCPLINT - > cb - > getSettings ( ) . getBoolean ( EGameSettings : : MODULE_STACK_EXPERIENCE ) | | info - > commander ! = nullptr ) & & info - > stackNode ;
2023-10-19 16:19:09 +02:00
mainSection = std : : make_shared < MainSection > ( this , pos . h , showExp , showArt ) ;
pos . w = mainSection - > pos . w ;
pos . h + = mainSection - > pos . h ;
if ( info - > stack ) // in battle
{
activeSpellsSection = std : : make_shared < ActiveSpellsSection > ( this , pos . h ) ;
pos . h + = activeSpellsSection - > pos . h ;
}
if ( info - > commander )
{
auto onCreate = [ = ] ( size_t index ) - > std : : shared_ptr < CIntObject >
{
auto obj = switchTab ( index ) ;
if ( obj )
{
obj - > activate ( ) ;
obj - > recActions | = ( UPDATE | SHOWALL ) ;
}
return obj ;
} ;
auto deactivateObj = [ = ] ( std : : shared_ptr < CIntObject > obj )
{
obj - > deactivate ( ) ;
obj - > recActions & = ~ ( UPDATE | SHOWALL ) ;
} ;
commanderMainSection = std : : make_shared < CommanderMainSection > ( this , 0 ) ;
auto size = std : : make_optional < size_t > ( ( info - > levelupInfo ) ? 4 : 3 ) ;
commanderBonusesSection = std : : make_shared < BonusesSection > ( this , 0 , size ) ;
deactivateObj ( commanderBonusesSection ) ;
commanderTab = std : : make_shared < CTabbedInt > ( onCreate , Point ( 0 , pos . h ) , 0 ) ;
pos . h + = commanderMainSection - > pos . h ;
}
if ( ! info - > commander & & ! activeBonuses . empty ( ) )
{
bonusesSection = std : : make_shared < BonusesSection > ( this , pos . h ) ;
pos . h + = bonusesSection - > pos . h ;
}
if ( ! info - > popupWindow )
{
buttonsSection = std : : make_shared < ButtonsSection > ( this , pos . h ) ;
pos . h + = buttonsSection - > pos . h ;
//FIXME: add status bar to image?
}
updateShadow ( ) ;
pos = center ( pos ) ;
}
std : : string CStackWindow : : generateStackExpDescription ( )
{
const CStackInstance * stack = info - > stackNode ;
const CCreature * creature = info - > creature ;
2024-10-12 16:02:35 +00:00
int tier = stack - > getType ( ) - > getLevel ( ) ;
2023-10-19 16:19:09 +02:00
int rank = stack - > getExpRank ( ) ;
if ( ! vstd : : iswithin ( tier , 1 , 7 ) )
tier = 0 ;
int number ;
std : : string expText = CGI - > generaltexth - > translate ( " vcmi.stackExperience.description " ) ;
boost : : replace_first ( expText , " %s " , creature - > getNamePluralTranslated ( ) ) ;
boost : : replace_first ( expText , " %s " , CGI - > generaltexth - > translate ( " vcmi.stackExperience.rank " , rank ) ) ;
boost : : replace_first ( expText , " %i " , std : : to_string ( rank ) ) ;
boost : : replace_first ( expText , " %i " , std : : to_string ( stack - > experience ) ) ;
number = static_cast < int > ( CGI - > creh - > expRanks [ tier ] [ rank ] - stack - > experience ) ;
boost : : replace_first ( expText , " %i " , std : : to_string ( number ) ) ;
number = CGI - > creh - > maxExpPerBattle [ tier ] ; //percent
boost : : replace_first ( expText , " %i% " , std : : to_string ( number ) ) ;
number * = CGI - > creh - > expRanks [ tier ] . back ( ) / 100 ; //actual amount
boost : : replace_first ( expText , " %i " , std : : to_string ( number ) ) ;
boost : : replace_first ( expText , " %i " , std : : to_string ( stack - > count ) ) ; //Number of Creatures in stack
int expmin = std : : max ( CGI - > creh - > expRanks [ tier ] [ std : : max ( rank - 1 , 0 ) ] , ( ui32 ) 1 ) ;
number = static_cast < int > ( ( stack - > count * ( stack - > experience - expmin ) ) / expmin ) ; //Maximum New Recruits without losing current Rank
boost : : replace_first ( expText , " %i " , std : : to_string ( number ) ) ; //TODO
boost : : replace_first ( expText , " %.2f " , std : : to_string ( 1 ) ) ; //TODO Experience Multiplier
number = CGI - > creh - > expAfterUpgrade ;
boost : : replace_first ( expText , " %.2f " , std : : to_string ( number ) + " % " ) ; //Upgrade Multiplier
expmin = CGI - > creh - > expRanks [ tier ] [ 9 ] ;
int expmax = CGI - > creh - > expRanks [ tier ] [ 10 ] ;
number = expmax - expmin ;
boost : : replace_first ( expText , " %i " , std : : to_string ( number ) ) ; //Experience after Rank 10
number = ( stack - > count * ( expmax - expmin ) ) / expmin ;
boost : : replace_first ( expText , " %i " , std : : to_string ( number ) ) ; //Maximum New Recruits to remain at Rank 10 if at Maximum Experience
return expText ;
}
2024-10-26 14:21:05 +00:00
std : : string CStackWindow : : getCommanderSkillDescription ( int skillIndex , int skillLevel )
{
constexpr std : : array skillNames = {
" attack " ,
" defence " ,
" health " ,
" damage " ,
" speed " ,
" magic "
} ;
std : : string textID = TextIdentifier ( " vcmi " , " commander " , " skill " , skillNames . at ( skillIndex ) , skillLevel ) . get ( ) ;
return CGI - > generaltexth - > translate ( textID ) ;
}
2023-10-19 16:19:09 +02:00
void CStackWindow : : setSelection ( si32 newSkill , std : : shared_ptr < CCommanderSkillIcon > newIcon )
{
auto getSkillDescription = [ this ] ( int skillIndex , bool selected ) - > std : : string
{
if ( selected )
2024-10-26 14:21:05 +00:00
return getCommanderSkillDescription ( skillIndex , info - > commander - > secondarySkills [ skillIndex ] + 1 ) ; //upgrade description
2023-10-19 16:19:09 +02:00
else
2024-10-26 14:21:05 +00:00
return getCommanderSkillDescription ( skillIndex , info - > commander - > secondarySkills [ skillIndex ] ) ;
2023-10-19 16:19:09 +02:00
} ;
auto getSkillImage = [ this ] ( int skillIndex )
{
bool selected = ( ( selectedSkill = = skillIndex ) & & info - > levelupInfo ) ;
return skillToFile ( skillIndex , info - > commander - > secondarySkills [ skillIndex ] , selected ) ;
} ;
2024-08-09 15:30:04 +00:00
OBJECT_CONSTRUCTION ;
2023-10-19 16:19:09 +02:00
int oldSelection = selectedSkill ; // update selection
selectedSkill = newSkill ;
if ( selectedIcon & & oldSelection < 100 ) // recreate image on old selection, only for skills
selectedIcon - > setObject ( std : : make_shared < CPicture > ( getSkillImage ( oldSelection ) ) ) ;
if ( selectedIcon )
{
if ( ! selectedIcon - > getIsMasterAbility ( ) ) //unlike WoG, in VCMI master skill descriptions are taken from bonus descriptions
{
selectedIcon - > text = getSkillDescription ( oldSelection , false ) ; //update previously selected icon's message to existing skill level
}
selectedIcon - > deselect ( ) ;
}
selectedIcon = newIcon ; // update new selection
if ( newSkill < 100 )
{
newIcon - > setObject ( std : : make_shared < CPicture > ( getSkillImage ( newSkill ) ) ) ;
if ( ! newIcon - > getIsMasterAbility ( ) )
{
newIcon - > text = getSkillDescription ( newSkill , true ) ; //update currently selected icon's message to show upgrade description
}
}
}
std : : shared_ptr < CIntObject > CStackWindow : : switchTab ( size_t index )
{
std : : shared_ptr < CIntObject > ret ;
switch ( index )
{
case 0 :
{
activeTab = 0 ;
ret = commanderMainSection ;
}
break ;
case 1 :
{
activeTab = 1 ;
ret = commanderBonusesSection ;
}
break ;
default :
break ;
}
return ret ;
}
void CStackWindow : : removeStackArtifact ( ArtifactPosition pos )
{
auto art = info - > stackNode - > getArt ( ArtifactPosition : : CREATURE_SLOT ) ;
if ( ! art )
{
logGlobal - > error ( " Attempt to remove missing artifact " ) ;
return ;
}
const auto slot = ArtifactUtils : : getArtBackpackPosition ( info - > owner , art - > getTypeId ( ) ) ;
if ( slot ! = ArtifactPosition : : PRE_FIRST )
{
2023-10-23 19:37:18 +03:00
auto artLoc = ArtifactLocation ( info - > owner - > id , pos ) ;
artLoc . creature = info - > stackNode - > armyObj - > findStack ( info - > stackNode ) ;
LOCPLINT - > cb - > swapArtifacts ( artLoc , ArtifactLocation ( info - > owner - > id , slot ) ) ;
2023-10-19 16:19:09 +02:00
stackArtifactButton . reset ( ) ;
2024-10-19 16:25:26 +03:00
stackArtifact . reset ( ) ;
2023-10-19 16:19:09 +02:00
redraw ( ) ;
}
}