1 /***************************************************************************
2  *   Copyright (C) 2010 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 <cassert>
25 
26 #include "ai.h"
27 #include "army.h"
28 #include "army_troop.h"
29 #include "audio.h"
30 #include "battle_arena.h"
31 #include "battle_army.h"
32 #include "battle_bridge.h"
33 #include "battle_catapult.h"
34 #include "battle_cell.h"
35 #include "battle_command.h"
36 #include "battle_interface.h"
37 #include "battle_tower.h"
38 #include "battle_troop.h"
39 #include "castle.h"
40 #include "ground.h"
41 #include "icn.h"
42 #include "logging.h"
43 #include "race.h"
44 #include "settings.h"
45 #include "spell_info.h"
46 #include "tools.h"
47 #include "translations.h"
48 #include "world.h"
49 
50 namespace Battle
51 {
52     Arena * arena = nullptr;
53 }
54 
55 namespace
56 {
57     // compute a new seed from a list of actions, so random actions happen differently depending on user inputs
UpdateRandomSeed(const size_t seed,const Battle::Actions & actions)58     size_t UpdateRandomSeed( const size_t seed, const Battle::Actions & actions )
59     {
60         size_t newSeed = seed;
61 
62         for ( const Battle::Command & command : actions ) {
63             if ( command.GetType() == Battle::CommandType::MSG_BATTLE_AUTO ) {
64                 continue; // "auto battle" button event is ignored for the purpose of this hash
65             }
66 
67             fheroes2::hashCombine( newSeed, command.GetType() );
68             for ( const int commandArg : command ) {
69                 fheroes2::hashCombine( newSeed, commandArg );
70             }
71         }
72 
73         return newSeed;
74     }
75 
GetCovr(int ground,std::mt19937 & gen)76     int GetCovr( int ground, std::mt19937 & gen )
77     {
78         std::vector<int> covrs;
79 
80         switch ( ground ) {
81         case Maps::Ground::SNOW:
82             covrs.push_back( ICN::COVR0007 );
83             covrs.push_back( ICN::COVR0008 );
84             covrs.push_back( ICN::COVR0009 );
85             covrs.push_back( ICN::COVR0010 );
86             covrs.push_back( ICN::COVR0011 );
87             covrs.push_back( ICN::COVR0012 );
88             break;
89 
90         case Maps::Ground::WASTELAND:
91             covrs.push_back( ICN::COVR0019 );
92             covrs.push_back( ICN::COVR0020 );
93             covrs.push_back( ICN::COVR0021 );
94             covrs.push_back( ICN::COVR0022 );
95             covrs.push_back( ICN::COVR0023 );
96             covrs.push_back( ICN::COVR0024 );
97             break;
98 
99         case Maps::Ground::DIRT:
100             covrs.push_back( ICN::COVR0013 );
101             covrs.push_back( ICN::COVR0014 );
102             covrs.push_back( ICN::COVR0015 );
103             covrs.push_back( ICN::COVR0016 );
104             covrs.push_back( ICN::COVR0017 );
105             covrs.push_back( ICN::COVR0018 );
106             break;
107 
108         case Maps::Ground::GRASS:
109             covrs.push_back( ICN::COVR0001 );
110             covrs.push_back( ICN::COVR0002 );
111             covrs.push_back( ICN::COVR0003 );
112             covrs.push_back( ICN::COVR0004 );
113             covrs.push_back( ICN::COVR0005 );
114             covrs.push_back( ICN::COVR0006 );
115             break;
116 
117         default:
118             break;
119         }
120 
121         return covrs.empty() ? ICN::UNKNOWN : Rand::GetWithGen( covrs, gen );
122     }
123 }
124 
operator ==(const TargetInfo & ta) const125 bool Battle::TargetInfo::operator==( const TargetInfo & ta ) const
126 {
127     return defender == ta.defender;
128 }
129 
GetArena(void)130 Battle::Arena * Battle::GetArena( void )
131 {
132     return arena;
133 }
134 
GetCastle(void)135 const Castle * Battle::Arena::GetCastle( void )
136 {
137     return arena->castle;
138 }
139 
GetBridge(void)140 Battle::Bridge * Battle::Arena::GetBridge( void )
141 {
142     return arena->bridge;
143 }
144 
GetBoard(void)145 Battle::Board * Battle::Arena::GetBoard( void )
146 {
147     return &arena->board;
148 }
149 
GetGraveyard(void)150 Battle::Graveyard * Battle::Arena::GetGraveyard( void )
151 {
152     return &arena->graveyard;
153 }
154 
GetInterface(void)155 Battle::Interface * Battle::Arena::GetInterface( void )
156 {
157     return arena->interface;
158 }
159 
GetTower(int type)160 Battle::Tower * Battle::Arena::GetTower( int type )
161 {
162     switch ( type ) {
163     case TWR_LEFT:
164         return arena->towers[0];
165     case TWR_CENTER:
166         return arena->towers[1];
167     case TWR_RIGHT:
168         return arena->towers[2];
169     default:
170         break;
171     }
172     return nullptr;
173 }
174 
Arena(Army & a1,Army & a2,s32 index,bool local,Rand::DeterministicRandomGenerator & randomGenerator)175 Battle::Arena::Arena( Army & a1, Army & a2, s32 index, bool local, Rand::DeterministicRandomGenerator & randomGenerator )
176     : army1( nullptr )
177     , army2( nullptr )
178     , armies_order( nullptr )
179     , current_color( Color::NONE )
180     , preferredColor( -1 ) // be aware of unknown color
181     , castle( world.getCastleEntrance( Maps::GetPoint( index ) ) )
182     , _isTown( castle != nullptr )
183     , catapult( nullptr )
184     , bridge( nullptr )
185     , interface( nullptr )
186     , icn_covr( ICN::UNKNOWN )
187     , current_turn( 0 )
188     , auto_battle( 0 )
189     , end_turn( false )
190     , _randomGenerator( randomGenerator )
191 {
192     const Settings & conf = Settings::Get();
193     usage_spells.reserve( 20 );
194 
195     assert( arena == nullptr );
196     arena = this;
197 
198     army1 = new Force( a1, false, _randomGenerator, _uidGenerator );
199     army2 = new Force( a2, true, _randomGenerator, _uidGenerator );
200 
201     // init castle (interface ahead)
202     if ( castle ) {
203         CastleHeroes heroes = world.GetHeroes( *castle );
204 
205         // skip if present guard and guest
206         if ( heroes.FullHouse() )
207             castle = nullptr;
208 
209         // skip for town
210         if ( castle && !castle->isCastle() )
211             castle = nullptr;
212     }
213 
214     // init interface
215     if ( local ) {
216         interface = new Interface( *this, index );
217         board.SetArea( interface->GetArea() );
218 
219         armies_order = new Units();
220         armies_order->reserve( 25 );
221         interface->SetArmiesOrder( armies_order );
222     }
223     else {
224         // no interface - force auto battle mode for human player
225         if ( a1.isControlHuman() ) {
226             auto_battle |= a1.GetColor();
227         }
228         if ( a2.isControlHuman() ) {
229             auto_battle |= a2.GetColor();
230         }
231     }
232 
233     towers[0] = nullptr;
234     towers[1] = nullptr;
235     towers[2] = nullptr;
236 
237     if ( castle ) {
238         // init
239         towers[0] = castle->isBuild( BUILD_LEFTTURRET ) ? new Tower( *castle, TWR_LEFT, _randomGenerator, _uidGenerator.GetUnique() ) : nullptr;
240         towers[1] = new Tower( *castle, TWR_CENTER, _randomGenerator, _uidGenerator.GetUnique() );
241         towers[2] = castle->isBuild( BUILD_RIGHTTURRET ) ? new Tower( *castle, TWR_RIGHT, _randomGenerator, _uidGenerator.GetUnique() ) : nullptr;
242 
243         catapult = army1->GetCommander() ? new Catapult( *army1->GetCommander(), _randomGenerator ) : nullptr;
244         bridge = new Bridge();
245 
246         // catapult cell
247         board[CATAPULT_POS].SetObject( 1 );
248 
249         // wall (3,2,1,0)
250         const int wallObject = castle->isFortificationBuild() ? 3 : 2;
251         board[CASTLE_FIRST_TOP_WALL_POS].SetObject( wallObject );
252         board[CASTLE_SECOND_TOP_WALL_POS].SetObject( wallObject );
253         board[CASTLE_THIRD_TOP_WALL_POS].SetObject( wallObject );
254         board[CASTLE_FOURTH_TOP_WALL_POS].SetObject( wallObject );
255 
256         // tower
257         board[CASTLE_TOP_GATE_TOWER_POS].SetObject( 2 );
258         board[CASTLE_BOTTOM_GATE_TOWER_POS].SetObject( 2 );
259 
260         // archers tower
261         board[CASTLE_TOP_ARCHER_TOWER_POS].SetObject( 2 );
262         board[CASTLE_BOTTOM_ARCHER_TOWER_POS].SetObject( 2 );
263 
264         // bridge
265         board[CASTLE_GATE_POS].SetObject( 1 );
266     }
267     else
268     // set obstacles
269     {
270         std::mt19937 seededGen( world.GetMapSeed() + static_cast<uint32_t>( index ) );
271 
272         icn_covr = Rand::GetWithGen( 0, 99, seededGen ) < 40 ? GetCovr( world.GetTiles( index ).GetGround(), seededGen ) : ICN::UNKNOWN;
273 
274         if ( icn_covr != ICN::UNKNOWN )
275             board.SetCovrObjects( icn_covr );
276         else
277             board.SetCobjObjects( world.GetTiles( index ), seededGen );
278     }
279 
280     if ( interface ) {
281         fheroes2::Display & display = fheroes2::Display::instance();
282 
283         if ( conf.ExtGameUseFade() )
284             fheroes2::FadeDisplay();
285 
286         interface->fullRedraw();
287         display.render();
288 
289         // pause for play M82::PREBATTL
290         while ( LocalEvent::Get().HandleEvents() && Mixer::isPlaying( -1 ) )
291             ;
292     }
293 }
294 
~Arena()295 Battle::Arena::~Arena()
296 {
297     delete army1;
298     delete army2;
299     delete towers[0];
300     delete towers[1];
301     delete towers[2];
302     delete catapult;
303     delete interface;
304     delete armies_order;
305     delete bridge;
306 
307     assert( arena == this );
308     arena = nullptr;
309 }
310 
TurnTroop(Unit * troop,const Units & orderHistory)311 void Battle::Arena::TurnTroop( Unit * troop, const Units & orderHistory )
312 {
313     DEBUG_LOG( DBG_BATTLE, DBG_TRACE, troop->String( true ) );
314 
315     if ( troop->isAffectedByMorale() ) {
316         troop->SetRandomMorale();
317     }
318 
319     end_turn = false;
320 
321     while ( !end_turn ) {
322         Actions actions;
323 
324         if ( !troop->isValid() ) { // looks like the unit died
325             end_turn = true;
326         }
327         else if ( troop->Modes( MORALE_BAD ) && !troop->Modes( TR_SKIPMOVE ) ) {
328             // bad morale, happens only if the unit wasn't waiting for a turn
329             actions.push_back( Command( CommandType::MSG_BATTLE_MORALE, troop->GetUID(), false ) );
330             end_turn = true;
331         }
332         else {
333             // re-calculate possible paths in case unit moved or it's a new turn
334             _pathfinder.calculate( *troop );
335 
336             // get task from player
337             if ( troop->isControlRemote() )
338                 RemoteTurn( *troop, actions );
339             else {
340                 if ( ( troop->GetCurrentControl() & CONTROL_AI ) || ( troop->GetCurrentColor() & auto_battle ) ) {
341                     AI::Get().BattleTurn( *this, *troop, actions );
342                 }
343                 else {
344                     HumanTurn( *troop, actions );
345                 }
346             }
347         }
348 
349         const size_t newSeed = UpdateRandomSeed( _randomGenerator.GetSeed(), actions );
350         _randomGenerator.UpdateSeed( newSeed );
351 
352         const bool troopHasAlreadySkippedMove = troop->Modes( TR_SKIPMOVE );
353         // apply task
354         while ( !actions.empty() ) {
355             // apply action
356             ApplyAction( actions.front() );
357             actions.pop_front();
358 
359             if ( armies_order ) {
360                 // some spell could kill someone or affect the speed of some unit, update units order
361                 Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
362             }
363 
364             // check end battle
365             if ( !BattleValid() ) {
366                 end_turn = true;
367                 break;
368             }
369 
370             const bool isImmovable = troop->Modes( SP_BLIND | IS_PARALYZE_MAGIC );
371             const bool troopSkipsMove = troopHasAlreadySkippedMove ? troop->Modes( TR_HARDSKIP ) : troop->Modes( TR_SKIPMOVE );
372 
373             // good morale
374             if ( !end_turn && troop->isValid() && troop->Modes( TR_MOVED ) && troop->Modes( MORALE_GOOD ) && !isImmovable && !troopSkipsMove ) {
375                 actions.emplace_back( CommandType::MSG_BATTLE_MORALE, troop->GetUID(), true );
376             }
377         }
378 
379         if ( troop->Modes( TR_MOVED ) || ( troop->Modes( TR_SKIPMOVE ) && !troopHasAlreadySkippedMove ) ) {
380             end_turn = true;
381         }
382 
383         board.Reset();
384 
385         if ( interface ) {
386             fheroes2::delayforMs( 10 );
387         }
388     }
389 }
390 
BattleValid(void) const391 bool Battle::Arena::BattleValid( void ) const
392 {
393     return army1->isValid() && army2->isValid() && 0 == result_game.army1 && 0 == result_game.army2;
394 }
395 
Turns(void)396 void Battle::Arena::Turns( void )
397 {
398     ++current_turn;
399 
400     DEBUG_LOG( DBG_BATTLE, DBG_TRACE, current_turn );
401 
402     const Settings & conf = Settings::Get();
403 
404     if ( interface ) {
405         interface->RedrawActionNewTurn();
406     }
407 
408     army1->NewTurn();
409     army2->NewTurn();
410 
411     // order history on the current turn
412     Units orderHistory;
413 
414     if ( armies_order ) {
415         orderHistory.reserve( 25 );
416 
417         // build initial units order
418         Force::UpdateOrderUnits( *army1, *army2, nullptr, preferredColor, orderHistory, *armies_order );
419     }
420 
421     {
422         bool tower_moved = false;
423         bool catapult_moved = false;
424 
425         Unit * troop = nullptr;
426 
427         while ( BattleValid() && ( troop = Force::GetCurrentUnit( *army1, *army2, true, preferredColor ) ) != nullptr ) {
428             current_color = troop->GetCurrentOrArmyColor();
429 
430             // switch preferred color for the next unit
431             preferredColor = troop->GetArmyColor() == army1->GetColor() ? army2->GetColor() : army1->GetColor();
432 
433             if ( armies_order ) {
434                 // add unit to the order history
435                 orderHistory.push_back( troop );
436 
437                 // update units order
438                 Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
439             }
440 
441             // first turn: castle and catapult action
442             if ( castle ) {
443                 if ( !catapult_moved && troop->GetColor() == army1->GetColor() ) {
444                     CatapultAction();
445                     catapult_moved = true;
446                 }
447 
448                 if ( !tower_moved && troop->GetColor() == army2->GetColor() ) {
449                     if ( towers[1] && towers[1]->isValid() ) {
450                         TowerAction( *towers[1] );
451 
452                         if ( armies_order ) {
453                             // tower could kill someone, update units order
454                             Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
455                         }
456                     }
457                     if ( towers[0] && towers[0]->isValid() ) {
458                         TowerAction( *towers[0] );
459 
460                         if ( armies_order ) {
461                             // tower could kill someone, update units order
462                             Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
463                         }
464                     }
465                     if ( towers[2] && towers[2]->isValid() ) {
466                         TowerAction( *towers[2] );
467 
468                         if ( armies_order ) {
469                             // tower could kill someone, update units order
470                             Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
471                         }
472                     }
473                     tower_moved = true;
474 
475                     // check dead last army from towers
476                     if ( !BattleValid() )
477                         break;
478                 }
479             }
480 
481             // set bridge passable
482             if ( bridge )
483                 bridge->SetPassable( *troop );
484 
485             // turn troop
486             TurnTroop( troop, orderHistory );
487 
488             if ( armies_order ) {
489                 // if unit hasn't finished its turn yet, then remove it from the order history
490                 if ( troop->Modes( TR_SKIPMOVE ) && !troop->Modes( TR_MOVED ) ) {
491                     orderHistory.pop_back();
492                 }
493             }
494         }
495     }
496 
497     // can skip move ?
498     if ( conf.ExtBattleSoftWait() ) {
499         Unit * troop = nullptr;
500 
501         while ( BattleValid() && ( troop = Force::GetCurrentUnit( *army1, *army2, false, preferredColor ) ) != nullptr ) {
502             current_color = troop->GetCurrentOrArmyColor();
503 
504             // switch preferred color for the next unit
505             preferredColor = troop->GetArmyColor() == army1->GetColor() ? army2->GetColor() : army1->GetColor();
506 
507             if ( armies_order ) {
508                 // add unit to the order history
509                 orderHistory.push_back( troop );
510 
511                 // update units order
512                 Force::UpdateOrderUnits( *army1, *army2, troop, preferredColor, orderHistory, *armies_order );
513             }
514 
515             // set bridge passable
516             if ( bridge )
517                 bridge->SetPassable( *troop );
518 
519             // turn troop
520             TurnTroop( troop, orderHistory );
521         }
522     }
523 
524     // end turn: fix result
525     if ( !army1->isValid() || ( result_game.army1 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) {
526         result_game.army1 |= RESULT_LOSS;
527         // check if any of the original troops in the army2 are still alive
528         result_game.army2 = army2->isValid( false ) ? RESULT_WINS : RESULT_LOSS;
529     }
530     else if ( !army2->isValid() || ( result_game.army2 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) {
531         result_game.army2 |= RESULT_LOSS;
532         // check if any of the original troops in the army1 are still alive
533         result_game.army1 = army1->isValid( false ) ? RESULT_WINS : RESULT_LOSS;
534     }
535 
536     // fix experience and killed
537     if ( result_game.army1 || result_game.army2 ) {
538         result_game.exp1 = army2->GetDeadHitPoints();
539         result_game.exp2 = army1->GetDeadHitPoints();
540 
541         if ( army1->GetCommander() && !( result_game.army1 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) {
542             result_game.exp2 += 500;
543         }
544         if ( ( _isTown || army2->GetCommander() ) && !( result_game.army2 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) {
545             result_game.exp1 += 500;
546         }
547 
548         const Force * army_loss = ( result_game.army1 & RESULT_LOSS ? army1 : ( result_game.army2 & RESULT_LOSS ? army2 : nullptr ) );
549         result_game.killed = army_loss ? army_loss->GetDeadCounts() : 0;
550     }
551 }
552 
RemoteTurn(const Unit & b,Actions & a)553 void Battle::Arena::RemoteTurn( const Unit & b, Actions & a )
554 {
555     DEBUG_LOG( DBG_BATTLE, DBG_WARN, "switch to AI turn" );
556     AI::Get().BattleTurn( *this, b, a );
557 }
558 
HumanTurn(const Unit & b,Actions & a)559 void Battle::Arena::HumanTurn( const Unit & b, Actions & a )
560 {
561     if ( interface )
562         interface->HumanTurn( b, a );
563 }
564 
TowerAction(const Tower & twr)565 void Battle::Arena::TowerAction( const Tower & twr )
566 {
567     board.Reset();
568     board.SetEnemyQuality( twr );
569     const Unit * enemy = GetEnemyMaxQuality( twr.GetColor() );
570 
571     if ( enemy ) {
572         Command cmd( CommandType::MSG_BATTLE_TOWER, twr.GetType(), enemy->GetUID() );
573         ApplyAction( cmd );
574     }
575 }
576 
CatapultAction(void)577 void Battle::Arena::CatapultAction( void )
578 {
579     if ( catapult ) {
580         u32 shots = catapult->GetShots();
581         std::vector<u32> values( CAT_CENTRAL_TOWER + 1, 0 );
582 
583         values[CAT_WALL1] = GetCastleTargetValue( CAT_WALL1 );
584         values[CAT_WALL2] = GetCastleTargetValue( CAT_WALL2 );
585         values[CAT_WALL3] = GetCastleTargetValue( CAT_WALL3 );
586         values[CAT_WALL4] = GetCastleTargetValue( CAT_WALL4 );
587         values[CAT_TOWER1] = GetCastleTargetValue( CAT_TOWER1 );
588         values[CAT_TOWER2] = GetCastleTargetValue( CAT_TOWER2 );
589         values[CAT_BRIDGE] = GetCastleTargetValue( CAT_BRIDGE );
590         values[CAT_CENTRAL_TOWER] = GetCastleTargetValue( CAT_CENTRAL_TOWER );
591 
592         Command cmd( CommandType::MSG_BATTLE_CATAPULT );
593 
594         cmd << shots;
595 
596         while ( shots-- ) {
597             const int target = catapult->GetTarget( values );
598             const uint32_t damage = std::min( catapult->GetDamage(), values[target] );
599             const bool hit = catapult->IsNextShotHit();
600 
601             cmd << target << damage << ( hit ? 1 : 0 );
602 
603             if ( hit ) {
604                 values[target] -= damage;
605             }
606         }
607 
608         // preserve the order of shots - command arguments will be extracted in reverse order
609         std::reverse( cmd.begin(), cmd.end() );
610 
611         ApplyAction( cmd );
612     }
613 }
614 
GetPath(const Unit & b,const Position & dst) const615 Battle::Indexes Battle::Arena::GetPath( const Unit & b, const Position & dst ) const
616 {
617     Indexes result = board.GetPath( b, dst );
618 
619     if ( !result.empty() && IS_DEBUG( DBG_BATTLE, DBG_TRACE ) ) {
620         std::stringstream ss;
621         for ( u32 ii = 0; ii < result.size(); ++ii )
622             ss << result[ii] << ", ";
623         DEBUG_LOG( DBG_BATTLE, DBG_TRACE, ss.str() );
624     }
625 
626     return result;
627 }
628 
CalculateTwoMoveOverlap(int32_t indexTo,uint32_t movementRange) const629 Battle::Indexes Battle::Arena::CalculateTwoMoveOverlap( int32_t indexTo, uint32_t movementRange ) const
630 {
631     return _pathfinder.findTwoMovesOverlap( indexTo, movementRange );
632 }
633 
CalculateMoveToUnit(const Unit & target) const634 std::pair<int, uint32_t> Battle::Arena::CalculateMoveToUnit( const Unit & target ) const
635 {
636     std::pair<int, uint32_t> result = { -1, 65535 };
637 
638     const Position & pos = target.GetPosition();
639     const Cell * head = pos.GetHead();
640     const Cell * tail = pos.GetTail();
641 
642     if ( head ) {
643         const ArenaNode & headNode = _pathfinder.getNode( head->GetIndex() );
644         if ( headNode._from != -1 ) {
645             result.first = headNode._from;
646             result.second = headNode._cost;
647         }
648     }
649 
650     if ( tail ) {
651         const ArenaNode & tailNode = _pathfinder.getNode( tail->GetIndex() );
652         if ( tailNode._from != -1 && tailNode._cost < result.second ) {
653             result.first = tailNode._from;
654             result.second = tailNode._cost;
655         }
656     }
657 
658     return result;
659 }
660 
CalculateMoveDistance(int32_t indexTo) const661 uint32_t Battle::Arena::CalculateMoveDistance( int32_t indexTo ) const
662 {
663     return Board::isValidIndex( indexTo ) ? _pathfinder.getDistance( indexTo ) : 65535;
664 }
665 
hexIsPassable(int32_t indexTo) const666 bool Battle::Arena::hexIsPassable( int32_t indexTo ) const
667 {
668     return Board::isValidIndex( indexTo ) && _pathfinder.hexIsPassable( indexTo );
669 }
670 
getAllAvailableMoves(uint32_t moveRange) const671 Battle::Indexes Battle::Arena::getAllAvailableMoves( uint32_t moveRange ) const
672 {
673     return _pathfinder.getAllAvailableMoves( moveRange );
674 }
675 
GetNearestReachableCell(const Unit & currentUnit,const int32_t dst) const676 int32_t Battle::Arena::GetNearestReachableCell( const Unit & currentUnit, const int32_t dst ) const
677 {
678     const Position dstPos = Position::GetReachable( currentUnit, dst );
679 
680     if ( dstPos.GetHead() != nullptr && ( !currentUnit.isWide() || dstPos.GetTail() != nullptr ) ) {
681         // Destination cell is already reachable
682         return dstPos.GetHead()->GetIndex();
683     }
684 
685     const Indexes path = _pathfinder.buildPath( dst );
686 
687     // Destination cell is unreachable in principle according to the ArenaPathfinder
688     if ( path.empty() ) {
689         return -1;
690     }
691 
692     // Search for the reachable cell nearest to the end of the path
693     for ( auto it = path.crbegin(); it != path.crend(); ++it ) {
694         const Position pos = Position::GetReachable( currentUnit, *it );
695 
696         if ( pos.GetHead() != nullptr && ( !currentUnit.isWide() || pos.GetTail() != nullptr ) ) {
697             return pos.GetHead()->GetIndex();
698         }
699     }
700 
701     return -1;
702 }
703 
GetTroopBoard(s32 index)704 Battle::Unit * Battle::Arena::GetTroopBoard( s32 index )
705 {
706     return Board::isValidIndex( index ) ? board[index].GetUnit() : nullptr;
707 }
708 
GetTroopBoard(s32 index) const709 const Battle::Unit * Battle::Arena::GetTroopBoard( s32 index ) const
710 {
711     return Board::isValidIndex( index ) ? board[index].GetUnit() : nullptr;
712 }
713 
GetCommander1(void) const714 const HeroBase * Battle::Arena::GetCommander1( void ) const
715 {
716     return army1->GetCommander();
717 }
718 
GetCommander2(void) const719 const HeroBase * Battle::Arena::GetCommander2( void ) const
720 {
721     return army2->GetCommander();
722 }
723 
GetArmyColor1(void) const724 int Battle::Arena::GetArmyColor1( void ) const
725 {
726     return army1->GetColor();
727 }
728 
GetArmyColor2(void) const729 int Battle::Arena::GetArmyColor2( void ) const
730 {
731     return army2->GetColor();
732 }
733 
GetCurrentColor(void) const734 int Battle::Arena::GetCurrentColor( void ) const
735 {
736     return current_color;
737 }
738 
GetOppositeColor(int col) const739 int Battle::Arena::GetOppositeColor( int col ) const
740 {
741     return col == GetArmyColor1() ? GetArmyColor2() : GetArmyColor1();
742 }
743 
GetTroopUID(u32 uid)744 Battle::Unit * Battle::Arena::GetTroopUID( u32 uid )
745 {
746     Units::iterator it = std::find_if( army1->begin(), army1->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } );
747 
748     if ( it != army1->end() )
749         return *it;
750 
751     it = std::find_if( army2->begin(), army2->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } );
752 
753     return it != army2->end() ? *it : nullptr;
754 }
755 
GetTroopUID(u32 uid) const756 const Battle::Unit * Battle::Arena::GetTroopUID( u32 uid ) const
757 {
758     Units::const_iterator it = std::find_if( army1->begin(), army1->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } );
759 
760     if ( it != army1->end() )
761         return *it;
762 
763     it = std::find_if( army2->begin(), army2->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } );
764 
765     return it != army2->end() ? *it : nullptr;
766 }
767 
GetEnemyMaxQuality(int my_color) const768 const Battle::Unit * Battle::Arena::GetEnemyMaxQuality( int my_color ) const
769 {
770     const Unit * res = nullptr;
771     s32 quality = 0;
772 
773     for ( Board::const_iterator it = board.begin(); it != board.end(); ++it ) {
774         const Unit * enemy = ( *it ).GetUnit();
775 
776         if ( enemy && enemy->GetColor() != my_color && ( !enemy->isWide() || enemy->GetTailIndex() != ( *it ).GetIndex() ) && quality < ( *it ).GetQuality() ) {
777             res = enemy;
778             quality = ( *it ).GetQuality();
779         }
780     }
781 
782     return res;
783 }
784 
FadeArena(bool clearMessageLog) const785 void Battle::Arena::FadeArena( bool clearMessageLog ) const
786 {
787     if ( interface )
788         interface->FadeArena( clearMessageLog );
789 }
790 
GetUsageSpells(void) const791 const SpellStorage & Battle::Arena::GetUsageSpells( void ) const
792 {
793     return usage_spells;
794 }
795 
GetFreePositionNearHero(const int heroColor) const796 int32_t Battle::Arena::GetFreePositionNearHero( const int heroColor ) const
797 {
798     std::vector<int> cellIds;
799     if ( army1->GetColor() == heroColor ) {
800         cellIds = { 11, 22, 33 };
801     }
802     else if ( army2->GetColor() == heroColor ) {
803         cellIds = { 21, 32, 43 };
804     }
805     else {
806         // Some third color?
807         return -1;
808     }
809 
810     assert( !cellIds.empty() );
811 
812     for ( const int cellId : cellIds ) {
813         if ( board[cellId].isPassable1( true ) && board[cellId].GetUnit() == nullptr ) {
814             return cellId;
815         }
816     }
817 
818     return -1;
819 }
820 
CanSurrenderOpponent(int color) const821 bool Battle::Arena::CanSurrenderOpponent( int color ) const
822 {
823     const HeroBase * hero1 = getEnemyCommander( color );
824     const HeroBase * hero2 = getCommander( color );
825     return hero1 && hero1->isHeroes() && hero2 && hero2->isHeroes() && !world.GetKingdom( hero2->GetColor() ).GetCastles().empty();
826 }
827 
CanRetreatOpponent(int color) const828 bool Battle::Arena::CanRetreatOpponent( int color ) const
829 {
830     const HeroBase * hero = getCommander( color );
831     return hero && hero->isHeroes() && ( color == army1->GetColor() || hero->inCastle() == nullptr );
832 }
833 
isSpellcastDisabled() const834 bool Battle::Arena::isSpellcastDisabled() const
835 {
836     const HeroBase * hero1 = army1->GetCommander();
837     const HeroBase * hero2 = army2->GetCommander();
838 
839     if ( ( hero1 && hero1->hasArtifact( Artifact::SPHERE_NEGATION ) ) || ( hero2 && hero2->hasArtifact( Artifact::SPHERE_NEGATION ) ) ) {
840         return true;
841     }
842     return false;
843 }
844 
isDisableCastSpell(const Spell & spell,std::string * msg)845 bool Battle::Arena::isDisableCastSpell( const Spell & spell, std::string * msg )
846 {
847     const HeroBase * current_commander = GetCurrentCommander();
848 
849     // check sphere negation (only for heroes)
850     if ( isSpellcastDisabled() ) {
851         if ( msg )
852             *msg = _( "The Sphere of Negation artifact is in effect for this battle, disabling all combat spells." );
853         return true;
854     }
855 
856     // check casted
857     if ( current_commander ) {
858         if ( current_commander->Modes( Heroes::SPELLCASTED ) ) {
859             if ( msg )
860                 *msg = _( "You have already cast a spell this round." );
861             return true;
862         }
863 
864         if ( spell == Spell::EARTHQUAKE && !castle ) {
865             *msg = _( "That spell will affect no one!" );
866             return true;
867         }
868         else if ( spell.isSummon() ) {
869             const Unit * elem = GetCurrentForce().FindMode( CAP_SUMMONELEM );
870             bool affect = true;
871 
872             if ( elem )
873                 switch ( spell.GetID() ) {
874                 case Spell::SUMMONEELEMENT:
875                     if ( elem->GetID() != Monster::EARTH_ELEMENT )
876                         affect = false;
877                     break;
878                 case Spell::SUMMONAELEMENT:
879                     if ( elem->GetID() != Monster::AIR_ELEMENT )
880                         affect = false;
881                     break;
882                 case Spell::SUMMONFELEMENT:
883                     if ( elem->GetID() != Monster::FIRE_ELEMENT )
884                         affect = false;
885                     break;
886                 case Spell::SUMMONWELEMENT:
887                     if ( elem->GetID() != Monster::WATER_ELEMENT )
888                         affect = false;
889                     break;
890                 default:
891                     break;
892                 }
893             if ( !affect ) {
894                 *msg = _( "You may only summon one type of elemental per combat." );
895                 return true;
896             }
897 
898             if ( 0 > GetFreePositionNearHero( current_color ) ) {
899                 *msg = _( "There is no open space adjacent to your hero to summon an Elemental to." );
900                 return true;
901             }
902         }
903         else if ( spell.isValid() ) {
904             // check army
905             for ( Board::const_iterator it = board.begin(); it != board.end(); ++it ) {
906                 const Battle::Unit * b = ( *it ).GetUnit();
907 
908                 if ( b ) {
909                     if ( b->AllowApplySpell( spell, current_commander, nullptr ) )
910                         return false;
911                 }
912                 else
913                     // check graveyard
914                     if ( GraveyardAllowResurrect( ( *it ).GetIndex(), spell ) )
915                     return false;
916             }
917             *msg = _( "That spell will affect no one!" );
918             return true;
919         }
920     }
921 
922     // may be check other..
923     /*
924      */
925 
926     return false;
927 }
928 
GraveyardAllowResurrect(s32 index,const Spell & spell) const929 bool Battle::Arena::GraveyardAllowResurrect( s32 index, const Spell & spell ) const
930 {
931     if ( !spell.isResurrect() )
932         return false;
933 
934     const HeroBase * hero = GetCurrentCommander();
935     if ( hero == nullptr )
936         return false;
937 
938     const Unit * killed = GetTroopUID( graveyard.GetLastTroopUID( index ) );
939     if ( killed == nullptr )
940         return false;
941 
942     if ( !killed->AllowApplySpell( spell, hero, nullptr ) )
943         return false;
944 
945     if ( Board::GetCell( index )->GetUnit() != nullptr )
946         return false;
947 
948     if ( !killed->isWide() )
949         return true;
950 
951     const int tailIndex = killed->GetTailIndex();
952     const int headIndex = killed->GetHeadIndex();
953     const int secondIndex = tailIndex == index ? headIndex : tailIndex;
954 
955     if ( Board::GetCell( secondIndex )->GetUnit() != nullptr )
956         return false;
957 
958     return true;
959 }
960 
GraveyardLastTroop(s32 index) const961 const Battle::Unit * Battle::Arena::GraveyardLastTroop( s32 index ) const
962 {
963     return GetTroopUID( graveyard.GetLastTroopUID( index ) );
964 }
965 
GetGraveyardTroops(const int32_t hexIndex) const966 std::vector<const Battle::Unit *> Battle::Arena::GetGraveyardTroops( const int32_t hexIndex ) const
967 {
968     const TroopUIDs & ids = graveyard.GetTroopUIDs( hexIndex );
969 
970     std::vector<const Battle::Unit *> units( ids.size() );
971     for ( size_t i = 0; i < ids.size(); ++i ) {
972         units[i] = GetTroopUID( ids[i] );
973     }
974 
975     return units;
976 }
977 
GraveyardClosedCells(void) const978 Battle::Indexes Battle::Arena::GraveyardClosedCells( void ) const
979 {
980     return graveyard.GetClosedCells();
981 }
982 
SetCastleTargetValue(int target,u32 value)983 void Battle::Arena::SetCastleTargetValue( int target, u32 value )
984 {
985     switch ( target ) {
986     case CAT_WALL1:
987         board[CASTLE_FIRST_TOP_WALL_POS].SetObject( value );
988         break;
989     case CAT_WALL2:
990         board[CASTLE_SECOND_TOP_WALL_POS].SetObject( value );
991         break;
992     case CAT_WALL3:
993         board[CASTLE_THIRD_TOP_WALL_POS].SetObject( value );
994         break;
995     case CAT_WALL4:
996         board[CASTLE_FOURTH_TOP_WALL_POS].SetObject( value );
997         break;
998 
999     case CAT_TOWER1:
1000         if ( towers[0] && towers[0]->isValid() )
1001             towers[0]->SetDestroy();
1002         break;
1003     case CAT_TOWER2:
1004         if ( towers[2] && towers[2]->isValid() )
1005             towers[2]->SetDestroy();
1006         break;
1007     case CAT_CENTRAL_TOWER:
1008         if ( towers[1] && towers[1]->isValid() )
1009             towers[1]->SetDestroy();
1010         break;
1011 
1012     case CAT_BRIDGE:
1013         if ( bridge->isValid() ) {
1014             if ( !bridge->isDown() ) {
1015                 if ( interface ) {
1016                     interface->RedrawBridgeAnimation( true );
1017                 }
1018 
1019                 bridge->SetDown( true );
1020             }
1021 
1022             bridge->SetDestroy();
1023         }
1024         break;
1025 
1026     default:
1027         break;
1028     }
1029 }
1030 
GetCastleTargetValue(int target) const1031 u32 Battle::Arena::GetCastleTargetValue( int target ) const
1032 {
1033     switch ( target ) {
1034     case CAT_WALL1:
1035         return board[CASTLE_FIRST_TOP_WALL_POS].GetObject();
1036     case CAT_WALL2:
1037         return board[CASTLE_SECOND_TOP_WALL_POS].GetObject();
1038     case CAT_WALL3:
1039         return board[CASTLE_THIRD_TOP_WALL_POS].GetObject();
1040     case CAT_WALL4:
1041         return board[CASTLE_FOURTH_TOP_WALL_POS].GetObject();
1042 
1043     case CAT_TOWER1:
1044         return towers[0] && towers[0]->isValid();
1045     case CAT_TOWER2:
1046         return towers[2] && towers[2]->isValid();
1047     case CAT_CENTRAL_TOWER:
1048         return towers[1] && towers[1]->isValid();
1049 
1050     case CAT_BRIDGE:
1051         return bridge->isValid();
1052 
1053     default:
1054         break;
1055     }
1056     return 0;
1057 }
1058 
GetCastleTargets(void) const1059 std::vector<int> Battle::Arena::GetCastleTargets( void ) const
1060 {
1061     std::vector<int> targets;
1062     targets.reserve( 8 );
1063 
1064     // check walls
1065     if ( 0 != board[CASTLE_FIRST_TOP_WALL_POS].GetObject() )
1066         targets.push_back( CAT_WALL1 );
1067     if ( 0 != board[CASTLE_SECOND_TOP_WALL_POS].GetObject() )
1068         targets.push_back( CAT_WALL2 );
1069     if ( 0 != board[CASTLE_THIRD_TOP_WALL_POS].GetObject() )
1070         targets.push_back( CAT_WALL3 );
1071     if ( 0 != board[CASTLE_FOURTH_TOP_WALL_POS].GetObject() )
1072         targets.push_back( CAT_WALL4 );
1073 
1074     // check right/left towers
1075     if ( towers[0] && towers[0]->isValid() )
1076         targets.push_back( CAT_TOWER1 );
1077     if ( towers[2] && towers[2]->isValid() )
1078         targets.push_back( CAT_TOWER2 );
1079 
1080     return targets;
1081 }
1082 
getCommander(const int color) const1083 const HeroBase * Battle::Arena::getCommander( const int color ) const
1084 {
1085     return ( army1->GetColor() == color ) ? army1->GetCommander() : army2->GetCommander();
1086 }
1087 
getEnemyCommander(const int color) const1088 const HeroBase * Battle::Arena::getEnemyCommander( const int color ) const
1089 {
1090     return ( army1->GetColor() == color ) ? army2->GetCommander() : army1->GetCommander();
1091 }
1092 
GetCurrentCommander(void) const1093 const HeroBase * Battle::Arena::GetCurrentCommander( void ) const
1094 {
1095     return getCommander( current_color );
1096 }
1097 
CreateElemental(const Spell & spell)1098 Battle::Unit * Battle::Arena::CreateElemental( const Spell & spell )
1099 {
1100     const HeroBase * hero = GetCurrentCommander();
1101     const int32_t pos = GetFreePositionNearHero( current_color );
1102 
1103     if ( pos < 0 || !hero ) {
1104         DEBUG_LOG( DBG_BATTLE, DBG_WARN, "internal error" );
1105         return nullptr;
1106     }
1107 
1108     Force & army = GetCurrentForce();
1109     Unit * elem = army.FindMode( CAP_SUMMONELEM );
1110     bool affect = true;
1111 
1112     if ( elem )
1113         switch ( spell.GetID() ) {
1114         case Spell::SUMMONEELEMENT:
1115             if ( elem->GetID() != Monster::EARTH_ELEMENT )
1116                 affect = false;
1117             break;
1118         case Spell::SUMMONAELEMENT:
1119             if ( elem->GetID() != Monster::AIR_ELEMENT )
1120                 affect = false;
1121             break;
1122         case Spell::SUMMONFELEMENT:
1123             if ( elem->GetID() != Monster::FIRE_ELEMENT )
1124                 affect = false;
1125             break;
1126         case Spell::SUMMONWELEMENT:
1127             if ( elem->GetID() != Monster::WATER_ELEMENT )
1128                 affect = false;
1129             break;
1130         default:
1131             break;
1132         }
1133 
1134     if ( !affect ) {
1135         DEBUG_LOG( DBG_BATTLE, DBG_WARN, "other elemental summon" );
1136         return nullptr;
1137     }
1138 
1139     Monster mons( spell );
1140 
1141     if ( !mons.isValid() ) {
1142         DEBUG_LOG( DBG_BATTLE, DBG_WARN, "unknown id" );
1143         return nullptr;
1144     }
1145 
1146     DEBUG_LOG( DBG_BATTLE, DBG_TRACE, mons.GetName() << ", position: " << pos );
1147 
1148     const uint32_t count = fheroes2::getSummonMonsterCount( spell, hero->GetPower(), hero );
1149     elem = new Unit( Troop( mons, count ), pos, hero == army2->GetCommander(), _randomGenerator, _uidGenerator.GetUnique() );
1150 
1151     if ( elem ) {
1152         elem->SetModes( CAP_SUMMONELEM );
1153         elem->SetArmy( hero->GetArmy() );
1154         army.push_back( elem );
1155     }
1156     else {
1157         DEBUG_LOG( DBG_BATTLE, DBG_WARN, "is nullptr" );
1158     }
1159 
1160     return elem;
1161 }
1162 
CreateMirrorImage(Unit & b,s32 pos)1163 Battle::Unit * Battle::Arena::CreateMirrorImage( Unit & b, s32 pos )
1164 {
1165     Unit * image = new Unit( b, pos, b.isReflect(), _randomGenerator, _uidGenerator.GetUnique() );
1166 
1167     if ( image ) {
1168         b.SetMirror( image );
1169         image->SetArmy( *b.GetArmy() );
1170         image->SetMirror( &b );
1171         image->SetModes( CAP_MIRRORIMAGE );
1172         b.SetModes( CAP_MIRROROWNER );
1173 
1174         GetCurrentForce().push_back( image );
1175     }
1176     else {
1177         DEBUG_LOG( DBG_BATTLE, DBG_WARN, "internal error" );
1178     }
1179 
1180     return image;
1181 }
1182 
IsShootingPenalty(const Unit & attacker,const Unit & defender) const1183 bool Battle::Arena::IsShootingPenalty( const Unit & attacker, const Unit & defender ) const
1184 {
1185     // no castle - no castle walls, penalty does not apply
1186     if ( castle == nullptr ) {
1187         return false;
1188     }
1189 
1190     // penalty does not apply to towers
1191     if ( defender.Modes( CAP_TOWER ) || attacker.Modes( CAP_TOWER ) )
1192         return false;
1193 
1194     // penalty does not apply if the attacker's hero has certain artifacts or skills
1195     const HeroBase * hero = attacker.GetCommander();
1196     if ( hero ) {
1197         // golden bow artifact
1198         if ( hero->hasArtifact( Artifact::GOLDEN_BOW ) )
1199             return false;
1200 
1201         // archery skill
1202         if ( hero->GetLevelSkill( Skill::Secondary::ARCHERY ) != Skill::Level::NONE )
1203             return false;
1204     }
1205 
1206     // penalty does not apply if the attacking unit (be it a castle attacker or a castle defender) is inside the castle walls
1207     if ( !attacker.OutOfWalls() ) {
1208         return false;
1209     }
1210 
1211     // penalty does not apply if both units are on the same side relative to the castle walls
1212     if ( attacker.OutOfWalls() == defender.OutOfWalls() ) {
1213         return false;
1214     }
1215 
1216     // penalty does not apply if the target unit is exposed due to the broken castle wall
1217     const std::vector<fheroes2::Point> points = GetLinePoints( attacker.GetBackPoint(), defender.GetBackPoint(), CELLW / 3 );
1218 
1219     for ( std::vector<fheroes2::Point>::const_iterator it = points.begin(); it != points.end(); ++it ) {
1220         if ( ( 0 == board[CASTLE_FIRST_TOP_WALL_POS].GetObject() && ( board[CASTLE_FIRST_TOP_WALL_POS].GetPos() & *it ) )
1221              || ( 0 == board[CASTLE_SECOND_TOP_WALL_POS].GetObject() && ( board[CASTLE_SECOND_TOP_WALL_POS].GetPos() & *it ) )
1222              || ( 0 == board[CASTLE_THIRD_TOP_WALL_POS].GetObject() && ( board[CASTLE_THIRD_TOP_WALL_POS].GetPos() & *it ) )
1223              || ( 0 == board[CASTLE_FOURTH_TOP_WALL_POS].GetObject() && ( board[CASTLE_FOURTH_TOP_WALL_POS].GetPos() & *it ) ) )
1224             return false;
1225     }
1226 
1227     return true;
1228 }
1229 
GetForce1(void)1230 Battle::Force & Battle::Arena::GetForce1( void )
1231 {
1232     return *army1;
1233 }
1234 
GetForce2(void)1235 Battle::Force & Battle::Arena::GetForce2( void )
1236 {
1237     return *army2;
1238 }
1239 
getForce(const int color)1240 Battle::Force & Battle::Arena::getForce( const int color )
1241 {
1242     return ( army1->GetColor() == color ) ? *army1 : *army2;
1243 }
1244 
getEnemyForce(const int color)1245 Battle::Force & Battle::Arena::getEnemyForce( const int color )
1246 {
1247     return ( army1->GetColor() == color ) ? *army2 : *army1;
1248 }
1249 
GetCurrentForce(void)1250 Battle::Force & Battle::Arena::GetCurrentForce( void )
1251 {
1252     return getForce( current_color );
1253 }
1254 
GetICNCovr(void) const1255 int Battle::Arena::GetICNCovr( void ) const
1256 {
1257     return icn_covr;
1258 }
1259 
GetCurrentTurn(void) const1260 u32 Battle::Arena::GetCurrentTurn( void ) const
1261 {
1262     return current_turn;
1263 }
1264 
GetResult(void)1265 Battle::Result & Battle::Arena::GetResult( void )
1266 {
1267     return result_game;
1268 }
1269 
CanBreakAutoBattle(void) const1270 bool Battle::Arena::CanBreakAutoBattle( void ) const
1271 {
1272     return ( auto_battle & current_color ) && GetCurrentCommander() && !GetCurrentCommander()->isControlAI();
1273 }
1274 
BreakAutoBattle(void)1275 void Battle::Arena::BreakAutoBattle( void )
1276 {
1277     auto_battle &= ~current_color;
1278 }
1279 
GetRandomGenerator() const1280 const Rand::DeterministicRandomGenerator & Battle::Arena::GetRandomGenerator() const
1281 {
1282     return _randomGenerator;
1283 }
1284