20130313
[gdash.git] / src / cave / caveset.cpp
blob43b64f3b6ad6144f9306424599718f7712b95970
1 /*
2 * Copyright (c) 2007-2013, Czirkos Zoltan http://code.google.com/p/gdash/
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include "config.h"
19 #include <glib/gi18n.h>
20 #include <fstream>
21 #include <stdexcept>
22 #include <cstdio>
23 #include "cave/cavestored.hpp"
24 #include "misc/printf.hpp"
25 #include "misc/logger.hpp"
26 #include "fileops/bdcffload.hpp"
27 #include "fileops/bdcffsave.hpp"
28 #include "cave/caveset.hpp"
31 /* list of possible extensions which can be opened */
32 const char *gd_caveset_extensions[]={"*.gds", "*.bd", "*.bdr", "*.brc", "*.vsf", "*.mem", NULL};
35 PropertyDescription const CaveSet::descriptor[] = {
36 /* default data */
37 {"", GD_TAB, 0, N_("Caveset data")},
38 {"Name", GD_TYPE_STRING, 0, N_("Name"), GetterBase::create_new(&CaveSet::name), N_("Name of the game")},
39 {"Description", GD_TYPE_STRING, 0, N_("Description"), GetterBase::create_new(&CaveSet::description), N_("Some words about the game")},
40 {"Author", GD_TYPE_STRING, 0, N_("Author"), GetterBase::create_new(&CaveSet::author), N_("Name of author")},
41 {"Date", GD_TYPE_STRING, 0, N_("Date"), GetterBase::create_new(&CaveSet::date), N_("Date of creation")},
42 {"WWW", GD_TYPE_STRING, 0, N_("WWW"), GetterBase::create_new(&CaveSet::www), N_("Web page or e-mail address")},
43 {"Difficulty", GD_TYPE_STRING, 0, N_("Difficulty"), GetterBase::create_new(&CaveSet::difficulty), N_("Difficulty (informative)")},
45 {"Charset", GD_TYPE_STRING, 0, N_("Character set"), GetterBase::create_new(&CaveSet::charset), N_("Theme used for displaying the game.")},
46 {"Fontset", GD_TYPE_STRING, 0, N_("Font set"), GetterBase::create_new(&CaveSet::fontset), N_("Font used during the game.")},
48 {"Lives", GD_TYPE_INT, 0, N_("Initial lives"), GetterBase::create_new(&CaveSet::initial_lives), N_("Number of lives you get at game start."), 3, 99},
49 {"Lives", GD_TYPE_INT, 0, N_("Maximum lives"), GetterBase::create_new(&CaveSet::maximum_lives), N_("Maximum number of lives you can have by collecting bonus points."), 3, 99},
50 {"BonusLife", GD_TYPE_INT, 0, N_("Bonus life score"), GetterBase::create_new(&CaveSet::bonus_life_score), N_("Number of points to collect for a bonus life."), 100, 5000},
52 {"", GD_TAB, 0, N_("Story")},
53 {"Story", GD_TYPE_LONGSTRING, 0, NULL, GetterBase::create_new(&CaveSet::story), N_("Long description of the game.")},
54 {"", GD_TAB, 0, N_("Remark")},
55 {"Remark", GD_TYPE_LONGSTRING, 0, NULL, GetterBase::create_new(&CaveSet::remark), N_("Remark (informative).")},
57 {"TitleScreen", GD_TYPE_LONGSTRING, GD_DONT_SHOW_IN_EDITOR, N_("Title screen"), GetterBase::create_new(&CaveSet::title_screen), N_("Title screen image")},
58 {"TitleScreenScroll", GD_TYPE_LONGSTRING, GD_DONT_SHOW_IN_EDITOR, N_("Title screen, scrolling"), GetterBase::create_new(&CaveSet::title_screen_scroll), N_("Scrolling background for title screen image")},
60 {NULL},
63 CaveSet::CaveSet() {
64 /* some bdcff defaults */
65 initial_lives=3;
66 maximum_lives=9;
67 bonus_life_score=500;
71 /********************************************************************************
73 * highscores saving in config dir
77 /* calculates an adler checksum, for which it uses all
78 elements of all cave-rendereds. */
79 unsigned CaveSet::checksum() const {
80 unsigned a=1, b=0;
81 for (unsigned int i=0; i<caves.size(); ++i) {
82 CaveRendered rendered(*caves.at(i), 0, 0); /* level=1, seed=0 */
83 gd_cave_adler_checksum_more(rendered, a, b);
85 return (b<<16)+a;
88 /* adds highscores of one cave to a keyfile given in userdat. this is
89 a g_list_foreach function.
90 it guesses the index, which is written to the file; by checking
91 the caveset (checking the index of cav in gd_caveset).
92 groups in the keyfile cannot be cave names, as more caves in the
93 caveset may have the same name. */
94 static void
95 cave_highscore_to_keyfile_func(GKeyFile *keyfile, const char *name, int index, HighScoreTable const &scores) {
96 char cavstr[10];
98 /* name of key group is the index */
99 g_snprintf(cavstr, sizeof(cavstr), "%d", index);
101 /* save highscores */
102 for (unsigned i=0; i<scores.size(); i++)
103 if (scores[i].score>0) { /* only save, if score is not zero */
104 char rankstr[10];
105 char *str;
107 /* key: rank */
108 g_snprintf(rankstr, sizeof(rankstr), "%d", i+1);
109 /* value: the score. for example: 510 Rob Hubbard */
110 str=g_strdup_printf("%d %s", scores[i].score, scores[i].name.c_str());
111 g_key_file_set_string(keyfile, cavstr, rankstr, str);
112 g_free(str);
114 g_key_file_set_comment(keyfile, cavstr, NULL, name, NULL);
117 /* make up a filename for the current caveset, to save highscores in. */
118 /* returns the file name; owned by the function (no need to free()) */
119 const char *CaveSet::filename_for_cave_highscores(const char *directory) const {
120 static char *outfile=NULL;
122 g_free(outfile);
124 guint32 cs=checksum();
126 char *canon;
127 if (name=="")
128 canon = g_strdup("highscore-");
129 else
130 canon = g_strdup(name.c_str());
131 /* allowed chars in the highscore file name; others are replaced with _ */
132 g_strcanon(canon, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", '_');
133 char *fname=g_strdup_printf("%08x-%s.hsc", cs, canon);
134 outfile=g_build_path(G_DIR_SEPARATOR_S, directory, fname, NULL);
135 g_free(fname);
136 g_free(canon);
138 return outfile;
141 /* save highscores of the current cave to the configuration directory.
142 this one chooses a filename on its own.
143 it is used to save highscores for imported caves.
145 void CaveSet::save_highscore(const char *directory) const {
146 GKeyFile *keyfile=g_key_file_new();
148 /* put the caveset highscores in the keyfile */
149 cave_highscore_to_keyfile_func(keyfile, name.c_str(), 0, highscore);
150 /* and put the highscores of all caves in the keyfile */
151 for (unsigned int i=0; i<caves.size(); ++i) {
152 CaveStored *cave=caves.at(i);
153 cave_highscore_to_keyfile_func(keyfile, cave->name.c_str(), i+1, cave->highscore);
156 GError *error=NULL;
157 char *data = g_key_file_to_data(keyfile, NULL, &error);
158 /* don't know what might happen... report to the user and forget. */
159 if (error) {
160 gd_message(error->message);
161 g_error_free(error);
162 return;
164 if (!data) {
165 gd_message("g_key_file_to_data returned NULL");
166 return;
168 g_key_file_free(keyfile);
170 /* if data came out empty, we do nothing. */
171 if (strlen(data)>0) {
172 g_mkdir_with_parents(directory, 0700);
173 g_file_set_contents(filename_for_cave_highscores(directory), data, -1, &error);
175 g_free(data);
179 /* load cave highscores from a parsed GKeyFile. */
180 /* i is the keyfile group, a number which is 0 for the game, and 1+ for caves. */
181 static bool
182 cave_highscores_load_from_keyfile(GKeyFile *keyfile, int i, HighScoreTable &scores) {
183 char cavstr[10];
185 /* check if keyfile has the group in question */
186 g_snprintf(cavstr, sizeof(cavstr), "%d", i);
187 if (!g_key_file_has_group(keyfile, cavstr))
188 /* if the cave had no highscore, there is no group. this is normal! */
189 return false;
191 /* first clear highscores for the cave */
192 scores.clear();
194 /* for all keys... we ignore the keys itself, as rebuilding the sorted list is more simple */
195 char **keys=g_key_file_get_keys(keyfile, cavstr, NULL, NULL);
196 for (int j=0; keys[j]!=NULL; j++) {
197 int score;
198 char *str;
200 str=g_key_file_get_string(keyfile, cavstr, keys[j], NULL);
201 if (!str) /* ?! not really possible but who knows */
202 continue;
204 if (strchr(str, ' ')!=NULL && sscanf(str, "%d", &score)==1)
205 /* we skip the space by adding +1 */
206 scores.add(strchr(str, ' ')+1, score); /* add to the list, sorted. does nothing, if no more space for this score */
207 else
208 gd_message(CPrintf("Invalid line in highscore file: %s") % str);
209 g_free(str);
211 g_strfreev(keys);
213 return true;
217 /* load highscores from a file saved in the configuration directory.
218 the file name is guessed automatically.
219 if there is some highscore for a cave, then they are deleted.
221 bool CaveSet::load_highscore(const char *directory) {
222 const char *filename=filename_for_cave_highscores(directory);
223 GKeyFile *keyfile=g_key_file_new();
224 GError *error=NULL;
225 bool success=g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error);
226 if (!success) {
227 g_key_file_free(keyfile);
228 /* skip file not found errors; report everything else. it is considered a normal thing when there is no .hsc file yet */
229 if (error->domain==G_FILE_ERROR && error->code==G_FILE_ERROR_NOENT)
230 return true;
232 gd_warning(error->message);
233 return false;
236 /* try to load for game */
237 cave_highscores_load_from_keyfile(keyfile, 0, highscore);
239 /* try to load for all caves */
240 for (unsigned int i=0; i<caves.size(); ++i) {
241 cave_highscores_load_from_keyfile(keyfile, i+1, caves.at(i)->highscore);
244 g_key_file_free(keyfile);
245 return true;
249 /********************************************************************************
251 * Misc caveset functions
255 /* return index of first selectable cave */
256 int CaveSet::first_selectable_cave_index() const {
257 for (unsigned int i=0; i<caves.size(); ++i) {
258 if (caves.at(i)->selectable)
259 return i;
262 gd_warning("no selectable cave in caveset!");
263 /* and return the first one. */
264 return 0;
268 void CaveSet::set_name_from_filename(const char *filename) {
270 /* make up a caveset name from the filename. */
271 char *name_str=g_path_get_basename(filename);
272 /* convert underscores to spaces, remove extension */
273 char *c;
274 while ((c=strchr(name_str, '_'))!=NULL)
275 *c=' ';
276 if ((c=strrchr(name_str, '.'))!=NULL)
277 *c=0;
279 name=name_str;
280 g_free(name_str);
284 /// Save caveset in BDCFF to the file.
285 /// @param filename The name of the file to write to.
286 /// @return true, if successful; false, if error.
287 void CaveSet::save_to_file(const char *filename) throw (std::runtime_error) {
288 std::ofstream outfile;
289 outfile.open(filename);
290 if (!outfile)
291 throw std::runtime_error(_("Could not open file for writing."));
292 std::list<std::string> saved;
293 save_to_bdcff(*this, saved);
294 for (std::list<std::string>::const_iterator it=saved.begin(); it!=saved.end(); ++it)
295 outfile << *it << std::endl;
296 outfile.close();
297 if (!outfile)
298 throw std::runtime_error(_("Error writing to file."));
299 this->filename = filename;
302 /// Check if there are any replays in the caveset.
303 /// @return True, if at least one of the caves has a replay.
304 bool CaveSet::has_replays() {
305 /* for all caves */
306 for (unsigned int i=0; i<caves.size(); ++i) {
307 if (!caves.at(i)->replays.empty())
308 return true;
311 /* no replays at all */
312 return false;
316 /// Return the index of a cave.
317 /// 0 is the first.
318 /// @param cave The cave to look for
319 /// @return The index in the container, or -1 if not found
320 int CaveSet::cave_index(CaveStored const *cave) const {
321 for (unsigned int i=0; i<caves.size(); ++i)
322 if (caves.at(i)==cave)
323 return i;
325 return -1; /* if not found */