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 <atomic>
25 #include <cassert>
26 #include <cmath>
27
28 #include "agg.h"
29 #include "audio.h"
30 #include "cursor.h"
31 #include "difficulty.h"
32 #include "game.h"
33 #include "game_credits.h"
34 #include "game_delays.h"
35 #include "game_interface.h"
36 #include "game_static.h"
37 #include "icn.h"
38 #include "m82.h"
39 #include "maps_tiles.h"
40 #include "monster.h"
41 #include "mp2.h"
42 #include "rand.h"
43 #include "save_format_version.h"
44 #include "settings.h"
45 #include "skill.h"
46 #include "text.h"
47 #include "tools.h"
48 #include "translations.h"
49 #include "world.h"
50
51 namespace
52 {
53 std::string lastMapFileName;
54 std::vector<Player> savedPlayers;
55
56 int save_version = CURRENT_FORMAT_VERSION;
57
58 std::string last_name;
59
60 bool updateSoundsOnFocusUpdate = true;
61 std::atomic<int> currentMusic{ MUS::UNKNOWN };
62
63 u32 maps_animation_frame = 0;
64
65 std::vector<int> reserved_vols( LOOPXX_COUNT, 0 );
66
GetMixerChannelFromObject(const Maps::Tiles & tile)67 uint32_t GetMixerChannelFromObject( const Maps::Tiles & tile )
68 {
69 // check stream first
70 if ( tile.isStream() ) {
71 return 13;
72 }
73
74 return M82::GetIndexLOOP00XXFromObject( tile.GetObject( false ) );
75 }
76 }
77
78 namespace Game
79 {
80 void AnimateDelaysInitialize( void );
81 void KeyboardGlobalFilter( int, int );
82
83 void HotKeysDefaults( void );
84 void HotKeysLoad( const std::string & );
85
86 namespace ObjectFadeAnimation
87 {
FadeTask(MP2::MapObjectType object_,uint32_t objectIndex_,uint32_t animationIndex_,int32_t fromIndex_,int32_t toIndex_,uint8_t alpha_,bool fadeOut_,bool fadeIn_,uint8_t objectTileset_)88 FadeTask::FadeTask( MP2::MapObjectType object_, uint32_t objectIndex_, uint32_t animationIndex_, int32_t fromIndex_, int32_t toIndex_, uint8_t alpha_,
89 bool fadeOut_, bool fadeIn_, uint8_t objectTileset_ )
90 : object( object_ )
91 , objectIndex( objectIndex_ )
92 , animationIndex( animationIndex_ )
93 , fromIndex( fromIndex_ )
94 , toIndex( toIndex_ )
95 , alpha( alpha_ )
96 , fadeOut( fadeOut_ )
97 , fadeIn( fadeIn_ )
98 , objectTileset( objectTileset_ )
99 {}
100
FadeTask()101 FadeTask::FadeTask()
102 : object( MP2::OBJ_ZERO )
103 , objectIndex( 0 )
104 , animationIndex( 0 )
105 , fromIndex( 0 )
106 , toIndex( 0 )
107 , alpha( 0 )
108 , fadeOut( false )
109 , fadeIn( false )
110 , objectTileset( 0 )
111 {}
112
113 // Single instance of FadeTask.
114 FadeTask fadeTask;
115 }
116 }
117
118 // Returns the difficulty level based on the type of game.
getDifficulty()119 int Game::getDifficulty()
120 {
121 const Settings & configuration = Settings::Get();
122
123 return ( configuration.isCampaignGameType() ? configuration.CurrentFileInfo().difficulty : configuration.GameDifficulty() );
124 }
125
LoadPlayers(const std::string & mapFileName,Players & players)126 void Game::LoadPlayers( const std::string & mapFileName, Players & players )
127 {
128 if ( lastMapFileName != mapFileName || savedPlayers.size() != players.size() ) {
129 return;
130 }
131
132 const auto newHumanCount = std::count_if( players.begin(), players.end(), []( const Player * player ) { return player->GetControl() == CONTROL_HUMAN; } );
133 const auto savedHumanCount = std::count_if( savedPlayers.begin(), savedPlayers.end(), []( const Player & player ) { return player.GetControl() == CONTROL_HUMAN; } );
134
135 if ( newHumanCount != savedHumanCount ) {
136 return;
137 }
138
139 players.clear();
140 for ( const Player & p : savedPlayers ) {
141 Player * player = new Player( p.GetColor() );
142 player->SetRace( p.GetRace() );
143 player->SetControl( p.GetControl() );
144 player->SetFriends( p.GetFriends() );
145 player->SetName( p.GetName() );
146 players.push_back( player );
147 Players::Set( Color::GetIndex( p.GetColor() ), player );
148 }
149 }
150
saveDifficulty(const int difficulty)151 void Game::saveDifficulty( const int difficulty )
152 {
153 Settings::Get().SetGameDifficulty( difficulty );
154 }
155
SavePlayers(const std::string & mapFileName,const Players & players)156 void Game::SavePlayers( const std::string & mapFileName, const Players & players )
157 {
158 lastMapFileName = mapFileName;
159 savedPlayers.clear();
160 for ( const Player * p : players ) {
161 Player player( p->GetColor() );
162 player.SetRace( p->GetRace() );
163 player.SetControl( p->GetControl() );
164 player.SetFriends( p->GetFriends() );
165 player.SetName( p->GetName() );
166 savedPlayers.push_back( player );
167 }
168 }
169
SetLoadVersion(int ver)170 void Game::SetLoadVersion( int ver )
171 {
172 save_version = ver;
173 }
174
GetLoadVersion(void)175 int Game::GetLoadVersion( void )
176 {
177 return save_version;
178 }
179
GetLastSavename(void)180 const std::string & Game::GetLastSavename( void )
181 {
182 return last_name;
183 }
184
SetLastSavename(const std::string & name)185 void Game::SetLastSavename( const std::string & name )
186 {
187 last_name = name;
188 }
189
Credits()190 fheroes2::GameMode Game::Credits()
191 {
192 ShowCredits();
193
194 return fheroes2::GameMode::MAIN_MENU;
195 }
196
UpdateSoundsOnFocusUpdate()197 bool Game::UpdateSoundsOnFocusUpdate()
198 {
199 return updateSoundsOnFocusUpdate;
200 }
201
SetUpdateSoundsOnFocusUpdate(bool update)202 void Game::SetUpdateSoundsOnFocusUpdate( bool update )
203 {
204 updateSoundsOnFocusUpdate = update;
205 }
206
Init(void)207 void Game::Init( void )
208 {
209 // default events
210 LocalEvent::SetStateDefaults();
211
212 // set global events
213 LocalEvent & le = LocalEvent::Get();
214 le.SetGlobalFilterMouseEvents( Cursor::Redraw );
215 le.SetGlobalFilterKeysEvents( Game::KeyboardGlobalFilter );
216
217 Game::AnimateDelaysInitialize();
218
219 HotKeysDefaults();
220
221 const std::string hotkeys = Settings::GetLastFile( "", "fheroes2.key" );
222 Game::HotKeysLoad( hotkeys );
223 }
224
CurrentMusic()225 int Game::CurrentMusic()
226 {
227 return currentMusic;
228 }
229
SetCurrentMusic(const int mus)230 void Game::SetCurrentMusic( const int mus )
231 {
232 currentMusic = mus;
233 }
234
PrepareFadeTask(const MP2::MapObjectType objectType,int32_t fromIndex,int32_t toIndex,bool fadeOut,bool fadeIn)235 void Game::ObjectFadeAnimation::PrepareFadeTask( const MP2::MapObjectType objectType, int32_t fromIndex, int32_t toIndex, bool fadeOut, bool fadeIn )
236 {
237 const uint8_t alpha = fadeOut ? 255u : 0;
238 const Maps::Tiles & fromTile = world.GetTiles( fromIndex );
239
240 if ( objectType == MP2::OBJ_ZERO ) {
241 fadeTask = FadeTask();
242 }
243 else if ( objectType == MP2::OBJ_MONSTER ) {
244 const auto & spriteIndicies = Maps::Tiles::GetMonsterSpriteIndices( fromTile, fromTile.QuantityMonster().GetSpriteIndex() );
245
246 fadeTask = FadeTask( objectType, spriteIndicies.first, spriteIndicies.second, fromIndex, toIndex, alpha, fadeOut, fadeIn, 0 );
247 }
248 else if ( objectType == MP2::OBJ_BOAT ) {
249 fadeTask = FadeTask( objectType, fromTile.GetObjectSpriteIndex(), 0, fromIndex, toIndex, alpha, fadeOut, fadeIn, 0 );
250 }
251 else {
252 const int icn = MP2::GetICNObject( fromTile.GetObjectTileset() );
253 const uint32_t animationIndex = ICN::AnimationFrame( icn, fromTile.GetObjectSpriteIndex(), Game::MapsAnimationFrame(), fromTile.GetQuantity2() != 0 );
254
255 fadeTask = FadeTask( objectType, fromTile.GetObjectSpriteIndex(), animationIndex, fromIndex, toIndex, alpha, fadeOut, fadeIn, fromTile.GetObjectTileset() );
256 }
257 }
258
PerformFadeTask()259 void Game::ObjectFadeAnimation::PerformFadeTask()
260 {
261 auto removeObject = []() {
262 Maps::Tiles & tile = world.GetTiles( fadeTask.fromIndex );
263
264 if ( tile.GetObject() == fadeTask.object ) {
265 tile.RemoveObjectSprite();
266 tile.setAsEmpty();
267 }
268 };
269 auto addObject = []() {
270 Maps::Tiles & tile = world.GetTiles( fadeTask.toIndex );
271
272 if ( tile.GetObject() != fadeTask.object && fadeTask.object == MP2::OBJ_BOAT ) {
273 tile.setBoat( Direction::RIGHT );
274 }
275 };
276 auto redrawGameArea = []() {
277 fheroes2::Display & display = fheroes2::Display::instance();
278 Interface::GameArea & gameArea = Interface::Basic::Get().GetGameArea();
279
280 gameArea.Redraw( display, Interface::LEVEL_ALL );
281
282 display.render();
283 };
284
285 LocalEvent & le = LocalEvent::Get();
286
287 while ( le.HandleEvents() && ( fadeTask.fadeOut || fadeTask.fadeIn ) ) {
288 if ( Game::validateAnimationDelay( Game::HEROES_PICKUP_DELAY ) ) {
289 if ( fadeTask.fadeOut ) {
290 if ( fadeTask.alpha > 20 ) {
291 fadeTask.alpha -= 20;
292 }
293 else {
294 removeObject();
295
296 if ( fadeTask.fadeIn ) {
297 fadeTask.fadeOut = false;
298 fadeTask.alpha = 0;
299 }
300 else {
301 fadeTask = FadeTask();
302 }
303 }
304 }
305 else if ( fadeTask.fadeIn ) {
306 if ( fadeTask.alpha == 0 ) {
307 addObject();
308 }
309
310 if ( fadeTask.alpha < 235 ) {
311 fadeTask.alpha += 20;
312 }
313 else {
314 fadeTask = FadeTask();
315 }
316 }
317
318 redrawGameArea();
319 }
320 }
321
322 if ( fadeTask.fadeOut ) {
323 removeObject();
324 }
325
326 if ( fadeTask.fadeIn ) {
327 addObject();
328 }
329
330 fadeTask = FadeTask();
331
332 redrawGameArea();
333 }
334
GetFadeTask()335 const Game::ObjectFadeAnimation::FadeTask & Game::ObjectFadeAnimation::GetFadeTask()
336 {
337 return fadeTask;
338 }
339
MapsAnimationFrame(void)340 u32 & Game::MapsAnimationFrame( void )
341 {
342 return maps_animation_frame;
343 }
344
345 // play environment sounds from the game area in focus
EnvironmentSoundMixer()346 void Game::EnvironmentSoundMixer()
347 {
348 size_t availableChannels = Mixer::getChannelCount();
349 if ( availableChannels == 0 ) {
350 return;
351 }
352
353 const fheroes2::Point abs_pt( Interface::GetFocusCenter() );
354 std::fill( reserved_vols.begin(), reserved_vols.end(), 0 );
355
356 const int32_t maxOffset = 3;
357
358 // Get all valid positions within 7 x 7 area.
359 std::vector<fheroes2::Point> positions;
360 for ( int32_t y = -maxOffset; y <= maxOffset; ++y ) {
361 for ( int32_t x = -maxOffset; x <= maxOffset; ++x ) {
362 if ( Maps::isValidAbsPoint( x + abs_pt.x, y + abs_pt.y ) ) {
363 positions.emplace_back( x, y );
364 }
365 }
366 }
367
368 // Sort positions by distance to the center.
369 std::stable_sort( positions.begin(), positions.end(),
370 []( const fheroes2::Point & p1, const fheroes2::Point & p2 ) { return p1.x * p1.x + p1.y * p1.y < p2.x * p2.x + p2.y * p2.y; } );
371
372 const double maxDistance = std::sqrt( maxOffset * maxOffset + maxOffset * maxOffset );
373 double maxVolume = Mixer::MaxVolume();
374 double minVolumeOnMaxDistance = maxVolume * 0.1; // 10% from maximum volume
375
376 maxVolume -= minVolumeOnMaxDistance; // need to remove these 10% from max value as we're going to add it later
377 minVolumeOnMaxDistance += 0.5; // this is done to make casting faster. We know that the value is always positive.
378
379 for ( const fheroes2::Point & pos : positions ) {
380 const uint32_t channel = GetMixerChannelFromObject( world.GetTiles( pos.x + abs_pt.x, pos.y + abs_pt.y ) );
381 if ( channel < reserved_vols.size() ) {
382 const double distance = std::sqrt( pos.x * pos.x + pos.y * pos.y );
383 const int32_t volume = static_cast<int32_t>( ( ( maxDistance - distance ) / maxDistance ) * maxVolume + minVolumeOnMaxDistance );
384
385 if ( reserved_vols[channel] == 0 ) {
386 if ( availableChannels == 0 ) {
387 // No new channel can be added.
388 continue;
389 }
390 --availableChannels;
391 }
392
393 if ( volume > reserved_vols[channel] ) {
394 reserved_vols[channel] = volume;
395 }
396 }
397 }
398
399 AGG::LoadLOOPXXSounds( reserved_vols, true );
400 }
401
restoreSoundsForCurrentFocus()402 void Game::restoreSoundsForCurrentFocus()
403 {
404 AGG::ResetMixer();
405
406 switch ( Interface::GetFocusType() ) {
407 case GameFocus::HEROES: {
408 const Heroes * focusedHero = Interface::GetFocusHeroes();
409 assert( focusedHero != nullptr );
410
411 const int heroIndexPos = focusedHero->GetIndex();
412 if ( heroIndexPos >= 0 ) {
413 Game::EnvironmentSoundMixer();
414 AGG::PlayMusic( MUS::FromGround( world.GetTiles( heroIndexPos ).GetGround() ), true, true );
415 }
416 break;
417 }
418
419 case GameFocus::CASTLE: {
420 const Castle * focusedCastle = Interface::GetFocusCastle();
421 assert( focusedCastle != nullptr );
422
423 Game::EnvironmentSoundMixer();
424 AGG::PlayMusic( MUS::FromGround( world.GetTiles( focusedCastle->GetIndex() ).GetGround() ), true, true );
425 break;
426 }
427
428 default:
429 break;
430 }
431 }
432
GetRating(void)433 u32 Game::GetRating( void )
434 {
435 const Settings & conf = Settings::Get();
436 u32 rating = 50;
437
438 switch ( conf.MapsDifficulty() ) {
439 case Difficulty::NORMAL:
440 rating += 20;
441 break;
442 case Difficulty::HARD:
443 rating += 40;
444 break;
445 case Difficulty::EXPERT:
446 case Difficulty::IMPOSSIBLE:
447 rating += 80;
448 break;
449 default:
450 break;
451 }
452
453 switch ( Game::getDifficulty() ) {
454 case Difficulty::NORMAL:
455 rating += 30;
456 break;
457 case Difficulty::HARD:
458 rating += 50;
459 break;
460 case Difficulty::EXPERT:
461 rating += 70;
462 break;
463 case Difficulty::IMPOSSIBLE:
464 rating += 90;
465 break;
466 default:
467 break;
468 }
469
470 return rating;
471 }
472
GetGameOverScores(void)473 u32 Game::GetGameOverScores( void )
474 {
475 const Settings & conf = Settings::Get();
476
477 uint32_t mapSizeFactor = 0;
478
479 switch ( conf.MapsSize().width ) {
480 case Maps::SMALL:
481 mapSizeFactor = 140;
482 break;
483 case Maps::MEDIUM:
484 mapSizeFactor = 100;
485 break;
486 case Maps::LARGE:
487 mapSizeFactor = 80;
488 break;
489 case Maps::XLARGE:
490 mapSizeFactor = 60;
491 break;
492 }
493
494 const uint32_t daysFactor = world.CountDay() * mapSizeFactor / 100;
495
496 uint32_t daysScore = 0;
497
498 if ( daysFactor <= 60 ) {
499 daysScore = daysFactor;
500 }
501 else if ( daysFactor <= 120 ) {
502 daysScore = daysFactor / 2 + 30;
503 }
504 else if ( daysFactor <= 360 ) {
505 daysScore = daysFactor / 4 + 60;
506 }
507 else if ( daysFactor <= 600 ) {
508 daysScore = daysFactor / 8 + 105;
509 }
510 else {
511 daysScore = 180;
512 }
513
514 return GetRating() * ( 200 - daysScore ) / 100;
515 }
516
ShowMapLoadingText(void)517 void Game::ShowMapLoadingText( void )
518 {
519 fheroes2::Display & display = fheroes2::Display::instance();
520 const fheroes2::Rect pos( 0, display.height() / 2, display.width(), display.height() / 2 );
521 TextBox text( _( "Map is loading..." ), Font::BIG, pos.width );
522
523 // blit test
524 display.fill( 0 );
525 text.Blit( pos.x, pos.y );
526 display.render();
527 }
528
GetLostTownDays(void)529 u32 Game::GetLostTownDays( void )
530 {
531 return GameStatic::GetGameOverLostDays();
532 }
533
GetViewDistance(u32 d)534 u32 Game::GetViewDistance( u32 d )
535 {
536 return GameStatic::GetOverViewDistance( d );
537 }
538
GetWhirlpoolPercent(void)539 u32 Game::GetWhirlpoolPercent( void )
540 {
541 return GameStatic::GetLostOnWhirlpoolPercent();
542 }
543
GetKingdomColors(void)544 int Game::GetKingdomColors( void )
545 {
546 return Settings::Get().GetPlayers().GetColors();
547 }
548
GetActualKingdomColors(void)549 int Game::GetActualKingdomColors( void )
550 {
551 return Settings::Get().GetPlayers().GetActualColors();
552 }
553
CountScoute(uint32_t count,int scoute,bool shorts)554 std::string Game::CountScoute( uint32_t count, int scoute, bool shorts )
555 {
556 double infelicity = 0;
557 std::string res;
558
559 switch ( scoute ) {
560 case Skill::Level::BASIC:
561 infelicity = count * 30 / 100.0;
562 break;
563
564 case Skill::Level::ADVANCED:
565 infelicity = count * 15 / 100.0;
566 break;
567
568 case Skill::Level::EXPERT:
569 res = shorts ? GetStringShort( count ) : std::to_string( count );
570 break;
571
572 default:
573 return Army::SizeString( count );
574 }
575
576 if ( res.empty() ) {
577 uint32_t min = Rand::Get( static_cast<uint32_t>( std::floor( count - infelicity + 0.5 ) ), static_cast<uint32_t>( std::floor( count + infelicity + 0.5 ) ) );
578 uint32_t max = 0;
579
580 if ( min > count ) {
581 max = min;
582 min = static_cast<uint32_t>( std::floor( count - infelicity + 0.5 ) );
583 }
584 else
585 max = static_cast<uint32_t>( std::floor( count + infelicity + 0.5 ) );
586
587 res = std::to_string( min );
588
589 if ( min != max ) {
590 res.append( "-" );
591 res.append( std::to_string( max ) );
592 }
593 }
594
595 return res;
596 }
597
CountThievesGuild(uint32_t monsterCount,int guildCount)598 std::string Game::CountThievesGuild( uint32_t monsterCount, int guildCount )
599 {
600 assert( guildCount > 0 );
601 return guildCount == 1 ? "???" : Army::SizeString( monsterCount );
602 }
603
PlayPickupSound(void)604 void Game::PlayPickupSound( void )
605 {
606 int wav = M82::UNKNOWN;
607
608 switch ( Rand::Get( 1, 7 ) ) {
609 case 1:
610 wav = M82::PICKUP01;
611 break;
612 case 2:
613 wav = M82::PICKUP02;
614 break;
615 case 3:
616 wav = M82::PICKUP03;
617 break;
618 case 4:
619 wav = M82::PICKUP04;
620 break;
621 case 5:
622 wav = M82::PICKUP05;
623 break;
624 case 6:
625 wav = M82::PICKUP06;
626 break;
627 case 7:
628 wav = M82::PICKUP07;
629 break;
630
631 default:
632 return;
633 }
634
635 AGG::PlaySound( wav );
636 }
637