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