1 /***************************************************************************
2  *   Copyright (C) 2012 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 
25 #include "battle.h"
26 #include "battle_arena.h"
27 #include "battle_army.h"
28 #include "battle_troop.h"
29 #include "heroes.h"
30 #include "monster_anim.h"
31 #include "settings.h"
32 #include "speed.h"
33 
34 #define CAPACITY 16
35 
36 namespace Battle
37 {
AllowPart1(const Unit * b)38     bool AllowPart1( const Unit * b )
39     {
40         return !b->Modes( TR_SKIPMOVE ) && b->GetSpeed() > Speed::STANDING;
41     }
42 
AllowPart2(const Unit * b)43     bool AllowPart2( const Unit * b )
44     {
45         return b->Modes( TR_SKIPMOVE ) && b->GetSpeed() > Speed::STANDING;
46     }
47 
ForceGetCurrentUnitPart(Units & units1,Units & units2,bool part1,bool units1_first,bool orders_mode)48     Unit * ForceGetCurrentUnitPart( Units & units1, Units & units2, bool part1, bool units1_first, bool orders_mode )
49     {
50         auto allowPartFunc = part1 ? AllowPart1 : AllowPart2;
51         Units::iterator it1 = std::find_if( units1.begin(), units1.end(), allowPartFunc );
52         Units::iterator it2 = std::find_if( units2.begin(), units2.end(), allowPartFunc );
53         Unit * result = nullptr;
54 
55         if ( it1 != units1.end() && it2 != units2.end() ) {
56             if ( ( *it1 )->GetSpeed() == ( *it2 )->GetSpeed() ) {
57                 result = units1_first ? *it1 : *it2;
58             }
59             else if ( part1 || Settings::Get().ExtBattleReverseWaitOrder() ) {
60                 if ( ( *it1 )->GetSpeed() > ( *it2 )->GetSpeed() )
61                     result = *it1;
62                 else if ( ( *it2 )->GetSpeed() > ( *it1 )->GetSpeed() )
63                     result = *it2;
64             }
65             else {
66                 if ( ( *it1 )->GetSpeed() < ( *it2 )->GetSpeed() )
67                     result = *it1;
68                 else if ( ( *it2 )->GetSpeed() < ( *it1 )->GetSpeed() )
69                     result = *it2;
70             }
71         }
72         else if ( it1 != units1.end() )
73             result = *it1;
74         else if ( it2 != units2.end() )
75             result = *it2;
76 
77         if ( result && orders_mode ) {
78             if ( it1 != units1.end() && result == *it1 )
79                 units1.erase( it1 );
80             else if ( it2 != units2.end() && result == *it2 )
81                 units2.erase( it2 );
82         }
83 
84         return result;
85     }
86 }
87 
Units()88 Battle::Units::Units()
89 {
90     reserve( CAPACITY );
91 }
92 
Units(const Units & units,bool filter)93 Battle::Units::Units( const Units & units, bool filter )
94     : std::vector<Unit *>()
95 {
96     reserve( CAPACITY < units.size() ? units.size() : CAPACITY );
97     assign( units.begin(), units.end() );
98     if ( filter )
99         erase( std::remove_if( begin(), end(), []( const Unit * unit ) { return !unit->isValid(); } ), end() );
100 }
101 
SortSlowest()102 void Battle::Units::SortSlowest()
103 {
104     std::stable_sort( begin(), end(), Army::SlowestTroop );
105 }
106 
SortFastest()107 void Battle::Units::SortFastest()
108 {
109     std::stable_sort( begin(), end(), Army::FastestTroop );
110 }
111 
SortArchers(void)112 void Battle::Units::SortArchers( void )
113 {
114     std::sort( begin(), end(), []( const Troop * t1, const Troop * t2 ) { return t1->isArchers() && !t2->isArchers(); } );
115 }
116 
FindUID(uint32_t pid) const117 Battle::Unit * Battle::Units::FindUID( uint32_t pid ) const
118 {
119     const_iterator it = std::find_if( begin(), end(), [pid]( const Unit * unit ) { return unit->isUID( pid ); } );
120 
121     return it == end() ? nullptr : *it;
122 }
123 
FindMode(uint32_t mod) const124 Battle::Unit * Battle::Units::FindMode( uint32_t mod ) const
125 {
126     const_iterator it = std::find_if( begin(), end(), [mod]( const Unit * unit ) { return unit->Modes( mod ); } );
127 
128     return it == end() ? nullptr : *it;
129 }
130 
Force(Army & parent,bool opposite,const Rand::DeterministicRandomGenerator & randomGenerator,TroopsUidGenerator & generator)131 Battle::Force::Force( Army & parent, bool opposite, const Rand::DeterministicRandomGenerator & randomGenerator, TroopsUidGenerator & generator )
132     : army( parent )
133 {
134     uids.reserve( army.Size() );
135 
136     for ( u32 index = 0; index < army.Size(); ++index ) {
137         const Troop * troop = army.GetTroop( index );
138         const u32 position = army.isSpreadFormat() ? index * 22 : 22 + index * 11;
139         u32 uid = 0;
140 
141         if ( troop && troop->isValid() ) {
142             push_back( new Unit( *troop, opposite ? position + 10 : position, opposite, randomGenerator, generator.GetUnique() ) );
143             back()->SetArmy( army );
144             uid = back()->GetUID();
145         }
146 
147         uids.push_back( uid );
148     }
149 }
150 
~Force()151 Battle::Force::~Force()
152 {
153     for ( iterator it = begin(); it != end(); ++it )
154         delete *it;
155 }
156 
GetCommander(void) const157 const HeroBase * Battle::Force::GetCommander( void ) const
158 {
159     return army.GetCommander();
160 }
161 
GetCommander(void)162 HeroBase * Battle::Force::GetCommander( void )
163 {
164     return army.GetCommander();
165 }
166 
GetColor(void) const167 int Battle::Force::GetColor( void ) const
168 {
169     return army.GetColor();
170 }
171 
GetControl(void) const172 int Battle::Force::GetControl( void ) const
173 {
174     return army.GetControl();
175 }
176 
isValid(const bool considerBattlefieldArmy) const177 bool Battle::Force::isValid( const bool considerBattlefieldArmy /* = true */ ) const
178 {
179     // Consider the state of the army on the battlefield (including resurrected units, summoned units, etc)
180     if ( considerBattlefieldArmy ) {
181         return std::any_of( begin(), end(), []( const Unit * unit ) { return unit->isValid(); } );
182     }
183 
184     // Consider only the state of the original army
185     for ( uint32_t index = 0; index < army.Size(); ++index ) {
186         const Troop * troop = army.GetTroop( index );
187 
188         if ( troop && troop->isValid() ) {
189             const Unit * unit = FindUID( uids.at( index ) );
190 
191             if ( unit && unit->GetDead() < unit->GetInitialCount() ) {
192                 return true;
193             }
194         }
195     }
196 
197     return false;
198 }
199 
GetSurrenderCost(void) const200 uint32_t Battle::Force::GetSurrenderCost( void ) const
201 {
202     double res = 0;
203 
204     for ( const_iterator it = begin(); it != end(); ++it )
205         if ( ( *it )->isValid() ) {
206             const payment_t & payment = ( *it )->GetCost();
207             res += payment.gold;
208         }
209 
210     const HeroBase * commander = GetCommander();
211     if ( commander ) {
212         const Artifact art( Artifact::STATESMAN_QUILL );
213         double mod = commander->hasArtifact( art ) ? art.ExtraValue() / 100.0 : 0.5;
214 
215         switch ( commander->GetLevelSkill( Skill::Secondary::DIPLOMACY ) ) {
216         case Skill::Level::BASIC:
217             mod *= 0.8;
218             break;
219         case Skill::Level::ADVANCED:
220             mod *= 0.6;
221             break;
222         case Skill::Level::EXPERT:
223             mod *= 0.4;
224             break;
225         }
226         res *= mod;
227     }
228     // Total cost should always be at least 1 gold
229     return res >= 1 ? static_cast<uint32_t>( res + 0.5 ) : 1;
230 }
231 
NewTurn(void)232 void Battle::Force::NewTurn( void )
233 {
234     if ( GetCommander() )
235         GetCommander()->ResetModes( Heroes::SPELLCASTED );
236 
237     std::for_each( begin(), end(), []( Unit * unit ) { unit->NewTurn(); } );
238 }
239 
UpdateOrderUnits(const Force & army1,const Force & army2,const Unit * activeUnit,int preferredColor,const Units & orderHistory,Units & orders)240 void Battle::Force::UpdateOrderUnits( const Force & army1, const Force & army2, const Unit * activeUnit, int preferredColor, const Units & orderHistory, Units & orders )
241 {
242     orders.clear();
243     orders.insert( orders.end(), orderHistory.begin(), orderHistory.end() );
244 
245     {
246         Units units1( army1, true );
247         Units units2( army2, true );
248 
249         units1.SortFastest();
250         units2.SortFastest();
251 
252         Unit * unit = nullptr;
253 
254         while ( ( unit = ForceGetCurrentUnitPart( units1, units2, true, preferredColor != army2.GetColor(), true ) ) != nullptr ) {
255             if ( unit != activeUnit && unit->isValid() ) {
256                 preferredColor = unit->GetArmyColor() == army1.GetColor() ? army2.GetColor() : army1.GetColor();
257 
258                 orders.push_back( unit );
259             }
260         }
261     }
262 
263     if ( Settings::Get().ExtBattleSoftWait() ) {
264         Units units1( army1, true );
265         Units units2( army2, true );
266 
267         if ( Settings::Get().ExtBattleReverseWaitOrder() ) {
268             units1.SortFastest();
269             units2.SortFastest();
270         }
271         else {
272             std::reverse( units1.begin(), units1.end() );
273             std::reverse( units2.begin(), units2.end() );
274 
275             units1.SortSlowest();
276             units2.SortSlowest();
277         }
278 
279         Unit * unit = nullptr;
280 
281         while ( ( unit = ForceGetCurrentUnitPart( units1, units2, false, preferredColor != army2.GetColor(), true ) ) != nullptr ) {
282             if ( unit != activeUnit && unit->isValid() ) {
283                 preferredColor = unit->GetArmyColor() == army1.GetColor() ? army2.GetColor() : army1.GetColor();
284 
285                 orders.push_back( unit );
286             }
287         }
288     }
289 }
290 
GetCurrentUnit(const Force & army1,const Force & army2,bool part1,int preferredColor)291 Battle::Unit * Battle::Force::GetCurrentUnit( const Force & army1, const Force & army2, bool part1, int preferredColor )
292 {
293     Units units1( army1, true );
294     Units units2( army2, true );
295 
296     if ( part1 || Settings::Get().ExtBattleReverseWaitOrder() ) {
297         units1.SortFastest();
298         units2.SortFastest();
299     }
300     else {
301         std::reverse( units1.begin(), units1.end() );
302         std::reverse( units2.begin(), units2.end() );
303 
304         units1.SortSlowest();
305         units2.SortSlowest();
306     }
307 
308     Unit * result = ForceGetCurrentUnitPart( units1, units2, part1, preferredColor != army2.GetColor(), false );
309 
310     return result && result->isValid() ? result : nullptr;
311 }
312 
GetKilledTroops(void) const313 Troops Battle::Force::GetKilledTroops( void ) const
314 {
315     Troops killed;
316 
317     for ( const_iterator it = begin(); it != end(); ++it ) {
318         const Unit & b = ( **it );
319         killed.PushBack( b, b.GetDead() );
320     }
321 
322     return killed;
323 }
324 
animateIdleUnits()325 bool Battle::Force::animateIdleUnits()
326 {
327     bool redrawNeeded = false;
328 
329     for ( Force::iterator it = begin(); it != end(); ++it ) {
330         Unit & unit = **it;
331 
332         // check if alive and not paralyzed
333         if ( unit.isValid() && !unit.Modes( SP_BLIND | IS_PARALYZE_MAGIC ) ) {
334             if ( unit.isIdling() ) {
335                 if ( unit.isFinishAnimFrame() ) {
336                     redrawNeeded = unit.SwitchAnimation( Monster_Info::STATIC ) || redrawNeeded;
337                 }
338                 else {
339                     unit.IncreaseAnimFrame();
340                     redrawNeeded = true;
341                 }
342             }
343             // checkIdleDelay() sets and checks unit's internal timer if we're ready to switch to next one
344             else if ( unit.GetAnimationState() == Monster_Info::STATIC && unit.checkIdleDelay() ) {
345                 redrawNeeded = unit.SwitchAnimation( Monster_Info::IDLE ) || redrawNeeded;
346             }
347         }
348     }
349     return redrawNeeded;
350 }
351 
resetIdleAnimation()352 void Battle::Force::resetIdleAnimation()
353 {
354     for ( Force::iterator it = begin(); it != end(); ++it ) {
355         Unit & unit = **it;
356 
357         // check if alive and not paralyzed
358         if ( unit.isValid() && !unit.Modes( SP_BLIND | IS_PARALYZE_MAGIC ) ) {
359             if ( unit.GetAnimationState() == Monster_Info::STATIC )
360                 unit.checkIdleDelay();
361         }
362     }
363 }
364 
HasMonster(const Monster & mons) const365 bool Battle::Force::HasMonster( const Monster & mons ) const
366 {
367     return std::any_of( begin(), end(), [&mons]( const Unit * unit ) { return unit->isMonster( mons.GetID() ); } );
368 }
369 
GetDeadCounts(void) const370 u32 Battle::Force::GetDeadCounts( void ) const
371 {
372     u32 res = 0;
373 
374     for ( const_iterator it = begin(); it != end(); ++it )
375         res += ( *it )->GetDead();
376 
377     return res;
378 }
379 
GetDeadHitPoints(void) const380 u32 Battle::Force::GetDeadHitPoints( void ) const
381 {
382     u32 res = 0;
383 
384     for ( const_iterator it = begin(); it != end(); ++it ) {
385         res += static_cast<Monster *>( *it )->GetHitPoints() * ( *it )->GetDead();
386     }
387 
388     return res;
389 }
390 
SyncArmyCount()391 void Battle::Force::SyncArmyCount()
392 {
393     for ( u32 index = 0; index < army.Size(); ++index ) {
394         Troop * troop = army.GetTroop( index );
395 
396         if ( troop && troop->isValid() ) {
397             const Unit * unit = FindUID( uids.at( index ) );
398 
399             if ( unit ) {
400                 troop->SetCount( unit->GetDead() > unit->GetInitialCount() ? 0 : unit->GetInitialCount() - unit->GetDead() );
401             }
402         }
403     }
404 }
405