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