melee / ranged effects
[gemrb.git] / gemrb / core / SaveGameIterator.cpp
blobae8b002a53dc90405ec93ccbb4e9b67a012a3378
1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "SaveGameIterator.h"
23 #include "iless.h"
24 #include "strrefs.h"
25 #include "win32def.h"
27 #include "DisplayMessage.h"
28 #include "GameData.h" // For ResourceHolder
29 #include "ImageMgr.h"
30 #include "ImageWriter.h"
31 #include "Interface.h"
32 #include "SaveGameMgr.h"
33 #include "Video.h"
34 #include "GUI/GameControl.h"
36 #if defined(__HAIKU__)
37 #include <unistd.h>
38 #endif
40 #include <cassert>
41 #include <set>
42 #include <time.h>
44 const TypeID SaveGame::ID = { "SaveGame" };
46 /** Extract date from save game ds into Date. */
47 static void ParseGameDate(DataStream *ds, char *Date)
49 Date[0] = '\0';
51 char Signature[8];
52 ieDword GameTime;
53 ds->Read(Signature, 8);
54 ds->ReadDword(&GameTime);
55 delete ds;
56 if (memcmp(Signature,"GAME",4) ) {
57 strcpy(Date, "ERROR");
58 return;
61 int hours = ((int)GameTime)/300;
62 int days = hours/24;
63 hours -= days*24;
64 char *a=NULL,*b=NULL,*c=NULL;
66 core->GetTokenDictionary()->SetAtCopy("GAMEDAYS", days);
67 if (days) {
68 if (days==1) a=core->GetString(10698);
69 else a=core->GetString(10697);
71 core->GetTokenDictionary()->SetAtCopy("HOUR", hours);
72 if (hours || !a) {
73 if (a) b=core->GetString(10699);
74 if (hours==1) c=core->GetString(10701);
75 else c=core->GetString(10700);
77 if (b) {
78 strcat(Date, a);
79 strcat(Date, " ");
80 strcat(Date, b);
81 strcat(Date, " ");
82 if (c)
83 strcat(Date, c);
84 } else {
85 if (a)
86 strcat(Date, a);
87 if (c)
88 strcat(Date, c);
90 core->FreeString(a);
91 core->FreeString(b);
92 core->FreeString(c);
95 SaveGame::SaveGame(const char* path, const char* name, const char* prefix, const char* slotname, int pCount, int saveID)
97 strncpy( Prefix, prefix, sizeof( Prefix ) );
98 strncpy( Path, path, sizeof( Path ) );
99 strncpy( Name, name, sizeof( Name ) );
100 strncpy( SlotName, slotname, sizeof( SlotName ) );
101 PortraitCount = pCount;
102 SaveID = saveID;
103 char nPath[_MAX_PATH];
104 struct stat my_stat;
105 PathJoinExt(nPath, Path, Prefix, "bmp");
106 memset(&my_stat,0,sizeof(my_stat));
107 stat( nPath, &my_stat );
108 strftime( Date, _MAX_PATH, "%c", localtime( &my_stat.st_mtime ) );
109 manager.AddSource(Path, Name, PLUGIN_RESOURCE_DIRECTORY);
110 GameDate[0] = '\0';
113 SaveGame::~SaveGame()
117 Sprite2D* SaveGame::GetPortrait(int index) const
119 if (index > PortraitCount) {
120 return NULL;
122 char nPath[_MAX_PATH];
123 sprintf( nPath, "PORTRT%d", index );
124 ResourceHolder<ImageMgr> im(nPath, manager, true);
125 if (!im)
126 return NULL;
127 return im->GetSprite2D();
130 Sprite2D* SaveGame::GetPreview() const
132 ResourceHolder<ImageMgr> im(Prefix, manager, true);
133 if (!im)
134 return NULL;
135 return im->GetSprite2D();
138 DataStream* SaveGame::GetGame() const
140 return manager.GetResource(Prefix, IE_GAM_CLASS_ID, true);
143 DataStream* SaveGame::GetWmap(int idx) const
145 return manager.GetResource(core->WorldMapName[idx], IE_WMP_CLASS_ID, true);
148 DataStream* SaveGame::GetSave() const
150 return manager.GetResource(Prefix, IE_SAV_CLASS_ID, true);
153 const char* SaveGame::GetGameDate() const
155 if (GameDate[0] == '\0')
156 ParseGameDate(GetGame(), GameDate);
157 return GameDate;
160 SaveGameIterator::SaveGameIterator(void)
164 SaveGameIterator::~SaveGameIterator(void)
168 /* mission pack save */
169 static const char* SaveDir()
171 ieDword playmode = 0;
172 core->GetDictionary()->Lookup( "SaveDir", playmode );
173 if (playmode == 1) {
174 return "mpsave";
176 return "save";
179 #define FormatQuickSavePath(destination, i) \
180 snprintf(destination,sizeof(destination),"%s%s%s%09d-%s", \
181 core->SavePath,SaveDir(), SPathDelimiter,i,folder);
184 * Returns the first 0 bit position of an integer
186 static int GetHole(int n)
188 int mask = 1;
189 int value = 0;
190 while(n&mask) {
191 mask<<=1;
192 value++;
194 return value;
198 * Returns the age of a quickslot entry. Returns 0 if it isn't a quickslot
200 static int IsQuickSaveSlot(const char* match, const char* slotname)
202 char savegameName[_MAX_PATH];
203 int savegameNumber = 0;
204 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
205 if (cnt != 2) {
206 return 0;
208 if (stricmp(savegameName, match) )
210 return 0;
212 return savegameNumber;
215 * Return true if directory Path/slotname is a potential save game
216 * slot, otherwise return false.
218 static bool IsSaveGameSlot(const char* Path, const char* slotname)
220 char savegameName[_MAX_PATH];
221 int savegameNumber = 0;
223 if (slotname[0] == '.')
224 return false;
226 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
227 if (cnt != 2) {
228 //The matcher didn't match: either this is not a valid dir
229 //or the SAVEGAME_DIRECTORY_MATCHER needs updating.
230 printMessage( "SaveGameIterator", " ", LIGHT_RED );
231 printf( "Invalid savegame directory '%s' in %s.\n", slotname, Path );
232 return false;
235 //The matcher got matched correctly.
236 char dtmp[_MAX_PATH];
237 PathJoin(dtmp, Path, slotname, NULL);
239 char ftmp[_MAX_PATH];
240 PathJoinExt(ftmp, dtmp, core->GameNameResRef, "bmp");
242 if (access( ftmp, R_OK )) {
243 printMessage("SaveGameIterator"," ",YELLOW);
244 printf("Ignoring slot %s because of no appropriate preview!\n", dtmp);
245 return false;
248 PathJoinExt(ftmp, dtmp, core->WorldMapName[0], "wmp");
249 if (access( ftmp, R_OK )) {
250 printMessage("SaveGameIterator"," ",YELLOW);
251 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp);
252 return false;
255 /* we might need something here as well
256 PathJoinExt(ftmp, dtmp, core->WorldMapName[1], "wmp");
257 if (access( ftmp, R_OK )) {
258 printMessage("SaveGameIterator"," ",YELLOW);
259 printf("Ignoring slot %s because of no appropriate worldmap!\n", dtmp);
260 return false;
264 return true;
267 bool SaveGameIterator::RescanSaveGames()
269 // delete old entries
270 save_slots.clear();
272 char Path[_MAX_PATH];
273 PathJoin(Path, core->SavePath, SaveDir(), NULL);
275 DirectoryIterator dir(Path);
276 // create the save game directory at first access
277 if (!dir) {
278 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
279 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
280 dir.Rewind();
282 if (!dir) { //If we cannot open the Directory
283 return false;
286 std::set<char*,iless> slots;
287 do {
288 const char *name = dir.GetName();
289 if (dir.IsDirectory() && IsSaveGameSlot( Path, name )) {
290 slots.insert(strdup(name));
292 } while (++dir);
294 for (std::set<char*,iless>::iterator i = slots.begin(); i != slots.end(); i++) {
295 save_slots.push_back(GetSaveGame(*i));
296 free(*i);
299 return true;
302 const std::vector<Holder<SaveGame> >& SaveGameIterator::GetSaveGames()
304 RescanSaveGames();
306 return save_slots;
309 Holder<SaveGame> SaveGameIterator::GetSaveGame(const char *slotname)
311 if (!slotname) {
312 return NULL;
315 int prtrt = 0;
316 char Path[_MAX_PATH];
317 //lets leave space for the filenames
318 PathJoin(Path, core->SavePath, SaveDir(), slotname, NULL);
320 char savegameName[_MAX_PATH]={0};
321 int savegameNumber = 0;
323 int cnt = sscanf( slotname, SAVEGAME_DIRECTORY_MATCHER, &savegameNumber, savegameName );
324 //maximum pathlength == 240, without 8+3 filenames
325 if ( (cnt != 2) || (strlen(Path)>240) ) {
326 printf( "Invalid savegame directory '%s' in %s.\n", slotname, Path );
327 return NULL;
330 DirectoryIterator dir(Path);
331 if (!dir) {
332 return NULL;
334 do {
335 if (strnicmp( dir.GetName(), "PORTRT", 6 ) == 0)
336 prtrt++;
337 } while (++dir);
339 SaveGame* sg = new SaveGame( Path, savegameName, core->GameNameResRef, slotname, prtrt, savegameNumber );
340 return sg;
343 void SaveGameIterator::PruneQuickSave(const char *folder)
345 char from[_MAX_PATH];
346 char to[_MAX_PATH];
348 //storing the quicksave ages in an array
349 std::vector<int> myslots;
350 for (charlist::iterator m = save_slots.begin();m!=save_slots.end();m++) {
351 int tmp = IsQuickSaveSlot(folder, (*m)->GetSlotName() );
352 if (tmp) {
353 size_t pos = myslots.size();
354 while(pos-- && myslots[pos]>tmp) ;
355 myslots.insert(myslots.begin()+pos+1,tmp);
358 //now we got an integer array in myslots
359 size_t size = myslots.size();
361 if (!size) {
362 return;
365 int n=myslots[size-1];
366 size_t hole = GetHole(n);
367 size_t i;
368 if (hole<size) {
369 //prune second path
370 FormatQuickSavePath(from, myslots[hole]);
371 myslots.erase(myslots.begin()+hole);
372 core->DelTree(from, false);
373 rmdir(from);
375 //shift paths, always do this, because they are aging
376 size = myslots.size();
377 for(i=size;i--;) {
378 FormatQuickSavePath(from, myslots[i]);
379 FormatQuickSavePath(to, myslots[i]+1);
380 rename(from,to);
384 /** Save game to given directory */
385 static bool DoSaveGame(const char *Path)
387 Game *game = core->GetGame();
388 //saving areas to cache currently in memory
389 unsigned int mc = (unsigned int) game->GetLoadedMapCount();
390 while (mc--) {
391 Map *map = game->GetMap(mc);
392 if (core->SwapoutArea(map)) {
393 return false;
397 //compress files in cache named: .STO and .ARE
398 //no .CRE would be saved in cache
399 if (core->CompressSave(Path)) {
400 return false;
403 //Create .gam file from Game() object
404 if (core->WriteGame(Path)) {
405 return false;
408 //Create .wmp file from WorldMap() object
409 if (core->WriteWorldMap(Path)) {
410 return false;
413 PluginHolder<ImageWriter> im(PLUGIN_IMAGE_WRITER_BMP);
414 if (!im) {
415 printMessage( "SaveGameIterator", "Couldn't create the BMPWriter!\n", LIGHT_RED );
416 return false;
419 //Create portraits
420 for (int i = 0; i < game->GetPartySize( false ); i++) {
421 Sprite2D* portrait = core->GetGameControl()->GetPortraitPreview( i );
422 if (portrait) {
423 char FName[_MAX_PATH];
424 snprintf( FName, sizeof(FName), "PORTRT%d", i );
425 FileStream outfile;
426 outfile.Create( Path, FName, IE_BMP_CLASS_ID );
427 im->PutImage( &outfile, portrait );
431 // Create area preview
432 Sprite2D* preview = core->GetGameControl()->GetPreview();
433 FileStream outfile;
434 outfile.Create( Path, core->GameNameResRef, IE_BMP_CLASS_ID );
435 im->PutImage( &outfile, preview );
437 return true;
440 int CanSave()
442 //some of these restrictions might not be needed
443 Store * store = core->GetCurrentStore();
444 if (store) {
445 return 1; //can't save while store is open
447 GameControl *gc = core->GetGameControl();
448 if (!gc) {
449 return -1; //no gamecontrol!!!
451 if (gc->GetDialogueFlags()&DF_IN_DIALOG) {
452 return 2; //can't save while in dialog?
455 //TODO: can't save while in combat
456 Game *game = core->GetGame();
457 if (!game) {
458 return -1;
460 if (game->CombatCounter) {
461 return 3;
464 Map *map = game->GetCurrentArea();
465 if (!map) {
466 return -1;
469 if (map->AreaFlags&AF_SAVE) {
470 //cannot save in area
471 return 4;
474 int i = game->GetPartySize(true);
475 while(i--) {
476 Actor *actor = game->GetPC(i, true);
477 //TODO: can't save while (party) actors are in helpless states
478 if (actor->GetStat(IE_STATE_ID) & STATE_NOSAVE) {
479 //some actor is in nosave state
480 return 5;
482 if (actor->GetCurrentArea()!=map) {
483 //scattered
484 return 6;
488 //TODO: can't save while AOE spells are in effect
489 //TODO: can't save while IF_NOINT is set on any actor
491 return 0;
494 static void CreateSavePath(char *Path, int index, const char *slotname)
496 PathJoin( Path, core->SavePath, SaveDir(), NULL );
498 //if the path exists in different case, don't make it again
499 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
500 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
501 //keep the first part we already determined existing
503 char dir[_MAX_PATH];
504 snprintf( dir, _MAX_PATH, "%09d-%s", index, slotname );
505 PathJoin(Path, Path, dir, NULL);
506 //this is required in case the old slot wasn't recognised but still there
507 core->DelTree(Path, false);
508 mkdir(Path,S_IWRITE|S_IREAD|S_IEXEC);
509 chmod(Path,S_IWRITE|S_IREAD|S_IEXEC);
512 int SaveGameIterator::CreateSaveGame(int index, bool mqs)
514 AutoTable tab("savegame");
515 const char *slotname = NULL;
516 int qsave = 0;
518 if (tab) {
519 slotname = tab->QueryField(index);
520 qsave = atoi(tab->QueryField(index, 1));
523 if (mqs) {
524 assert(qsave);
525 PruneQuickSave(slotname);
528 if (int cansave = CanSave())
529 return cansave;
531 //if index is not an existing savegame, we create a unique slotname
532 for (size_t i = 0; i < save_slots.size(); ++i) {
533 Holder<SaveGame> save = save_slots[i];
534 if (save->GetSaveID() == index) {
535 DeleteSaveGame(save);
536 break;
539 char Path[_MAX_PATH];
540 CreateSavePath(Path, index, slotname);
541 GameControl *gc = core->GetGameControl();
543 if (!DoSaveGame(Path)) {
544 displaymsg->DisplayConstantString(STR_CANTSAVE, 0xbcefbc);
545 if (gc) {
546 gc->SetDisplayText(STR_CANTSAVE, 30);
548 return -1;
551 // Save succesful / Quick-save succesful
552 if (qsave) {
553 displaymsg->DisplayConstantString(STR_QSAVESUCCEED, 0xbcefbc);
554 if (gc) {
555 gc->SetDisplayText(STR_QSAVESUCCEED, 30);
557 } else {
558 displaymsg->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
559 if (gc) {
560 gc->SetDisplayText(STR_SAVESUCCEED, 30);
563 return 0;
566 int SaveGameIterator::CreateSaveGame(Holder<SaveGame> save, const char *slotname)
568 if (!slotname) {
569 return -1;
572 if (int cansave = CanSave())
573 return cansave;
575 GameControl *gc = core->GetGameControl();
576 int index;
578 if (save) {
579 index = save->GetSaveID();
581 DeleteSaveGame(save);
582 save.release();
583 } else {
584 //leave space for autosaves
585 //probably the hardcoded slot names should be read by this object
586 //in that case 7 == size of hardcoded slot names array (savegame.2da)
587 index = 7;
588 for (size_t i = 0; i < save_slots.size(); ++i) {
589 Holder<SaveGame> save = save_slots[i];
590 if (save->GetSaveID() >= index) {
591 index = save->GetSaveID() + 1;
596 char Path[_MAX_PATH];
597 CreateSavePath(Path, index, slotname);
599 if (!DoSaveGame(Path)) {
600 displaymsg->DisplayConstantString(STR_CANTSAVE, 0xbcefbc);
601 if (gc) {
602 gc->SetDisplayText(STR_CANTSAVE, 30);
604 return -1;
607 // Save succesful
608 displaymsg->DisplayConstantString(STR_SAVESUCCEED, 0xbcefbc);
609 if (gc) {
610 gc->SetDisplayText(STR_SAVESUCCEED, 30);
612 return 0;
615 void SaveGameIterator::DeleteSaveGame(Holder<SaveGame> game)
617 if (!game) {
618 return;
621 core->DelTree( game->GetPath(), false ); //remove all files from folder
622 rmdir( game->GetPath() );