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