1 /***************************************************************************
2 savegamedialog.cpp - The save/load game dialog
3 -------------------
4 begin : 9/9/2005
5 copyright : (C) 2005 by Gabor Torok
6 email : cctorok@yahoo.com
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "common/constants.h"
19 #include "savegamedialog.h"
20 #include "scourge.h"
21 #include "shapepalette.h"
22 #include "persist.h"
23 #include "creature.h"
24 #include "rpg/character.h"
25 #include "io/file.h"
26 #include "gui/window.h"
27 #include "gui/button.h"
28 #include "gui/scrollinglist.h"
29 #include "gui/label.h"
30 #include "gui/confirmdialog.h"
31 #include "gui/canvas.h"
32
33 // ###### MS Visual C++ specific ######
34 #if defined(_MSC_VER) && defined(_DEBUG)
35 # define new DEBUG_NEW
36 # undef THIS_FILE
37 static char THIS_FILE[] = __FILE__;
38 #endif
39
40 bool savegamesChanged = true;
41 int maxFileSuffix = 0;
42
SavegameDialog(Scourge * scourge)43 SavegameDialog::SavegameDialog( Scourge *scourge ) {
44 this->scourge = scourge;
45 int w = 600;
46 int h = 400;
47 win =
48 scourge->createWindow( scourge->getScreenWidth() / 2 - w / 2,
49 scourge->getScreenHeight() / 2 - h / 2,
50 w, h,
51 _( "Saved games" ) );
52 win->createLabel( 10, 20, _( "Current saved games:" ) );
53 files = new ScrollingList( 10, 30, w - 130, h - 70,
54 scourge->getHighlightTexture(), NULL, 70 );
55 files->setTextLinewrap( true );
56 files->setIconBorder( true );
57 win->addWidget( files );
58
59 newSave = win->createButton( w - 105, 30, w - 15, 50, _( "New Save" ) );
60 save = win->createButton( w - 105, 60, w - 15, 80, _( "Save" ) );
61 load = win->createButton( w - 105, 90, w - 15, 110, _( "Load" ) );
62 deleteSave = win->createButton( w - 105, 150, w - 15, 170, _( "Delete" ) );
63 cancel = win->createButton( w - 105, 210, w - 15, 230, _( "Cancel" ) );
64
65 confirm = new ConfirmDialog( scourge->getSDLHandler(), _( "Overwrite existing file?" ) );
66 win->registerEventHandler( this );
67 confirm->win->registerEventHandler( this );
68 }
69
~SavegameDialog()70 SavegameDialog::~SavegameDialog() {
71 delete win;
72 // win deletes it as widget: delete files;
73 delete confirm;
74 for ( size_t i = 0; i != fileInfos.size(); ++i ) {
75 delete fileInfos[i];
76 }
77 }
78
79 #define SAVE_MODE 1
80 #define LOAD_MODE 2
81 #define DELETE_MODE 3
82
handleEvent(Widget * widget,SDL_Event * event)83 bool SavegameDialog::handleEvent( Widget *widget, SDL_Event *event ) {
84 static int selectedFile;
85 if ( widget == confirm->okButton ) {
86 confirm->setVisible( false );
87 if ( confirm->getMode() == SAVE_MODE ) {
88 if ( createSaveGame( fileInfos[ selectedFile ] ) ) {
89 scourge->showMessageDialog( _( "Game saved successfully." ) );
90 } else {
91 scourge->showMessageDialog( _( "Error saving game." ) );
92 }
93 } else if ( confirm->getMode() == DELETE_MODE ) {
94 getWindow()->setVisible( false );
95 savegamesChanged = true;
96 string tmp = get_file_name( fileInfos[ selectedFile ]->path );
97 if ( deleteDirectory( tmp ) ) {
98 scourge->showMessageDialog( _( "Game was successfully removed." ) );
99 } else {
100 scourge->showMessageDialog( _( "Could not delete saved game." ) );
101 }
102 } else {
103 loadGame( selectedFile );
104 }
105 } else if ( widget == confirm->cancelButton ) {
106 confirm->setVisible( false );
107 } else if ( widget == cancel || widget == win->closeButton ) {
108 win->setVisible( false );
109 } else if ( widget == save ) {
110 int n = files->getSelectedLine();
111 if ( n > -1 ) {
112 selectedFile = n;
113 confirm->setText( _( "Are you sure you want to overwrite this file?" ) );
114 confirm->setMode( SAVE_MODE );
115 confirm->setVisible( true );
116 }
117 } else if ( widget == newSave ) {
118 if ( createNewSaveGame() ) {
119 scourge->showMessageDialog( _( "Game saved successfully." ) );
120 } else {
121 scourge->showMessageDialog( _( "Error saving game." ) );
122 }
123 } else if ( widget == load ) {
124 int n = files->getSelectedLine();
125 if ( n > -1 ) {
126 if ( save->isEnabled() ) {
127 selectedFile = n;
128 confirm->setText( _( "Are you sure you want to load this file?" ) );
129 confirm->setMode( LOAD_MODE );
130 confirm->setVisible( true );
131 } else {
132 loadGame( n );
133 }
134 }
135 } else if ( widget == deleteSave ) {
136 int n = files->getSelectedLine();
137 if ( n > -1 ) {
138 if ( fileInfos[ n ]->path == scourge->getSession()->getSavegameName() ) {
139 scourge->showMessageDialog( _( "You can't delete the current game." ) );
140 } else {
141 selectedFile = n;
142 confirm->setText( _( "Are you sure you want to delete this file?" ) );
143 confirm->setMode( DELETE_MODE );
144 confirm->setVisible( true );
145 }
146 }
147 }
148 return false;
149 }
150
loadGame(int n)151 void SavegameDialog::loadGame( int n ) {
152 getWindow()->setVisible( false );
153 scourge->getSession()->setLoadgameName( fileInfos[n]->path );
154 scourge->getSession()->setLoadgameTitle( fileInfos[n]->title );
155 scourge->getSDLHandler()->endMainLoop();
156 }
157
show(bool inSaveMode)158 void SavegameDialog::show( bool inSaveMode ) {
159 if ( inSaveMode ) {
160 save->setEnabled( true );
161 newSave->setEnabled( true );
162 win->setTitle( _( "Create a new saved game or load an existing one" ) );
163 } else {
164 // check for save games
165 save->setEnabled( false );
166 newSave->setEnabled( false );
167 win->setTitle( _( "Open an existing saved game file" ) );
168 }
169 if ( savegamesChanged ) {
170 if ( !findFiles() ) {
171 scourge->showMessageDialog( _( "No savegames have been created yet." ) );
172 savegamesChanged = true; // check again next time
173 return;
174 }
175 }
176 win->setVisible( true );
177 }
178
findFiles()179 bool SavegameDialog::findFiles() {
180 fileInfos.clear();
181 screens.clear();
182 filenames.clear();
183
184 vector<string> fileNameList, realList;
185 findFilesInDir( get_file_name( "" ), &fileNameList );
186
187 maxFileSuffix = 0;
188 for ( vector<string>::reverse_iterator i = fileNameList.rbegin(); i != fileNameList.rend(); i++ ) {
189 if ( i->substr( 0, 5 ) == "save_" && readFileDetails( *i ) ) {
190 filenames.push_back( fileInfos.back()->title );
191 Texture tex;
192 tex.loadShot( fileInfos.back()->path );
193 screens.push_back( tex );
194
195 int n = static_cast<int>( strtol( i->c_str() + 5, ( char** )NULL, 16 ) );
196 if ( n > maxFileSuffix )
197 maxFileSuffix = n;
198 }
199 }
200 files->setLines( filenames.begin(), filenames.end(), NULL, screens.empty() ? NULL : &screens[0] );
201 savegamesChanged = false;
202 return( filenames.size() > 0 );
203 }
204
205 /* unused:
206 GLuint SavegameDialog::loadScreenshot( const string& dirName ) {
207 string path = get_file_name( dirName + "/screen.bmp" );
208 SDL_Surface* surface = SDL_LoadBMP( path.c_str() );
209
210 if ( surface == NULL ) {
211 cerr << "*** Error loading screenshot image (" << path << "): " << IMG_GetError() << endl;
212 return NULL;
213 }
214
215 GLuint texture;
216 glPixelStorei( GL_UNPACK_ALIGNMENT, 4 );
217 glGenTextures( 1, &texture );
218 glBindTexture( GL_TEXTURE_2D, texture );
219
220 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
221 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
222
223 gluBuild2DMipmaps( GL_TEXTURE_2D, GL_RGB, surface->w, surface->h, GL_BGR, GL_UNSIGNED_BYTE, surface->pixels );
224
225 SDL_FreeSurface( surface );
226 return texture;
227 }
228 */
readFileDetails(const string & dirname)229 bool SavegameDialog::readFileDetails( const string& dirname ) {
230 string path = get_file_name( dirname + "/savegame.dat" );
231 cerr << "Loading: " << path << endl;
232 FILE *fp = fopen( path.c_str(), "rb" );
233 if ( !fp ) {
234 cerr << "*** error: Can't find file." << endl;
235 return false;
236 }
237 File *file = new File( fp );
238 Uint32 version = PERSIST_VERSION;
239 file->read( &version );
240 if ( version < OLDEST_HANDLED_VERSION ) {
241 cerr << "*** Error: Savegame file is too old (v" << version <<
242 " vs. current v" << PERSIST_VERSION <<
243 ", vs. last handled v" << OLDEST_HANDLED_VERSION <<
244 "): ignoring data in file." << endl;
245 delete file;
246 //strcpy( error, "Error: Saved game version is too old." );
247 return false;
248 } else {
249 if ( version < PERSIST_VERSION ) {
250 cerr << "*** Warning: loading older savegame file: v" << version <<
251 " vs. v" << PERSIST_VERSION << ". Will try to convert it." << endl;
252 }
253 }
254
255 Uint8 title[3000];
256 file->read( title, 3000 );
257
258 delete file;
259
260 SavegameInfo *info = new SavegameInfo();
261 info->path = dirname;
262 info->title = ( char* )title;
263 fileInfos.push_back( info );
264
265 return true;
266 }
267 namespace { // anonymous local force
268
269 typedef char POS_TXT[10];
270
getPosition(int n,POS_TXT & buff)271 void getPosition( int n, POS_TXT& buff ) {
272 snprintf( buff, 10, "%d", n );
273 switch ( buff[ strlen( buff ) - 1 ] ) {
274 case '1': strcat( buff, _( "st" ) ); break;
275 case '2': strcat( buff, _( "nd" ) ); break;
276 case '3': strcat( buff, _( "rd" ) ); break;
277 default: strcat( buff, _( "th" ) );
278 }
279 }
280
281 } // anonymous namespace
282
setSavegameInfoTitle(SavegameInfo * info)283 void SavegameDialog::setSavegameInfoTitle( SavegameInfo *info ) {
284 Creature *player = scourge->getParty()->getParty( 0 );
285 if ( player ) {
286 char tmp[10];
287 getPosition( player->getLevel(), tmp );
288 enum {PLACE_SIZE = 255};
289 char place[PLACE_SIZE];
290
291 if ( scourge->getSession()->getCurrentMission() ) {
292 if ( strstr( scourge->getSession()->getCurrentMission()->getMapName(), "outdoors" ) ) {
293 strcpy( place, _( "Somewhere in the wilderness." ) );
294 } else if ( strstr( scourge->getSession()->getCurrentMission()->getMapName(), "caves" ) ) {
295 snprintf( place, PLACE_SIZE, _( "In a cave on level %d." ),
296 ( scourge->getCurrentDepth() + 1 ) );
297 } else {
298 snprintf( place, PLACE_SIZE, _( "Dungeon level %d at %s." ),
299 ( scourge->getCurrentDepth() + 1 ),
300 scourge->getSession()->getCurrentMission()->getMapName() );
301 }
302 } else {
303 strcpy( place, _( "Resting at HQ." ) );
304 }
305 char tmp2[300];
306 snprintf( tmp2, 300, _( "Party of %s the %s level %s." ), player->getName(), tmp, player->getCharacter()->getDisplayName() );
307 info->title = scourge->getSession()->getParty()->getCalendar()->getCurrentDate().getDateString() + string( " " ) + scourge->getSession()->getBoard()->getStorylineTitle() + string( ", " ) + tmp2 + string( " " ) + place;
308 } else {
309 info->title = scourge->getSession()->getParty()->getCalendar()->getCurrentDate().getDateString() + string( " " ) + scourge->getSession()->getBoard()->getStorylineTitle();
310 }
311 }
312
createNewSaveGame()313 bool SavegameDialog::createNewSaveGame() {
314
315 // in case we're called from the outside
316 if ( savegamesChanged ) findFiles();
317
318 // create a new save game
319 SavegameInfo info;
320 stringstream tmp;
321 tmp << "save_" << std::hex << ++maxFileSuffix; // incr. maxFileSuffix in case we crash and the file is created
322 info.path = tmp.str();
323 setSavegameInfoTitle( &info );
324
325 // make its directory
326 makeDirectory( get_file_name( info.path ) );
327
328 return saveGameInternal( &info );
329 }
330
quickLoad()331 void SavegameDialog::quickLoad() {
332 scourge->getSession()->setLoadgameName( scourge->getSession()->getSavegameName() );
333 scourge->getSession()->setLoadgameTitle( scourge->getSession()->getSavegameTitle() );
334 scourge->getSDLHandler()->endMainLoop();
335 }
336
quickSave(const string & dirName,const string & title)337 bool SavegameDialog::quickSave( const string& dirName, const string& title ) {
338 SavegameInfo info;
339 info.path = dirName;
340 info.title = title;
341 // create a new save game title
342 setSavegameInfoTitle( &info );
343
344 return saveGameInternal( &info );
345 }
346
createSaveGame(SavegameInfo * info)347 bool SavegameDialog::createSaveGame( SavegameInfo *info ) {
348 // create a new save game title
349 setSavegameInfoTitle( info );
350
351 return saveGameInternal( info );
352 }
353
saveGameInternal(SavegameInfo * info)354 bool SavegameDialog::saveGameInternal( SavegameInfo *info ) {
355 // save the game here
356 bool b = scourge->saveGame( scourge->getSession(), info->path, info->title );
357 if ( b ) {
358
359 // if there is a current game and it's diff. than the one we're saving, copy the maps over
360 string s( scourge->getSession()->getSavegameName() );
361 if ( s != info->path && s != "" ) {
362 b = copyMaps( s, info->path );
363 }
364
365 // pre-emptively save the current map (just in case it hasn't been done yet and we crash later
366 if ( scourge->getSession()->getCurrentMission() ) {
367 scourge->saveCurrentMap( info->path );
368 }
369
370 // delete any unreferenced map files
371 // (these are either left when saving over an old game
372 // or completed and no longer on the board)
373 deleteUnreferencedMaps( info->path );
374
375 if ( b ) {
376
377 getWindow()->setVisible( false );
378 saveScreenshot( info->path );
379
380 // reload the next time
381 savegamesChanged = true;
382
383 // set it to be the current savegame
384 scourge->getSession()->setSavegameName( info->path );
385 scourge->getSession()->setSavegameTitle( info->title );
386
387 }
388 }
389 return b;
390 }
391
deleteUnvisitedMaps(const string & dirName,set<string> * visitedMaps)392 void SavegameDialog::deleteUnvisitedMaps( const string& dirName, set<string> *visitedMaps ) {
393
394 string path = get_file_name( dirName );
395 vector<string> fileNameList;
396 findFilesInDir( path, &fileNameList );
397 for ( vector<string>::iterator i = fileNameList.begin(); i < fileNameList.end(); i++ ) {
398 if ( ( *i )[0] == '_' ) {
399 if ( visitedMaps->find( *i ) == visitedMaps->end() ) {
400 string tmp = path + "/" + *i;
401 cerr << "\tDeleting un-visited map file: " << tmp << endl;
402 int n = remove( tmp.c_str() );
403 cerr << "\t\t" << ( !n ? "success" : "can't delete file" ) << endl;
404 }
405 }
406 }
407 }
408
deleteUnreferencedMaps(const string & dirName)409 void SavegameDialog::deleteUnreferencedMaps( const string& dirName ) {
410 vector<string> referencedMaps;
411 for ( int i = 0; i < scourge->getSession()->getBoard()->getMissionCount(); i++ ) {
412 string s = scourge->getSession()->getBoard()->getMission( i )->getSavedMapName();
413 if ( s == "" ) {
414 s = "_"; s += scourge->getSession()->getBoard()->getMission( i )->getMapName();
415 }
416 for ( int d = 0; d <= scourge->getSession()->getBoard()->getMission( i )->getDepth(); d++ ) {
417 stringstream mapName;
418 mapName << s << "_" << d;
419 //cerr << "REFMAP: " << mapName.str() << endl;
420 referencedMaps.push_back( mapName.str() );
421 }
422 }
423
424 string path = get_file_name( dirName );
425 vector<string> fileNameList;
426 findFilesInDir( path, &fileNameList );
427 for ( vector<string>::iterator i = fileNameList.begin(); i != fileNameList.end(); i++ ) {
428 if ( ( *i )[0] == '_' ) {
429 bool found = false;
430 for ( vector<string>::iterator t = referencedMaps.begin(); t != referencedMaps.end(); t++ ) {
431 if ( i->compare( 0, t->length(), *t ) == 0 ) {
432 found = true;
433 break;
434 }
435 }
436
437 if ( !found ) {
438 string tmp = path + "/" + *i;
439 cerr << "\tDeleting un-referenced map file: " << tmp << endl;
440 int n = remove( tmp.c_str() );
441 cerr << "\t\t" << ( !n ? "success" : "can't delete file" ) << endl;
442 }
443 }
444 }
445 }
446
copyMaps(const string & fromDirName,const string & toDirName)447 bool SavegameDialog::copyMaps( const string& fromDirName, const string& toDirName ) {
448 vector<string> fileNameList;
449 findFilesInDir( get_file_name( fromDirName ), &fileNameList );
450 for ( vector<string>::iterator i = fileNameList.begin(); i != fileNameList.end(); i++ ) {
451 if ( ( *i )[0] == '_' )
452 if ( !copyFile( fromDirName, toDirName, *i ) )
453 return false;
454 }
455 return true;
456 }
457
458 #define BUFFER_SIZE 4096
copyFile(const string & fromDirName,const string & toDirName,const string & fileName)459 bool SavegameDialog::copyFile( const string& fromDirName, const string& toDirName, const string& fileName ) {
460 string fromPath = get_file_name( fromDirName + "/" + fileName );
461 string toPath = get_file_name( toDirName + "/" + fileName );
462
463 // a bandaid to keep from creating 0-length files
464 if ( fromPath == toPath ) {
465 cerr << "*** ERROR: copying over the same file. This will create a 0-length file." << endl;
466 return false;
467 }
468
469 cerr << "+++ copying file: " << fromPath << " to " << toPath << endl;
470
471 FILE *from = fopen( fromPath.c_str(), "rb" );
472 if ( from ) {
473 FILE *to = fopen( toPath.c_str(), "wb" );
474 if ( to ) {
475 unsigned char buff[ BUFFER_SIZE ];
476 size_t count;
477 while ( ( count = fread( buff, 1, BUFFER_SIZE, from ) ) ) fwrite( buff, 1, count, to );
478 bool result = ( feof( from ) != 0 );
479 fclose( to );
480 return result;
481 }
482 fclose( from );
483 }
484 return false;
485 }
486
saveScreenshot(const string & dirName)487 void SavegameDialog::saveScreenshot( const string& dirName ) {
488 string path = get_file_name( dirName + "/screen.bmp" );
489 cerr << "Saving: " << path << endl;
490
491 scourge->getSDLHandler()->saveScreen( path, true );
492 }
493
makeDirectory(const string & path)494 void SavegameDialog::makeDirectory( const string& path ) {
495 #ifdef WIN32
496 CreateDirectory( path.c_str(), NULL );
497 #else
498 int err = mkdir( path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP );
499 if ( err ) {
500 cerr << "Error creating config directory: " << path << endl;
501 cerr << "Error: " << err << endl;
502 perror( "SavegameDialog::makeDirectory: " );
503 exit( 1 );
504 }
505 #endif
506 }
507
findFilesInDir(const string & path,vector<string> * fileNameList)508 void SavegameDialog::findFilesInDir( const string& path, vector<string> *fileNameList ) {
509 #ifdef WIN32
510 string winpath = path + "/*.*";
511
512 WIN32_FIND_DATA FindData;
513 HANDLE hFind = FindFirstFile ( winpath.c_str(), &FindData );
514 if ( hFind == INVALID_HANDLE_VALUE ) {
515 cerr << "*** Error: can't open path: " << path << " error: " << GetLastError() << endl;
516 return;
517 }
518
519 fileNameList->push_back( FindData.cFileName );
520 while ( FindNextFile ( hFind, &FindData ) ) {
521 fileNameList->push_back( FindData.cFileName );
522 }
523 FindClose ( hFind );
524 #else
525 DIR *dir = opendir( path.c_str() );
526 if ( !dir ) {
527 cerr << "*** Error: can't open path: " << path << " error: " << errno << endl;
528 return;
529 }
530 struct dirent *de;
531 while ( ( de = readdir( dir ) ) ) {
532 string s = de->d_name;
533 fileNameList->push_back( s );
534 }
535 closedir( dir );
536 #endif
537 }
538
deleteDirectory(const string & path)539 bool SavegameDialog::deleteDirectory( const string& path ) {
540 vector<string> fileNameList;
541 findFilesInDir( path, &fileNameList );
542 for ( vector<string>::iterator i = fileNameList.begin(); i != fileNameList.end(); i++ ) {
543 string toDelete = path + "/" + *i;
544 cerr << "\tDeleting file: " << toDelete << endl;
545 #ifdef WIN32
546 int n = !DeleteFile( toDelete.c_str() );
547 #else
548 int n = remove( toDelete.c_str() );
549 #endif
550 cerr << "\t\t" << ( !n ? "success" : "can't delete file" ) << endl;
551 }
552 cerr << "\tDeleting directory: " << path << endl;
553 #ifdef WIN32
554 int n = !RemoveDirectory( path.c_str() );
555 #else
556 int n = remove( path.c_str() );
557 #endif
558 cerr << "\t\t" << ( !n ? "success" : "can't delete directory" ) << endl;
559 return( !n ? true : false );
560 }
561
checkIfFileExists(const string & filename)562 bool SavegameDialog::checkIfFileExists( const string& filename ) {
563 ifstream fin;
564 fin.open( get_file_name( filename ).c_str() );
565
566 if ( !fin )
567 return false;
568
569 return true;
570 }
571
572