Adonthell  0.4
gamedata.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2001/2002 by Kai Sterker <kai.sterker@gmail.com>
3  Part of the Adonthell Project <http://adonthell.nongnu.org>
4 
5  Adonthell is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9 
10  Adonthell is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with Adonthell. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 
20 /**
21  * @file gamedata.cc
22  * @author Kai Sterker <kai.sterker@gmail.com>
23  *
24  * @brief Defines the gamedata and data classes.
25  *
26  *
27  */
28 
29 
30 #include <iostream>
31 #include <cstdio>
32 #include <time.h>
33 #include <unistd.h>
34 #include <dirent.h>
35 #include <sys/stat.h>
36 
37 #include "achievements.h"
38 #include "audio.h"
39 #include "gamedata.h"
40 #include "python_class.h"
41 #include "event_handler.h"
42 
43 // File format versions of the various data files
44 // *** Increase when changing file format! ***
45 #define ENGINE_DAT_VER 5
46 #define AUDIO_DAT_VER 2
47 #define CHAR_DAT_VER 4
48 #define QUEST_DAT_VER 1
49 #define SAVE_DAT_VER 3
50 #define ACVMENT_DAT_VER 1
51 
52 vector<gamedata*> gamedata::saves; // The list of available savegames
53 string gamedata::user_data_dir_; // The user's private adonthell directory
54 string gamedata::game_data_dir_; // The adonthell data directory
55 string gamedata::game_name; // The adonthell data directory
56 u_int8 gamedata::quick_load; // Whether Quick-load is active or not
57 
58 using namespace std;
59 
60 
62 {
63 }
64 
65 gamedata::gamedata (string dir, string desc, string time)
66 {
67  Timestamp = 0;
68  Directory = dir;
69  Description = desc;
70  Gametime = time;
71 }
72 
74 {
75 }
76 
78 {
79  if (!fileops::get_version (file, SAVE_DAT_VER, SAVE_DAT_VER, "save.data"))
80  return false;
81 
82  Timestamp << file;
83  Description << file;
84  Location << file;
85  Gametime << file;
86 
87  return true;
88 }
89 
91 {
92  fileops::put_version (file, SAVE_DAT_VER);
93 
94  // get current time for Quick-Loading
95  Timestamp = time (NULL);
96 
97  Timestamp >> file;
98  Description >> file;
99  Location >> file;
100  Gametime >> file;
101 }
102 
103 void gamedata::set_description (string desc)
104 {
105  Description = desc;
106 }
107 
108 void gamedata::set_directory (string dir)
109 {
110  Directory = dir;
111 }
112 
113 void gamedata::set_gametime (string time)
114 {
115  Gametime = time;
116 }
117 
119 {
120  igzstream in;
121 
122  string filepath;
123  character *mynpc;
124 
125  // try to open character.data
126  filepath = saves[pos]->directory ();
127  filepath += "/character.data";
128  in.open (filepath);
129 
130  if (!in.is_open ())
131  {
132  cerr << "Couldn't open \"" << filepath << "\" - stopping\n" << endl;
133  return false;
134  }
135 
136  if (!fileops::get_version (in, CHAR_DAT_VER, CHAR_DAT_VER, filepath))
137  {
138  in.close ();
139  return false;
140  }
141 
142  // load characters
143  char ctemp;
144 
145  // first, the player
146  data::the_player = new character ();
147  data::the_player->character_base::get_state (in);
148  data::characters[data::the_player->get_id ().c_str ()] = data::the_player;
149 
150  // then all the others
151  while (ctemp << in)
152  {
153  mynpc = new character;
154  mynpc->character_base::get_state (in);
155 
156  // Make this character available to the engine
157  data::characters[mynpc->get_id ().c_str ()] = mynpc;
158  }
159 
160  in.close ();
161  return true;
162 }
163 
165 {
166  igzstream in;
167 
168  string filepath;
169  quest *myquest;
170 
171  // try to open quest.data
172  filepath = saves[pos]->directory ();
173  filepath += "/quest.data";
174  in.open (filepath);
175 
176  if (!in.is_open ())
177  {
178  cerr << "Couldn't open \"" << filepath << " - stopping\n" << endl;
179  return false;
180  }
181 
182  if (!fileops::get_version (in, QUEST_DAT_VER, QUEST_DAT_VER, filepath))
183  {
184  in.close ();
185  return false;
186  }
187 
188  // load quests
189  char ctemp;
190  while (ctemp << in)
191  {
192  myquest = new quest;
193  myquest->load (in);
194 
195  // Make this quest available to the engine
196  data::quests[myquest->name.c_str ()] = myquest;
197  }
198 
199  in.close ();
200  return true;
201 }
202 
204 {
205  igzstream in;
206 
207  string filepath;
208 
209  // Load mapengine state
210  filepath = saves[pos]->directory();
211  filepath += "/mapengine.data";
212  in.open (filepath);
213 
214  if (!in.is_open ())
215  {
216  cerr << "Couldn't open \"" << filepath << " - stopping\n" << endl;
217  return false;
218  }
219 
220  if (!fileops::get_version (in, ENGINE_DAT_VER, ENGINE_DAT_VER, filepath))
221  {
222  in.close ();
223  return false;
224  }
225 
226  if (!data::engine->get_state(in))
227  {
228  cerr << "Couldn't load \"" << filepath << " - stopping\n" << endl;
229  in.close ();
230  return false;
231  }
232 
233  in.close ();
234  return true;
235 }
236 
238 {
239  igzstream in;
240  string filepath;
241 
242  // Load mapengine state
243  filepath = saves[pos]->directory();
244  filepath += "/audio.data";
245  in.open (filepath);
246 
247  if (!in.is_open ())
248  {
249  cerr << "Couldn't open \"" << filepath << " - stopping\n" << endl;
250  return false;
251  }
252 
253  if (!fileops::get_version (in, AUDIO_DAT_VER, AUDIO_DAT_VER, filepath))
254  {
255  in.close ();
256  return false;
257  }
258 
259  if (!audio::get_state (in))
260  {
261  cerr << "Couldn't load \"" << filepath << " - stopping\n" << endl;
262  in.close ();
263  return false;
264  }
265 
266  in.close ();
267  return true;
268 }
269 
270 bool gamedata::load_achievements (u_int32 pos)
271 {
272  igzstream in;
273  string filepath;
274 
275  // Load mapengine state
276  filepath = saves[pos]->directory();
277  filepath += "/achievements.data";
278  in.open (filepath);
279 
280  if (!in.is_open ())
281  {
282  cerr << "Couldn't open \"" << filepath << " - stopping\n" << endl;
283  return false;
284  }
285 
286  if (!fileops::get_version (in, ACVMENT_DAT_VER, ACVMENT_DAT_VER, filepath))
287  {
288  in.close ();
289  return false;
290  }
291 
292  if (!achievements::get_state (in))
293  {
294  cerr << "Couldn't load \"" << filepath << " - stopping\n" << endl;
295  in.close ();
296  return false;
297  }
298 
299  in.close ();
300  return true;
301 }
302 
304 {
305  // First, unload the current game
306  unload ();
307 
308  if (!load_characters (pos)) return false;
309  if (!load_quests (pos)) return false;
310  if (!load_mapengine (pos)) return false;
311  if (!load_audio (pos)) return false;
312  if (!load_achievements(pos)) return false;
313 
314  return true;
315 }
316 
318 {
319  // Quick-load off / no save game available
320  if (!quick_load || saves.size () <= 1) return false;
321 
322  u_int32 timestamp = 0;
323  u_int32 index = 0;
324  u_int32 newest;
325 
326  for (vector<gamedata*>::iterator i = saves.begin (); i != saves.end (); i++)
327  {
328  if ((*i)->timestamp () > timestamp)
329  {
330  timestamp = (*i)->timestamp ();
331  newest = index;
332  }
333 
334  index++;
335  }
336 
337  return load (newest);
338 }
339 
340 bool gamedata::save (u_int32 pos, string desc, string time)
341 {
342  gamedata *gdata;
343  string filepath;
344  char t[10];
345  ogzstream file;
346  char vnbr;
347 
348  // make sure we don't overwrite the default game
349  if (pos == 0) return false;
350 
351  // see whether we're going to save to a new slot
352  if (pos >= saves.size ())
353  {
354  int success = 1;
355 
356  // make sure we save to an unused directory
357  while (success)
358  {
359  // that's the directory we're going to save to
360  sprintf(t, "%03i", pos++);
361  filepath = user_data_dir ();
362  filepath += "/" + game_name + "-save-";
363  filepath += t;
364 
365 #ifdef WIN32
366  success = mkdir (filepath.c_str());
367 #else
368  success = mkdir (filepath.c_str(), 0700);
369 #endif
370 
371  // prevent infinite loop if we can't write to the directory
372  if (pos >= 1000)
373  {
374  cerr << "Save failed - seems like you have no write permission in\n"
375  << user_data_dir () << endl;
376  return false;
377  }
378  }
379 
380  // we'll need a new gamedata record
381  gdata = new gamedata (filepath, desc, time);
382  }
383  else
384  {
385  gdata = saves[pos];
386  gdata->set_description (desc);
387  gdata->set_gametime (time);
388  }
389 
390  // save characters
391  filepath = gdata->directory ();
392  filepath += "/character.data";
393  file.open (filepath);
394 
395  if (!file.is_open ())
396  {
397  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
398  return false;
399  }
400 
401  fileops::put_version (file, CHAR_DAT_VER);
402 
403  // save the player first
404  data::the_player->character_base::put_state (file);
405 
406  // now save all the other characters
408  for (itc = data::characters.begin (); itc != data::characters.end (); itc++)
409  {
410  // don't save the player
411  if (itc->second == (character*) data::the_player) continue;
412 
413  // tell the character.data loader that another entry follows
414  vnbr = 1;
415  vnbr >> file;
416 
417  // append the character data
418  itc->second->character_base::put_state (file);
419  }
420 
421  // write EOF
422  vnbr = 0;
423  vnbr >> file;
424  file.close ();
425 
426  // save quests
427  filepath = gdata->directory ();
428  filepath += "/quest.data";
429  file.open (filepath);
430 
431  if (!file.is_open ())
432  {
433  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
434  return false;
435  }
436 
437  fileops::put_version (file, QUEST_DAT_VER);
438 
440  for (itq = data::quests.begin (); itq != data::quests.end (); itq++)
441  {
442  // tell the quest.data loader that another entry follows
443  vnbr = 1;
444  vnbr >> file;
445 
446  // append the character data
447  itq->second->save (file);
448  }
449 
450  // write EOF
451  vnbr = 0;
452  vnbr >> file;
453  file.close ();
454 
455  // Save mapengine state
456  filepath = gdata->directory();
457  filepath += "/mapengine.data";
458  file.open (filepath);
459 
460  if (!file.is_open ())
461  {
462  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
463  return false;
464  }
465 
466  fileops::put_version (file, ENGINE_DAT_VER);
467  data::engine->put_state(file);
468  file.close ();
469 
470  // save music
471  filepath = gdata->directory ();
472  filepath += "/audio.data";
473  file.open (filepath);
474 
475  if (!file.is_open ())
476  {
477  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
478  return false;
479  }
480 
481  fileops::put_version (file, AUDIO_DAT_VER);
482  audio::put_state (file);
483  file.close ();
484 
485  // save achievements
486  filepath = gdata->directory ();
487  filepath += "/achievements.data";
488  file.open (filepath);
489 
490  if (!file.is_open ())
491  {
492  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
493  return false;
494  }
495 
496  fileops::put_version (file, ACVMENT_DAT_VER);
498  file.close ();
499 
500  // save gamedata
501  filepath = gdata->directory ();
502  filepath += "/save.data";
503 
504  file.open (filepath);
505  if (!file.is_open ())
506  {
507  cerr << "Couldn't create \"" << filepath << "\" - save failed\n";
508  return false;
509  }
510 
511  gdata->put (file);
512  file.close ();
513 
514 
515  // only now it is safe to add the new record to the array
516  if (pos >= saves.size ()) saves.push_back (gdata);
517 
518  return true;
519 }
520 
522 {
523  static vector<gamedata*>::iterator i = saves.begin ();
524  static u_int32 size = saves.size ();
525 
526  // check whether a new save has been added
527  if (size != saves.size ())
528  {
529  size = saves.size ();
530  i = saves.begin ();
531  }
532 
533  // check whether we reached the end of the list
534  if (++i == saves.end ())
535  {
536  i = saves.begin ();
537  return NULL;
538  }
539 
540  return *i;
541 }
542 
543 
544 bool gamedata::init (string udir, string gdir, string gname, u_int8 qload)
545 {
546  DIR *dir;
547  igzstream in;
548  struct dirent *dirent;
549  struct stat statbuf;
550  gamedata *gdata;
551 
552  user_data_dir_ = udir;
553  game_data_dir_ = gdir;
554  game_name = gname;
555  quick_load = qload;
556 
557  // try to change into data directory
558  if (chdir (game_data_dir ().c_str ()))
559  {
560  fprintf (stderr, "Seems like %s is no valid data directory.\n", game_data_dir ().c_str ());
561  fprintf (stderr, "Please make sure that your Adonthell installation is correct.\n");
562  return false;
563  }
564 
565  // Add the default savegame used to start a new game to the list of saves
566  gdata = new gamedata (gdir, "Start New Game", "Day 0 - 00:00");
567  saves.push_back (gdata);
568 
569  // Read the user's saved games (if any) - they'll be located in
570  // $HOME/.adonthell/ and called <game_name>-save-<xxx>
571  if ((dir = opendir (user_data_dir ().c_str ())) != NULL)
572  {
573  while ((dirent = readdir (dir)) != NULL)
574  {
575  string filepath = user_data_dir () + "/";
576  filepath += dirent->d_name;
577 
578  string name_save = game_name + "-save-";
579 
580  if (stat (filepath.c_str (), &statbuf) != -1 && S_ISDIR (statbuf.st_mode) &&
581  strncmp (name_save.c_str (), dirent->d_name, name_save.length ()) == 0)
582  {
583  // found a (possibly) valid saved game directory
584  filepath += "/save.data";
585  // Now try to read the saved game's data record
586  in.open (filepath);
587 
588  if (in.is_open ())
589  {
590  // restore the pathname
591  filepath = user_data_dir ();
592  filepath += "/";
593  filepath += dirent->d_name;
594 
595  gdata = new gamedata;
596  if (gdata->get (in))
597  {
598  gdata->set_directory (filepath);
599  saves.push_back (gdata);
600  }
601  else delete gdata;
602 
603  in.close ();
604  }
605  }
606  }
607  closedir (dir);
608  }
609  return true;
610 }
611 
613 {
614  for (vector<gamedata*>::iterator i = saves.begin (); i != saves.end (); i++)
615  delete *i;
616  saves.clear ();
617  unload ();
618 }
619 
621 {
622  // stop the music
623  audio::fade_out_background (500);
624 
625  // delete all characters
627  for (itc = data::characters.begin (); itc != data::characters.end (); itc++)
628  {
629  itc->second->remove_from_map ();
630  delete itc->second;
631  }
632  data::characters.clear ();
633 
634  data::the_player = NULL;
635 
636  // delete all quests
638  for (itq = data::quests.begin (); itq != data::quests.end (); itq++)
639  delete itq->second;
640  data::quests.clear ();
641 }
static bool save(u_int32 pos, string desc, string time)
Save a game.
Definition: gamedata.cc:340
Class to write data from a Gzip compressed file.
Definition: fileops.h:227
void close()
Close the file that was opened.
Definition: fileops.cc:63
void set_description(string)
Sets the description for this game.
Definition: gamedata.cc:103
Declares the event_handler class.
static void cleanup()
Cleanup the saved game array.
Definition: gamedata.cc:612
Class to read data from a Gzip compressed file.
Definition: fileops.h:135
static bool load(u_int32 pos)
Loads a previously saved game.
Definition: gamedata.cc:303
const char * directory()
A bunch of methods to access the private attributes.
Definition: gamedata.h:107
static void put_state(ogzstream &file)
Save achievement data to stream.
Class holding game characters.
Definition: character.h:39
static bool load_characters(u_int32 pos)
Load the characters state from a saved game.
Definition: gamedata.cc:118
Definition: quest.h:27
static bool get_state(igzstream &file)
Load achievement data from stream.
gamedata()
Default constructor.
Definition: gamedata.cc:61
static bool init(string udir, string gdir, string gname, u_int8 qload)
Initialise the saved games array.
Definition: gamedata.cc:544
Definition: str_hash.h:71
string get_id()
Returns an unique identifier of the character.
#define u_int32
32 bits long unsigned integer
Definition: types.h:41
#define u_int8
8 bits long unsigned integer
Definition: types.h:35
Stores objects of any kind.
Definition: storage.h:231
static bool load_audio(u_int32 pos)
Load the audio system state from a saved game.
Definition: gamedata.cc:237
Manages in-game achievements.
static gamedata * next_save()
Returns a pointer to the next saved game.
Definition: gamedata.cc:521
bool open(const string &fname)
Opens a file for write access.
Definition: fileops.cc:266
bool open(const string &fname)
Opens a file for read access.
Definition: fileops.cc:81
static bool get_version(igzstream &file, u_int16 min, u_int16 max, string name)
Definition: fileops.cc:369
Declares the gamedata and data classes.
bool is_open()
Returns whether the file is opened or not.
Definition: fileops.h:103
static bool load_newest()
Loads the most recent saved game.
Definition: gamedata.cc:317
Defines the python class. This file is named this way so it doesn&#39;t conflicts with Python...
~gamedata()
Destructor.
Definition: gamedata.cc:73
bool get(igzstream &)
Load a record from an opened file.
Definition: gamedata.cc:77
static bool load_quests(u_int32 pos)
Load the quests state from a saved game.
Definition: gamedata.cc:164
static void put_version(ogzstream &file, u_int16 version)
Sets the version number of a file.
Definition: fileops.cc:361
s_int8 put_state(ogzstream &file)
Save the engine&#39;s state.
Definition: adonthell.cc:189
void set_gametime(string)
Set the in-game time of the saved game.
Definition: gamedata.cc:113
static void unload()
Unloads the current game, resetting the engine to it&#39;s initial state.
Definition: gamedata.cc:620
void put(ogzstream &)
Save a record to an opened file.
Definition: gamedata.cc:90
static bool load_mapengine(u_int32 pos)
Load the mapengine state from a saved game.
Definition: gamedata.cc:203
void set_directory(string)
Sets the directory for this game.
Definition: gamedata.cc:108
Contains all the attributes related to a saved game and the high level methods for loading/saving the...
Definition: gamedata.h:52