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 <numeric>
26 
27 #include "agg_image.h"
28 #include "army.h"
29 #include "campaign_data.h"
30 #include "campaign_savedata.h"
31 #include "castle.h"
32 #include "color.h"
33 #include "game.h"
34 #include "heroes.h"
35 #include "heroes_base.h"
36 #include "icn.h"
37 #include "kingdom.h"
38 #include "logging.h"
39 #include "luck.h"
40 #include "maps_tiles.h"
41 #include "morale.h"
42 #include "payment.h"
43 #include "race.h"
44 #include "rand.h"
45 #include "screen.h"
46 #include "serialize.h"
47 #include "settings.h"
48 #include "text.h"
49 #include "tools.h"
50 #include "translations.h"
51 #include "world.h"
52 
53 enum armysize_t
54 {
55     ARMY_FEW = 1,
56     ARMY_SEVERAL = 5,
57     ARMY_PACK = 10,
58     ARMY_LOTS = 20,
59     ARMY_HORDE = 50,
60     ARMY_THRONG = 100,
61     ARMY_SWARM = 250,
62     ARMY_ZOUNDS = 500,
63     ARMY_LEGION = 1000
64 };
65 
ArmyGetSize(u32 count)66 armysize_t ArmyGetSize( u32 count )
67 {
68     if ( ARMY_LEGION <= count )
69         return ARMY_LEGION;
70     else if ( ARMY_ZOUNDS <= count )
71         return ARMY_ZOUNDS;
72     else if ( ARMY_SWARM <= count )
73         return ARMY_SWARM;
74     else if ( ARMY_THRONG <= count )
75         return ARMY_THRONG;
76     else if ( ARMY_HORDE <= count )
77         return ARMY_HORDE;
78     else if ( ARMY_LOTS <= count )
79         return ARMY_LOTS;
80     else if ( ARMY_PACK <= count )
81         return ARMY_PACK;
82     else if ( ARMY_SEVERAL <= count )
83         return ARMY_SEVERAL;
84 
85     return ARMY_FEW;
86 }
87 
TroopSizeString(const Troop & troop)88 std::string Army::TroopSizeString( const Troop & troop )
89 {
90     std::string str;
91 
92     switch ( ArmyGetSize( troop.GetCount() ) ) {
93     default:
94         str = _( "A few\n%{monster}" );
95         break;
96     case ARMY_SEVERAL:
97         str = _( "Several\n%{monster}" );
98         break;
99     case ARMY_PACK:
100         str = _( "A pack of\n%{monster}" );
101         break;
102     case ARMY_LOTS:
103         str = _( "Lots of\n%{monster}" );
104         break;
105     case ARMY_HORDE:
106         str = _( "A horde of\n%{monster}" );
107         break;
108     case ARMY_THRONG:
109         str = _( "A throng of\n%{monster}" );
110         break;
111     case ARMY_SWARM:
112         str = _( "A swarm of\n%{monster}" );
113         break;
114     case ARMY_ZOUNDS:
115         str = _( "Zounds of\n%{monster}" );
116         break;
117     case ARMY_LEGION:
118         str = _( "A legion of\n%{monster}" );
119         break;
120     }
121 
122     StringReplace( str, "%{monster}", StringLower( troop.GetMultiName() ) );
123     return str;
124 }
125 
SizeString(u32 size)126 std::string Army::SizeString( u32 size )
127 {
128     switch ( ArmyGetSize( size ) ) {
129     default:
130         break;
131     case ARMY_SEVERAL:
132         return _( "army|Several" );
133     case ARMY_PACK:
134         return _( "army|Pack" );
135     case ARMY_LOTS:
136         return _( "army|Lots" );
137     case ARMY_HORDE:
138         return _( "army|Horde" );
139     case ARMY_THRONG:
140         return _( "army|Throng" );
141     case ARMY_SWARM:
142         return _( "army|Swarm" );
143     case ARMY_ZOUNDS:
144         return _( "army|Zounds" );
145     case ARMY_LEGION:
146         return _( "army|Legion" );
147     }
148 
149     return _( "army|Few" );
150 }
151 
Troops(const Troops & troops)152 Troops::Troops( const Troops & troops )
153     : std::vector<Troop *>()
154 {
155     *this = troops;
156 }
157 
operator =(const Troops & rhs)158 Troops & Troops::operator=( const Troops & rhs )
159 {
160     reserve( rhs.size() );
161     for ( const_iterator it = rhs.begin(); it != rhs.end(); ++it )
162         push_back( new Troop( **it ) );
163     return *this;
164 }
165 
~Troops()166 Troops::~Troops()
167 {
168     for ( iterator it = begin(); it != end(); ++it )
169         delete *it;
170 }
171 
Size(void) const172 size_t Troops::Size( void ) const
173 {
174     return size();
175 }
176 
Assign(const Troop * it1,const Troop * it2)177 void Troops::Assign( const Troop * it1, const Troop * it2 )
178 {
179     Clean();
180 
181     iterator it = begin();
182 
183     while ( it != end() && it1 != it2 ) {
184         if ( it1->isValid() )
185             ( *it )->Set( *it1 );
186         ++it;
187         ++it1;
188     }
189 }
190 
Assign(const Troops & troops)191 void Troops::Assign( const Troops & troops )
192 {
193     Clean();
194 
195     iterator it1 = begin();
196     const_iterator it2 = troops.begin();
197 
198     while ( it1 != end() && it2 != troops.end() ) {
199         if ( ( *it2 )->isValid() )
200             ( *it1 )->Set( **it2 );
201         ++it2;
202         ++it1;
203     }
204 }
205 
Insert(const Troops & troops)206 void Troops::Insert( const Troops & troops )
207 {
208     for ( const_iterator it = troops.begin(); it != troops.end(); ++it )
209         push_back( new Troop( **it ) );
210 }
211 
PushBack(const Monster & mons,u32 count)212 void Troops::PushBack( const Monster & mons, u32 count )
213 {
214     push_back( new Troop( mons, count ) );
215 }
216 
PopBack(void)217 void Troops::PopBack( void )
218 {
219     if ( !empty() ) {
220         delete back();
221         pop_back();
222     }
223 }
224 
GetTroop(size_t pos)225 Troop * Troops::GetTroop( size_t pos )
226 {
227     return pos < size() ? at( pos ) : nullptr;
228 }
229 
GetTroop(size_t pos) const230 const Troop * Troops::GetTroop( size_t pos ) const
231 {
232     return pos < size() ? at( pos ) : nullptr;
233 }
234 
UpgradeMonsters(const Monster & m)235 void Troops::UpgradeMonsters( const Monster & m )
236 {
237     for ( iterator it = begin(); it != end(); ++it ) {
238         if ( **it == m ) {
239             assert( ( *it )->isValid() );
240             ( *it )->Upgrade();
241         }
242     }
243 }
244 
GetCountMonsters(const Monster & m) const245 u32 Troops::GetCountMonsters( const Monster & m ) const
246 {
247     u32 c = 0;
248 
249     for ( const_iterator it = begin(); it != end(); ++it )
250         if ( ( *it )->isValid() && **it == m )
251             c += ( *it )->GetCount();
252 
253     return c;
254 }
255 
getReinforcementValue(const Troops & reinforcement) const256 double Troops::getReinforcementValue( const Troops & reinforcement ) const
257 {
258     // NB items that are added in this vector are all of Troop* type, and not ArmyTroop* type
259     // So the GetStrength() computation will be done based on troop strength only (not based on hero bonuses)
260     Troops combined( *this );
261     const double initialValue = combined.GetStrength();
262 
263     combined.Insert( reinforcement.GetOptimized() );
264     combined.MergeTroops();
265     combined.SortStrongest();
266 
267     while ( combined.Size() > ARMYMAXTROOPS ) {
268         combined.PopBack();
269     }
270 
271     return combined.GetStrength() - initialValue;
272 }
273 
isValid(void) const274 bool Troops::isValid( void ) const
275 {
276     for ( const_iterator it = begin(); it != end(); ++it ) {
277         if ( ( *it )->isValid() )
278             return true;
279     }
280     return false;
281 }
282 
GetCount(void) const283 u32 Troops::GetCount( void ) const
284 {
285     uint32_t total = 0;
286     for ( const_iterator it = begin(); it != end(); ++it ) {
287         if ( ( *it )->isValid() )
288             ++total;
289     }
290     return total;
291 }
292 
HasMonster(const Monster & mons) const293 bool Troops::HasMonster( const Monster & mons ) const
294 {
295     const int monsterID = mons.GetID();
296     for ( const_iterator it = begin(); it != end(); ++it ) {
297         if ( ( *it )->isValid() && ( *it )->isMonster( monsterID ) ) {
298             return true;
299         }
300     }
301     return false;
302 }
303 
AllTroopsAreUndead() const304 bool Troops::AllTroopsAreUndead() const
305 {
306     for ( const_iterator it = begin(); it != end(); ++it ) {
307         if ( ( *it )->isValid() && !( *it )->isUndead() ) {
308             return false;
309         }
310     }
311 
312     return true;
313 }
314 
CanJoinTroop(const Monster & mons) const315 bool Troops::CanJoinTroop( const Monster & mons ) const
316 {
317     return std::any_of( begin(), end(), [&mons]( const Troop * troop ) { return troop->isMonster( mons.GetID() ); } )
318            || std::any_of( begin(), end(), []( const Troop * troop ) { return !troop->isValid(); } );
319 }
320 
JoinTroop(const Monster & mons,uint32_t count,bool emptySlotFirst)321 bool Troops::JoinTroop( const Monster & mons, uint32_t count, bool emptySlotFirst )
322 {
323     if ( mons.isValid() && count ) {
324         auto findEmptySlot = []( const Troop * troop ) { return !troop->isValid(); };
325         auto findMonster = [&mons]( const Troop * troop ) { return troop->isValid() && troop->isMonster( mons.GetID() ); };
326 
327         iterator it = emptySlotFirst ? std::find_if( begin(), end(), findEmptySlot ) : std::find_if( begin(), end(), findMonster );
328         if ( it == end() ) {
329             it = emptySlotFirst ? std::find_if( begin(), end(), findMonster ) : std::find_if( begin(), end(), findEmptySlot );
330         }
331 
332         if ( it != end() ) {
333             if ( ( *it )->isValid() )
334                 ( *it )->SetCount( ( *it )->GetCount() + count );
335             else
336                 ( *it )->Set( mons, count );
337 
338             DEBUG_LOG( DBG_GAME, DBG_INFO, std::dec << count << " " << ( *it )->GetName() );
339             return true;
340         }
341     }
342 
343     return false;
344 }
345 
JoinTroop(const Troop & troop)346 bool Troops::JoinTroop( const Troop & troop )
347 {
348     return troop.isValid() ? JoinTroop( troop.GetMonster(), troop.GetCount() ) : false;
349 }
350 
CanJoinTroops(const Troops & troops2) const351 bool Troops::CanJoinTroops( const Troops & troops2 ) const
352 {
353     if ( this == &troops2 )
354         return false;
355 
356     Troops troops1;
357     troops1.Insert( *this );
358 
359     for ( const_iterator it = troops2.begin(); it != troops2.end(); ++it ) {
360         if ( ( *it )->isValid() && !troops1.JoinTroop( **it ) ) {
361             return false;
362         }
363     }
364 
365     return true;
366 }
367 
JoinTroops(Troops & troops2)368 void Troops::JoinTroops( Troops & troops2 )
369 {
370     if ( this == &troops2 )
371         return;
372 
373     for ( iterator it = troops2.begin(); it != troops2.end(); ++it )
374         if ( ( *it )->isValid() ) {
375             JoinTroop( **it );
376             ( *it )->Reset();
377         }
378 }
379 
MoveTroops(Troops & from)380 void Troops::MoveTroops( Troops & from )
381 {
382     if ( this == &from )
383         return;
384 
385     size_t validTroops = 0;
386     for ( Troop * troop : from ) {
387         if ( troop && troop->isValid() ) {
388             ++validTroops;
389         }
390     }
391 
392     for ( Troop * troop : from ) {
393         if ( troop && troop->isValid() ) {
394             if ( validTroops == 1 ) {
395                 if ( JoinTroop( troop->GetMonster(), troop->GetCount() - 1 ) ) {
396                     troop->SetCount( 1 );
397                     break;
398                 }
399             }
400             else if ( JoinTroop( *troop ) ) {
401                 --validTroops;
402                 troop->Reset();
403             }
404         }
405     }
406 }
407 
408 // Return true when all valid troops have the same ID, or when there are no troops
AllTroopsAreTheSame(void) const409 bool Troops::AllTroopsAreTheSame( void ) const
410 {
411     int firstMonsterId = Monster::UNKNOWN;
412     for ( const Troop * troop : *this ) {
413         if ( troop->isValid() ) {
414             if ( firstMonsterId == Monster::UNKNOWN ) {
415                 firstMonsterId = troop->GetID();
416             }
417             else if ( troop->GetID() != firstMonsterId ) {
418                 return false;
419             }
420         }
421     }
422     return true;
423 }
424 
GetStrength() const425 double Troops::GetStrength() const
426 {
427     double strength = 0;
428     for ( const Troop * troop : *this ) {
429         if ( troop && troop->isValid() )
430             strength += troop->GetStrength();
431     }
432     return strength;
433 }
434 
Clean(void)435 void Troops::Clean( void )
436 {
437     std::for_each( begin(), end(), []( Troop * troop ) { troop->Reset(); } );
438 }
439 
UpgradeTroops(const Castle & castle)440 void Troops::UpgradeTroops( const Castle & castle )
441 {
442     for ( iterator it = begin(); it != end(); ++it )
443         if ( ( *it )->isValid() ) {
444             payment_t payment = ( *it )->GetUpgradeCost();
445             Kingdom & kingdom = castle.GetKingdom();
446 
447             if ( castle.GetRace() == ( *it )->GetRace() && castle.isBuild( ( *it )->GetUpgrade().GetDwelling() ) && kingdom.AllowPayment( payment ) ) {
448                 kingdom.OddFundsResource( payment );
449                 ( *it )->Upgrade();
450             }
451         }
452 }
453 
GetFirstValid(void)454 Troop * Troops::GetFirstValid( void )
455 {
456     iterator it = std::find_if( begin(), end(), []( const Troop * troop ) { return troop->isValid(); } );
457     return it == end() ? nullptr : *it;
458 }
459 
GetWeakestTroop(void)460 Troop * Troops::GetWeakestTroop( void )
461 {
462     iterator first = begin();
463     iterator last = end();
464 
465     while ( first != last )
466         if ( ( *first )->isValid() )
467             break;
468         else
469             ++first;
470 
471     if ( first == end() )
472         return nullptr;
473 
474     iterator lowest = first;
475 
476     if ( first != last )
477         while ( ++first != last )
478             if ( ( *first )->isValid() && Army::WeakestTroop( *first, *lowest ) )
479                 lowest = first;
480 
481     return *lowest;
482 }
483 
GetSlowestTroop() const484 const Troop * Troops::GetSlowestTroop() const
485 {
486     const_iterator first = begin();
487     const_iterator last = end();
488 
489     while ( first != last )
490         if ( ( *first )->isValid() )
491             break;
492         else
493             ++first;
494 
495     if ( first == end() )
496         return nullptr;
497     const_iterator lowest = first;
498 
499     if ( first != last )
500         while ( ++first != last )
501             if ( ( *first )->isValid() && Army::SlowestTroop( *first, *lowest ) )
502                 lowest = first;
503 
504     return *lowest;
505 }
506 
MergeTroops()507 void Troops::MergeTroops()
508 {
509     for ( size_t slot = 0; slot < size(); ++slot ) {
510         Troop * troop = at( slot );
511         if ( !troop || !troop->isValid() )
512             continue;
513 
514         const int id = troop->GetID();
515         for ( size_t secondary = slot + 1; secondary < size(); ++secondary ) {
516             Troop * secondaryTroop = at( secondary );
517             if ( secondaryTroop && secondaryTroop->isValid() && id == secondaryTroop->GetID() ) {
518                 troop->SetCount( troop->GetCount() + secondaryTroop->GetCount() );
519                 secondaryTroop->Reset();
520             }
521         }
522     }
523 }
524 
GetOptimized(void) const525 Troops Troops::GetOptimized( void ) const
526 {
527     Troops result;
528     result.reserve( size() );
529 
530     for ( const_iterator it1 = begin(); it1 != end(); ++it1 )
531         if ( ( *it1 )->isValid() ) {
532             const int monsterId = ( *it1 )->GetID();
533             iterator it2 = std::find_if( result.begin(), result.end(), [monsterId]( const Troop * troop ) { return troop->isMonster( monsterId ); } );
534 
535             if ( it2 == result.end() )
536                 result.push_back( new Troop( **it1 ) );
537             else
538                 ( *it2 )->SetCount( ( *it2 )->GetCount() + ( *it1 )->GetCount() );
539         }
540 
541     return result;
542 }
543 
SortStrongest()544 void Troops::SortStrongest()
545 {
546     std::sort( begin(), end(), Army::StrongestTroop );
547 }
548 
549 // Pre-battle arrangement for Monster or Neutral troops
ArrangeForBattle(bool upgrade)550 void Troops::ArrangeForBattle( bool upgrade )
551 {
552     const Troops & priority = GetOptimized();
553 
554     if ( priority.size() == 1 ) {
555         const Monster & m = *priority.back();
556         const u32 count = priority.back()->GetCount();
557 
558         Clean();
559 
560         if ( 49 < count ) {
561             const u32 c = count / 5;
562             at( 0 )->Set( m, c );
563             at( 1 )->Set( m, c );
564             at( 2 )->Set( m, c + count - ( c * 5 ) );
565             at( 3 )->Set( m, c );
566             at( 4 )->Set( m, c );
567 
568             if ( upgrade && at( 2 )->isAllowUpgrade() )
569                 at( 2 )->Upgrade();
570         }
571         else if ( 20 < count ) {
572             const u32 c = count / 3;
573             at( 1 )->Set( m, c );
574             at( 2 )->Set( m, c + count - ( c * 3 ) );
575             at( 3 )->Set( m, c );
576 
577             if ( upgrade && at( 2 )->isAllowUpgrade() )
578                 at( 2 )->Upgrade();
579         }
580         else
581             at( 2 )->Set( m, count );
582     }
583     else {
584         Assign( priority );
585     }
586 }
587 
JoinStrongest(Troops & troops2,bool saveLast)588 void Troops::JoinStrongest( Troops & troops2, bool saveLast )
589 {
590     if ( this == &troops2 )
591         return;
592 
593     // validate the size (can be different from ARMYMAXTROOPS)
594     if ( troops2.size() < size() )
595         troops2.resize( size() );
596 
597     // first try to keep units in the same slots
598     for ( size_t slot = 0; slot < size(); ++slot ) {
599         Troop * leftTroop = at( slot );
600         Troop * rightTroop = troops2[slot];
601         if ( rightTroop && rightTroop->isValid() ) {
602             if ( !leftTroop->isValid() ) {
603                 // if slot is empty, simply move the unit
604                 leftTroop->Set( *rightTroop );
605                 rightTroop->Reset();
606             }
607             else if ( leftTroop->GetID() == rightTroop->GetID() ) {
608                 // check if we can merge them
609                 leftTroop->SetCount( leftTroop->GetCount() + rightTroop->GetCount() );
610                 rightTroop->Reset();
611             }
612         }
613     }
614 
615     // there's still unmerged units left and there's empty room for them
616     for ( size_t slot = 0; slot < troops2.size(); ++slot ) {
617         Troop * rightTroop = troops2[slot];
618         if ( rightTroop && JoinTroop( rightTroop->GetMonster(), rightTroop->GetCount(), true ) ) {
619             rightTroop->Reset();
620         }
621     }
622 
623     // if there's more units than slots, start optimizing
624     if ( troops2.GetCount() ) {
625         Troops rightPriority = troops2.GetOptimized();
626         troops2.Clean();
627         // strongest at the end
628         std::sort( rightPriority.begin(), rightPriority.end(), Army::WeakestTroop );
629 
630         // 1. Merge any remaining stacks to free some space
631         MergeTroops();
632 
633         // 2. Fill empty slots with best troops (if there are any)
634         uint32_t count = GetCount();
635         while ( count < ARMYMAXTROOPS && !rightPriority.empty() ) {
636             JoinTroop( *rightPriority.back() );
637             rightPriority.PopBack();
638             ++count;
639         }
640 
641         // 3. Swap weakest and strongest unit until there's no left
642         while ( !rightPriority.empty() ) {
643             Troop * weakest = GetWeakestTroop();
644 
645             if ( !weakest || Army::StrongestTroop( weakest, rightPriority.back() ) ) {
646                 // we're done processing if second army units are weaker
647                 break;
648             }
649 
650             Army::SwapTroops( *weakest, *rightPriority.back() );
651             std::sort( rightPriority.begin(), rightPriority.end(), Army::WeakestTroop );
652         }
653 
654         // 4. The rest goes back to second army
655         while ( !rightPriority.empty() ) {
656             troops2.JoinTroop( *rightPriority.back() );
657             rightPriority.PopBack();
658         }
659     }
660 
661     // save weakest unit to army2 (for heroes)
662     if ( saveLast && !troops2.isValid() ) {
663         Troop * weakest = GetWeakestTroop();
664 
665         if ( weakest && weakest->isValid() ) {
666             troops2.JoinTroop( *weakest, 1 );
667             weakest->SetCount( weakest->GetCount() - 1 );
668         }
669     }
670 }
671 
DrawMons32Line(int32_t cx,int32_t cy,uint32_t width,uint32_t first,uint32_t count,uint32_t drawPower,bool compact,bool isScouteView) const672 void Troops::DrawMons32Line( int32_t cx, int32_t cy, uint32_t width, uint32_t first, uint32_t count, uint32_t drawPower, bool compact, bool isScouteView ) const
673 {
674     if ( isValid() ) {
675         if ( 0 == count )
676             count = GetCount();
677 
678         const int chunk = width / count;
679         if ( !compact )
680             cx += chunk / 2;
681 
682         Text text;
683         text.Set( Font::SMALL );
684 
685         for ( const_iterator it = begin(); it != end(); ++it ) {
686             if ( ( *it )->isValid() ) {
687                 if ( 0 == first && count ) {
688                     const fheroes2::Sprite & monster = fheroes2::AGG::GetICN( ICN::MONS32, ( *it )->GetSpriteIndex() );
689                     text.Set( isScouteView ? Game::CountScoute( ( *it )->GetCount(), drawPower, compact ) : Game::CountThievesGuild( ( *it )->GetCount(), drawPower ) );
690 
691                     if ( compact ) {
692                         const int offsetY = ( monster.height() < 37 ) ? 37 - monster.height() : 0;
693                         int offset = ( chunk - monster.width() - text.w() ) / 2;
694                         if ( offset < 0 )
695                             offset = 0;
696                         fheroes2::Blit( monster, fheroes2::Display::instance(), cx + offset, cy + offsetY + monster.y() );
697                         text.Blit( cx + chunk - text.w() - offset, cy + 23 );
698                     }
699                     else {
700                         const int offsetY = 30 - monster.height();
701                         fheroes2::Blit( monster, fheroes2::Display::instance(), cx - monster.width() / 2 + monster.x(), cy + offsetY + monster.y() );
702                         text.Blit( cx - text.w() / 2, cy + 29 );
703                     }
704                     cx += chunk;
705                     --count;
706                 }
707                 else
708                     --first;
709             }
710         }
711     }
712 }
713 
SplitTroopIntoFreeSlots(const Troop & troop,const Troop & selectedSlot,const uint32_t slots)714 void Troops::SplitTroopIntoFreeSlots( const Troop & troop, const Troop & selectedSlot, const uint32_t slots )
715 {
716     if ( slots < 1 || slots > ( Size() - GetCount() ) )
717         return;
718 
719     const uint32_t chunk = troop.GetCount() / slots;
720     uint32_t remainingCount = troop.GetCount() % slots;
721     uint32_t remainingSlots = slots;
722 
723     auto TryCreateTroopChunk = [&remainingSlots, &remainingCount, chunk, troop]( Troop & newTroop ) {
724         if ( remainingSlots <= 0 )
725             return;
726 
727         if ( !newTroop.isValid() ) {
728             newTroop.Set( troop.GetMonster(), remainingCount > 0 ? chunk + 1 : chunk );
729             --remainingSlots;
730 
731             if ( remainingCount > 0 )
732                 --remainingCount;
733         }
734     };
735 
736     const iterator selectedSlotIterator = std::find( begin(), end(), &selectedSlot );
737 
738     // this means the selected slot is actually not part of the army, which is not the intended logic
739     if ( selectedSlotIterator == end() )
740         return;
741 
742     const size_t iteratorIndex = selectedSlotIterator - begin();
743 
744     // try to create chunks to the right of the selected slot
745     for ( size_t i = iteratorIndex + 1; i < Size(); ++i ) {
746         TryCreateTroopChunk( *GetTroop( i ) );
747     }
748 
749     // this time, try to create chunks to the left of the selected slot
750     for ( int i = static_cast<int>( iteratorIndex ) - 1; i >= 0; --i ) {
751         TryCreateTroopChunk( *GetTroop( i ) );
752     }
753 }
754 
AssignToFirstFreeSlot(const Troop & troop,const uint32_t splitCount)755 void Troops::AssignToFirstFreeSlot( const Troop & troop, const uint32_t splitCount )
756 {
757     for ( iterator it = begin(); it != end(); ++it ) {
758         if ( ( *it )->isValid() )
759             continue;
760 
761         ( *it )->Set( troop.GetMonster(), splitCount );
762         break;
763     }
764 }
765 
JoinAllTroopsOfType(const Troop & targetTroop)766 void Troops::JoinAllTroopsOfType( const Troop & targetTroop )
767 {
768     const int troopID = targetTroop.GetID();
769     const int totalMonsterCount = GetCountMonsters( troopID );
770 
771     for ( iterator it = begin(); it != end(); ++it ) {
772         Troop * troop = *it;
773         if ( !troop->isValid() || troop->GetID() != troopID )
774             continue;
775 
776         if ( troop == &targetTroop ) {
777             troop->SetCount( totalMonsterCount );
778         }
779         else {
780             troop->Reset();
781         }
782     }
783 }
784 
Army(HeroBase * s)785 Army::Army( HeroBase * s )
786     : commander( s )
787     , combat_format( true )
788     , color( Color::NONE )
789 {
790     reserve( ARMYMAXTROOPS );
791     for ( u32 ii = 0; ii < ARMYMAXTROOPS; ++ii )
792         push_back( new ArmyTroop( this ) );
793 }
794 
Army(const Maps::Tiles & t)795 Army::Army( const Maps::Tiles & t )
796     : commander( nullptr )
797     , combat_format( true )
798     , color( Color::NONE )
799 {
800     reserve( ARMYMAXTROOPS );
801     for ( u32 ii = 0; ii < ARMYMAXTROOPS; ++ii )
802         push_back( new ArmyTroop( this ) );
803 
804     setFromTile( t );
805 }
806 
~Army()807 Army::~Army()
808 {
809     for ( iterator it = begin(); it != end(); ++it )
810         delete *it;
811     clear();
812 }
813 
setFromTile(const Maps::Tiles & tile)814 void Army::setFromTile( const Maps::Tiles & tile )
815 {
816     Reset();
817 
818     const bool isCaptureObject = MP2::isCaptureObject( tile.GetObject() );
819     if ( isCaptureObject )
820         color = tile.QuantityColor();
821 
822     switch ( tile.GetObject( false ) ) {
823     case MP2::OBJ_PYRAMID:
824         at( 0 )->Set( Monster::VAMPIRE_LORD, 10 );
825         at( 1 )->Set( Monster::ROYAL_MUMMY, 10 );
826         at( 2 )->Set( Monster::ROYAL_MUMMY, 10 );
827         at( 3 )->Set( Monster::ROYAL_MUMMY, 10 );
828         at( 4 )->Set( Monster::VAMPIRE_LORD, 10 );
829         break;
830 
831     case MP2::OBJ_GRAVEYARD:
832         at( 0 )->Set( Monster::MUTANT_ZOMBIE, 100 );
833         ArrangeForBattle( false );
834         break;
835 
836     case MP2::OBJ_SHIPWRECK:
837         at( 0 )->Set( Monster::GHOST, tile.GetQuantity2() );
838         ArrangeForBattle( false );
839         break;
840 
841     case MP2::OBJ_DERELICTSHIP:
842         at( 0 )->Set( Monster::SKELETON, 200 );
843         ArrangeForBattle( false );
844         break;
845 
846     case MP2::OBJ_ARTIFACT:
847         switch ( tile.QuantityVariant() ) {
848         case 6:
849             at( 0 )->Set( Monster::ROGUE, 50 );
850             break;
851         case 7:
852             at( 0 )->Set( Monster::GENIE, 1 );
853             break;
854         case 8:
855             at( 0 )->Set( Monster::PALADIN, 1 );
856             break;
857         case 9:
858             at( 0 )->Set( Monster::CYCLOPS, 1 );
859             break;
860         case 10:
861             at( 0 )->Set( Monster::PHOENIX, 1 );
862             break;
863         case 11:
864             at( 0 )->Set( Monster::GREEN_DRAGON, 1 );
865             break;
866         case 12:
867             at( 0 )->Set( Monster::TITAN, 1 );
868             break;
869         case 13:
870             at( 0 )->Set( Monster::BONE_DRAGON, 1 );
871             break;
872         default:
873             break;
874         }
875         ArrangeForBattle( false );
876         break;
877 
878         // case MP2::OBJ_ABANDONEDMINE:
879         //    at(0) = Troop(t);
880         //    ArrangeForBattle(false);
881         //    break;
882 
883     case MP2::OBJ_CITYDEAD:
884         at( 0 )->Set( Monster::ZOMBIE, 20 );
885         at( 1 )->Set( Monster::VAMPIRE_LORD, 5 );
886         at( 2 )->Set( Monster::POWER_LICH, 5 );
887         at( 3 )->Set( Monster::VAMPIRE_LORD, 5 );
888         at( 4 )->Set( Monster::ZOMBIE, 20 );
889         break;
890 
891     case MP2::OBJ_TROLLBRIDGE:
892         at( 0 )->Set( Monster::TROLL, 4 );
893         at( 1 )->Set( Monster::WAR_TROLL, 4 );
894         at( 2 )->Set( Monster::TROLL, 4 );
895         at( 3 )->Set( Monster::WAR_TROLL, 4 );
896         at( 4 )->Set( Monster::TROLL, 4 );
897         break;
898 
899     case MP2::OBJ_DRAGONCITY: {
900         uint32_t monsterCount = 1;
901         if ( Settings::Get().isCampaignGameType() ) {
902             const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition();
903             if ( victoryCondition == Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ) {
904                 monsterCount = 2;
905             }
906         }
907 
908         at( 0 )->Set( Monster::GREEN_DRAGON, monsterCount );
909         at( 1 )->Set( Monster::GREEN_DRAGON, monsterCount );
910         at( 2 )->Set( Monster::GREEN_DRAGON, monsterCount );
911         at( 3 )->Set( Monster::RED_DRAGON, monsterCount );
912         at( 4 )->Set( Monster::BLACK_DRAGON, monsterCount );
913         break;
914     }
915 
916     case MP2::OBJ_DAEMONCAVE:
917         at( 0 )->Set( Monster::EARTH_ELEMENT, 2 );
918         at( 1 )->Set( Monster::EARTH_ELEMENT, 2 );
919         at( 2 )->Set( Monster::EARTH_ELEMENT, 2 );
920         at( 3 )->Set( Monster::EARTH_ELEMENT, 2 );
921         break;
922 
923     default:
924         if ( isCaptureObject ) {
925             CapturedObject & co = world.GetCapturedObject( tile.GetIndex() );
926             const Troop & troop = co.GetTroop();
927 
928             switch ( co.GetSplit() ) {
929             case 3:
930                 if ( 3 > troop.GetCount() )
931                     at( 0 )->Set( co.GetTroop() );
932                 else {
933                     at( 0 )->Set( troop.GetMonster(), troop.GetCount() / 3 );
934                     at( 4 )->Set( troop.GetMonster(), troop.GetCount() / 3 );
935                     at( 2 )->Set( troop.GetMonster(), troop.GetCount() - at( 4 )->GetCount() - at( 0 )->GetCount() );
936                 }
937                 break;
938 
939             case 5:
940                 if ( 5 > troop.GetCount() )
941                     at( 0 )->Set( co.GetTroop() );
942                 else {
943                     at( 0 )->Set( troop.GetMonster(), troop.GetCount() / 5 );
944                     at( 1 )->Set( troop.GetMonster(), troop.GetCount() / 5 );
945                     at( 3 )->Set( troop.GetMonster(), troop.GetCount() / 5 );
946                     at( 4 )->Set( troop.GetMonster(), troop.GetCount() / 5 );
947                     at( 2 )->Set( troop.GetMonster(), troop.GetCount() - at( 0 )->GetCount() - at( 1 )->GetCount() - at( 3 )->GetCount() - at( 4 )->GetCount() );
948                 }
949                 break;
950 
951             default:
952                 at( 0 )->Set( co.GetTroop() );
953                 break;
954             }
955         }
956         else {
957             Troop troop = tile.QuantityTroop();
958 
959             at( 0 )->Set( troop );
960             if ( troop.isValid() )
961                 ArrangeForBattle( true );
962         }
963         break;
964     }
965 }
966 
isFullHouse(void) const967 bool Army::isFullHouse( void ) const
968 {
969     return GetCount() == size();
970 }
971 
SetSpreadFormat(bool f)972 void Army::SetSpreadFormat( bool f )
973 {
974     combat_format = f;
975 }
976 
isSpreadFormat(void) const977 bool Army::isSpreadFormat( void ) const
978 {
979     return combat_format;
980 }
981 
GetColor(void) const982 int Army::GetColor( void ) const
983 {
984     const HeroBase * currentCommander = GetCommander();
985     return currentCommander != nullptr ? currentCommander->GetColor() : color;
986 }
987 
SetColor(int cl)988 void Army::SetColor( int cl )
989 {
990     color = cl;
991 }
992 
GetLuck(void) const993 int Army::GetLuck( void ) const
994 {
995     const HeroBase * currentCommander = GetCommander();
996     return currentCommander != nullptr ? currentCommander->GetLuck() : GetLuckModificator( nullptr );
997 }
998 
GetLuckModificator(const std::string *) const999 int Army::GetLuckModificator( const std::string * ) const
1000 {
1001     return Luck::NORMAL;
1002 }
1003 
GetMorale(void) const1004 int Army::GetMorale( void ) const
1005 {
1006     const HeroBase * currentCommander = GetCommander();
1007     return currentCommander != nullptr ? currentCommander->GetMorale() : GetMoraleModificator( nullptr );
1008 }
1009 
GetMoraleModificator(std::string * strs) const1010 int Army::GetMoraleModificator( std::string * strs ) const
1011 {
1012     // different race penalty
1013     std::set<int> races;
1014     bool hasUndead = false;
1015     bool allUndead = true;
1016 
1017     for ( const Troop * troop : *this )
1018         if ( troop->isValid() ) {
1019             races.insert( troop->GetRace() );
1020             hasUndead = hasUndead || troop->isUndead();
1021             allUndead = allUndead && troop->isUndead();
1022         }
1023 
1024     if ( allUndead )
1025         return Morale::NORMAL;
1026 
1027     int result = Morale::NORMAL;
1028 
1029     // artifact "Arm of the Martyr" adds the undead morale penalty
1030     hasUndead = hasUndead || ( GetCommander() && GetCommander()->hasArtifact( Artifact::ARM_MARTYR ) );
1031 
1032     const int count = static_cast<int>( races.size() );
1033     switch ( count ) {
1034     case 0:
1035     case 2:
1036         break;
1037     case 1:
1038         if ( !hasUndead && !AllTroopsAreTheSame() ) { // presence of undead discards "All %{race} troops +1" bonus
1039             ++result;
1040             if ( strs ) {
1041                 std::string str = _( "All %{race} troops +1" );
1042                 StringReplace( str, "%{race}", *races.begin() == Race::NONE ? _( "Multiple" ) : Race::String( *races.begin() ) );
1043                 strs->append( str );
1044                 *strs += '\n';
1045             }
1046         }
1047         break;
1048     default:
1049         const int penalty = count - 2;
1050         result -= penalty;
1051         if ( strs ) {
1052             std::string str = _( "Troops of %{count} alignments -%{penalty}" );
1053             StringReplace( str, "%{count}", count );
1054             StringReplace( str, "%{penalty}", penalty );
1055             strs->append( str );
1056             *strs += '\n';
1057         }
1058         break;
1059     }
1060 
1061     // undead in life group
1062     if ( hasUndead ) {
1063         result -= 1;
1064         if ( strs ) {
1065             strs->append( _( "Some undead in group -1" ) );
1066             *strs += '\n';
1067         }
1068     }
1069 
1070     return result;
1071 }
1072 
GetStrength() const1073 double Army::GetStrength() const
1074 {
1075     double result = 0;
1076     const uint32_t archery = ( commander ) ? commander->GetSecondaryValues( Skill::Secondary::ARCHERY ) : 0;
1077     // Hero bonus calculation is slow, cache it
1078     const int bonusAttack = ( commander ? commander->GetAttack() : 0 );
1079     const int bonusDefense = ( commander ? commander->GetDefense() : 0 );
1080     const int armyMorale = GetMorale();
1081     const int armyLuck = GetLuck();
1082 
1083     for ( const_iterator it = begin(); it != end(); ++it ) {
1084         const Troop * troop = *it;
1085         if ( troop != nullptr && troop->isValid() ) {
1086             double strength = troop->GetStrengthWithBonus( bonusAttack, bonusDefense );
1087 
1088             if ( archery > 0 && troop->isArchers() ) {
1089                 strength *= sqrt( 1 + static_cast<double>( archery ) / 100 );
1090             }
1091 
1092             // GetMorale checks if unit is affected by it
1093             if ( troop->isAffectedByMorale() )
1094                 strength *= 1 + ( ( armyMorale < 0 ) ? armyMorale / 12.0 : armyMorale / 24.0 );
1095 
1096             strength *= 1 + armyLuck / 24.0;
1097 
1098             result += strength;
1099         }
1100     }
1101 
1102     if ( commander ) {
1103         result += commander->GetSpellcastStrength( result );
1104     }
1105 
1106     return result;
1107 }
1108 
Reset(bool soft)1109 void Army::Reset( bool soft )
1110 {
1111     Troops::Clean();
1112 
1113     if ( commander && commander->isHeroes() ) {
1114         const Monster mons1( commander->GetRace(), DWELLING_MONSTER1 );
1115 
1116         if ( soft ) {
1117             const Monster mons2( commander->GetRace(), DWELLING_MONSTER2 );
1118 
1119             switch ( mons1.GetID() ) {
1120             case Monster::PEASANT:
1121                 JoinTroop( mons1, Rand::Get( 30, 50 ) );
1122                 break;
1123             case Monster::GOBLIN:
1124                 JoinTroop( mons1, Rand::Get( 15, 25 ) );
1125                 break;
1126             case Monster::SPRITE:
1127                 JoinTroop( mons1, Rand::Get( 10, 20 ) );
1128                 break;
1129             default:
1130                 JoinTroop( mons1, Rand::Get( 6, 10 ) );
1131                 break;
1132             }
1133 
1134             if ( Rand::Get( 1, 10 ) != 1 ) {
1135                 switch ( mons2.GetID() ) {
1136                 case Monster::ARCHER:
1137                 case Monster::ORC:
1138                     JoinTroop( mons2, Rand::Get( 3, 5 ) );
1139                     break;
1140                 default:
1141                     JoinTroop( mons2, Rand::Get( 2, 4 ) );
1142                     break;
1143                 }
1144             }
1145         }
1146         else {
1147             JoinTroop( mons1, 1 );
1148         }
1149     }
1150 }
1151 
SetCommander(HeroBase * c)1152 void Army::SetCommander( HeroBase * c )
1153 {
1154     commander = c;
1155 }
1156 
GetCommander(void)1157 HeroBase * Army::GetCommander( void )
1158 {
1159     return ( !commander || ( commander->isCaptain() && !commander->isValid() ) ) ? nullptr : commander;
1160 }
1161 
inCastle(void) const1162 const Castle * Army::inCastle( void ) const
1163 {
1164     return commander ? commander->inCastle() : nullptr;
1165 }
1166 
GetCommander(void) const1167 const HeroBase * Army::GetCommander( void ) const
1168 {
1169     return ( !commander || ( commander->isCaptain() && !commander->isValid() ) ) ? nullptr : commander;
1170 }
1171 
GetControl(void) const1172 int Army::GetControl( void ) const
1173 {
1174     return commander ? commander->GetControl() : ( color == Color::NONE ? CONTROL_AI : Players::GetPlayerControl( color ) );
1175 }
1176 
getTotalCount() const1177 uint32_t Army::getTotalCount() const
1178 {
1179     return std::accumulate( begin(), end(), 0u, []( const uint32_t count, const Troop * troop ) { return troop->isValid() ? count + troop->GetCount() : count; } );
1180 }
1181 
String(void) const1182 std::string Army::String( void ) const
1183 {
1184     std::ostringstream os;
1185 
1186     os << "color(" << Color::String( commander ? commander->GetColor() : color ) << "), ";
1187 
1188     if ( GetCommander() )
1189         os << "commander(" << GetCommander()->GetName() << "), ";
1190 
1191     os << ": ";
1192 
1193     for ( const_iterator it = begin(); it != end(); ++it )
1194         if ( ( *it )->isValid() )
1195             os << std::dec << ( *it )->GetCount() << " " << ( *it )->GetName() << ", ";
1196 
1197     return os.str();
1198 }
1199 
JoinStrongestFromArmy(Army & army2)1200 void Army::JoinStrongestFromArmy( Army & army2 )
1201 {
1202     bool save_last = army2.commander && army2.commander->isHeroes();
1203     JoinStrongest( army2, save_last );
1204 }
1205 
ActionToSirens(void)1206 u32 Army::ActionToSirens( void )
1207 {
1208     u32 res = 0;
1209 
1210     for ( iterator it = begin(); it != end(); ++it )
1211         if ( ( *it )->isValid() ) {
1212             const u32 kill = ( *it )->GetCount() * 30 / 100;
1213 
1214             if ( kill ) {
1215                 ( *it )->SetCount( ( *it )->GetCount() - kill );
1216                 res += kill * static_cast<Monster *>( *it )->GetHitPoints();
1217             }
1218         }
1219 
1220     return res;
1221 }
1222 
isStrongerThan(const Army & target,double safetyRatio) const1223 bool Army::isStrongerThan( const Army & target, double safetyRatio ) const
1224 {
1225     if ( !target.isValid() )
1226         return true;
1227 
1228     const double str1 = GetStrength();
1229     const double str2 = target.GetStrength() * safetyRatio;
1230 
1231     DEBUG_LOG( DBG_GAME, DBG_TRACE, "Comparing troops: " << str1 << " versus " << str2 );
1232 
1233     return str1 > str2;
1234 }
1235 
isMeleeDominantArmy() const1236 bool Army::isMeleeDominantArmy() const
1237 {
1238     double meleeInfantry = 0;
1239     double other = 0;
1240 
1241     for ( const Troop * troop : *this ) {
1242         if ( troop != nullptr && troop->isValid() ) {
1243             if ( !troop->isArchers() && !troop->isFlying() ) {
1244                 meleeInfantry += troop->GetStrength();
1245             }
1246             else {
1247                 other += troop->GetStrength();
1248             }
1249         }
1250     }
1251     return meleeInfantry > other;
1252 }
1253 
1254 /* draw MONS32 sprite in line, first valid = 0, count = 0 */
DrawMons32Line(const Troops & troops,s32 cx,s32 cy,u32 width,u32 first,u32 count)1255 void Army::DrawMons32Line( const Troops & troops, s32 cx, s32 cy, u32 width, u32 first, u32 count )
1256 {
1257     troops.DrawMons32Line( cx, cy, width, first, count, Skill::Level::EXPERT, false, true );
1258 }
1259 
DrawMonsterLines(const Troops & troops,int32_t posX,int32_t posY,uint32_t lineWidth,uint32_t drawType,bool compact,bool isScouteView)1260 void Army::DrawMonsterLines( const Troops & troops, int32_t posX, int32_t posY, uint32_t lineWidth, uint32_t drawType, bool compact, bool isScouteView )
1261 {
1262     const uint32_t count = troops.GetCount();
1263     const int offsetX = lineWidth / 6;
1264     const int offsetY = compact ? 31 : 50;
1265 
1266     if ( count < 3 ) {
1267         troops.DrawMons32Line( posX + offsetX, posY + offsetY / 2 + 1, lineWidth * 2 / 3, 0, 0, drawType, compact, isScouteView );
1268     }
1269     else {
1270         const int firstLineTroopCount = 2;
1271         const int secondLineTroopCount = count - firstLineTroopCount;
1272         const int secondLineWidth = secondLineTroopCount == 2 ? lineWidth * 2 / 3 : lineWidth;
1273 
1274         troops.DrawMons32Line( posX + offsetX, posY, lineWidth * 2 / 3, 0, firstLineTroopCount, drawType, compact, isScouteView );
1275         troops.DrawMons32Line( posX, posY + offsetY, secondLineWidth, firstLineTroopCount, secondLineTroopCount, drawType, compact, isScouteView );
1276     }
1277 }
1278 
GetJoinSolution(const Heroes & hero,const Maps::Tiles & tile,const Troop & troop)1279 NeutralMonsterJoiningCondition Army::GetJoinSolution( const Heroes & hero, const Maps::Tiles & tile, const Troop & troop )
1280 {
1281     // Check for creature alliance/bane campaign awards, campaign only and of course, for human players
1282     // creature alliance -> if we have an alliance with the appropriate creature (inc. players) they will join for free
1283     // creature curse/bane -> same as above but all of them will flee even if you have just 1 peasant
1284     if ( Settings::Get().isCampaignGameType() && hero.isControlHuman() ) {
1285         const std::vector<Campaign::CampaignAwardData> campaignAwards = Campaign::CampaignSaveData::Get().getObtainedCampaignAwards();
1286 
1287         for ( size_t i = 0; i < campaignAwards.size(); ++i ) {
1288             const bool isAlliance = campaignAwards[i]._type == Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE;
1289             const bool isCurse = campaignAwards[i]._type == Campaign::CampaignAwardData::TYPE_CREATURE_CURSE;
1290 
1291             if ( !isAlliance && !isCurse )
1292                 continue;
1293 
1294             Monster monster( campaignAwards[i]._subType );
1295             while ( true ) {
1296                 if ( troop.GetID() == monster.GetID() ) {
1297                     if ( isAlliance ) {
1298                         return { NeutralMonsterJoiningCondition::Reason::Alliance, troop.GetCount(),
1299                                  Campaign::CampaignAwardData::getAllianceJoiningMessage( monster.GetID() ),
1300                                  Campaign::CampaignAwardData::getAllianceFleeingMessage( monster.GetID() ) };
1301                     }
1302                     else {
1303                         return { NeutralMonsterJoiningCondition::Reason::Bane, troop.GetCount(), nullptr,
1304                                  Campaign::CampaignAwardData::getBaneFleeingMessage( monster.GetID() ) };
1305                     }
1306                 }
1307 
1308                 // try to cycle through the creature's upgrades
1309                 if ( !monster.isAllowUpgrade() )
1310                     break;
1311 
1312                 monster = monster.GetUpgrade();
1313             }
1314         }
1315     }
1316 
1317     if ( hero.hasArtifact( Artifact::HIDEOUS_MASK ) ) {
1318         return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr };
1319     }
1320 
1321     if ( tile.MonsterJoinConditionSkip() || !troop.isValid() ) {
1322         return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr };
1323     }
1324 
1325     // Neutral monsters don't care about hero's stats. Ignoring hero's stats makes hero's army strength be smaller in eyes of neutrals and they won't join so often.
1326     const double armyStrengthRatio = static_cast<const Troops &>( hero.GetArmy() ).GetStrength() / troop.GetStrength();
1327 
1328     if ( armyStrengthRatio > 2 ) {
1329         if ( tile.MonsterJoinConditionFree() ) {
1330             return { NeutralMonsterJoiningCondition::Reason::Free, troop.GetCount(), nullptr, nullptr };
1331         }
1332 
1333         if ( hero.HasSecondarySkill( Skill::Secondary::DIPLOMACY ) ) {
1334             const uint32_t amountToJoin = Monster::GetCountFromHitPoints( troop, troop.GetHitPoints() * hero.GetSecondaryValues( Skill::Secondary::DIPLOMACY ) / 100 );
1335             if ( amountToJoin > 0 ) {
1336                 return { NeutralMonsterJoiningCondition::Reason::ForMoney, amountToJoin, nullptr, nullptr };
1337             }
1338         }
1339     }
1340 
1341     if ( armyStrengthRatio > 5 && !hero.isControlAI() ) {
1342         // ... surely flee before us
1343         return { NeutralMonsterJoiningCondition::Reason::RunAway, 0, nullptr, nullptr };
1344     }
1345 
1346     return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr };
1347 }
1348 
WeakestTroop(const Troop * t1,const Troop * t2)1349 bool Army::WeakestTroop( const Troop * t1, const Troop * t2 )
1350 {
1351     return t1->GetStrength() < t2->GetStrength();
1352 }
1353 
StrongestTroop(const Troop * t1,const Troop * t2)1354 bool Army::StrongestTroop( const Troop * t1, const Troop * t2 )
1355 {
1356     return t1->GetStrength() > t2->GetStrength();
1357 }
1358 
SlowestTroop(const Troop * t1,const Troop * t2)1359 bool Army::SlowestTroop( const Troop * t1, const Troop * t2 )
1360 {
1361     return t1->GetSpeed() < t2->GetSpeed();
1362 }
1363 
FastestTroop(const Troop * t1,const Troop * t2)1364 bool Army::FastestTroop( const Troop * t1, const Troop * t2 )
1365 {
1366     return t1->GetSpeed() > t2->GetSpeed();
1367 }
1368 
SwapTroops(Troop & t1,Troop & t2)1369 void Army::SwapTroops( Troop & t1, Troop & t2 )
1370 {
1371     std::swap( t1, t2 );
1372 }
1373 
SaveLastTroop(void) const1374 bool Army::SaveLastTroop( void ) const
1375 {
1376     return commander && commander->isHeroes() && 1 == GetCount();
1377 }
1378 
GetStrongestMonster() const1379 Monster Army::GetStrongestMonster() const
1380 {
1381     Monster monster( Monster::UNKNOWN );
1382     for ( const Troop * troop : *this ) {
1383         if ( troop->isValid() && troop->GetMonster().GetMonsterStrength() > monster.GetMonsterStrength() ) {
1384             monster = troop->GetID();
1385         }
1386     }
1387     return monster;
1388 }
1389 
resetInvalidMonsters()1390 void Army::resetInvalidMonsters()
1391 {
1392     for ( Troop * troop : *this ) {
1393         if ( troop->GetID() != Monster::UNKNOWN && !troop->isValid() ) {
1394             troop->Set( Monster::UNKNOWN, 0 );
1395         }
1396     }
1397 }
1398 
operator <<(StreamBase & msg,const Army & army)1399 StreamBase & operator<<( StreamBase & msg, const Army & army )
1400 {
1401     msg << static_cast<u32>( army.size() );
1402 
1403     // Army: fixed size
1404     for ( Army::const_iterator it = army.begin(); it != army.end(); ++it )
1405         msg << **it;
1406 
1407     return msg << army.combat_format << army.color;
1408 }
1409 
operator >>(StreamBase & msg,Army & army)1410 StreamBase & operator>>( StreamBase & msg, Army & army )
1411 {
1412     u32 armysz;
1413     msg >> armysz;
1414 
1415     for ( Army::iterator it = army.begin(); it != army.end(); ++it )
1416         msg >> **it;
1417 
1418     msg >> army.combat_format >> army.color;
1419 
1420     // set army
1421     for ( Army::iterator it = army.begin(); it != army.end(); ++it ) {
1422         ArmyTroop * troop = static_cast<ArmyTroop *>( *it );
1423         if ( troop )
1424             troop->SetArmy( army );
1425     }
1426 
1427     // set later from owner (castle, heroes)
1428     army.commander = nullptr;
1429 
1430     return msg;
1431 }
1432