1 /***************************************************************************
2  *   Copyright (C) 2009 by Andrey Afletdinov <fheroes2@gmail.com>          *
3  *                                                                         *
4  *   Part of the Free Heroes2 Engine:                                      *
5  *   http://sourceforge.net/projects/fheroes2                              *
6  *                                                                         *
7  *   This program is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU General Public License as published by  *
9  *   the Free Software Foundation; either version 2 of the License, or     *
10  *   (at your option) any later version.                                   *
11  *                                                                         *
12  *   This program is distributed in the hope that it will be useful,       *
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
15  *   GNU General Public License for more details.                          *
16  *                                                                         *
17  *   You should have received a copy of the GNU General Public License     *
18  *   along with this program; if not, write to the                         *
19  *   Free Software Foundation, Inc.,                                       *
20  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
21  ***************************************************************************/
22 
23 #include <algorithm>
24 #include <cmath>
25 #include <functional>
26 
27 #include "agg.h"
28 #include "agg_image.h"
29 #include "ai.h"
30 #include "artifact.h"
31 #include "battle.h"
32 #include "castle.h"
33 #include "dialog.h"
34 #include "difficulty.h"
35 #include "direction.h"
36 #include "game.h"
37 #include "game_static.h"
38 #include "ground.h"
39 #include "heroes.h"
40 #include "icn.h"
41 #include "interface_icons.h"
42 #include "kingdom.h"
43 #include "logging.h"
44 #include "luck.h"
45 #include "maps_objects.h"
46 #include "monster.h"
47 #include "morale.h"
48 #include "mp2.h"
49 #include "payment.h"
50 #include "race.h"
51 #include "serialize.h"
52 #include "settings.h"
53 #include "speed.h"
54 #include "text.h"
55 #include "tools.h"
56 #include "translations.h"
57 #include "world.h"
58 
59 namespace
60 {
ObjectVisitedModifiersResult(const std::vector<MP2::MapObjectType> & objectTypes,const Heroes & hero,std::string * strs)61     int ObjectVisitedModifiersResult( const std::vector<MP2::MapObjectType> & objectTypes, const Heroes & hero, std::string * strs )
62     {
63         int result = 0;
64 
65         for ( const MP2::MapObjectType objectType : objectTypes ) {
66             if ( hero.isObjectTypeVisited( objectType ) ) {
67                 result += GameStatic::ObjectVisitedModifiers( objectType );
68 
69                 if ( strs ) {
70                     switch ( objectType ) {
71                     case MP2::OBJ_GRAVEYARD:
72                     case MP2::OBJN_GRAVEYARD:
73                     case MP2::OBJ_SHIPWRECK:
74                     case MP2::OBJN_SHIPWRECK:
75                     case MP2::OBJ_DERELICTSHIP:
76                     case MP2::OBJN_DERELICTSHIP: {
77                         std::string modRobber = _( "%{object} robber" );
78                         StringReplace( modRobber, "%{object}", _( MP2::StringObject( objectType ) ) );
79                         strs->append( modRobber );
80                         break;
81                     }
82                     case MP2::OBJ_PYRAMID:
83                     case MP2::OBJN_PYRAMID: {
84                         std::string modRaided = _( "%{object} raided" );
85                         StringReplace( modRaided, "%{object}", _( MP2::StringObject( objectType ) ) );
86                         strs->append( modRaided );
87                         break;
88                     }
89                     default:
90                         strs->append( _( MP2::StringObject( objectType ) ) );
91                         break;
92                     }
93 
94                     StringAppendModifiers( *strs, GameStatic::ObjectVisitedModifiers( objectType ) );
95                     strs->append( "\n" );
96                 }
97             }
98         }
99 
100         return result;
101     }
102 }
103 
GetName(int id)104 const char * Heroes::GetName( int id )
105 {
106     const char * names[]
107         = { // knight
108             _( "Lord Kilburn" ), _( "Sir Gallant" ), _( "Ector" ), _( "Gwenneth" ), _( "Tyro" ), _( "Ambrose" ), _( "Ruby" ), _( "Maximus" ), _( "Dimitry" ),
109             // barbarian
110             _( "Thundax" ), _( "Fineous" ), _( "Jojosh" ), _( "Crag Hack" ), _( "Jezebel" ), _( "Jaclyn" ), _( "Ergon" ), _( "Tsabu" ), _( "Atlas" ),
111             // sorceress
112             _( "Astra" ), _( "Natasha" ), _( "Troyan" ), _( "Vatawna" ), _( "Rebecca" ), _( "Gem" ), _( "Ariel" ), _( "Carlawn" ), _( "Luna" ),
113             // warlock
114             _( "Arie" ), _( "Alamar" ), _( "Vesper" ), _( "Crodo" ), _( "Barok" ), _( "Kastore" ), _( "Agar" ), _( "Falagar" ), _( "Wrathmont" ),
115             // wizard
116             _( "Myra" ), _( "Flint" ), _( "Dawn" ), _( "Halon" ), _( "Myrini" ), _( "Wilfrey" ), _( "Sarakin" ), _( "Kalindra" ), _( "Mandigal" ),
117             // necromant
118             _( "Zom" ), _( "Darlana" ), _( "Zam" ), _( "Ranloo" ), _( "Charity" ), _( "Rialdo" ), _( "Roxana" ), _( "Sandro" ), _( "Celia" ),
119             // campains
120             _( "Roland" ), _( "Lord Corlagon" ), _( "Sister Eliza" ), _( "Archibald" ), _( "Lord Halton" ), _( "Brother Brax" ),
121             // loyalty version
122             _( "Solmyr" ), _( "Dainwin" ), _( "Mog" ), _( "Uncle Ivan" ), _( "Joseph" ), _( "Gallavant" ), _( "Elderian" ), _( "Ceallach" ), _( "Drakonia" ),
123             _( "Martine" ), _( "Jarkonas" ),
124             // debug
125             "Debug Hero", "Unknown" };
126 
127     return names[id];
128 }
129 
Heroes()130 Heroes::Heroes()
131     : experience( 0 )
132     , move_point_scale( -1 )
133     , army( this )
134     , hid( UNKNOWN )
135     , portrait( UNKNOWN )
136     , _race( UNKNOWN )
137     , save_maps_object( 0 )
138     , path( *this )
139     , direction( Direction::RIGHT )
140     , sprite_index( 18 )
141     , patrol_square( 0 )
142     , _alphaValue( 255 )
143     , _attackedMonsterTileIndex( -1 )
144     , _aiRole( Role::HUNTER )
145 {}
146 
Heroes(int heroID,int race,int initialLevel)147 Heroes::Heroes( int heroID, int race, int initialLevel )
148     : Heroes( heroID, race )
149 {
150     // level 1 is technically regarded as 0, so reduce the initial level by 1
151     experience = GetExperienceFromLevel( initialLevel - 1 );
152 
153     for ( int i = 1; i < initialLevel; ++i ) {
154         LevelUp( false, true );
155     }
156 }
157 
Heroes(int heroid,int rc)158 Heroes::Heroes( int heroid, int rc )
159     : HeroBase( HeroBase::HEROES, rc )
160     , ColorBase( Color::NONE )
161     , experience( GetStartingXp() )
162     , move_point_scale( -1 )
163     , secondary_skills( rc )
164     , army( this )
165     , hid( heroid )
166     , portrait( heroid )
167     , _race( rc )
168     , save_maps_object( MP2::OBJ_ZERO )
169     , path( *this )
170     , direction( Direction::RIGHT )
171     , sprite_index( 18 )
172     , patrol_square( 0 )
173     , _alphaValue( 255 )
174     , _attackedMonsterTileIndex( -1 )
175     , _aiRole( Role::HUNTER )
176 {
177     name = _( Heroes::GetName( heroid ) );
178 
179     // set default army
180     army.Reset( true );
181 
182     // extra hero
183     switch ( hid ) {
184     case DEBUG_HERO:
185         army.Clean();
186         army.JoinTroop( Monster::BLACK_DRAGON, 2 );
187         army.JoinTroop( Monster::RED_DRAGON, 3 );
188 
189         secondary_skills = Skill::SecSkills();
190         secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::PATHFINDING, Skill::Level::ADVANCED ) );
191         secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::LOGISTICS, Skill::Level::ADVANCED ) );
192         secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::SCOUTING, Skill::Level::BASIC ) );
193         secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::MYSTICISM, Skill::Level::BASIC ) );
194 
195         PickupArtifact( Artifact::STEALTH_SHIELD );
196         PickupArtifact( Artifact::DRAGON_SWORD );
197         PickupArtifact( Artifact::NOMAD_BOOTS_MOBILITY );
198         PickupArtifact( Artifact::TRAVELER_BOOTS_MOBILITY );
199         PickupArtifact( Artifact::TRUE_COMPASS_MOBILITY );
200 
201         experience = 777;
202         magic_point = 120;
203 
204         // all spell in magic book
205         for ( u32 spell = Spell::FIREBALL; spell < Spell::STONE; ++spell )
206             AppendSpellToBook( Spell( spell ), true );
207         break;
208 
209     default:
210         break;
211     }
212 
213     if ( !magic_point )
214         SetSpellPoints( GetMaxSpellPoints() );
215     move_point = GetMaxMovePoints();
216 }
217 
LoadFromMP2(s32 map_index,int cl,int rc,StreamBuf st)218 void Heroes::LoadFromMP2( s32 map_index, int cl, int rc, StreamBuf st )
219 {
220     // reset modes
221     modes = 0;
222 
223     SetIndex( map_index );
224     SetColor( cl );
225 
226     // unknown
227     st.skip( 1 );
228 
229     // custom troops
230     if ( st.get() ) {
231         Troop troops[5];
232 
233         // set monster id
234         for ( Troop & troop : troops )
235             troop.SetMonster( st.get() + 1 );
236 
237         // set count
238         for ( Troop & troop : troops )
239             troop.SetCount( st.getLE16() );
240 
241         army.Assign( troops, std::end( troops ) );
242     }
243     else
244         st.skip( 15 );
245 
246     // custom portrate
247     bool custom_portrait = ( st.get() != 0 );
248 
249     if ( custom_portrait ) {
250         SetModes( NOTDEFAULTS );
251 
252         // index sprite portrait
253         portrait = st.get();
254 
255         if ( UNKNOWN <= portrait ) {
256             DEBUG_LOG( DBG_GAME, DBG_WARN, "custom portrait incorrect: " << portrait );
257             portrait = hid;
258         }
259 
260         // fixed race for custom portrait (after level up)
261         _race = rc;
262     }
263     else {
264         st.skip( 1 );
265     }
266 
267     // 3 artifacts
268     PickupArtifact( Artifact( st.get() ) );
269     PickupArtifact( Artifact( st.get() ) );
270     PickupArtifact( Artifact( st.get() ) );
271 
272     // unknown byte
273     st.skip( 1 );
274 
275     // experience
276     experience = st.getLE32();
277 
278     if ( experience == 0 )
279         experience = GetStartingXp();
280 
281     const bool custom_secskill = ( st.get() != 0 );
282 
283     // custom skill
284     if ( custom_secskill ) {
285         SetModes( NOTDEFAULTS );
286         SetModes( CUSTOMSKILLS );
287         std::vector<Skill::Secondary> secs( 8 );
288 
289         for ( std::vector<Skill::Secondary>::iterator it = secs.begin(); it != secs.end(); ++it )
290             ( *it ).SetSkill( st.get() + 1 );
291 
292         for ( std::vector<Skill::Secondary>::iterator it = secs.begin(); it != secs.end(); ++it )
293             ( *it ).SetLevel( st.get() );
294 
295         secondary_skills = Skill::SecSkills();
296 
297         for ( std::vector<Skill::Secondary>::const_iterator it = secs.begin(); it != secs.end(); ++it )
298             if ( ( *it ).isValid() )
299                 secondary_skills.AddSkill( *it );
300     }
301     else {
302         st.skip( 16 );
303     }
304 
305     // unknown
306     st.skip( 1 );
307 
308     // custom name
309     if ( st.get() ) {
310         SetModes( NOTDEFAULTS );
311         name = st.toString( 13 );
312     }
313     else {
314         st.skip( 13 );
315     }
316 
317     // patrol
318     if ( st.get() ) {
319         SetModes( PATROL );
320         patrol_center = GetCenter();
321     }
322 
323     // count square
324     patrol_square = st.get();
325 
326     PostLoad();
327 }
328 
PostLoad(void)329 void Heroes::PostLoad( void )
330 {
331     killer_color.SetColor( Color::NONE );
332 
333     // save general object
334     save_maps_object = MP2::OBJ_ZERO;
335 
336     // fix zero army
337     if ( !army.isValid() )
338         army.Reset( false );
339     else
340         SetModes( CUSTOMARMY );
341 
342     // level up
343     int level = GetLevel();
344     while ( 1 < level-- ) {
345         SetModes( NOTDEFAULTS );
346         LevelUp( Modes( CUSTOMSKILLS ), true );
347     }
348 
349     if ( ( _race & ( Race::SORC | Race::WRLK | Race::WZRD | Race::NECR ) ) && !HaveSpellBook() ) {
350         Spell spell = Skill::Primary::GetInitialSpell( _race );
351         if ( spell.isValid() ) {
352             SpellBookActivate();
353             AppendSpellToBook( spell, true );
354         }
355     }
356 
357     // other param
358     SetSpellPoints( GetMaxSpellPoints() );
359     move_point = GetMaxMovePoints();
360 
361     if ( isControlAI() ) {
362         AI::Get().HeroesPostLoad( *this );
363     }
364 
365     DEBUG_LOG( DBG_GAME, DBG_INFO, name << ", color: " << Color::String( GetColor() ) << ", race: " << Race::String( _race ) );
366 }
367 
GetID(void) const368 int Heroes::GetID( void ) const
369 {
370     return hid;
371 }
372 
GetRace(void) const373 int Heroes::GetRace( void ) const
374 {
375     return _race;
376 }
377 
GetName(void) const378 const std::string & Heroes::GetName( void ) const
379 {
380     return name;
381 }
382 
GetColor(void) const383 int Heroes::GetColor( void ) const
384 {
385     return ColorBase::GetColor();
386 }
387 
GetType(void) const388 int Heroes::GetType( void ) const
389 {
390     return HeroBase::HEROES;
391 }
392 
GetArmy(void) const393 const Army & Heroes::GetArmy( void ) const
394 {
395     return army;
396 }
397 
GetArmy(void)398 Army & Heroes::GetArmy( void )
399 {
400     return army;
401 }
402 
GetMobilityIndexSprite(void) const403 int Heroes::GetMobilityIndexSprite( void ) const
404 {
405     // valid range (0 - 25)
406     int index = CanMove() ? ( move_point + 50 ) / 100 : 0;
407     return 25 >= index ? index : 25;
408 }
409 
GetManaIndexSprite(void) const410 int Heroes::GetManaIndexSprite( void ) const
411 {
412     // Add 2 to round values.
413     const int value = ( GetSpellPoints() + 2 ) / 5;
414     return value >= 25 ? 25 : value;
415 }
416 
getStatsValue() const417 int Heroes::getStatsValue() const
418 {
419     // experience and artifacts don't matter here, only natural stats
420     return attack + defense + power + knowledge + secondary_skills.GetTotalLevel();
421 }
422 
getRecruitValue() const423 double Heroes::getRecruitValue() const
424 {
425     return army.GetStrength() + ( ( bag_artifacts.getArtifactValue() * 10.0 + getStatsValue() ) * SKILL_VALUE );
426 }
427 
getMeetingValue(const Heroes & recievingHero) const428 double Heroes::getMeetingValue( const Heroes & recievingHero ) const
429 {
430     // Magic Book is not transferable.
431     const uint32_t artCount = bag_artifacts.CountArtifacts() - bag_artifacts.Count( Artifact::MAGIC_BOOK );
432     const uint32_t canFit = HEROESMAXARTIFACT - recievingHero.bag_artifacts.CountArtifacts();
433 
434     double artifactValue = bag_artifacts.getArtifactValue() * 5.0;
435     if ( artCount > canFit ) {
436         artifactValue = canFit * ( artifactValue / artCount );
437     }
438 
439     return recievingHero.army.getReinforcementValue( army ) + artifactValue * SKILL_VALUE;
440 }
441 
GetAttack(void) const442 int Heroes::GetAttack( void ) const
443 {
444     return GetAttack( nullptr );
445 }
446 
GetAttack(std::string * strs) const447 int Heroes::GetAttack( std::string * strs ) const
448 {
449     int result = attack + GetAttackModificator( strs );
450     return result < 0 ? 0 : ( result > 255 ? 255 : result );
451 }
452 
GetDefense(void) const453 int Heroes::GetDefense( void ) const
454 {
455     return GetDefense( nullptr );
456 }
457 
GetDefense(std::string * strs) const458 int Heroes::GetDefense( std::string * strs ) const
459 {
460     int result = defense + GetDefenseModificator( strs );
461     return result < 0 ? 0 : ( result > 255 ? 255 : result );
462 }
463 
GetPower(void) const464 int Heroes::GetPower( void ) const
465 {
466     return GetPower( nullptr );
467 }
468 
GetPower(std::string * strs) const469 int Heroes::GetPower( std::string * strs ) const
470 {
471     const int result = power + GetPowerModificator( strs );
472     return result < 1 ? 1 : ( result > 255 ? 255 : result );
473 }
474 
GetKnowledge(void) const475 int Heroes::GetKnowledge( void ) const
476 {
477     return GetKnowledge( nullptr );
478 }
479 
GetKnowledge(std::string * strs) const480 int Heroes::GetKnowledge( std::string * strs ) const
481 {
482     int result = knowledge + GetKnowledgeModificator( strs );
483     return result < 0 ? 0 : ( result > 255 ? 255 : result );
484 }
485 
IncreasePrimarySkill(int skill)486 void Heroes::IncreasePrimarySkill( int skill )
487 {
488     switch ( skill ) {
489     case Skill::Primary::ATTACK:
490         ++attack;
491         break;
492     case Skill::Primary::DEFENSE:
493         ++defense;
494         break;
495     case Skill::Primary::POWER:
496         ++power;
497         break;
498     case Skill::Primary::KNOWLEDGE:
499         ++knowledge;
500         break;
501     default:
502         break;
503     }
504 }
505 
GetExperience(void) const506 u32 Heroes::GetExperience( void ) const
507 {
508     return experience;
509 }
510 
IncreaseMovePoints(u32 point)511 void Heroes::IncreaseMovePoints( u32 point )
512 {
513     move_point += point;
514 }
515 
GetMovePoints(void) const516 u32 Heroes::GetMovePoints( void ) const
517 {
518     return move_point;
519 }
520 
GetMaxSpellPoints(void) const521 u32 Heroes::GetMaxSpellPoints( void ) const
522 {
523     return 10 * GetKnowledge();
524 }
525 
GetMaxMovePoints(void) const526 u32 Heroes::GetMaxMovePoints( void ) const
527 {
528     uint32_t point = 0;
529 
530     // start point
531     if ( isShipMaster() ) {
532         point = 1500;
533 
534         // skill navigation
535         point = UpdateMovementPoints( point, Skill::Secondary::NAVIGATION );
536 
537         // artifact bonus
538         point += artifactCount( Artifact::SAILORS_ASTROLABE_MOBILITY ) * 1000;
539 
540         // visited object
541         point += 500 * world.CountCapturedObject( MP2::OBJ_LIGHTHOUSE, GetColor() );
542     }
543     else {
544         const Troop * troop = army.GetSlowestTroop();
545 
546         if ( troop )
547             switch ( troop->GetSpeed() ) {
548             default:
549                 break;
550             case Speed::CRAWLING:
551             case Speed::VERYSLOW:
552                 point = 1000;
553                 break;
554             case Speed::SLOW:
555                 point = 1100;
556                 break;
557             case Speed::AVERAGE:
558                 point = 1200;
559                 break;
560             case Speed::FAST:
561                 point = 1300;
562                 break;
563             case Speed::VERYFAST:
564                 point = 1400;
565                 break;
566             case Speed::ULTRAFAST:
567             case Speed::BLAZING:
568             case Speed::INSTANT:
569                 point = 1500;
570                 break;
571             }
572 
573         // skill logistics
574         point = UpdateMovementPoints( point, Skill::Secondary::LOGISTICS );
575 
576         // artifact bonus
577         point += artifactCount( Artifact::NOMAD_BOOTS_MOBILITY ) * 600;
578         point += artifactCount( Artifact::TRAVELER_BOOTS_MOBILITY ) * 300;
579 
580         // visited object
581         if ( isObjectTypeVisited( MP2::OBJ_STABLES ) )
582             point += 400;
583     }
584 
585     point += artifactCount( Artifact::TRUE_COMPASS_MOBILITY ) * 500;
586 
587     if ( isControlAI() ) {
588         point += Difficulty::GetHeroMovementBonus( Game::getDifficulty() );
589     }
590 
591     return point;
592 }
593 
GetMorale(void) const594 int Heroes::GetMorale( void ) const
595 {
596     return GetMoraleWithModificators( nullptr );
597 }
598 
GetMoraleWithModificators(std::string * strs) const599 int Heroes::GetMoraleWithModificators( std::string * strs ) const
600 {
601     int result = Morale::NORMAL;
602 
603     // bonus artifact
604     result += GetMoraleModificator( strs );
605 
606     // bonus leadership
607     result += Skill::GetLeadershipModifiers( GetLevelSkill( Skill::Secondary::LEADERSHIP ), strs );
608 
609     // object visited
610     const std::vector<MP2::MapObjectType> objectTypes{ MP2::OBJ_BUOY,      MP2::OBJ_OASIS,        MP2::OBJ_WATERINGHOLE, MP2::OBJ_TEMPLE,
611                                                        MP2::OBJ_GRAVEYARD, MP2::OBJ_DERELICTSHIP, MP2::OBJ_SHIPWRECK };
612     result += ObjectVisitedModifiersResult( objectTypes, *this, strs );
613 
614     // result
615     return Morale::Normalize( result );
616 }
617 
GetLuck(void) const618 int Heroes::GetLuck( void ) const
619 {
620     return GetLuckWithModificators( nullptr );
621 }
622 
GetLuckWithModificators(std::string * strs) const623 int Heroes::GetLuckWithModificators( std::string * strs ) const
624 {
625     int result = Luck::NORMAL;
626 
627     // bonus artifact
628     result += GetLuckModificator( strs );
629 
630     // bonus luck
631     result += Skill::GetLuckModifiers( GetLevelSkill( Skill::Secondary::LUCK ), strs );
632 
633     // object visited
634     const std::vector<MP2::MapObjectType> objectTypes{ MP2::OBJ_MERMAID, MP2::OBJ_FAERIERING, MP2::OBJ_FOUNTAIN, MP2::OBJ_IDOL, MP2::OBJ_PYRAMID };
635     result += ObjectVisitedModifiersResult( objectTypes, *this, strs );
636 
637     return Luck::Normalize( result );
638 }
639 
640 /* recrut hero */
Recruit(int cl,const fheroes2::Point & pt)641 bool Heroes::Recruit( int cl, const fheroes2::Point & pt )
642 {
643     if ( GetColor() != Color::NONE ) {
644         DEBUG_LOG( DBG_GAME, DBG_WARN, "not freeman" );
645         return false;
646     }
647 
648     Kingdom & kingdom = world.GetKingdom( cl );
649 
650     if ( kingdom.AllowRecruitHero( false, 0 ) ) {
651         Maps::Tiles & tiles = world.GetTiles( pt.x, pt.y );
652         SetColor( cl );
653         killer_color.SetColor( Color::NONE );
654         SetCenter( pt );
655         setDirection( Direction::RIGHT );
656         if ( !Modes( SAVE_MP_POINTS ) )
657             move_point = GetMaxMovePoints();
658         MovePointsScaleFixed();
659 
660         if ( !army.isValid() )
661             army.Reset( false );
662 
663         tiles.SetHeroes( this );
664         kingdom.AddHeroes( this );
665 
666         return true;
667     }
668 
669     return false;
670 }
671 
Recruit(const Castle & castle)672 bool Heroes::Recruit( const Castle & castle )
673 {
674     if ( Recruit( castle.GetColor(), castle.GetCenter() ) ) {
675         if ( castle.GetLevelMageGuild() ) {
676             // learn spells
677             castle.MageGuildEducateHero( *this );
678         }
679         SetVisited( GetIndex() );
680         return true;
681     }
682 
683     return false;
684 }
685 
ActionNewDay(void)686 void Heroes::ActionNewDay( void )
687 {
688     // recovery move points
689     move_point = GetMaxMovePoints();
690     MovePointsScaleFixed();
691 
692     // replenish spell points
693     ReplenishSpellPoints();
694 
695     // remove day visit object
696     visit_object.remove_if( Visit::isDayLife );
697 
698     // new day, new capacities
699     ResetModes( SAVE_MP_POINTS );
700 }
701 
ActionNewWeek(void)702 void Heroes::ActionNewWeek( void )
703 {
704     // remove week visit object
705     visit_object.remove_if( Visit::isWeekLife );
706 }
707 
ActionNewMonth(void)708 void Heroes::ActionNewMonth( void )
709 {
710     // remove month visit object
711     visit_object.remove_if( Visit::isMonthLife );
712 }
713 
ActionAfterBattle(void)714 void Heroes::ActionAfterBattle( void )
715 {
716     // remove month visit object
717     visit_object.remove_if( Visit::isBattleLife );
718 
719     SetModes( ACTION );
720 }
721 
ReplenishSpellPoints()722 void Heroes::ReplenishSpellPoints()
723 {
724     const uint32_t maxp = GetMaxSpellPoints();
725     uint32_t curr = GetSpellPoints();
726 
727     // spell points may be doubled in artesian spring, leave as is
728     if ( curr >= maxp ) {
729         return;
730     }
731 
732     const Castle * castle = inCastle();
733 
734     // in castle?
735     if ( castle && castle->GetLevelMageGuild() ) {
736         // restore from mage guild
737         if ( Settings::Get().ExtCastleGuildRestorePointsTurn() ) {
738             curr += maxp * GameStatic::GetMageGuildRestoreSpellPointsPercentDay( castle->GetLevelMageGuild() ) / 100;
739         }
740         else {
741             curr = maxp;
742         }
743     }
744 
745     // everyday
746     curr += GameStatic::GetHeroesRestoreSpellPointsPerDay();
747 
748     // power ring action
749     curr += artifactCount( Artifact::POWER_RING ) * Artifact( Artifact::POWER_RING ).ExtraValue();
750 
751     // secondary skill
752     curr += GetSecondaryValues( Skill::Secondary::MYSTICISM );
753 
754     SetSpellPoints( std::min( curr, maxp ) );
755 }
756 
RescanPathPassable(void)757 void Heroes::RescanPathPassable( void )
758 {
759     if ( path.isValid() )
760         path.RescanPassable();
761 }
762 
RescanPath(void)763 void Heroes::RescanPath( void )
764 {
765     if ( !path.isValid() )
766         path.clear();
767 
768     if ( path.isValid() ) {
769         const Maps::Tiles & tile = world.GetTiles( path.GetDestinationIndex() );
770 
771         if ( !isShipMaster() && tile.isWater() && !MP2::isAccessibleFromBeach( tile.GetObject() ) )
772             path.PopBack();
773     }
774 
775     if ( path.isValid() ) {
776         if ( isControlAI() ) {
777             if ( path.hasObstacle() )
778                 path.Reset();
779         }
780         else {
781             path.RescanObstacle();
782         }
783     }
784 }
785 
786 /* if hero in castle */
inCastle() const787 const Castle * Heroes::inCastle() const
788 {
789     return inCastleMutable();
790 }
791 
inCastleMutable() const792 Castle * Heroes::inCastleMutable() const
793 {
794     if ( GetColor() == Color::NONE ) {
795         return nullptr;
796     }
797 
798     if ( Modes( Heroes::GUARDIAN ) ) {
799         const fheroes2::Point & heroPoint = GetCenter();
800         const fheroes2::Point castlePoint( heroPoint.x, heroPoint.y + 1 );
801 
802         Castle * castle = world.getCastleEntrance( castlePoint );
803 
804         return castle && castle->GetHeroes() == this ? castle : nullptr;
805     }
806 
807     Castle * castle = world.getCastleEntrance( GetCenter() );
808 
809     return castle && castle->GetHeroes() == this ? castle : nullptr;
810 }
811 
isVisited(const Maps::Tiles & tile,Visit::type_t type) const812 bool Heroes::isVisited( const Maps::Tiles & tile, Visit::type_t type ) const
813 {
814     const int32_t index = tile.GetIndex();
815     const MP2::MapObjectType objectType = tile.GetObject( false );
816 
817     if ( Visit::GLOBAL == type )
818         return GetKingdom().isVisited( index, objectType );
819 
820     return visit_object.end() != std::find( visit_object.begin(), visit_object.end(), IndexObject( index, objectType ) );
821 }
822 
isObjectTypeVisited(const MP2::MapObjectType objectType,Visit::type_t type) const823 bool Heroes::isObjectTypeVisited( const MP2::MapObjectType objectType, Visit::type_t type ) const
824 {
825     if ( Visit::GLOBAL == type )
826         return GetKingdom().isVisited( objectType );
827 
828     return std::any_of( visit_object.begin(), visit_object.end(), [objectType]( const IndexObject & v ) { return v.isObject( objectType ); } );
829 }
830 
SetVisited(s32 index,Visit::type_t type)831 void Heroes::SetVisited( s32 index, Visit::type_t type )
832 {
833     const Maps::Tiles & tile = world.GetTiles( index );
834     const MP2::MapObjectType objectType = tile.GetObject( false );
835 
836     if ( Visit::GLOBAL == type ) {
837         GetKingdom().SetVisited( index, objectType );
838     }
839     else if ( !isVisited( tile ) && MP2::OBJ_ZERO != objectType ) {
840         visit_object.push_front( IndexObject( index, objectType ) );
841     }
842 }
843 
setVisitedForAllies(const int32_t tileIndex) const844 void Heroes::setVisitedForAllies( const int32_t tileIndex ) const
845 {
846     const Maps::Tiles & tile = world.GetTiles( tileIndex );
847     const MP2::MapObjectType objectType = tile.GetObject( false );
848 
849     // Set visited to all allies as well.
850     const Colors friendColors( Players::GetPlayerFriends( GetColor() ) );
851     for ( const int friendColor : friendColors ) {
852         world.GetKingdom( friendColor ).SetVisited( tileIndex, objectType );
853     }
854 }
855 
SetVisitedWideTile(s32 index,const MP2::MapObjectType objectType,Visit::type_t type)856 void Heroes::SetVisitedWideTile( s32 index, const MP2::MapObjectType objectType, Visit::type_t type )
857 {
858     const Maps::Tiles & tile = world.GetTiles( index );
859     const uint32_t uid = tile.GetObjectUID();
860     int wide = 0;
861 
862     switch ( objectType ) {
863     case MP2::OBJ_SKELETON:
864     case MP2::OBJ_OASIS:
865     case MP2::OBJ_STANDINGSTONES:
866     case MP2::OBJ_ARTESIANSPRING:
867         wide = 2;
868         break;
869     case MP2::OBJ_WATERINGHOLE:
870         wide = 4;
871         break;
872     default:
873         break;
874     }
875 
876     if ( tile.GetObject( false ) == objectType && wide ) {
877         for ( s32 ii = tile.GetIndex() - ( wide - 1 ); ii <= tile.GetIndex() + ( wide - 1 ); ++ii )
878             if ( Maps::isValidAbsIndex( ii ) && world.GetTiles( ii ).GetObjectUID() == uid )
879                 SetVisited( ii, type );
880     }
881 }
882 
markHeroMeeting(int heroID)883 void Heroes::markHeroMeeting( int heroID )
884 {
885     if ( heroID < UNKNOWN && !hasMetWithHero( heroID ) )
886         visit_object.push_front( IndexObject( heroID, MP2::OBJ_HEROES ) );
887 }
888 
unmarkHeroMeeting()889 void Heroes::unmarkHeroMeeting()
890 {
891     const KingdomHeroes & heroes = GetKingdom().GetHeroes();
892     for ( Heroes * hero : heroes ) {
893         if ( hero == nullptr || hero == this ) {
894             continue;
895         }
896 
897         hero->visit_object.remove( IndexObject( hid, MP2::OBJ_HEROES ) );
898         visit_object.remove( IndexObject( hero->hid, MP2::OBJ_HEROES ) );
899     }
900 }
901 
hasMetWithHero(int heroID) const902 bool Heroes::hasMetWithHero( int heroID ) const
903 {
904     return visit_object.end() != std::find( visit_object.begin(), visit_object.end(), IndexObject( heroID, MP2::OBJ_HEROES ) );
905 }
906 
GetSpriteIndex(void) const907 int Heroes::GetSpriteIndex( void ) const
908 {
909     return sprite_index;
910 }
911 
SetSpriteIndex(int index)912 void Heroes::SetSpriteIndex( int index )
913 {
914     sprite_index = index;
915 }
916 
SetOffset(const fheroes2::Point & offset)917 void Heroes::SetOffset( const fheroes2::Point & offset )
918 {
919     _offset = offset;
920 }
921 
isAction(void) const922 bool Heroes::isAction( void ) const
923 {
924     return Modes( ACTION );
925 }
926 
ResetAction(void)927 void Heroes::ResetAction( void )
928 {
929     ResetModes( ACTION );
930 }
931 
GetCountArtifacts(void) const932 u32 Heroes::GetCountArtifacts( void ) const
933 {
934     return bag_artifacts.CountArtifacts();
935 }
936 
HasUltimateArtifact(void) const937 bool Heroes::HasUltimateArtifact( void ) const
938 {
939     return bag_artifacts.ContainUltimateArtifact();
940 }
941 
IsFullBagArtifacts(void) const942 bool Heroes::IsFullBagArtifacts( void ) const
943 {
944     return bag_artifacts.isFull();
945 }
946 
PickupArtifact(const Artifact & art)947 bool Heroes::PickupArtifact( const Artifact & art )
948 {
949     if ( !art.isValid() )
950         return false;
951 
952     if ( !bag_artifacts.PushArtifact( art ) ) {
953         if ( isControlHuman() ) {
954             art.GetID() == Artifact::MAGIC_BOOK ? Dialog::Message(
955                 GetName(),
956                 _( "You must purchase a spell book to use the mage guild, but you currently have no room for a spell book. Try giving one of your artifacts to another hero." ),
957                 Font::BIG, Dialog::OK )
958                                                 : Dialog::Message( art.GetName(), _( "You cannot pick up this artifact, you already have a full load!" ), Font::BIG,
959                                                                    Dialog::OK );
960         }
961         return false;
962     }
963 
964     // check: artifact sets such as anduran garb
965     const auto assembledArtifacts = bag_artifacts.assembleArtifactSetIfPossible();
966     if ( isControlHuman() ) {
967         for ( const ArtifactSetData & artifactSetData : assembledArtifacts )
968             Dialog::ArtifactInfo( "", artifactSetData._assembleMessage, artifactSetData._assembledArtifactID );
969     }
970 
971     return true;
972 }
973 
974 /* return level hero */
GetLevel(void) const975 int Heroes::GetLevel( void ) const
976 {
977     return GetLevelFromExperience( experience );
978 }
979 
GetPath(void) const980 const Route::Path & Heroes::GetPath( void ) const
981 {
982     return path;
983 }
984 
GetPath(void)985 Route::Path & Heroes::GetPath( void )
986 {
987     return path;
988 }
989 
ShowPath(bool f)990 void Heroes::ShowPath( bool f )
991 {
992     f ? path.Show() : path.Hide();
993 }
994 
IncreaseExperience(const uint32_t amount,const bool autoselect)995 void Heroes::IncreaseExperience( const uint32_t amount, const bool autoselect )
996 {
997     int oldLevel = GetLevelFromExperience( experience );
998     int newLevel = GetLevelFromExperience( experience + amount );
999 
1000     for ( int level = oldLevel; level < newLevel; ++level )
1001         LevelUp( false, autoselect );
1002 
1003     experience += amount;
1004 }
1005 
1006 /* calc level from exp */
GetLevelFromExperience(u32 exp)1007 int Heroes::GetLevelFromExperience( u32 exp )
1008 {
1009     for ( int lvl = 1; lvl < 255; ++lvl )
1010         if ( exp < GetExperienceFromLevel( lvl ) )
1011             return lvl;
1012 
1013     return 0;
1014 }
1015 
1016 /* calc exp from level */
GetExperienceFromLevel(int lvl)1017 u32 Heroes::GetExperienceFromLevel( int lvl )
1018 {
1019     switch ( lvl ) {
1020     case 0:
1021         return 0;
1022     case 1:
1023         return 1000;
1024     case 2:
1025         return 2000;
1026     case 3:
1027         return 3200;
1028     case 4:
1029         return 4500;
1030     case 5:
1031         return 6000;
1032     case 6:
1033         return 7700;
1034     case 7:
1035         return 9000;
1036     case 8:
1037         return 11000;
1038     case 9:
1039         return 13200;
1040     case 10:
1041         return 15500;
1042     case 11:
1043         return 18500;
1044     case 12:
1045         return 22100;
1046     case 13:
1047         return 26400;
1048     case 14:
1049         return 31600;
1050     case 15:
1051         return 37800;
1052     case 16:
1053         return 45300;
1054     case 17:
1055         return 54200;
1056     case 18:
1057         return 65000;
1058     case 19:
1059         return 78000;
1060     case 20:
1061         return 93600;
1062     case 21:
1063         return 112300;
1064     case 22:
1065         return 134700;
1066     case 23:
1067         return 161600;
1068     case 24:
1069         return 193900;
1070     case 25:
1071         return 232700;
1072     case 26:
1073         return 279300;
1074     case 27:
1075         return 335200;
1076     case 28:
1077         return 402300;
1078     case 29:
1079         return 482800;
1080     case 30:
1081         return 579400;
1082     case 31:
1083         return 695300;
1084     case 32:
1085         return 834400;
1086     case 33:
1087         return 1001300;
1088     case 34:
1089         return 1201600;
1090     case 35:
1091         return 1442000;
1092     case 36:
1093         return 1730500;
1094     case 37:
1095         return 2076700;
1096     case 38:
1097         return 2492100;
1098     case 39:
1099         return 2990600;
1100 
1101     default:
1102         break;
1103     }
1104 
1105     const u32 l1 = GetExperienceFromLevel( lvl - 1 );
1106     return ( l1 + static_cast<u32>( round( ( l1 - GetExperienceFromLevel( lvl - 2 ) ) * 1.2 / 100 ) * 100 ) );
1107 }
1108 
1109 /* buy book */
BuySpellBook(const Castle * castle,int shrine)1110 bool Heroes::BuySpellBook( const Castle * castle, int shrine )
1111 {
1112     if ( HaveSpellBook() || Color::NONE == GetColor() )
1113         return false;
1114 
1115     const payment_t payment = PaymentConditions::BuySpellBook( shrine );
1116     Kingdom & kingdom = GetKingdom();
1117 
1118     std::string header = _( "To cast spells, you must first buy a spell book for %{gold} gold." );
1119     StringReplace( header, "%{gold}", payment.gold );
1120 
1121     if ( !kingdom.AllowPayment( payment ) ) {
1122         if ( isControlHuman() ) {
1123             const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::RESOURCE, 7 );
1124             fheroes2::Sprite sprite = border;
1125             fheroes2::Blit( fheroes2::AGG::GetICN( ICN::ARTIFACT, Artifact( Artifact::MAGIC_BOOK ).IndexSprite64() ), sprite, 5, 5 );
1126 
1127             header.append( " " );
1128             header.append( _( "Unfortunately, you seem to be a little short of cash at the moment." ) );
1129             Dialog::SpriteInfo( "", header, sprite, Dialog::OK );
1130         }
1131         return false;
1132     }
1133 
1134     if ( isControlHuman() ) {
1135         const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::RESOURCE, 7 );
1136         fheroes2::Sprite sprite = border;
1137 
1138         fheroes2::Blit( fheroes2::AGG::GetICN( ICN::ARTIFACT, Artifact( Artifact::MAGIC_BOOK ).IndexSprite64() ), sprite, 5, 5 );
1139 
1140         header.append( " " );
1141         header.append( _( "Do you wish to buy one?" ) );
1142 
1143         if ( Dialog::NO == Dialog::SpriteInfo( GetName(), header, sprite, Dialog::YES | Dialog::NO ) )
1144             return false;
1145     }
1146 
1147     if ( SpellBookActivate() ) {
1148         kingdom.OddFundsResource( payment );
1149 
1150         // add all spell to book
1151         if ( castle )
1152             castle->MageGuildEducateHero( *this );
1153 
1154         return true;
1155     }
1156 
1157     return false;
1158 }
1159 
1160 /* return true is move enable */
isMoveEnabled(void) const1161 bool Heroes::isMoveEnabled( void ) const
1162 {
1163     return Modes( ENABLEMOVE ) && path.isValid() && path.hasAllowedSteps();
1164 }
1165 
CanMove(void) const1166 bool Heroes::CanMove( void ) const
1167 {
1168     const Maps::Tiles & tile = world.GetTiles( GetIndex() );
1169     return move_point >= ( tile.isRoad() ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( tile, GetLevelSkill( Skill::Secondary::PATHFINDING ) ) );
1170 }
1171 
1172 /* set enable move */
SetMove(bool f)1173 void Heroes::SetMove( bool f )
1174 {
1175     if ( f ) {
1176         ResetModes( SLEEPER );
1177 
1178         SetModes( ENABLEMOVE );
1179     }
1180     else {
1181         ResetModes( ENABLEMOVE );
1182 
1183         // reset sprite position
1184         switch ( direction ) {
1185         case Direction::TOP:
1186             sprite_index = 0;
1187             break;
1188         case Direction::BOTTOM:
1189             sprite_index = 36;
1190             break;
1191         case Direction::TOP_RIGHT:
1192         case Direction::TOP_LEFT:
1193             sprite_index = 9;
1194             break;
1195         case Direction::BOTTOM_RIGHT:
1196         case Direction::BOTTOM_LEFT:
1197             sprite_index = 27;
1198             break;
1199         case Direction::RIGHT:
1200         case Direction::LEFT:
1201             sprite_index = 18;
1202             break;
1203         default:
1204             break;
1205         }
1206     }
1207 }
1208 
isShipMaster(void) const1209 bool Heroes::isShipMaster( void ) const
1210 {
1211     return Modes( SHIPMASTER );
1212 }
1213 
SetShipMaster(bool f)1214 void Heroes::SetShipMaster( bool f )
1215 {
1216     f ? SetModes( SHIPMASTER ) : ResetModes( SHIPMASTER );
1217 }
1218 
lastGroundRegion() const1219 uint32_t Heroes::lastGroundRegion() const
1220 {
1221     return _lastGroundRegion;
1222 }
1223 
setLastGroundRegion(uint32_t regionID)1224 void Heroes::setLastGroundRegion( uint32_t regionID )
1225 {
1226     _lastGroundRegion = regionID;
1227 }
1228 
GetSecondarySkills(void)1229 Skill::SecSkills & Heroes::GetSecondarySkills( void )
1230 {
1231     return secondary_skills;
1232 }
1233 
HasSecondarySkill(int skill) const1234 bool Heroes::HasSecondarySkill( int skill ) const
1235 {
1236     return Skill::Level::NONE != secondary_skills.GetLevel( skill );
1237 }
1238 
GetSecondaryValues(int skill) const1239 u32 Heroes::GetSecondaryValues( int skill ) const
1240 {
1241     return secondary_skills.GetValues( skill );
1242 }
1243 
HasMaxSecondarySkill(void) const1244 bool Heroes::HasMaxSecondarySkill( void ) const
1245 {
1246     return HEROESMAXSKILL <= secondary_skills.Count();
1247 }
1248 
GetLevelSkill(int skill) const1249 int Heroes::GetLevelSkill( int skill ) const
1250 {
1251     return secondary_skills.GetLevel( skill );
1252 }
1253 
LearnSkill(const Skill::Secondary & skill)1254 void Heroes::LearnSkill( const Skill::Secondary & skill )
1255 {
1256     if ( skill.isValid() )
1257         secondary_skills.AddSkill( skill );
1258 }
1259 
Scoute(const int tileIndex) const1260 void Heroes::Scoute( const int tileIndex ) const
1261 {
1262     Maps::ClearFog( tileIndex, GetScoute(), GetColor() );
1263 }
1264 
GetScoute(void) const1265 int Heroes::GetScoute( void ) const
1266 {
1267     return static_cast<int>( artifactCount( Artifact::TELESCOPE ) * Game::GetViewDistance( Game::VIEW_TELESCOPE ) + Game::GetViewDistance( Game::VIEW_HEROES )
1268                              + GetSecondaryValues( Skill::Secondary::SCOUTING ) );
1269 }
1270 
UpdateMovementPoints(const uint32_t movePoints,const int skill) const1271 uint32_t Heroes::UpdateMovementPoints( const uint32_t movePoints, const int skill ) const
1272 {
1273     const int level = GetLevelSkill( skill );
1274     if ( level == Skill::Level::NONE )
1275         return movePoints;
1276 
1277     const uint32_t skillValue = GetSecondaryValues( skill );
1278 
1279     if ( skillValue == 33 ) {
1280         return movePoints * 4 / 3;
1281     }
1282     else if ( skillValue == 66 ) {
1283         return movePoints * 5 / 3;
1284     }
1285 
1286     return movePoints + skillValue * movePoints / 100;
1287 }
1288 
GetVisionsDistance(void) const1289 u32 Heroes::GetVisionsDistance( void ) const
1290 {
1291     return 8 * std::max( 1U, artifactCount( Artifact::CRYSTAL_BALL ) );
1292 }
1293 
GetDirection(void) const1294 int Heroes::GetDirection( void ) const
1295 {
1296     return direction;
1297 }
1298 
setDirection(int directionToSet)1299 void Heroes::setDirection( int directionToSet )
1300 {
1301     if ( directionToSet != Direction::UNKNOWN )
1302         direction = directionToSet;
1303 }
1304 
1305 /* return route range in days */
GetRangeRouteDays(s32 dst) const1306 int Heroes::GetRangeRouteDays( s32 dst ) const
1307 {
1308     const u32 maxMovePoints = GetMaxMovePoints();
1309 
1310     uint32_t total = world.getDistance( *this, dst );
1311     DEBUG_LOG( DBG_GAME, DBG_TRACE, "path distance: " << total );
1312 
1313     if ( total > 0 ) {
1314         // check if last step is diagonal and pre-adjust the total
1315         const Route::Step lastStep = world.getPath( *this, dst ).back();
1316         if ( Direction::isDiagonal( lastStep.GetDirection() ) ) {
1317             total -= lastStep.GetPenalty() / 3;
1318         }
1319 
1320         if ( move_point >= total )
1321             return 1;
1322 
1323         total -= move_point;
1324 
1325         int moveDays = 2;
1326         while ( moveDays < 8 ) {
1327             if ( maxMovePoints >= total )
1328                 return moveDays;
1329 
1330             total -= maxMovePoints;
1331             ++moveDays;
1332         }
1333 
1334         return 8;
1335     }
1336 
1337     DEBUG_LOG( DBG_GAME, DBG_TRACE, "unreachable point: " << dst );
1338     return 0;
1339 }
1340 
LevelUp(bool skipsecondary,bool autoselect)1341 void Heroes::LevelUp( bool skipsecondary, bool autoselect )
1342 {
1343     const HeroSeedsForLevelUp seeds = GetSeedsForLevelUp();
1344 
1345     // level up primary skill
1346     const int primarySkill = Skill::Primary::LevelUp( _race, GetLevel(), seeds.seedPrimarySkill );
1347 
1348     DEBUG_LOG( DBG_GAME, DBG_INFO, "for " << GetName() << ", up " << Skill::Primary::String( primarySkill ) );
1349 
1350     if ( !skipsecondary )
1351         LevelUpSecondarySkill( seeds, primarySkill, ( autoselect || isControlAI() ) );
1352     if ( isControlAI() )
1353         AI::Get().HeroesLevelUp( *this );
1354 }
1355 
LevelUpSecondarySkill(const HeroSeedsForLevelUp & seeds,int primary,bool autoselect)1356 void Heroes::LevelUpSecondarySkill( const HeroSeedsForLevelUp & seeds, int primary, bool autoselect )
1357 {
1358     Skill::Secondary sec1;
1359     Skill::Secondary sec2;
1360 
1361     secondary_skills.FindSkillsForLevelUp( _race, seeds.seedSecondaySkill1, seeds.seedSecondaySkill2, sec1, sec2 );
1362     DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << " select " << Skill::Secondary::String( sec1.Skill() ) << " or " << Skill::Secondary::String( sec2.Skill() ) );
1363 
1364     Skill::Secondary selected;
1365 
1366     if ( autoselect ) {
1367         if ( sec1.isValid() && sec2.isValid() ) {
1368             selected = Rand::GetWithSeed( 0, 1, seeds.seedSecondaySkillRandomChoose ) ? sec1 : sec2;
1369         }
1370         else {
1371             selected = sec1.isValid() ? sec1 : sec2;
1372         }
1373     }
1374     else {
1375         AGG::PlaySound( M82::NWHEROLV );
1376         int result = Dialog::LevelUpSelectSkill( name, Skill::Primary::String( primary ), sec1, sec2, *this );
1377 
1378         if ( Skill::Secondary::UNKNOWN != result )
1379             selected = result == sec2.Skill() ? sec2 : sec1;
1380     }
1381 
1382     // level up sec. skill
1383     if ( selected.isValid() ) {
1384         DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << ", selected: " << Skill::Secondary::String( selected.Skill() ) );
1385         Skill::Secondary * secs = secondary_skills.FindSkill( selected.Skill() );
1386 
1387         if ( secs )
1388             secs->NextLevel();
1389         else
1390             secondary_skills.AddSkill( Skill::Secondary( selected.Skill(), Skill::Level::BASIC ) );
1391 
1392         // post action
1393         if ( selected.Skill() == Skill::Secondary::SCOUTING ) {
1394             Scoute( GetIndex() );
1395         }
1396     }
1397 }
1398 
1399 /* apply penalty */
ApplyPenaltyMovement(uint32_t penalty)1400 void Heroes::ApplyPenaltyMovement( uint32_t penalty )
1401 {
1402     if ( move_point >= penalty )
1403         move_point -= penalty;
1404     else
1405         move_point = 0;
1406 }
1407 
ResetMovePoints(void)1408 void Heroes::ResetMovePoints( void )
1409 {
1410     move_point = 0;
1411 }
1412 
MayStillMove(const bool ignorePath,const bool ignoreSleeper) const1413 bool Heroes::MayStillMove( const bool ignorePath, const bool ignoreSleeper ) const
1414 {
1415     if ( Modes( GUARDIAN ) || isFreeman() ) {
1416         return false;
1417     }
1418 
1419     if ( !ignoreSleeper && Modes( SLEEPER ) ) {
1420         return false;
1421     }
1422 
1423     if ( path.isValid() && !ignorePath ) {
1424         return path.hasAllowedSteps();
1425     }
1426 
1427     return CanMove();
1428 }
1429 
MayCastAdventureSpells() const1430 bool Heroes::MayCastAdventureSpells() const
1431 {
1432     return !Modes( GUARDIAN ) && !isFreeman();
1433 }
1434 
isValid(void) const1435 bool Heroes::isValid( void ) const
1436 {
1437     return hid != UNKNOWN;
1438 }
1439 
isFreeman(void) const1440 bool Heroes::isFreeman( void ) const
1441 {
1442     return isValid() && Color::NONE == GetColor() && !Modes( JAIL );
1443 }
1444 
SetFreeman(int reason)1445 void Heroes::SetFreeman( int reason )
1446 {
1447     if ( !isFreeman() ) {
1448         bool savepoints = false;
1449         Kingdom & kingdom = GetKingdom();
1450 
1451         if ( ( Battle::RESULT_RETREAT | Battle::RESULT_SURRENDER ) & reason ) {
1452             if ( Settings::Get().ExtHeroRememberPointsForRetreating() )
1453                 savepoints = true;
1454             kingdom.SetLastLostHero( *this );
1455         }
1456 
1457         // if not surrendering, reset army
1458         if ( ( reason & Battle::RESULT_SURRENDER ) == 0 )
1459             army.Reset( true );
1460 
1461         if ( GetColor() != Color::NONE )
1462             kingdom.RemoveHeroes( this );
1463 
1464         SetColor( Color::NONE );
1465         world.GetTiles( GetIndex() ).SetHeroes( nullptr );
1466         modes = 0;
1467         SetIndex( -1 );
1468         move_point_scale = -1;
1469         path.Reset();
1470         SetMove( false );
1471         SetModes( ACTION );
1472         if ( savepoints )
1473             SetModes( SAVE_MP_POINTS );
1474     }
1475 }
1476 
SetKillerColor(int col)1477 void Heroes::SetKillerColor( int col )
1478 {
1479     killer_color.SetColor( col );
1480 }
1481 
GetKillerColor(void) const1482 int Heroes::GetKillerColor( void ) const
1483 {
1484     return killer_color.GetColor();
1485 }
1486 
GetControl(void) const1487 int Heroes::GetControl( void ) const
1488 {
1489     return GetKingdom().GetControl();
1490 }
1491 
GetStartingXp()1492 uint32_t Heroes::GetStartingXp()
1493 {
1494     return Rand::Get( 40, 90 );
1495 }
1496 
GetMapsObject(void) const1497 MP2::MapObjectType Heroes::GetMapsObject( void ) const
1498 {
1499     return static_cast<MP2::MapObjectType>( save_maps_object );
1500 }
1501 
SetMapsObject(const MP2::MapObjectType objectType)1502 void Heroes::SetMapsObject( const MP2::MapObjectType objectType )
1503 {
1504     save_maps_object = objectType != MP2::OBJ_HEROES ? objectType : MP2::OBJ_ZERO;
1505 }
1506 
ActionPreBattle()1507 void Heroes::ActionPreBattle()
1508 {
1509     // Do nothing.
1510 }
1511 
ActionNewPosition(const bool allowMonsterAttack)1512 void Heroes::ActionNewPosition( const bool allowMonsterAttack )
1513 {
1514     if ( allowMonsterAttack ) {
1515         // scan for monsters around
1516         const MapsIndexes targets = Maps::GetTilesUnderProtection( GetIndex() );
1517 
1518         if ( !targets.empty() ) {
1519             SetMove( false );
1520             GetPath().Hide();
1521 
1522             // first fight the monsters on the destination tile (if any)
1523             MapsIndexes::const_iterator it = std::find( targets.begin(), targets.end(), GetPath().GetDestinedIndex() );
1524 
1525             if ( it != targets.end() ) {
1526                 Action( *it, true );
1527             }
1528             // otherwise fight the monsters on the first adjacent tile
1529             else {
1530                 Action( targets.front(), true );
1531             }
1532         }
1533     }
1534 
1535     if ( !isFreeman() && GetMapsObject() == MP2::OBJ_EVENT ) {
1536         const MapEvent * event = world.GetMapEvent( GetCenter() );
1537 
1538         if ( event && event->isAllow( GetColor() ) ) {
1539             Action( GetIndex(), false );
1540             SetMove( false );
1541         }
1542     }
1543 
1544     if ( isControlAI() )
1545         AI::Get().HeroesActionNewPosition( *this );
1546 
1547     ResetModes( VISIONS );
1548 }
1549 
SetCenterPatrol(const fheroes2::Point & pt)1550 void Heroes::SetCenterPatrol( const fheroes2::Point & pt )
1551 {
1552     patrol_center = pt;
1553 }
1554 
GetCenterPatrol(void) const1555 const fheroes2::Point & Heroes::GetCenterPatrol( void ) const
1556 {
1557     return patrol_center;
1558 }
1559 
GetSquarePatrol(void) const1560 int Heroes::GetSquarePatrol( void ) const
1561 {
1562     return patrol_square;
1563 }
1564 
MovePointsScaleFixed(void)1565 void Heroes::MovePointsScaleFixed( void )
1566 {
1567     move_point_scale = move_point * 1000 / GetMaxMovePoints();
1568 }
1569 
1570 // Move hero to a new position. This function applies no action and no penalty
Move2Dest(const int32_t dstIndex)1571 void Heroes::Move2Dest( const int32_t dstIndex )
1572 {
1573     if ( dstIndex != GetIndex() ) {
1574         world.GetTiles( GetIndex() ).SetHeroes( nullptr );
1575         SetIndex( dstIndex );
1576         Scoute( dstIndex );
1577         world.GetTiles( dstIndex ).SetHeroes( this );
1578     }
1579 }
1580 
GetPortrait(int id,int type)1581 const fheroes2::Sprite & Heroes::GetPortrait( int id, int type )
1582 {
1583     if ( Heroes::UNKNOWN != id )
1584         switch ( type ) {
1585         case PORT_BIG:
1586             return fheroes2::AGG::GetICN( ICN::PORTxxxx( id ), 0 );
1587         case PORT_MEDIUM: {
1588             // Original ICN::PORTMEDI sprites are badly rendered. Instead of them we're getting high quality ICN:PORT00xx file and resize it to a smaller image.
1589             // TODO: find a better way to store these images, ideally in agg_image.cpp file.
1590             static std::map<int, fheroes2::Sprite> mediumSizePortait;
1591             auto iter = mediumSizePortait.find( id );
1592             if ( iter != mediumSizePortait.end() ) {
1593                 return iter->second;
1594             }
1595 
1596             const fheroes2::Sprite & original = fheroes2::AGG::GetICN( ICN::PORTxxxx( id ), 0 );
1597             fheroes2::Sprite output( 50, 47 );
1598             fheroes2::Resize( original, output );
1599 
1600             return mediumSizePortait.emplace( id, std::move( output ) ).first->second;
1601         }
1602         case PORT_SMALL:
1603             return Heroes::DEBUG_HERO > id ? fheroes2::AGG::GetICN( ICN::MINIPORT, id ) : fheroes2::AGG::GetICN( ICN::MINIPORT, BAX );
1604         default:
1605             break;
1606         }
1607 
1608     return fheroes2::AGG::GetICN( -1, 0 );
1609 }
1610 
GetPortrait(int type) const1611 const fheroes2::Sprite & Heroes::GetPortrait( int type ) const
1612 {
1613     return Heroes::GetPortrait( portrait, type );
1614 }
1615 
PortraitRedraw(const int32_t px,const int32_t py,const PortraitType type,fheroes2::Image & dstsf) const1616 void Heroes::PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const
1617 {
1618     const fheroes2::Sprite & port = GetPortrait( portrait, type );
1619     fheroes2::Point mp;
1620 
1621     if ( !port.empty() ) {
1622         if ( PORT_BIG == type ) {
1623             fheroes2::Blit( port, dstsf, px, py );
1624             mp.y = 2;
1625             mp.x = port.width() - 12;
1626         }
1627         else if ( PORT_MEDIUM == type ) {
1628             fheroes2::Blit( port, dstsf, px, py );
1629             mp.x = port.width() - 10;
1630         }
1631         else if ( PORT_SMALL == type ) {
1632             const fheroes2::Sprite & mobility = fheroes2::AGG::GetICN( ICN::MOBILITY, GetMobilityIndexSprite() );
1633             const fheroes2::Sprite & mana = fheroes2::AGG::GetICN( ICN::MANA, GetManaIndexSprite() );
1634 
1635             const int iconsw = Interface::IconsBar::GetItemWidth();
1636             const int iconsh = Interface::IconsBar::GetItemHeight();
1637             const int barw = 7;
1638 
1639             // background
1640             fheroes2::Fill( dstsf, px, py, iconsw, iconsh, 0 );
1641 
1642             // mobility
1643             const uint8_t blueColor = fheroes2::GetColorId( 15, 30, 120 );
1644             fheroes2::Fill( dstsf, px, py, barw, iconsh, blueColor );
1645             fheroes2::Blit( mobility, dstsf, px, py + mobility.y() );
1646 
1647             // portrait
1648             fheroes2::Blit( port, dstsf, px + barw + 1, py );
1649 
1650             // mana
1651             fheroes2::Fill( dstsf, px + barw + port.width() + 2, py, barw, iconsh, blueColor );
1652             fheroes2::Blit( mana, dstsf, px + barw + port.width() + 2, py + mana.y() );
1653 
1654             mp.x = 35;
1655         }
1656     }
1657 
1658     if ( Modes( Heroes::GUARDIAN ) ) {
1659         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MISC6, 11 );
1660         fheroes2::Image guardianBG( sprite.width(), sprite.height() );
1661         guardianBG.fill( 0 );
1662 
1663         fheroes2::Blit( guardianBG, dstsf, px + mp.x + 3, py + mp.y );
1664         fheroes2::Blit( sprite, dstsf, px + mp.x + 3, py + mp.y );
1665         mp.y = sprite.height();
1666     }
1667 
1668     if ( Modes( Heroes::SLEEPER ) ) {
1669         const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MISC4, 14 );
1670         fheroes2::Image sleeperBG( sprite.width() - 4, sprite.height() - 4 );
1671         sleeperBG.fill( 0 );
1672 
1673         fheroes2::Blit( sleeperBG, dstsf, px + mp.x + 3, py + mp.y - 1 );
1674         fheroes2::Blit( sprite, dstsf, px + mp.x + 1, py + mp.y - 3 );
1675     }
1676 }
1677 
String(void) const1678 std::string Heroes::String( void ) const
1679 {
1680     std::ostringstream os;
1681 
1682     os << "name            : " << name << std::endl
1683        << "race            : " << Race::String( _race ) << std::endl
1684        << "color           : " << Color::String( GetColor() ) << std::endl
1685        << "experience      : " << experience << std::endl
1686        << "level           : " << GetLevel() << std::endl
1687        << "magic point     : " << GetSpellPoints() << std::endl
1688        << "position x      : " << GetCenter().x << std::endl
1689        << "position y      : " << GetCenter().y << std::endl
1690        << "move point      : " << move_point << std::endl
1691        << "max magic point : " << GetMaxSpellPoints() << std::endl
1692        << "max move point  : " << GetMaxMovePoints() << std::endl
1693        << "direction       : " << Direction::String( direction ) << std::endl
1694        << "index sprite    : " << sprite_index << std::endl
1695        << "in castle       : " << ( inCastle() ? "true" : "false" ) << std::endl
1696        << "save object     : " << MP2::StringObject( world.GetTiles( GetIndex() ).GetObject( false ) ) << std::endl
1697        << "flags           : " << ( Modes( SHIPMASTER ) ? "SHIPMASTER," : "" ) << ( Modes( PATROL ) ? "PATROL" : "" ) << std::endl;
1698 
1699     if ( Modes( PATROL ) ) {
1700         os << "patrol square   : " << patrol_square << std::endl;
1701     }
1702 
1703     if ( !visit_object.empty() ) {
1704         os << "visit objects   : ";
1705         for ( std::list<IndexObject>::const_iterator it = visit_object.begin(); it != visit_object.end(); ++it )
1706             os << MP2::StringObject( static_cast<MP2::MapObjectType>( ( *it ).second ) ) << "(" << ( *it ).first << "), ";
1707         os << std::endl;
1708     }
1709 
1710     if ( isControlAI() ) {
1711         os << "skills          : " << secondary_skills.String() << std::endl
1712            << "artifacts       : " << bag_artifacts.String() << std::endl
1713            << "spell book      : " << ( HaveSpellBook() ? spell_book.String() : "disabled" ) << std::endl
1714            << "army dump       : " << army.String() << std::endl;
1715 
1716         os << AI::Get().HeroesString( *this );
1717     }
1718 
1719     return os.str();
1720 }
1721 
GetAttackedMonsterTileIndex() const1722 int Heroes::GetAttackedMonsterTileIndex() const
1723 {
1724     return _attackedMonsterTileIndex;
1725 }
1726 
SetAttackedMonsterTileIndex(int idx)1727 void Heroes::SetAttackedMonsterTileIndex( int idx )
1728 {
1729     _attackedMonsterTileIndex = idx;
1730 }
1731 
AllHeroes()1732 AllHeroes::AllHeroes()
1733 {
1734     reserve( HEROESMAXCOUNT + 2 );
1735 }
1736 
~AllHeroes()1737 AllHeroes::~AllHeroes()
1738 {
1739     AllHeroes::clear();
1740 }
1741 
Init(void)1742 void AllHeroes::Init( void )
1743 {
1744     if ( !empty() )
1745         AllHeroes::clear();
1746 
1747     // knight: LORDKILBURN, SIRGALLANTH, ECTOR, GVENNETH, TYRO, AMBROSE, RUBY, MAXIMUS, DIMITRY
1748     for ( u32 hid = Heroes::LORDKILBURN; hid <= Heroes::DIMITRY; ++hid )
1749         push_back( new Heroes( hid, Race::KNGT ) );
1750 
1751     // barbarian: THUNDAX, FINEOUS, JOJOSH, CRAGHACK, JEZEBEL, JACLYN, ERGON, TSABU, ATLAS
1752     for ( u32 hid = Heroes::THUNDAX; hid <= Heroes::ATLAS; ++hid )
1753         push_back( new Heroes( hid, Race::BARB ) );
1754 
1755     // sorceress: ASTRA, NATASHA, TROYAN, VATAWNA, REBECCA, GEM, ARIEL, CARLAWN, LUNA
1756     for ( u32 hid = Heroes::ASTRA; hid <= Heroes::LUNA; ++hid )
1757         push_back( new Heroes( hid, Race::SORC ) );
1758 
1759     // warlock: ARIE, ALAMAR, VESPER, CRODO, BAROK, KASTORE, AGAR, FALAGAR, WRATHMONT
1760     for ( u32 hid = Heroes::ARIE; hid <= Heroes::WRATHMONT; ++hid )
1761         push_back( new Heroes( hid, Race::WRLK ) );
1762 
1763     // wizard: MYRA, FLINT, DAWN, HALON, MYRINI, WILFREY, SARAKIN, KALINDRA, MANDIGAL
1764     for ( u32 hid = Heroes::MYRA; hid <= Heroes::MANDIGAL; ++hid )
1765         push_back( new Heroes( hid, Race::WZRD ) );
1766 
1767     // necromancer: ZOM, DARLANA, ZAM, RANLOO, CHARITY, RIALDO, ROXANA, SANDRO, CELIA
1768     for ( u32 hid = Heroes::ZOM; hid <= Heroes::CELIA; ++hid )
1769         push_back( new Heroes( hid, Race::NECR ) );
1770 
1771     // from campain
1772     push_back( new Heroes( Heroes::ROLAND, Race::WZRD, 5 ) );
1773     push_back( new Heroes( Heroes::CORLAGON, Race::KNGT, 5 ) );
1774     push_back( new Heroes( Heroes::ELIZA, Race::SORC, 5 ) );
1775     push_back( new Heroes( Heroes::ARCHIBALD, Race::WRLK, 5 ) );
1776     push_back( new Heroes( Heroes::HALTON, Race::KNGT, 5 ) );
1777     push_back( new Heroes( Heroes::BAX, Race::NECR, 5 ) );
1778 
1779     // loyalty version
1780     if ( Settings::Get().isCurrentMapPriceOfLoyalty() ) {
1781         push_back( new Heroes( Heroes::SOLMYR, Race::WZRD, 5 ) );
1782         push_back( new Heroes( Heroes::DAINWIN, Race::WRLK, 5 ) );
1783         push_back( new Heroes( Heroes::MOG, Race::NECR, 5 ) );
1784         push_back( new Heroes( Heroes::UNCLEIVAN, Race::BARB, 5 ) );
1785         push_back( new Heroes( Heroes::JOSEPH, Race::KNGT, 5 ) );
1786         push_back( new Heroes( Heroes::GALLAVANT, Race::KNGT, 5 ) );
1787         push_back( new Heroes( Heroes::ELDERIAN, Race::WRLK, 5 ) );
1788         push_back( new Heroes( Heroes::CEALLACH, Race::KNGT, 5 ) );
1789         push_back( new Heroes( Heroes::DRAKONIA, Race::WZRD, 5 ) );
1790         push_back( new Heroes( Heroes::MARTINE, Race::SORC, 5 ) );
1791         push_back( new Heroes( Heroes::JARKONAS, Race::BARB, 5 ) );
1792     }
1793     else {
1794         // for non-PoL maps, just add unknown heroes instead in place of the PoL-specific ones
1795         for ( int i = Heroes::SOLMYR; i <= Heroes::JARKONAS; ++i )
1796             push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) );
1797     }
1798 
1799     // devel
1800     if ( IS_DEVEL() ) {
1801         push_back( new Heroes( Heroes::DEBUG_HERO, Race::WRLK ) );
1802     }
1803     else {
1804         push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) );
1805     }
1806 
1807     push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) );
1808 }
1809 
clear(void)1810 void AllHeroes::clear( void )
1811 {
1812     for ( iterator it = begin(); it != end(); ++it )
1813         delete *it;
1814     std::vector<Heroes *>::clear();
1815 }
1816 
Get(int hid) const1817 Heroes * VecHeroes::Get( int hid ) const
1818 {
1819     const std::vector<Heroes *> & vec = *this;
1820     return 0 <= hid && hid < Heroes::UNKNOWN ? vec[hid] : nullptr;
1821 }
1822 
Get(const fheroes2::Point & center) const1823 Heroes * VecHeroes::Get( const fheroes2::Point & center ) const
1824 {
1825     const_iterator it = begin();
1826     for ( ; it != end(); ++it )
1827         if ( ( *it )->isPosition( center ) )
1828             break;
1829     return end() != it ? *it : nullptr;
1830 }
1831 
GetGuest(const Castle & castle) const1832 Heroes * AllHeroes::GetGuest( const Castle & castle ) const
1833 {
1834     const_iterator it
1835         = std::find_if( begin(), end(), [&castle]( const Heroes * hero ) { return castle.GetCenter() == hero->GetCenter() && !hero->Modes( Heroes::GUARDIAN ); } );
1836     return end() != it ? *it : nullptr;
1837 }
1838 
GetGuard(const Castle & castle) const1839 Heroes * AllHeroes::GetGuard( const Castle & castle ) const
1840 {
1841     const_iterator it = Settings::Get().ExtCastleAllowGuardians() ? std::find_if( begin(), end(),
1842                                                                                   [&castle]( const Heroes * hero ) {
1843                                                                                       const fheroes2::Point & cpt = castle.GetCenter();
1844                                                                                       const fheroes2::Point & hpt = hero->GetCenter();
1845                                                                                       return cpt.x == hpt.x && cpt.y == hpt.y + 1 && hero->Modes( Heroes::GUARDIAN );
1846                                                                                   } )
1847                                                                   : end();
1848     return end() != it ? *it : nullptr;
1849 }
1850 
GetFreeman(int race) const1851 Heroes * AllHeroes::GetFreeman( int race ) const
1852 {
1853     int min = Heroes::UNKNOWN;
1854     int max = Heroes::UNKNOWN;
1855 
1856     switch ( race ) {
1857     case Race::KNGT:
1858         min = Heroes::LORDKILBURN;
1859         max = Heroes::DIMITRY;
1860         break;
1861 
1862     case Race::BARB:
1863         min = Heroes::THUNDAX;
1864         max = Heroes::ATLAS;
1865         break;
1866 
1867     case Race::SORC:
1868         min = Heroes::ASTRA;
1869         max = Heroes::LUNA;
1870         break;
1871 
1872     case Race::WRLK:
1873         min = Heroes::ARIE;
1874         max = Heroes::WRATHMONT;
1875         break;
1876 
1877     case Race::WZRD:
1878         min = Heroes::MYRA;
1879         max = Heroes::MANDIGAL;
1880         break;
1881 
1882     case Race::NECR:
1883         min = Heroes::ZOM;
1884         max = Heroes::CELIA;
1885         break;
1886 
1887     default:
1888         min = Heroes::LORDKILBURN;
1889         max = Heroes::CELIA;
1890         break;
1891     }
1892 
1893     std::vector<int> freeman_heroes;
1894     freeman_heroes.reserve( HEROESMAXCOUNT );
1895 
1896     // find freeman in race (skip: manual changes)
1897     for ( int ii = min; ii <= max; ++ii )
1898         if ( at( ii )->isFreeman() && !at( ii )->Modes( Heroes::NOTDEFAULTS ) )
1899             freeman_heroes.push_back( ii );
1900 
1901     // not found, find any race
1902     if ( Race::NONE != race && freeman_heroes.empty() ) {
1903         min = Heroes::LORDKILBURN;
1904         max = Heroes::CELIA;
1905 
1906         for ( int ii = min; ii <= max; ++ii )
1907             if ( at( ii )->isFreeman() )
1908                 freeman_heroes.push_back( ii );
1909     }
1910 
1911     // not found, all heroes busy
1912     if ( freeman_heroes.empty() ) {
1913         DEBUG_LOG( DBG_GAME, DBG_WARN, "freeman not found, all heroes busy." );
1914         return nullptr;
1915     }
1916 
1917     return at( Rand::Get( freeman_heroes ) );
1918 }
1919 
GetFreemanSpecial(int heroID) const1920 Heroes * AllHeroes::GetFreemanSpecial( int heroID ) const
1921 {
1922     assert( at( heroID ) && at( heroID )->isFreeman() );
1923     return at( heroID );
1924 }
1925 
Scoute(int colors) const1926 void AllHeroes::Scoute( int colors ) const
1927 {
1928     for ( const_iterator it = begin(); it != end(); ++it )
1929         if ( colors & ( *it )->GetColor() )
1930             ( *it )->Scoute( ( *it )->GetIndex() );
1931 }
1932 
FromJail(s32 index) const1933 Heroes * AllHeroes::FromJail( s32 index ) const
1934 {
1935     const_iterator it = std::find_if( begin(), end(), [index]( const Heroes * hero ) { return hero->Modes( Heroes::JAIL ) && index == hero->GetIndex(); } );
1936     return end() != it ? *it : nullptr;
1937 }
1938 
HaveTwoFreemans(void) const1939 bool AllHeroes::HaveTwoFreemans( void ) const
1940 {
1941     return 2 <= std::count_if( begin(), end(), []( const Heroes * hero ) { return hero->isFreeman(); } );
1942 }
1943 
GetSeedsForLevelUp() const1944 HeroSeedsForLevelUp Heroes::GetSeedsForLevelUp() const
1945 {
1946     /* We generate seeds based on the hero and global world map seed
1947      * The idea is that, we want the skill selection to be randomized at each map restart,
1948      * but deterministic for a given hero.
1949      * We also want the available skills to change depending on current skills/stats of the hero,
1950      * to avoid giving out the same skills/stats at each level up. We can't use the level field for this, as it
1951      * doesn't change when we level up several levels at once.
1952      * We also need to generate different seeds for each possible call to the random number generator,
1953      * in order to avoid always drawing the same random number at level-up: otherwise this
1954      * would mean that for all possible games, the 2nd secondary
1955      * skill would always be the same once the 1st one is selected.
1956      * */
1957 
1958     size_t hash = world.GetMapSeed();
1959     fheroes2::hashCombine( hash, hid );
1960     fheroes2::hashCombine( hash, _race );
1961     fheroes2::hashCombine( hash, attack );
1962     fheroes2::hashCombine( hash, defense );
1963     fheroes2::hashCombine( hash, power );
1964     fheroes2::hashCombine( hash, knowledge );
1965     for ( int skillId = Skill::Secondary::PATHFINDING; skillId <= Skill::Secondary::ESTATES; ++skillId ) {
1966         fheroes2::hashCombine( hash, GetLevelSkill( skillId ) );
1967     }
1968 
1969     HeroSeedsForLevelUp seeds;
1970     seeds.seedPrimarySkill = static_cast<uint32_t>( hash );
1971     seeds.seedSecondaySkill1 = seeds.seedPrimarySkill + 1;
1972     seeds.seedSecondaySkill2 = seeds.seedPrimarySkill + 2;
1973     seeds.seedSecondaySkillRandomChoose = seeds.seedPrimarySkill + 3;
1974     return seeds;
1975 }
1976 
operator <<(StreamBase & msg,const VecHeroes & heroes)1977 StreamBase & operator<<( StreamBase & msg, const VecHeroes & heroes )
1978 {
1979     msg << static_cast<u32>( heroes.size() );
1980 
1981     for ( AllHeroes::const_iterator it = heroes.begin(); it != heroes.end(); ++it )
1982         msg << ( *it ? ( *it )->GetID() : Heroes::UNKNOWN );
1983 
1984     return msg;
1985 }
1986 
operator >>(StreamBase & msg,VecHeroes & heroes)1987 StreamBase & operator>>( StreamBase & msg, VecHeroes & heroes )
1988 {
1989     u32 size;
1990     msg >> size;
1991 
1992     heroes.resize( size, nullptr );
1993 
1994     for ( AllHeroes::iterator it = heroes.begin(); it != heroes.end(); ++it ) {
1995         u32 hid;
1996         msg >> hid;
1997         *it = ( hid != Heroes::UNKNOWN ? world.GetHeroes( hid ) : nullptr );
1998     }
1999 
2000     return msg;
2001 }
2002 
operator <<(StreamBase & msg,const Heroes & hero)2003 StreamBase & operator<<( StreamBase & msg, const Heroes & hero )
2004 {
2005     const HeroBase & base = hero;
2006     const ColorBase & col = hero;
2007 
2008     msg << base;
2009 
2010     // heroes
2011     msg << hero.name << col << hero.killer_color << hero.experience << hero.move_point_scale << hero.secondary_skills << hero.army << hero.hid << hero.portrait
2012         << hero._race << hero.save_maps_object << hero.path << hero.direction << hero.sprite_index;
2013 
2014     // TODO: before 0.9.4 Point was int16_t type
2015     const int16_t patrolX = static_cast<int16_t>( hero.patrol_center.x );
2016     const int16_t patrolY = static_cast<int16_t>( hero.patrol_center.y );
2017 
2018     msg << patrolX << patrolY << hero.patrol_square << hero.visit_object << hero._lastGroundRegion;
2019 
2020     return msg;
2021 }
2022 
operator >>(StreamBase & msg,Heroes & hero)2023 StreamBase & operator>>( StreamBase & msg, Heroes & hero )
2024 {
2025     HeroBase & base = hero;
2026     ColorBase & col = hero;
2027 
2028     msg >> base >> hero.name >> col >> hero.killer_color >> hero.experience >> hero.move_point_scale >> hero.secondary_skills >> hero.army >> hero.hid >> hero.portrait
2029         >> hero._race >> hero.save_maps_object >> hero.path >> hero.direction >> hero.sprite_index;
2030 
2031     // TODO: before 0.9.4 Point was int16_t type
2032     int16_t patrolX = 0;
2033     int16_t patrolY = 0;
2034 
2035     msg >> patrolX >> patrolY;
2036     hero.patrol_center = fheroes2::Point( patrolX, patrolY );
2037 
2038     msg >> hero.patrol_square >> hero.visit_object >> hero._lastGroundRegion;
2039 
2040     hero.army.SetCommander( &hero );
2041     return msg;
2042 }
2043 
operator <<(StreamBase & msg,const AllHeroes & heroes)2044 StreamBase & operator<<( StreamBase & msg, const AllHeroes & heroes )
2045 {
2046     msg << static_cast<u32>( heroes.size() );
2047 
2048     for ( AllHeroes::const_iterator it = heroes.begin(); it != heroes.end(); ++it )
2049         msg << **it;
2050 
2051     return msg;
2052 }
2053 
operator >>(StreamBase & msg,AllHeroes & heroes)2054 StreamBase & operator>>( StreamBase & msg, AllHeroes & heroes )
2055 {
2056     u32 size;
2057     msg >> size;
2058 
2059     heroes.clear();
2060     heroes.resize( size, nullptr );
2061 
2062     for ( AllHeroes::iterator it = heroes.begin(); it != heroes.end(); ++it ) {
2063         *it = new Heroes();
2064         msg >> **it;
2065     }
2066 
2067     return msg;
2068 }
2069