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, ¤tColor]( 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