1 /***************************************************************************
2 * Copyright (C) 2009 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 "difficulty.h"
28 #include "game.h"
29 #include "icn.h"
30 #include "kingdom.h"
31 #include "logging.h"
32 #include "maps.h"
33 #include "maps_tiles.h"
34 #include "race.h"
35 #include "serialize.h"
36 #include "translations.h"
37 #include "world.h"
38
39 namespace
40 {
getTileToClearIndicies(const int32_t tileIndex,int scouteValue,const int playerColor)41 std::vector<int32_t> getTileToClearIndicies( const int32_t tileIndex, int scouteValue, const int playerColor )
42 {
43 std::vector<int32_t> indicies;
44
45 if ( scouteValue <= 0 || !Maps::isValidAbsIndex( tileIndex ) ) {
46 return indicies;
47 }
48
49 const fheroes2::Point center = Maps::GetPoint( tileIndex );
50
51 // AI is cheating!
52 const bool isAIPlayer = world.GetKingdom( playerColor ).isControlAI();
53 if ( isAIPlayer ) {
54 scouteValue += Difficulty::GetScoutingBonus( Game::getDifficulty() );
55 }
56
57 const int revealRadiusSquared = scouteValue * scouteValue + 4; // constant factor for "backwards compatibility"
58 for ( int32_t y = center.y - scouteValue; y <= center.y + scouteValue; ++y ) {
59 if ( y < 0 || y >= world.h() )
60 continue;
61
62 for ( int32_t x = center.x - scouteValue; x <= center.x + scouteValue; ++x ) {
63 if ( x < 0 || x >= world.w() )
64 continue;
65
66 const int32_t dx = x - center.x;
67 const int32_t dy = y - center.y;
68 if ( revealRadiusSquared >= dx * dx + dy * dy ) {
69 indicies.emplace_back( Maps::GetIndexFromAbsPoint( x, y ) );
70 }
71 }
72 }
73
74 return indicies;
75 }
76
MapsIndexesFilteredObject(const Maps::Indexes & indexes,const MP2::MapObjectType objectType,const bool ignoreHeroes=true)77 Maps::Indexes MapsIndexesFilteredObject( const Maps::Indexes & indexes, const MP2::MapObjectType objectType, const bool ignoreHeroes = true )
78 {
79 Maps::Indexes result;
80 for ( size_t idx = 0; idx < indexes.size(); ++idx ) {
81 if ( world.GetTiles( indexes[idx] ).GetObject( !ignoreHeroes ) == objectType ) {
82 result.push_back( indexes[idx] );
83 }
84 }
85 return result;
86 }
87
MapsIndexesObject(const MP2::MapObjectType objectType,const bool ignoreHeroes=true)88 Maps::Indexes MapsIndexesObject( const MP2::MapObjectType objectType, const bool ignoreHeroes = true )
89 {
90 Maps::Indexes result;
91 const int32_t size = static_cast<int32_t>( world.getSize() );
92 for ( int32_t idx = 0; idx < size; ++idx ) {
93 if ( world.GetTiles( idx ).GetObject( !ignoreHeroes ) == objectType ) {
94 result.push_back( idx );
95 }
96 }
97 return result;
98 }
99 }
100
101 struct ComparisonDistance
102 {
ComparisonDistanceComparisonDistance103 explicit ComparisonDistance( const int32_t index )
104 : centerPoint( Maps::GetPoint( index ) )
105 {}
106
107 ComparisonDistance() = delete;
108
operator ()ComparisonDistance109 bool operator()( const int32_t index1, const int32_t index2 ) const
110 {
111 const fheroes2::Point point1( Maps::GetPoint( index1 ) );
112 const fheroes2::Point point2( Maps::GetPoint( index2 ) );
113
114 const int32_t diffX1 = std::abs( centerPoint.x - point1.x );
115 const int32_t diffY1 = std::abs( centerPoint.y - point1.y );
116 const int32_t diffX2 = std::abs( centerPoint.x - point2.x );
117 const int32_t diffY2 = std::abs( centerPoint.y - point2.y );
118
119 return ( diffX1 * diffX1 + diffY1 * diffY1 ) < ( diffX2 * diffX2 + diffY2 * diffY2 );
120 }
121
122 const fheroes2::Point centerPoint;
123 };
124
SizeString(int s)125 const char * Maps::SizeString( int s )
126 {
127 switch ( s ) {
128 case SMALL:
129 return _( "maps|Small" );
130 case MEDIUM:
131 return _( "maps|Medium" );
132 case LARGE:
133 return _( "maps|Large" );
134 case XLARGE:
135 return _( "maps|Extra Large" );
136 default:
137 break;
138 }
139
140 return _( "maps|Custom Size" );
141 }
142
GetMinesName(int type)143 const char * Maps::GetMinesName( int type )
144 {
145 switch ( type ) {
146 case Resource::ORE:
147 return _( "Ore Mine" );
148 case Resource::SULFUR:
149 return _( "Sulfur Mine" );
150 case Resource::CRYSTAL:
151 return _( "Crystal Mine" );
152 case Resource::GEMS:
153 return _( "Gems Mine" );
154 case Resource::GOLD:
155 return _( "Gold Mine" );
156 default:
157 break;
158 }
159
160 return _( "Mine" );
161 }
162
GetDirection(int from,int to)163 int Maps::GetDirection( int from, int to )
164 {
165 if ( from == to )
166 return Direction::CENTER;
167
168 const int diff = to - from;
169 const int width = world.w();
170
171 if ( diff == ( -width - 1 ) ) {
172 return Direction::TOP_LEFT;
173 }
174 else if ( diff == -width ) {
175 return Direction::TOP;
176 }
177 else if ( diff == ( -width + 1 ) ) {
178 return Direction::TOP_RIGHT;
179 }
180 else if ( diff == -1 ) {
181 return Direction::LEFT;
182 }
183 else if ( diff == 1 ) {
184 return Direction::RIGHT;
185 }
186 else if ( diff == width - 1 ) {
187 return Direction::BOTTOM_LEFT;
188 }
189 else if ( diff == width ) {
190 return Direction::BOTTOM;
191 }
192 else if ( diff == width + 1 ) {
193 return Direction::BOTTOM_RIGHT;
194 }
195
196 return Direction::UNKNOWN;
197 }
198
GetDirectionIndex(int32_t from,int vector)199 int32_t Maps::GetDirectionIndex( int32_t from, int vector )
200 {
201 switch ( vector ) {
202 case Direction::TOP:
203 return from - world.w();
204 case Direction::TOP_RIGHT:
205 return from - world.w() + 1;
206 case Direction::RIGHT:
207 return from + 1;
208 case Direction::BOTTOM_RIGHT:
209 return from + world.w() + 1;
210 case Direction::BOTTOM:
211 return from + world.w();
212 case Direction::BOTTOM_LEFT:
213 return from + world.w() - 1;
214 case Direction::LEFT:
215 return from - 1;
216 case Direction::TOP_LEFT:
217 return from - world.w() - 1;
218 default:
219 break;
220 }
221
222 return -1;
223 }
224
225 // check bound
isValidDirection(int32_t from,int vector)226 bool Maps::isValidDirection( int32_t from, int vector )
227 {
228 const int32_t width = world.w();
229
230 switch ( vector ) {
231 case Direction::TOP:
232 return ( from >= width );
233 case Direction::RIGHT:
234 return ( ( from % width ) < ( width - 1 ) );
235 case Direction::BOTTOM:
236 return ( from < width * ( world.h() - 1 ) );
237 case Direction::LEFT:
238 return ( from % width ) != 0;
239
240 case Direction::TOP_RIGHT:
241 return ( from >= width ) && ( ( from % width ) < ( width - 1 ) );
242
243 case Direction::BOTTOM_RIGHT:
244 return ( from < width * ( world.h() - 1 ) ) && ( ( from % width ) < ( width - 1 ) );
245
246 case Direction::BOTTOM_LEFT:
247 return ( from < width * ( world.h() - 1 ) ) && ( from % width );
248
249 case Direction::TOP_LEFT:
250 return ( from >= width ) && ( from % width );
251
252 default:
253 break;
254 }
255
256 return false;
257 }
258
GetPoint(const int32_t index)259 fheroes2::Point Maps::GetPoint( const int32_t index )
260 {
261 return fheroes2::Point( index % world.w(), index / world.w() );
262 }
263
isValidAbsIndex(const int32_t index)264 bool Maps::isValidAbsIndex( const int32_t index )
265 {
266 return 0 <= index && index < world.w() * world.h();
267 }
268
isValidAbsPoint(const int32_t x,const int32_t y)269 bool Maps::isValidAbsPoint( const int32_t x, const int32_t y )
270 {
271 return 0 <= x && world.w() > x && 0 <= y && world.h() > y;
272 }
273
GetIndexFromAbsPoint(const fheroes2::Point & mp)274 int32_t Maps::GetIndexFromAbsPoint( const fheroes2::Point & mp )
275 {
276 if ( mp.x < 0 || mp.y < 0 ) {
277 return -1;
278 }
279
280 return mp.y * world.w() + mp.x;
281 }
282
GetIndexFromAbsPoint(const int32_t x,const int32_t y)283 int32_t Maps::GetIndexFromAbsPoint( const int32_t x, const int32_t y )
284 {
285 if ( x < 0 || y < 0 ) {
286 return -1;
287 }
288
289 return y * world.w() + x;
290 }
291
getAroundIndexes(const int32_t tileIndex,const int32_t maxDistanceFromTile)292 Maps::Indexes Maps::getAroundIndexes( const int32_t tileIndex, const int32_t maxDistanceFromTile /* = 1 */ )
293 {
294 Indexes results;
295
296 if ( !isValidAbsIndex( tileIndex ) || maxDistanceFromTile <= 0 ) {
297 return results;
298 }
299
300 results.reserve( ( maxDistanceFromTile * 2 + 1 ) * ( maxDistanceFromTile * 2 + 1 ) - 1 );
301
302 assert( world.w() > 0 );
303
304 const int32_t centerX = tileIndex % world.w();
305 const int32_t centerY = tileIndex / world.w();
306
307 for ( int32_t y = -maxDistanceFromTile; y <= maxDistanceFromTile; ++y ) {
308 for ( int32_t x = -maxDistanceFromTile; x <= maxDistanceFromTile; ++x ) {
309 // the central tile is not included
310 if ( x == 0 && y == 0 ) {
311 continue;
312 }
313
314 const int32_t tileX = centerX + x;
315 const int32_t tileY = centerY + y;
316
317 if ( isValidAbsPoint( tileX, tileY ) ) {
318 results.push_back( tileY * world.w() + tileX );
319 }
320 }
321 }
322
323 return results;
324 }
325
ClearFog(const int32_t tileIndex,const int scouteValue,const int playerColor)326 void Maps::ClearFog( const int32_t tileIndex, const int scouteValue, const int playerColor )
327 {
328 const std::vector<int32_t> tileIndicies = getTileToClearIndicies( tileIndex, scouteValue, playerColor );
329 if ( tileIndicies.empty() ) {
330 // Nothing to uncover.
331 return;
332 }
333
334 const bool isAIPlayer = world.GetKingdom( playerColor ).isControlAI();
335 const int alliedColors = Players::GetPlayerFriends( playerColor );
336
337 for ( const int32_t index : tileIndicies ) {
338 Maps::Tiles & tile = world.GetTiles( index );
339 if ( isAIPlayer && tile.isFog( playerColor ) ) {
340 AI::Get().revealFog( tile );
341 }
342
343 tile.ClearFog( alliedColors );
344 }
345 }
346
getFogTileCountToBeRevealed(const int32_t tileIndex,const int scouteValue,const int playerColor)347 int32_t Maps::getFogTileCountToBeRevealed( const int32_t tileIndex, const int scouteValue, const int playerColor )
348 {
349 const std::vector<int32_t> tileIndicies = getTileToClearIndicies( tileIndex, scouteValue, playerColor );
350
351 int32_t tileCount = 0;
352
353 for ( const int32_t index : tileIndicies ) {
354 const Maps::Tiles & tile = world.GetTiles( index );
355 if ( tile.isFog( playerColor ) ) {
356 ++tileCount;
357 }
358 }
359
360 return tileCount;
361 }
362
ScanAroundObject(const int32_t center,const MP2::MapObjectType objectType,const bool ignoreHeroes)363 Maps::Indexes Maps::ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType, const bool ignoreHeroes )
364 {
365 Indexes results = getAroundIndexes( center );
366 return MapsIndexesFilteredObject( results, objectType, ignoreHeroes );
367 }
368
GetFreeIndexesAroundTile(const int32_t center)369 Maps::Indexes Maps::GetFreeIndexesAroundTile( const int32_t center )
370 {
371 Indexes results = getAroundIndexes( center );
372 results.erase( std::remove_if( results.begin(), results.end(), []( const int32_t tile ) { return !world.GetTiles( tile ).isClearGround(); } ), results.end() );
373 return results;
374 }
375
ScanAroundObject(const int32_t center,const MP2::MapObjectType objectType)376 Maps::Indexes Maps::ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType )
377 {
378 Indexes results = getAroundIndexes( center );
379 return MapsIndexesFilteredObject( results, objectType );
380 }
381
ScanAroundObjectWithDistance(const int32_t center,const uint32_t dist,const MP2::MapObjectType objectType)382 Maps::Indexes Maps::ScanAroundObjectWithDistance( const int32_t center, const uint32_t dist, const MP2::MapObjectType objectType )
383 {
384 Indexes results = getAroundIndexes( center, dist );
385 std::sort( results.begin(), results.end(), ComparisonDistance( center ) );
386 return MapsIndexesFilteredObject( results, objectType );
387 }
388
GetObjectPositions(const MP2::MapObjectType objectType,bool ignoreHeroes)389 Maps::Indexes Maps::GetObjectPositions( const MP2::MapObjectType objectType, bool ignoreHeroes )
390 {
391 return MapsIndexesObject( objectType, ignoreHeroes );
392 }
393
GetObjectPositions(int32_t center,const MP2::MapObjectType objectType,bool ignoreHeroes)394 Maps::Indexes Maps::GetObjectPositions( int32_t center, const MP2::MapObjectType objectType, bool ignoreHeroes )
395 {
396 Indexes results = MapsIndexesObject( objectType, ignoreHeroes );
397 std::sort( results.begin(), results.end(), ComparisonDistance( center ) );
398 return results;
399 }
400
MapsTileIsUnderProtection(int32_t from,int32_t index)401 bool MapsTileIsUnderProtection( int32_t from, int32_t index ) /* from: center, index: monster */
402 {
403 const Maps::Tiles & tile1 = world.GetTiles( from );
404 const Maps::Tiles & tile2 = world.GetTiles( index );
405
406 if ( !MP2::isPickupObject( tile1.GetObject() ) && tile2.GetObject() == MP2::OBJ_MONSTER && tile1.isWater() == tile2.isWater() ) {
407 const int monsterDirection = Maps::GetDirection( index, from );
408 // if monster can attack to
409 if ( ( tile2.GetPassable() & monsterDirection ) && ( tile1.GetPassable() & Maps::GetDirection( from, index ) ) )
410 return true;
411
412 // h2 specific monster attack: BOTTOM_LEFT impassable!
413 if ( Direction::BOTTOM_LEFT == monsterDirection && ( Direction::LEFT & tile2.GetPassable() ) && ( Direction::TOP & tile1.GetPassable() ) )
414 return true;
415
416 // h2 specific monster attack: BOTTOM_RIGHT impassable!
417 if ( Direction::BOTTOM_RIGHT == monsterDirection && ( Direction::RIGHT & tile2.GetPassable() ) && ( Direction::TOP & tile1.GetPassable() ) )
418 return true;
419 }
420
421 return false;
422 }
423
TileIsUnderProtection(int32_t center)424 bool Maps::TileIsUnderProtection( int32_t center )
425 {
426 return MP2::OBJ_MONSTER == world.GetTiles( center ).GetObject() ? true : !GetTilesUnderProtection( center ).empty();
427 }
428
GetTilesUnderProtection(int32_t center)429 Maps::Indexes Maps::GetTilesUnderProtection( int32_t center )
430 {
431 Indexes result;
432 if ( !isValidAbsIndex( center ) )
433 return result;
434
435 result.reserve( 9 );
436 const int width = world.w();
437 const int x = center % width;
438 const int y = center / width;
439
440 auto validateAndInsert = [&result, ¢er]( const int index ) {
441 if ( MapsTileIsUnderProtection( center, index ) )
442 result.push_back( index );
443 };
444
445 if ( y > 0 ) {
446 if ( x > 0 )
447 validateAndInsert( center - width - 1 );
448
449 validateAndInsert( center - width );
450
451 if ( x < width - 1 )
452 validateAndInsert( center - width + 1 );
453 }
454
455 if ( x > 0 )
456 validateAndInsert( center - 1 );
457 if ( MP2::OBJ_MONSTER == world.GetTiles( center ).GetObject() )
458 result.push_back( center );
459 if ( x < width - 1 )
460 validateAndInsert( center + 1 );
461
462 if ( y < world.h() - 1 ) {
463 if ( x > 0 )
464 validateAndInsert( center + width - 1 );
465
466 validateAndInsert( center + width );
467
468 if ( x < width - 1 )
469 validateAndInsert( center + width + 1 );
470 }
471
472 return result;
473 }
474
GetApproximateDistance(const int32_t pos1,const int32_t pos2)475 uint32_t Maps::GetApproximateDistance( const int32_t pos1, const int32_t pos2 )
476 {
477 const fheroes2::Point point1( GetPoint( pos1 ) );
478 const fheroes2::Point point2( GetPoint( pos2 ) );
479
480 const fheroes2::Size sz( std::abs( point1.x - point2.x ), std::abs( point1.y - point2.y ) );
481 // diagonal move costs 1.5 as much
482 return std::max( sz.width, sz.height ) + std::min( sz.width, sz.height ) / 2;
483 }
484
ReplaceRandomCastleObjectId(const fheroes2::Point & center)485 void Maps::ReplaceRandomCastleObjectId( const fheroes2::Point & center )
486 {
487 // Reset castle ID
488 for ( int32_t y = -3; y < 2; ++y ) {
489 for ( int32_t x = -2; x < 3; ++x ) {
490 Maps::Tiles & tile = world.GetTiles( center.x + x, center.y + y );
491
492 if ( MP2::OBJN_RNDCASTLE == tile.GetObject() || MP2::OBJN_RNDTOWN == tile.GetObject() ) {
493 tile.SetObject( MP2::OBJN_CASTLE );
494 }
495 }
496 }
497
498 // restore center ID
499 world.GetTiles( center.x, center.y ).SetObject( MP2::OBJ_CASTLE );
500 }
501
UpdateCastleSprite(const fheroes2::Point & center,int race,bool isCastle,bool isRandom)502 void Maps::UpdateCastleSprite( const fheroes2::Point & center, int race, bool isCastle, bool isRandom )
503 {
504 /*
505 Castle/Town object image consists of 42 tile sprites:
506 10 base tiles (OBJNTWBA) with 16 shadow tiles on left side (OBJNTWSH) overlayed by 16 town tiles (OBJNTOWN)
507
508 Shadows (OBJNTWSH) Castle (OBJNTOWN)
509 0
510 32 33 34 35 1 2 3 4 5
511 36 37 38 39 40 6 7 8 9 10
512 41 42 43 44 11 12 13 14 15
513 45 46 47
514 */
515
516 // correct only RND town and castle
517 const Maps::Tiles & entranceTile = world.GetTiles( center.x, center.y );
518 const MP2::MapObjectType objectType = entranceTile.GetObject();
519 const uint32_t castleID = entranceTile.GetObjectUID();
520
521 if ( isRandom && ( objectType != MP2::OBJ_RNDCASTLE && objectType != MP2::OBJ_RNDTOWN ) ) {
522 DEBUG_LOG( DBG_GAME, DBG_WARN,
523 "incorrect object"
524 << ", index: " << GetIndexFromAbsPoint( center.x, center.y ) );
525 return;
526 }
527
528 int raceIndex = 0; // Race::KNIGHT
529 switch ( race ) {
530 case Race::BARB:
531 raceIndex = 1;
532 break;
533 case Race::SORC:
534 raceIndex = 2;
535 break;
536 case Race::WRLK:
537 raceIndex = 3;
538 break;
539 case Race::WZRD:
540 raceIndex = 4;
541 break;
542 case Race::NECR:
543 raceIndex = 5;
544 break;
545 default:
546 break;
547 }
548
549 const int castleCoordinates[16][2] = { { 0, -3 }, { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 }, { -1, -1 },
550 { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 } };
551 const int shadowCoordinates[16][2] = { { -4, -2 }, { -3, -2 }, { -2, -2 }, { -1, -2 }, { -5, -1 }, { -4, -1 }, { -3, -1 }, { -2, -1 },
552 { -1, -1 }, { -4, 0 }, { -3, 0 }, { -2, 0 }, { -1, 0 }, { -3, 1 }, { -2, 1 }, { -1, 1 } };
553
554 for ( int index = 0; index < 16; ++index ) {
555 const int fullTownIndex = index + ( isCastle ? 0 : 16 ) + raceIndex * 32;
556 const int lookupID = isRandom ? index + ( isCastle ? 0 : 16 ) : fullTownIndex;
557
558 const int castleTile = GetIndexFromAbsPoint( center.x + castleCoordinates[index][0], center.y + castleCoordinates[index][1] );
559 if ( isValidAbsIndex( castleTile ) ) {
560 Tiles & tile = world.GetTiles( castleTile );
561
562 if ( isRandom )
563 tile.ReplaceObjectSprite( castleID, 38, 35 * 4, lookupID, fullTownIndex ); // OBJNTWRD to OBJNTOWN
564 else
565 tile.UpdateObjectSprite( castleID, 35, 35 * 4, -16 ); // no change in tileset
566
567 if ( index == 0 ) {
568 TilesAddon * addon = tile.FindAddonLevel2( castleID );
569 if ( addon && MP2::GetICNObject( addon->object ) == ICN::OBJNTWRD ) {
570 addon->object -= 12;
571 addon->index = fullTownIndex - 16;
572 }
573 }
574 }
575
576 const int shadowTile = GetIndexFromAbsPoint( center.x + shadowCoordinates[index][0], center.y + shadowCoordinates[index][1] );
577 if ( isValidAbsIndex( shadowTile ) ) {
578 if ( isRandom )
579 world.GetTiles( shadowTile ).ReplaceObjectSprite( castleID, 38, 37 * 4, lookupID + 32, fullTownIndex ); // OBJNTWRD to OBJNTWSH
580 else
581 world.GetTiles( shadowTile ).UpdateObjectSprite( castleID, 37, 37 * 4, -16 ); // no change in tileset
582 }
583 }
584 }
585
operator >>(StreamBase & sb,IndexObject & st)586 StreamBase & operator>>( StreamBase & sb, IndexObject & st )
587 {
588 return sb >> st.first >> st.second;
589 }
590
operator >>(StreamBase & sb,ObjectColor & st)591 StreamBase & operator>>( StreamBase & sb, ObjectColor & st )
592 {
593 return sb >> st.first >> st.second;
594 }
595