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.
19 #include <glib/gi18n.h>
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
[] = {
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")},
64 /* some bdcff defaults */
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 {
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
);
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. */
95 cave_highscore_to_keyfile_func(GKeyFile
*keyfile
, const char *name
, int index
, HighScoreTable
const &scores
) {
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 */
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
);
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
;
124 guint32 cs
=checksum();
128 canon
= g_strdup("highscore-");
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
);
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
);
157 char *data
= g_key_file_to_data(keyfile
, NULL
, &error
);
158 /* don't know what might happen... report to the user and forget. */
160 gd_message(error
->message
);
165 gd_message("g_key_file_to_data returned NULL");
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
);
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. */
182 cave_highscores_load_from_keyfile(GKeyFile
*keyfile
, int i
, HighScoreTable
&scores
) {
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! */
191 /* first clear highscores for the cave */
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
++) {
200 str
=g_key_file_get_string(keyfile
, cavstr
, keys
[j
], NULL
);
201 if (!str
) /* ?! not really possible but who knows */
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 */
208 gd_message(CPrintf("Invalid line in highscore file: %s") % str
);
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();
225 bool success
=g_key_file_load_from_file(keyfile
, filename
, G_KEY_FILE_NONE
, &error
);
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
)
232 gd_warning(error
->message
);
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
);
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
)
262 gd_warning("no selectable cave in caveset!");
263 /* and return the first one. */
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 */
274 while ((c
=strchr(name_str
, '_'))!=NULL
)
276 if ((c
=strrchr(name_str
, '.'))!=NULL
)
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
);
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
;
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() {
306 for (unsigned int i
=0; i
<caves
.size(); ++i
) {
307 if (!caves
.at(i
)->replays
.empty())
311 /* no replays at all */
316 /// Return the index of a cave.
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
)
325 return -1; /* if not found */