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