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 <array>
25 #include <cassert>
26 #include <cstdint>
27 #include <iterator>
28 #include <set>
29 
30 #include "battle_arena.h"
31 #include "battle_army.h"
32 #include "battle_board.h"
33 #include "battle_bridge.h"
34 #include "battle_troop.h"
35 #include "castle.h"
36 #include "game_static.h"
37 #include "ground.h"
38 #include "icn.h"
39 #include "logging.h"
40 #include "maps_tiles.h"
41 #include "rand.h"
42 #include "tools.h"
43 #include "translations.h"
44 
45 namespace
46 {
GetRandomObstaclePosition(std::mt19937 & gen)47     int GetRandomObstaclePosition( std::mt19937 & gen )
48     {
49         return Rand::GetWithGen( 3, 6, gen ) + ( 11 * Rand::GetWithGen( 1, 7, gen ) );
50     }
51 
isTwoHexObject(const int icnId)52     bool isTwoHexObject( const int icnId )
53     {
54         switch ( icnId ) {
55         case ICN::COBJ0004:
56         case ICN::COBJ0005:
57         case ICN::COBJ0007:
58         case ICN::COBJ0011:
59         case ICN::COBJ0014:
60         case ICN::COBJ0015:
61         case ICN::COBJ0017:
62         case ICN::COBJ0018:
63         case ICN::COBJ0019:
64         case ICN::COBJ0020:
65         case ICN::COBJ0022:
66         case ICN::COBJ0030:
67         case ICN::COBJ0031:
68             return true;
69 
70         default:
71             break;
72         }
73 
74         return false;
75     }
76 }
77 
Board()78 Battle::Board::Board()
79 {
80     reserve( ARENASIZE );
81     for ( u32 ii = 0; ii < ARENASIZE; ++ii )
82         push_back( Cell( ii ) );
83 }
84 
SetArea(const fheroes2::Rect & area)85 void Battle::Board::SetArea( const fheroes2::Rect & area )
86 {
87     for ( iterator it = begin(); it != end(); ++it )
88         ( *it ).SetArea( area );
89 }
90 
Reset(void)91 void Battle::Board::Reset( void )
92 {
93     for ( iterator it = begin(); it != end(); ++it ) {
94         Unit * unit = it->GetUnit();
95         if ( unit && !unit->isValid() ) {
96             unit->PostKilledAction();
97         }
98         it->resetReachability();
99         it->ResetQuality();
100     }
101 }
102 
SetPositionQuality(const Unit & b) const103 void Battle::Board::SetPositionQuality( const Unit & b ) const
104 {
105     Arena * arena = GetArena();
106     Units enemies( arena->getEnemyForce( b.GetCurrentColor() ), true );
107 
108     // Make sure archers are first here, so melee unit's score won't be double counted
109     enemies.SortArchers();
110 
111     for ( const Unit * unit : enemies ) {
112         if ( !unit || !unit->isValid() ) {
113             continue;
114         }
115 
116         const Indexes around = GetAroundIndexes( *unit );
117         for ( const int32_t index : around ) {
118             Cell * cell2 = GetCell( index );
119             if ( !cell2 || !cell2->isPassable3( b, false ) )
120                 continue;
121 
122             const int32_t quality = cell2->GetQuality();
123             const int32_t attackValue = OptimalAttackValue( b, *unit, index );
124 
125             // Only sum up quality score if it's archers; otherwise just pick the highest
126             if ( unit->isArchers() )
127                 cell2->SetQuality( quality + attackValue );
128             else if ( attackValue > quality )
129                 cell2->SetQuality( attackValue );
130         }
131     }
132 }
133 
SetEnemyQuality(const Unit & unit) const134 void Battle::Board::SetEnemyQuality( const Unit & unit ) const
135 {
136     Arena * arena = GetArena();
137     Units enemies( arena->getEnemyForce( unit.GetColor() ), true );
138     if ( unit.Modes( SP_BERSERKER ) ) {
139         Units allies( arena->getForce( unit.GetColor() ), true );
140         enemies.insert( enemies.end(), allies.begin(), allies.end() );
141     }
142 
143     for ( Units::const_iterator it = enemies.begin(); it != enemies.end(); ++it ) {
144         const Unit * enemy = *it;
145 
146         if ( enemy && enemy->isValid() ) {
147             const s32 score = enemy->GetScoreQuality( unit );
148             Cell * cell = GetCell( enemy->GetHeadIndex() );
149 
150             cell->SetQuality( score );
151 
152             if ( enemy->isWide() )
153                 GetCell( enemy->GetTailIndex() )->SetQuality( score );
154 
155             DEBUG_LOG( DBG_BATTLE, DBG_TRACE, score << " for " << enemy->String() );
156         }
157     }
158 }
159 
GetDistance(s32 index1,s32 index2)160 uint32_t Battle::Board::GetDistance( s32 index1, s32 index2 )
161 {
162     if ( isValidIndex( index1 ) && isValidIndex( index2 ) ) {
163         const int32_t x1 = index1 % ARENAW;
164         const int32_t y1 = index1 / ARENAW;
165 
166         const int32_t x2 = index2 % ARENAW;
167         const int32_t y2 = index2 / ARENAW;
168 
169         const int32_t du = y2 - y1;
170         const int32_t dv = ( x2 + y2 / 2 ) - ( x1 + y1 / 2 );
171 
172         if ( ( du >= 0 && dv >= 0 ) || ( du < 0 && dv < 0 ) ) {
173             return std::max( std::abs( du ), std::abs( dv ) );
174         }
175         else {
176             return std::abs( du ) + std::abs( dv );
177         }
178     }
179 
180     return 0;
181 }
182 
SetScanPassability(const Unit & unit)183 void Battle::Board::SetScanPassability( const Unit & unit )
184 {
185     std::for_each( begin(), end(), []( Battle::Cell & cell ) { cell.resetReachability(); } );
186 
187     at( unit.GetHeadIndex() ).setReachableForHead();
188 
189     if ( unit.isWide() ) {
190         at( unit.GetTailIndex() ).setReachableForTail();
191     }
192 
193     if ( unit.isFlying() ) {
194         const Bridge * bridge = Arena::GetBridge();
195         const bool isPassableBridge = bridge == nullptr || bridge->isPassable( unit );
196 
197         for ( std::size_t i = 0; i < size(); i++ ) {
198             if ( at( i ).isPassable3( unit, false ) && ( isPassableBridge || !isBridgeIndex( static_cast<int32_t>( i ), unit ) ) ) {
199                 at( i ).setReachableForHead();
200 
201                 if ( unit.isWide() ) {
202                     at( i ).setReachableForTail();
203                 }
204             }
205         }
206     }
207     else {
208         // Set passable cells.
209         for ( const int32_t idx : GetDistanceIndexes( unit.GetHeadIndex(), unit.GetSpeed() ) ) {
210             GetPath( unit, Position::GetPositionWhenMoved( unit, idx ), false );
211         }
212     }
213 }
214 
GetPathForUnit(const Unit & unit,const Position & destination,const uint32_t remainingSteps,const int32_t currentCellId,std::vector<bool> & visitedCells,Indexes & result) const215 bool Battle::Board::GetPathForUnit( const Unit & unit, const Position & destination, const uint32_t remainingSteps, const int32_t currentCellId,
216                                     std::vector<bool> & visitedCells, Indexes & result ) const
217 {
218     if ( remainingSteps == 0 ) {
219         return false;
220     }
221 
222     const Castle * castle = Arena::GetCastle();
223     const bool isMoatBuilt = castle && castle->isBuild( BUILD_MOAT );
224 
225     const int32_t dstCellId = destination.GetHead()->GetIndex();
226 
227     // Upper distance limit
228     if ( GetDistance( currentCellId, dstCellId ) > remainingSteps ) {
229         return false;
230     }
231 
232     std::multimap<uint32_t, int32_t> cellCosts;
233 
234     for ( const int32_t cellId : GetAroundIndexes( currentCellId ) ) {
235         const Cell & cell = at( cellId );
236 
237         // Ignore already visited or impassable cell
238         if ( visitedCells.at( cellId ) || !cell.isPassable4( unit, at( currentCellId ) ) ) {
239             continue;
240         }
241 
242         // Unit is already at its destination
243         if ( cellId == dstCellId ) {
244             result.push_back( cellId );
245 
246             return true;
247         }
248 
249         // Unit steps into the moat, do not let it pass through the moat
250         if ( isMoatBuilt && isMoatIndex( cellId, unit ) ) {
251             continue;
252         }
253 
254         // Calculate the distance from the cell in question to the destination, sort cells by distance
255         cellCosts.emplace( GetDistance( cellId, dstCellId ), cellId );
256     }
257 
258     // Scan the available cells recursively in ascending order of distance
259     for ( const auto & cellCost : cellCosts ) {
260         const int32_t cellId = cellCost.second;
261 
262         // Mark the cell as visited for further steps
263         visitedCells.at( cellId ) = true;
264 
265         if ( GetPathForUnit( unit, destination, remainingSteps - 1, cellId, visitedCells, result ) ) {
266             result.push_back( cellId );
267 
268             return true;
269         }
270 
271         // Unmark the cell as visited
272         visitedCells.at( cellId ) = false;
273     }
274 
275     return false;
276 }
277 
GetPathForWideUnit(const Unit & unit,const Position & destination,const uint32_t remainingSteps,const int32_t currentHeadCellId,const int32_t prevHeadCellId,std::vector<bool> & visitedCells,Indexes & result) const278 bool Battle::Board::GetPathForWideUnit( const Unit & unit, const Position & destination, const uint32_t remainingSteps, const int32_t currentHeadCellId,
279                                         const int32_t prevHeadCellId, std::vector<bool> & visitedCells, Indexes & result ) const
280 {
281     if ( remainingSteps == 0 ) {
282         return false;
283     }
284 
285     const Castle * castle = Arena::GetCastle();
286     const bool isMoatBuilt = castle && castle->isBuild( BUILD_MOAT );
287 
288     const int32_t dstHeadCellId = destination.GetHead()->GetIndex();
289     const int32_t dstTailCellId = destination.GetTail()->GetIndex();
290 
291     const bool isCurrentLeftDirection = prevHeadCellId < 0 ? unit.isReflect() : ( ( GetDirection( prevHeadCellId, currentHeadCellId ) & LEFT_SIDE ) != 0 );
292     const int32_t currentTailCellId = isCurrentLeftDirection ? currentHeadCellId + 1 : currentHeadCellId - 1;
293 
294     // Upper distance limit
295     if ( GetDistance( currentHeadCellId, dstHeadCellId ) > remainingSteps && GetDistance( currentTailCellId, dstHeadCellId ) > remainingSteps ) {
296         return false;
297     }
298 
299     std::multimap<uint32_t, int32_t> cellCosts;
300 
301     for ( const int32_t headCellId : GetMoveWideIndexes( currentHeadCellId, isCurrentLeftDirection ) ) {
302         const Cell & cell = at( headCellId );
303 
304         // Ignore already visited or impassable cell
305         if ( visitedCells.at( headCellId ) || !cell.isPassable4( unit, at( currentHeadCellId ) ) ) {
306             continue;
307         }
308 
309         const int32_t tailCellId = ( GetDirection( currentHeadCellId, headCellId ) & LEFT_SIDE ) ? headCellId + 1 : headCellId - 1;
310 
311         // Unit is already at its destination
312         if ( headCellId == dstHeadCellId && tailCellId == dstTailCellId ) {
313             result.push_back( headCellId );
314 
315             return true;
316         }
317 
318         // Unit is already at its destination, but in the opposite direction
319         if ( headCellId == dstTailCellId && tailCellId == dstHeadCellId ) {
320             result.push_back( tailCellId );
321             result.push_back( headCellId );
322 
323             return true;
324         }
325 
326         // Unit steps into the moat
327         if ( isMoatBuilt && ( isMoatIndex( headCellId, unit ) || isMoatIndex( tailCellId, unit ) ) ) {
328             // In the moat it is only allowed to turn back, do not let the unit pass through the moat
329             if ( ( tailCellId != currentHeadCellId || !isMoatIndex( tailCellId, unit ) ) && ( headCellId != currentTailCellId || !isMoatIndex( headCellId, unit ) ) ) {
330                 continue;
331             }
332         }
333 
334         // Calculate the distance from the cell in question to the destination, sort cells by distance
335         cellCosts.emplace( GetDistance( headCellId, dstHeadCellId ) + GetDistance( tailCellId, dstTailCellId ), headCellId );
336     }
337 
338     // Scan the available cells recursively in ascending order of distance
339     for ( const auto & cellCost : cellCosts ) {
340         const int32_t headCellId = cellCost.second;
341 
342         // Mark the cell as visited for further steps
343         visitedCells.at( headCellId ) = true;
344 
345         // Turning back is not a movement
346         const uint32_t steps = headCellId == currentTailCellId ? remainingSteps : remainingSteps - 1;
347 
348         if ( GetPathForWideUnit( unit, destination, steps, headCellId, currentHeadCellId, visitedCells, result ) ) {
349             result.push_back( headCellId );
350 
351             return true;
352         }
353 
354         // Unmark the cell as visited
355         visitedCells.at( headCellId ) = false;
356     }
357 
358     return false;
359 }
360 
StraightenPathForUnit(const int32_t currentCellId,Indexes & path) const361 void Battle::Board::StraightenPathForUnit( const int32_t currentCellId, Indexes & path ) const
362 {
363     // A path less than 2 steps long cannot contain detours, leave it as is
364     if ( path.size() < 2 ) {
365         return;
366     }
367 
368     // Remember that the steps in the path are stored in reverse order
369     // Temporarily append the current cell of the unit to the end of the path
370     path.push_back( currentCellId );
371 
372     for ( std::size_t curr = 0; path.size() > 2 && curr < path.size() - 2; ++curr ) {
373         const std::size_t next = curr + 1;
374 
375         // Check whether we are passing through one of the neighboring cells at any of the future steps (excluding the next step)
376         for ( const int32_t cellId : GetAroundIndexes( path[curr] ) ) {
377             std::size_t pos;
378 
379             // Search for the last occurence of the current neighboring cell in the path (excluding the next step)
380             // Using path.size() - 1 should be safe here, because, due to the condition in the outer loop, path should never be empty
381             assert( !path.empty() );
382             for ( pos = path.size() - 1; pos > next; --pos ) {
383                 if ( path[pos] == cellId ) {
384                     break;
385                 }
386             }
387 
388             // If found, then remove the extra steps
389             if ( pos > next ) {
390                 path.erase( path.begin() + next, path.begin() + pos );
391 
392                 break;
393             }
394         }
395     }
396 
397     // Remove the current cell of the unit from the path
398     assert( !path.empty() );
399     path.pop_back();
400 }
401 
GetPath(const Unit & unit,const Position & destination,const bool debug) const402 Battle::Indexes Battle::Board::GetPath( const Unit & unit, const Position & destination, const bool debug ) const
403 {
404     Indexes result;
405 
406     const bool isWideUnit = unit.isWide();
407 
408     // Check if destination is valid
409     if ( !destination.GetHead() || ( isWideUnit && !destination.GetTail() ) ) {
410         ERROR_LOG( "Invalid destination for unit " + unit.String() );
411         return result;
412     }
413 
414     result.reserve( 15 );
415 
416     std::vector<bool> visitedCells( ARENASIZE, false );
417 
418     // Mark the current cell of the unit as visited
419     visitedCells.at( unit.GetHeadIndex() ) = true;
420 
421     if ( isWideUnit ) {
422         GetPathForWideUnit( unit, destination, unit.GetSpeed(), unit.GetHeadIndex(), -1, visitedCells, result );
423     }
424     else {
425         GetPathForUnit( unit, destination, unit.GetSpeed(), unit.GetHeadIndex(), visitedCells, result );
426 
427         // Try to straighten the unit's path by eliminating possible detours
428         StraightenPathForUnit( unit.GetHeadIndex(), result );
429     }
430 
431     if ( !result.empty() ) {
432         std::reverse( result.begin(), result.end() );
433 
434         // Set direction info for cells
435         for ( std::size_t i = 0; i < result.size(); i++ ) {
436             const int32_t cellId = result[i];
437 
438             Cell * headCell = GetCell( cellId );
439             assert( headCell != nullptr );
440 
441             headCell->setReachableForHead();
442 
443             if ( isWideUnit ) {
444                 const int32_t prevCellId = i == 0 ? unit.GetHeadIndex() : result[i - 1];
445 
446                 Cell * tailCell = GetCell( cellId, LEFT_SIDE & GetDirection( cellId, prevCellId ) ? LEFT : RIGHT );
447                 assert( tailCell != nullptr );
448 
449                 tailCell->setReachableForTail();
450             }
451         }
452     }
453 
454     if ( debug && result.empty() ) {
455         DEBUG_LOG( DBG_BATTLE, DBG_WARN,
456                    "Path is not found for " << unit.String() << ", destination: "
457                                             << "(head cell ID: " << destination.GetHead()->GetIndex()
458                                             << ", tail cell ID: " << ( isWideUnit ? destination.GetTail()->GetIndex() : -1 ) << ")" );
459     }
460 
461     return result;
462 }
463 
GetNearestTroops(const Unit * startUnit,const std::vector<Battle::Unit * > & blackList)464 std::vector<Battle::Unit *> Battle::Board::GetNearestTroops( const Unit * startUnit, const std::vector<Battle::Unit *> & blackList )
465 {
466     std::vector<std::pair<Battle::Unit *, uint32_t> > foundUnits;
467 
468     for ( Cell & cell : *this ) {
469         Unit * cellUnit = cell.GetUnit();
470         if ( cellUnit == nullptr || startUnit == cellUnit || cell.GetIndex() != cellUnit->GetHeadIndex() ) {
471             continue;
472         }
473 
474         const bool isBlackListed = std::find( blackList.begin(), blackList.end(), cellUnit ) != blackList.end();
475         if ( !isBlackListed ) {
476             foundUnits.emplace_back( cellUnit, GetDistance( startUnit->GetHeadIndex(), cellUnit->GetHeadIndex() ) );
477         }
478     }
479 
480     std::sort( foundUnits.begin(), foundUnits.end(),
481                []( const std::pair<Battle::Unit *, uint32_t> & first, const std::pair<Battle::Unit *, uint32_t> & second ) { return first.second < second.second; } );
482 
483     std::vector<Battle::Unit *> units;
484     units.reserve( foundUnits.size() );
485 
486     for ( const auto & foundUnit : foundUnits ) {
487         units.push_back( foundUnit.first );
488     }
489 
490     return units;
491 }
492 
DoubleCellAttackValue(const Unit & attacker,const Unit & target,const int32_t from,const int32_t targetCell)493 int32_t Battle::Board::DoubleCellAttackValue( const Unit & attacker, const Unit & target, const int32_t from, const int32_t targetCell )
494 {
495     const Cell * behind = GetCell( targetCell, GetDirection( from, targetCell ) );
496     const Unit * secondaryTarget = ( behind ) ? behind->GetUnit() : nullptr;
497     if ( secondaryTarget && secondaryTarget->GetUID() != target.GetUID() && secondaryTarget->GetUID() != attacker.GetUID() ) {
498         return secondaryTarget->GetScoreQuality( attacker );
499     }
500     return 0;
501 }
502 
OptimalAttackTarget(const Unit & attacker,const Unit & target,const int32_t from)503 int32_t Battle::Board::OptimalAttackTarget( const Unit & attacker, const Unit & target, const int32_t from )
504 {
505     const int32_t headIndex = target.GetHeadIndex();
506     const int32_t tailIndex = target.GetTailIndex();
507 
508     // isNearIndexes should return false if we pass in invalid tail index (-1)
509     if ( isNearIndexes( from, tailIndex ) ) {
510         if ( attacker.isDoubleCellAttack() && isNearIndexes( from, headIndex )
511              && DoubleCellAttackValue( attacker, target, from, headIndex ) > DoubleCellAttackValue( attacker, target, from, tailIndex ) ) {
512             // Special case when attacking wide unit from the middle cell and could turn around
513             return headIndex;
514         }
515         return tailIndex;
516     }
517     return headIndex;
518 }
519 
OptimalAttackValue(const Unit & attacker,const Unit & target,const int32_t from)520 int32_t Battle::Board::OptimalAttackValue( const Unit & attacker, const Unit & target, const int32_t from )
521 {
522     if ( attacker.isDoubleCellAttack() ) {
523         const int32_t targetCell = OptimalAttackTarget( attacker, target, from );
524         return target.GetScoreQuality( attacker ) + DoubleCellAttackValue( attacker, target, from, targetCell );
525     }
526 
527     if ( attacker.isAllAdjacentCellsAttack() ) {
528         Position position = Position::GetPositionWhenMoved( attacker, from );
529         Indexes aroundAttacker = GetAroundIndexes( position );
530 
531         std::set<const Unit *> unitsUnderAttack;
532         Board * board = Arena::GetBoard();
533         for ( const int32_t index : aroundAttacker ) {
534             const Unit * unit = board->at( index ).GetUnit();
535             if ( unit != nullptr && unit->GetColor() != attacker.GetColor() ) {
536                 unitsUnderAttack.insert( unit );
537             }
538         }
539 
540         int32_t attackValue = 0;
541         for ( const Unit * unit : unitsUnderAttack ) {
542             attackValue += unit->GetScoreQuality( attacker );
543         }
544         return attackValue;
545     }
546     return target.GetScoreQuality( attacker );
547 }
548 
GetDirection(s32 index1,s32 index2)549 int Battle::Board::GetDirection( s32 index1, s32 index2 )
550 {
551     if ( isValidIndex( index1 ) && isValidIndex( index2 ) ) {
552         if ( index1 == index2 )
553             return CENTER;
554         else
555             for ( direction_t dir = TOP_LEFT; dir < CENTER; ++dir )
556                 if ( isValidDirection( index1, dir ) && index2 == GetIndexDirection( index1, dir ) )
557                     return dir;
558     }
559 
560     return UNKNOWN;
561 }
562 
isNearIndexes(s32 index1,s32 index2)563 bool Battle::Board::isNearIndexes( s32 index1, s32 index2 )
564 {
565     return index1 != index2 && UNKNOWN != GetDirection( index1, index2 );
566 }
567 
GetReflectDirection(int d)568 int Battle::Board::GetReflectDirection( int d )
569 {
570     switch ( d ) {
571     case TOP_LEFT:
572         return BOTTOM_RIGHT;
573     case TOP_RIGHT:
574         return BOTTOM_LEFT;
575     case LEFT:
576         return RIGHT;
577     case RIGHT:
578         return LEFT;
579     case BOTTOM_LEFT:
580         return TOP_RIGHT;
581     case BOTTOM_RIGHT:
582         return TOP_LEFT;
583     default:
584         break;
585     }
586 
587     return UNKNOWN;
588 }
589 
isReflectDirection(int d)590 bool Battle::Board::isReflectDirection( int d )
591 {
592     switch ( d ) {
593     case TOP_LEFT:
594     case LEFT:
595     case BOTTOM_LEFT:
596         return true;
597     default:
598         break;
599     }
600 
601     return false;
602 }
603 
IsLeftDirection(const int32_t startCellId,const int32_t endCellId,const bool prevLeftDirection)604 bool Battle::Board::IsLeftDirection( const int32_t startCellId, const int32_t endCellId, const bool prevLeftDirection )
605 {
606     const int startX = startCellId % ARENAW;
607     const int endX = endCellId % ARENAW;
608 
609     if ( prevLeftDirection )
610         return endX <= startX;
611     else
612         return endX < startX;
613 }
614 
isNegativeDistance(s32 index1,s32 index2)615 bool Battle::Board::isNegativeDistance( s32 index1, s32 index2 )
616 {
617     return ( index1 % ARENAW ) - ( index2 % ARENAW ) < 0;
618 }
619 
DistanceFromOriginX(int32_t index,bool reflect)620 int Battle::Board::DistanceFromOriginX( int32_t index, bool reflect )
621 {
622     const int xPos = index % ARENAW;
623     return std::max( 1, reflect ? ARENAW - xPos - 1 : xPos );
624 }
625 
isValidDirection(s32 index,int dir)626 bool Battle::Board::isValidDirection( s32 index, int dir )
627 {
628     if ( isValidIndex( index ) ) {
629         const s32 x = index % ARENAW;
630         const s32 y = index / ARENAW;
631 
632         switch ( dir ) {
633         case CENTER:
634             return true;
635         case TOP_LEFT:
636             return !( 0 == y || ( 0 == x && ( y % 2 ) ) );
637         case TOP_RIGHT:
638             return !( 0 == y || ( ( ARENAW - 1 ) == x && !( y % 2 ) ) );
639         case LEFT:
640             return !( 0 == x );
641         case RIGHT:
642             return !( ( ARENAW - 1 ) == x );
643         case BOTTOM_LEFT:
644             return !( ( ARENAH - 1 ) == y || ( 0 == x && ( y % 2 ) ) );
645         case BOTTOM_RIGHT:
646             return !( ( ARENAH - 1 ) == y || ( ( ARENAW - 1 ) == x && !( y % 2 ) ) );
647         default:
648             break;
649         }
650     }
651 
652     return false;
653 }
654 
GetIndexDirection(s32 index,int dir)655 s32 Battle::Board::GetIndexDirection( s32 index, int dir )
656 {
657     if ( isValidIndex( index ) ) {
658         const s32 y = index / ARENAW;
659 
660         switch ( dir ) {
661         case CENTER:
662             return index;
663         case TOP_LEFT:
664             return index - ( ( y % 2 ) ? ARENAW + 1 : ARENAW );
665         case TOP_RIGHT:
666             return index - ( ( y % 2 ) ? ARENAW : ARENAW - 1 );
667         case LEFT:
668             return index - 1;
669         case RIGHT:
670             return index + 1;
671         case BOTTOM_LEFT:
672             return index + ( ( y % 2 ) ? ARENAW - 1 : ARENAW );
673         case BOTTOM_RIGHT:
674             return index + ( ( y % 2 ) ? ARENAW : ARENAW + 1 );
675         default:
676             break;
677         }
678     }
679 
680     return -1;
681 }
682 
GetIndexAbsPosition(const fheroes2::Point & pt) const683 s32 Battle::Board::GetIndexAbsPosition( const fheroes2::Point & pt ) const
684 {
685     const_iterator it = begin();
686 
687     for ( ; it != end(); ++it )
688         if ( ( *it ).isPositionIncludePoint( pt ) )
689             break;
690 
691     return it != end() ? ( *it ).GetIndex() : -1;
692 }
693 
isValidIndex(s32 index)694 bool Battle::Board::isValidIndex( s32 index )
695 {
696     return 0 <= index && index < ARENASIZE;
697 }
698 
isCastleIndex(s32 index)699 bool Battle::Board::isCastleIndex( s32 index )
700 {
701     return ( ( 8 < index && index <= 10 ) || ( 19 < index && index <= 21 ) || ( 29 < index && index <= 32 ) || ( 40 < index && index <= 43 )
702              || ( 50 < index && index <= 54 ) || ( 62 < index && index <= 65 ) || ( 73 < index && index <= 76 ) || ( 85 < index && index <= 87 )
703              || ( 96 < index && index <= 98 ) );
704 }
705 
isOutOfWallsIndex(s32 index)706 bool Battle::Board::isOutOfWallsIndex( s32 index )
707 {
708     return ( ( index <= 8 ) || ( 11 <= index && index <= 19 ) || ( 22 <= index && index <= 29 ) || ( 33 <= index && index <= 40 ) || ( 44 <= index && index <= 50 )
709              || ( 55 <= index && index <= 62 ) || ( 66 <= index && index <= 73 ) || ( 77 <= index && index <= 85 ) || ( 88 <= index && index <= 96 ) );
710 }
711 
isBridgeIndex(s32 index,const Unit & b)712 bool Battle::Board::isBridgeIndex( s32 index, const Unit & b )
713 {
714     const Bridge * bridge = Arena::GetBridge();
715 
716     return ( index == 49 && !b.isFlying() && bridge && bridge->isPassable( b ) ) || index == 50;
717 }
718 
isMoatIndex(s32 index,const Unit & b)719 bool Battle::Board::isMoatIndex( s32 index, const Unit & b )
720 {
721     switch ( index ) {
722     case 7:
723     case 18:
724     case 28:
725     case 39:
726     case 61:
727     case 72:
728     case 84:
729     case 95:
730         return true;
731     case 49: {
732         const Bridge * bridge = Arena::GetBridge();
733         return b.isFlying() || bridge == nullptr || !bridge->isPassable( b );
734     }
735 
736     default:
737         break;
738     }
739 
740     return false;
741 }
742 
SetCobjObjects(const Maps::Tiles & tile,std::mt19937 & gen)743 void Battle::Board::SetCobjObjects( const Maps::Tiles & tile, std::mt19937 & gen )
744 {
745     bool grave = MP2::OBJ_GRAVEYARD == tile.GetObject( false );
746     int ground = tile.GetGround();
747     std::vector<int> objs;
748 
749     if ( grave ) {
750         objs.push_back( ICN::COBJ0000 );
751         objs.push_back( ICN::COBJ0001 );
752         objs.push_back( ICN::COBJ0025 );
753     }
754     else
755         switch ( ground ) {
756         case Maps::Ground::DESERT:
757             objs.push_back( ICN::COBJ0009 );
758             objs.push_back( ICN::COBJ0024 );
759             break;
760 
761         case Maps::Ground::SNOW:
762             objs.push_back( ICN::COBJ0022 );
763             objs.push_back( ICN::COBJ0026 );
764             break;
765 
766         case Maps::Ground::SWAMP:
767             objs.push_back( ICN::COBJ0005 );
768             objs.push_back( ICN::COBJ0006 );
769             objs.push_back( ICN::COBJ0007 );
770             objs.push_back( ICN::COBJ0008 );
771             objs.push_back( ICN::COBJ0011 );
772             objs.push_back( ICN::COBJ0012 );
773             objs.push_back( ICN::COBJ0014 );
774             objs.push_back( ICN::COBJ0015 );
775             objs.push_back( ICN::COBJ0016 );
776             objs.push_back( ICN::COBJ0017 );
777             objs.push_back( ICN::COBJ0027 );
778             break;
779 
780         case Maps::Ground::BEACH:
781             objs.push_back( ICN::COBJ0005 );
782             objs.push_back( ICN::COBJ0011 );
783             objs.push_back( ICN::COBJ0017 );
784             break;
785 
786         case Maps::Ground::DIRT:
787             objs.push_back( ICN::COBJ0002 );
788             objs.push_back( ICN::COBJ0005 );
789             objs.push_back( ICN::COBJ0007 );
790             objs.push_back( ICN::COBJ0011 );
791             objs.push_back( ICN::COBJ0014 );
792             objs.push_back( ICN::COBJ0019 );
793             objs.push_back( ICN::COBJ0027 );
794             break;
795 
796         case Maps::Ground::GRASS:
797             objs.push_back( ICN::COBJ0002 );
798             objs.push_back( ICN::COBJ0004 );
799             objs.push_back( ICN::COBJ0005 );
800             objs.push_back( ICN::COBJ0008 );
801             objs.push_back( ICN::COBJ0011 );
802             objs.push_back( ICN::COBJ0012 );
803             objs.push_back( ICN::COBJ0014 );
804             objs.push_back( ICN::COBJ0015 );
805             objs.push_back( ICN::COBJ0019 );
806             objs.push_back( ICN::COBJ0027 );
807             objs.push_back( ICN::COBJ0028 );
808             break;
809 
810         case Maps::Ground::WASTELAND:
811             objs.push_back( ICN::COBJ0009 );
812             objs.push_back( ICN::COBJ0013 );
813             objs.push_back( ICN::COBJ0018 );
814             objs.push_back( ICN::COBJ0020 );
815             objs.push_back( ICN::COBJ0021 );
816             objs.push_back( ICN::COBJ0024 );
817             break;
818 
819         case Maps::Ground::LAVA:
820             objs.push_back( ICN::COBJ0007 );
821             objs.push_back( ICN::COBJ0029 );
822             objs.push_back( ICN::COBJ0031 );
823             break;
824 
825         case Maps::Ground::WATER:
826             objs.push_back( ICN::COBJ0003 );
827             objs.push_back( ICN::COBJ0010 );
828             objs.push_back( ICN::COBJ0023 );
829             break;
830 
831         default:
832             break;
833         }
834 
835     Rand::ShuffleWithGen( objs, gen );
836 
837     const size_t objectsToPlace = std::min( objs.size(), static_cast<size_t>( Rand::GetWithGen( 0, 4, gen ) ) );
838 
839     for ( size_t i = 0; i < objectsToPlace; ++i ) {
840         const bool checkRightCell = isTwoHexObject( objs[i] );
841 
842         int32_t dest = GetRandomObstaclePosition( gen );
843         while ( at( dest ).GetObject() != 0 || ( checkRightCell && at( dest + 1 ).GetObject() != 0 ) ) {
844             dest = GetRandomObstaclePosition( gen );
845         }
846 
847         SetCobjObject( objs[i], dest );
848     }
849 }
850 
SetCobjObject(const int icn,const int32_t dst)851 void Battle::Board::SetCobjObject( const int icn, const int32_t dst )
852 {
853     at( dst ).SetObject( 0x80 + ( icn - ICN::COBJ0000 ) );
854 
855     if ( isTwoHexObject( icn ) ) {
856         assert( at( dst + 1 ).GetObject() == 0 );
857         at( dst + 1 ).SetObject( 0x40 );
858     }
859 }
860 
SetCovrObjects(int icn)861 void Battle::Board::SetCovrObjects( int icn )
862 {
863     switch ( icn ) {
864     case ICN::COVR0001:
865     case ICN::COVR0007:
866     case ICN::COVR0013:
867     case ICN::COVR0019:
868         at( 25 ).SetObject( 0x40 );
869         at( 26 ).SetObject( 0x40 );
870         at( 27 ).SetObject( 0x40 );
871         at( 28 ).SetObject( 0x40 );
872         at( 40 ).SetObject( 0x40 );
873         at( 51 ).SetObject( 0x40 );
874         break;
875 
876     case ICN::COVR0002:
877     case ICN::COVR0008:
878     case ICN::COVR0014:
879     case ICN::COVR0020:
880         at( 47 ).SetObject( 0x40 );
881         at( 48 ).SetObject( 0x40 );
882         at( 49 ).SetObject( 0x40 );
883         at( 50 ).SetObject( 0x40 );
884         at( 51 ).SetObject( 0x40 );
885         break;
886 
887     case ICN::COVR0003:
888     case ICN::COVR0015:
889     case ICN::COVR0021:
890         at( 35 ).SetObject( 0x40 );
891         at( 41 ).SetObject( 0x40 );
892         at( 46 ).SetObject( 0x40 );
893         at( 47 ).SetObject( 0x40 );
894         at( 48 ).SetObject( 0x40 );
895         at( 49 ).SetObject( 0x40 );
896         at( 50 ).SetObject( 0x40 );
897         at( 51 ).SetObject( 0x40 );
898         break;
899 
900     case ICN::COVR0009:
901         at( 35 ).SetObject( 0x40 );
902         at( 40 ).SetObject( 0x40 );
903         at( 46 ).SetObject( 0x40 );
904         at( 47 ).SetObject( 0x40 );
905         at( 48 ).SetObject( 0x40 );
906         at( 49 ).SetObject( 0x40 );
907         at( 50 ).SetObject( 0x40 );
908         break;
909 
910     case ICN::COVR0004:
911     case ICN::COVR0010:
912     case ICN::COVR0016:
913     case ICN::COVR0022:
914         at( 41 ).SetObject( 0x40 );
915         at( 51 ).SetObject( 0x40 );
916         at( 58 ).SetObject( 0x40 );
917         at( 59 ).SetObject( 0x40 );
918         at( 60 ).SetObject( 0x40 );
919         at( 61 ).SetObject( 0x40 );
920         at( 62 ).SetObject( 0x40 );
921         break;
922 
923     case ICN::COVR0005:
924     case ICN::COVR0017:
925         at( 24 ).SetObject( 0x40 );
926         at( 25 ).SetObject( 0x40 );
927         at( 26 ).SetObject( 0x40 );
928         at( 27 ).SetObject( 0x40 );
929         at( 28 ).SetObject( 0x40 );
930         at( 29 ).SetObject( 0x40 );
931         at( 30 ).SetObject( 0x40 );
932         at( 58 ).SetObject( 0x40 );
933         at( 59 ).SetObject( 0x40 );
934         at( 60 ).SetObject( 0x40 );
935         at( 61 ).SetObject( 0x40 );
936         at( 62 ).SetObject( 0x40 );
937         at( 63 ).SetObject( 0x40 );
938         at( 68 ).SetObject( 0x40 );
939         at( 74 ).SetObject( 0x40 );
940         break;
941 
942     case ICN::COVR0006:
943     case ICN::COVR0018:
944         at( 14 ).SetObject( 0x40 );
945         at( 15 ).SetObject( 0x40 );
946         at( 16 ).SetObject( 0x40 );
947         at( 17 ).SetObject( 0x40 );
948         at( 18 ).SetObject( 0x40 );
949         at( 24 ).SetObject( 0x40 );
950         at( 68 ).SetObject( 0x40 );
951         at( 80 ).SetObject( 0x40 );
952         at( 81 ).SetObject( 0x40 );
953         at( 82 ).SetObject( 0x40 );
954         at( 83 ).SetObject( 0x40 );
955         at( 84 ).SetObject( 0x40 );
956         break;
957 
958     case ICN::COVR0011:
959     case ICN::COVR0023:
960         at( 15 ).SetObject( 0x40 );
961         at( 25 ).SetObject( 0x40 );
962         at( 36 ).SetObject( 0x40 );
963         at( 51 ).SetObject( 0x40 );
964         at( 62 ).SetObject( 0x40 );
965         at( 71 ).SetObject( 0x40 );
966         at( 72 ).SetObject( 0x40 );
967         break;
968 
969     case ICN::COVR0012:
970     case ICN::COVR0024:
971         at( 18 ).SetObject( 0x40 );
972         at( 29 ).SetObject( 0x40 );
973         at( 41 ).SetObject( 0x40 );
974         at( 59 ).SetObject( 0x40 );
975         at( 70 ).SetObject( 0x40 );
976         at( 82 ).SetObject( 0x40 );
977         at( 83 ).SetObject( 0x40 );
978         break;
979 
980     default:
981         break;
982     }
983 }
984 
GetCell(s32 position,int dir)985 Battle::Cell * Battle::Board::GetCell( s32 position, int dir )
986 {
987     if ( isValidIndex( position ) && dir != UNKNOWN ) {
988         Board * board = Arena::GetBoard();
989         if ( dir == CENTER ) {
990             return &board->at( position );
991         }
992         if ( isValidDirection( position, dir ) ) {
993             return &board->at( GetIndexDirection( position, dir ) );
994         }
995     }
996 
997     return nullptr;
998 }
999 
GetMoveWideIndexes(s32 center,bool reflect)1000 Battle::Indexes Battle::Board::GetMoveWideIndexes( s32 center, bool reflect )
1001 {
1002     Indexes result;
1003 
1004     if ( isValidIndex( center ) ) {
1005         result.reserve( 4 );
1006 
1007         if ( reflect ) {
1008             if ( isValidDirection( center, LEFT ) )
1009                 result.push_back( GetIndexDirection( center, LEFT ) );
1010             if ( isValidDirection( center, RIGHT ) )
1011                 result.push_back( GetIndexDirection( center, RIGHT ) );
1012             if ( isValidDirection( center, TOP_LEFT ) )
1013                 result.push_back( GetIndexDirection( center, TOP_LEFT ) );
1014             if ( isValidDirection( center, BOTTOM_LEFT ) )
1015                 result.push_back( GetIndexDirection( center, BOTTOM_LEFT ) );
1016         }
1017         else {
1018             if ( isValidDirection( center, LEFT ) )
1019                 result.push_back( GetIndexDirection( center, LEFT ) );
1020             if ( isValidDirection( center, RIGHT ) )
1021                 result.push_back( GetIndexDirection( center, RIGHT ) );
1022             if ( isValidDirection( center, TOP_RIGHT ) )
1023                 result.push_back( GetIndexDirection( center, TOP_RIGHT ) );
1024             if ( isValidDirection( center, BOTTOM_RIGHT ) )
1025                 result.push_back( GetIndexDirection( center, BOTTOM_RIGHT ) );
1026         }
1027     }
1028     return result;
1029 }
1030 
GetAroundIndexes(s32 center,s32 ignore)1031 Battle::Indexes Battle::Board::GetAroundIndexes( s32 center, s32 ignore )
1032 {
1033     Indexes result;
1034 
1035     if ( isValidIndex( center ) ) {
1036         result.reserve( 12 );
1037 
1038         for ( direction_t dir = TOP_LEFT; dir < CENTER; ++dir )
1039             if ( isValidDirection( center, dir ) && GetIndexDirection( center, dir ) != ignore )
1040                 result.push_back( GetIndexDirection( center, dir ) );
1041     }
1042 
1043     return result;
1044 }
1045 
GetAroundIndexes(const Unit & b)1046 Battle::Indexes Battle::Board::GetAroundIndexes( const Unit & b )
1047 {
1048     return GetAroundIndexes( b.GetPosition() );
1049 }
1050 
GetAroundIndexes(const Position & position)1051 Battle::Indexes Battle::Board::GetAroundIndexes( const Position & position )
1052 {
1053     const int headIdx = position.GetHead()->GetIndex();
1054 
1055     if ( position.GetTail() ) {
1056         const int tailIdx = position.GetTail()->GetIndex();
1057 
1058         Indexes around = GetAroundIndexes( headIdx, tailIdx );
1059         const Indexes & tail = GetAroundIndexes( tailIdx, headIdx );
1060         around.insert( around.end(), tail.begin(), tail.end() );
1061 
1062         std::sort( around.begin(), around.end() );
1063         around.erase( std::unique( around.begin(), around.end() ), around.end() );
1064 
1065         return around;
1066     }
1067 
1068     return GetAroundIndexes( headIdx );
1069 }
1070 
GetDistanceIndexes(s32 center,u32 radius)1071 Battle::Indexes Battle::Board::GetDistanceIndexes( s32 center, u32 radius )
1072 {
1073     Indexes result;
1074 
1075     if ( isValidIndex( center ) ) {
1076         std::set<s32> st;
1077         Indexes abroad;
1078 
1079         st.insert( center );
1080         abroad.push_back( center );
1081 
1082         while ( !abroad.empty() && radius ) {
1083             std::set<s32> tm = st;
1084 
1085             for ( Indexes::const_iterator it = abroad.begin(); it != abroad.end(); ++it ) {
1086                 const Indexes around = GetAroundIndexes( *it );
1087                 tm.insert( around.begin(), around.end() );
1088             }
1089 
1090             abroad.resize( tm.size() );
1091 
1092             Indexes::iterator abroad_end = std::set_difference( tm.begin(), tm.end(), st.begin(), st.end(), abroad.begin() );
1093 
1094             abroad.resize( std::distance( abroad.begin(), abroad_end ) );
1095 
1096             st.swap( tm );
1097             --radius;
1098         }
1099 
1100         st.erase( center );
1101         result.reserve( st.size() );
1102         std::copy( st.begin(), st.end(), std::back_inserter( result ) );
1103     }
1104 
1105     return result;
1106 }
1107 
isValidMirrorImageIndex(s32 index,const Unit * troop)1108 bool Battle::Board::isValidMirrorImageIndex( s32 index, const Unit * troop )
1109 {
1110     if ( troop == nullptr )
1111         return false;
1112 
1113     const Cell * cell = GetCell( index );
1114     if ( cell == nullptr )
1115         return false;
1116 
1117     const bool doubleHex = troop->isWide();
1118     if ( index == troop->GetHeadIndex() || ( doubleHex && index == troop->GetTailIndex() ) )
1119         return false;
1120 
1121     if ( !cell->isPassable3( *troop, true ) )
1122         return false;
1123 
1124     if ( doubleHex ) {
1125         const bool isReflected = troop->GetHeadIndex() < troop->GetTailIndex();
1126         const int32_t tailIndex = isReflected ? index + 1 : index - 1;
1127         const Cell * tailCell = GetCell( tailIndex );
1128         if ( tailCell == nullptr || tailIndex == troop->GetHeadIndex() || tailIndex == troop->GetTailIndex() )
1129             return false;
1130 
1131         if ( !tailCell->isPassable3( *troop, true ) )
1132             return false;
1133     }
1134 
1135     return true;
1136 }
1137 
CanAttackUnitFromCell(const Unit & currentUnit,const int32_t from)1138 bool Battle::Board::CanAttackUnitFromCell( const Unit & currentUnit, const int32_t from )
1139 {
1140     const Cell * fromCell = GetCell( from );
1141     assert( fromCell != nullptr );
1142 
1143     // Target unit cannot be attacked if out of reach
1144     if ( !fromCell->isReachableForHead() && ( !currentUnit.isWide() || !fromCell->isReachableForTail() ) ) {
1145         return false;
1146     }
1147 
1148     const Castle * castle = Arena::GetCastle();
1149 
1150     // No moat - no further restrictions
1151     if ( !castle || !castle->isBuild( BUILD_MOAT ) ) {
1152         return true;
1153     }
1154 
1155     // Target unit isn't attacked from the moat
1156     if ( !isMoatIndex( from, currentUnit ) ) {
1157         return true;
1158     }
1159 
1160     // The moat doesn't stop flying units
1161     if ( currentUnit.isFlying() ) {
1162         return true;
1163     }
1164 
1165     // Attacker is already near the target
1166     if ( from == currentUnit.GetHeadIndex() || ( currentUnit.isWide() && from == currentUnit.GetTailIndex() ) ) {
1167         return true;
1168     }
1169 
1170     // In all other cases, the attack is prohibited
1171     return false;
1172 }
1173 
CanAttackUnitFromPosition(const Unit & currentUnit,const Unit & target,const int32_t dst)1174 bool Battle::Board::CanAttackUnitFromPosition( const Unit & currentUnit, const Unit & target, const int32_t dst )
1175 {
1176     // Get the actual position of the attacker before attacking
1177     const Position pos = Position::GetReachable( currentUnit, dst );
1178 
1179     // Check that the attacker is actually capable of attacking the target from this position
1180     const std::array<const Cell *, 2> cells = { pos.GetHead(), pos.GetTail() };
1181 
1182     for ( const Cell * cell : cells ) {
1183         if ( cell == nullptr ) {
1184             continue;
1185         }
1186 
1187         if ( !CanAttackUnitFromCell( currentUnit, cell->GetIndex() ) ) {
1188             continue;
1189         }
1190 
1191         for ( const int32_t aroundIdx : GetAroundIndexes( cell->GetIndex() ) ) {
1192             const Cell * aroundCell = GetCell( aroundIdx );
1193             assert( aroundCell != nullptr );
1194 
1195             if ( aroundCell->GetUnit() == &target ) {
1196                 return true;
1197             }
1198         }
1199     }
1200 
1201     return false;
1202 }
1203 
GetAdjacentEnemies(const Unit & unit)1204 Battle::Indexes Battle::Board::GetAdjacentEnemies( const Unit & unit )
1205 {
1206     Indexes result;
1207     const bool isWide = unit.isWide();
1208     const int currentColor = unit.GetArmyColor();
1209     result.reserve( isWide ? 8 : 6 );
1210 
1211     const int leftmostIndex = ( isWide && !unit.isReflect() ) ? unit.GetTailIndex() : unit.GetHeadIndex();
1212     const int x = leftmostIndex % ARENAW;
1213     const int y = leftmostIndex / ARENAW;
1214     const int mod = y % 2;
1215 
1216     auto validateAndInsert = [&result, &currentColor]( const int index ) {
1217         const Unit * vUnit = GetCell( index )->GetUnit();
1218         if ( vUnit && currentColor != vUnit->GetArmyColor() )
1219             result.push_back( index );
1220     };
1221 
1222     if ( y > 0 ) {
1223         const int topRowIndex = ( y - 1 ) * ARENAW + x - mod;
1224         if ( x - mod >= 0 )
1225             validateAndInsert( topRowIndex );
1226 
1227         if ( x < ARENAW - 1 )
1228             validateAndInsert( topRowIndex + 1 );
1229 
1230         if ( isWide && x < ARENAW - 2 )
1231             validateAndInsert( topRowIndex + 2 );
1232     }
1233 
1234     if ( x > 0 )
1235         validateAndInsert( leftmostIndex - 1 );
1236 
1237     if ( x < ARENAW - ( isWide ? 2 : 1 ) )
1238         validateAndInsert( leftmostIndex + ( isWide ? 2 : 1 ) );
1239 
1240     if ( y < ARENAH - 1 ) {
1241         const int bottomRowIndex = ( y + 1 ) * ARENAW + x - mod;
1242         if ( x - mod >= 0 )
1243             validateAndInsert( bottomRowIndex );
1244 
1245         if ( x < ARENAW - 1 )
1246             validateAndInsert( bottomRowIndex + 1 );
1247 
1248         if ( isWide && x < ARENAW - 2 )
1249             validateAndInsert( bottomRowIndex + 2 );
1250     }
1251 
1252     return result;
1253 }
1254 
FixupDestinationCell(const Unit & currentUnit,const int32_t dst)1255 int32_t Battle::Board::FixupDestinationCell( const Unit & currentUnit, const int32_t dst )
1256 {
1257     // Only wide units may need this fixup
1258     if ( !currentUnit.isWide() ) {
1259         return dst;
1260     }
1261 
1262     const Position pos = Position::GetReachable( currentUnit, dst );
1263 
1264     assert( pos.GetHead() != nullptr );
1265     assert( pos.GetTail() != nullptr );
1266 
1267     return pos.GetHead()->GetIndex();
1268 }
1269 
GetMoatInfo(void)1270 std::string Battle::Board::GetMoatInfo( void )
1271 {
1272     std::string msg = _( "The Moat reduces by -%{count} the defense skill of any unit and slows to half movement rate." );
1273     StringReplace( msg, "%{count}", GameStatic::GetBattleMoatReduceDefense() );
1274 
1275     return msg;
1276 }
1277