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