2009050
[gdash.git] / src / editorexport.c
blobd0f8b424b068530146ac921cd9f31915788225b2
1 /*
2 * Copyright (c) 2007, 2008, 2009, Czirkos Zoltan <cirix@fw.hu>
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 <glib.h>
18 #include <string.h>
19 #include <gtk/gtk.h>
20 #include <glib/gi18n.h>
21 #include "util.h"
22 #include "cave.h"
23 #include "cavedb.h"
24 #include "caveobject.h"
25 #include "caveset.h"
26 #include "c64import.h"
27 #include "gtkgfx.h"
29 static int crli_steel_code=0x38; /* magic value: the code of steel wall in crli */
32 static int element_to_crli(GdElement e, GHashTable *unknown)
34 int i;
35 int code=-1;
37 /* hack: there is no separate horizontal and vertical growing wall in crli. */
38 /* only the switch can determine the direction. */
39 if (e==O_V_EXPANDING_WALL)
40 e=O_H_EXPANDING_WALL;
42 /* 128 is the number of elements in gd_crazylight_import_table */
43 for (i=0; i<128; i++)
44 if (gd_crazylight_import_table[i]==e) {
45 code=i;
46 break; /* if found, exit loop */
48 if (code==-1 && (e&SCANNED)) {
49 e=e & ~SCANNED;
51 /* try once again, without the "scanned" flag, as the element we use may not have a delayed state originally */
52 for (i=0; i<128; i++)
53 if (gd_crazylight_import_table[i]==e) {
54 code=i;
55 break; /* if found, exit loop */
59 if (code==-1) {
60 g_assert(gd_crazylight_import_table[crli_steel_code]==O_STEEL);
62 if (g_hash_table_lookup(unknown, GINT_TO_POINTER(code))==NULL) {
63 g_warning("the element '%s' can not be saved in crli, saving steel wall instead", gd_elements[e].name);
64 g_hash_table_insert(unknown, GINT_TO_POINTER(e), GINT_TO_POINTER(e));
67 code=crli_steel_code;
70 return code;
73 static int
74 crli_export(GdCave *to_convert, const int level, guint8 *compressed)
76 guint8 output[0x3b0];
77 int x, y, i;
78 GdCave *cave;
79 /* for rle */
80 guint8 prev;
81 int count;
82 int out;
83 int prob;
84 GHashTable *unknown;
85 gboolean has_horizontal, has_vertical;
87 /* render cave with seed=0 */
88 cave=gd_cave_new_rendered(to_convert, level, 0);
89 gd_error_set_context(to_convert->name);
90 unknown=g_hash_table_new(g_direct_hash, g_direct_equal); /* hash table to remember unconvertable elements */
92 /* do some checks */
93 if (!cave->lineshift)
94 g_warning("crli only supports line shifting map wraparound");
95 if (!cave->pal_timing || cave->scheduling!=GD_SCHEDULING_PLCK)
96 g_warning("only applicable timing settings for crli are pal timing=true, scheduling=plck");
97 if (cave->amoeba_timer_started_immediately)
98 g_warning("crli amoeba timer is only started when the amoeba is let free!");
99 if (!cave->amoeba_timer_wait_for_hatching) {
100 cave->amoeba_time+=2;
101 g_message("crli amoeba timer waits for hatching; added 2 seconds for correction");
103 if (!cave->voodoo_dies_by_stone || !cave->voodoo_collects_diamonds || cave->voodoo_disappear_in_explosion)
104 g_warning("crli voodoo dies by stone hit, can collect diamonds and can't be destroyed");
105 if (cave->short_explosions)
106 g_warning("crli explosions are slower than original");
107 if (!cave->magic_timer_wait_for_hatching) {
108 cave->magic_wall_time+=2;
109 g_message("crli magic wall timer waits for hatching; added 2 seconds for correction");
111 if (!cave->slime_predictable)
112 g_warning("crli only supports predictable slime");
114 /* fill data bytes with some defaults */
115 g_assert(gd_crazylight_import_table[crli_steel_code]==O_STEEL); /* check magic value */
116 for (i=0; i<40*22; i++) /* fill map with steel wall */
117 output[i]=crli_steel_code;
118 for (i=40*22; i<G_N_ELEMENTS(output); i++) /* fill properties with zero */
119 output[i]=0;
121 /* check cave sizes */
122 if (cave->w!=40 || cave->h!=22)
123 g_warning("cave sizes out of range, should be 40x22 instead of %dx%d", cave->w, cave->h);
124 gd_cave_correct_visible_size(cave);
125 if (cave->intermission) { /* visible size for intermissions */
126 if (cave->x1!=0 || cave->y1!=0 || cave->x2!=19 || cave->y2!=11)
127 g_warning("for intermissions, the upper left 20x12 elements should be visible");
128 } else { /* visible size for normal caves */
129 if (cave->x1!=0 || cave->y1!=0 || cave->x2!=cave->w-1 || cave->y2!=cave->h-1)
130 g_warning("for normal caves, the whole cave should be visible");
133 /* convert map */
134 has_horizontal=FALSE;
135 has_vertical=FALSE;
136 for (y=0; y<cave->h && y<22; y++)
137 for (x=0; x<cave->w && x<40; x++) {
138 output[y*40+x]=element_to_crli(cave->map[y][x], unknown);
140 if (cave->map[y][x]==O_H_EXPANDING_WALL)
141 has_horizontal=TRUE;
142 if (cave->map[y][x]==O_V_EXPANDING_WALL)
143 has_vertical=TRUE;
146 output[0x370]=cave->level_time[level]/100%10;
147 output[0x371]=cave->level_time[level]/10%10;
148 output[0x372]=cave->level_time[level]/1%10;
150 output[0x373]=cave->level_diamonds[level]/100%10;
151 output[0x374]=cave->level_diamonds[level]/10%10;
152 output[0x375]=cave->level_diamonds[level]/1%10;
154 output[0x376]=cave->extra_diamond_value/100%10;
155 output[0x377]=cave->extra_diamond_value/10%10;
156 output[0x378]=cave->extra_diamond_value/1%10;
158 output[0x379]=cave->diamond_value/100%10;
159 output[0x37a]=cave->diamond_value/10%10;
160 output[0x37b]=cave->diamond_value/1%10;
162 output[0x37c]=cave->amoeba_time/256;
163 output[0x37d]=cave->amoeba_time%256;
165 output[0x37e]=cave->magic_wall_time/256;
166 output[0x37f]=cave->magic_wall_time%256;
168 if (cave->creatures_direction_auto_change_time) {
169 output[0x380]=1;
170 output[0x381]=cave->creatures_direction_auto_change_time;
173 prob=(int)(4.0/cave->amoeba_growth_prob-1+0.5);
174 if (prob!=0 && prob!=1 && prob!=3 && prob!=7 && prob!=15 && prob!=31 && prob!=63 && prob!=127)
175 g_warning("cannot precisely export amoeba slow growth probability, %d", prob);
176 output[0x382]=prob;
177 prob=(int)(4.0/cave->amoeba_fast_growth_prob-1+0.5);
178 if (prob!=0 && prob!=1 && prob!=3 && prob!=7 && prob!=15 && prob!=31 && prob!=63 && prob!=127)
179 g_warning("cannot precisely export amoeba fast growth probability");
180 output[0x383]=prob;
182 output[0x384]=gd_color_get_c64_index_try(cave->colorb);
183 output[0x385]=gd_color_get_c64_index_try(cave->color0);
184 output[0x386]=gd_color_get_c64_index_try(cave->color1);
185 output[0x387]=gd_color_get_c64_index_try(cave->color2);
186 x=gd_color_get_c64_index_try(cave->color3);
187 if (x>7)
188 g_warning("allowed colors for color3 are Black..Yellow, but it is %d", x);
189 output[0x388]=x|8;
191 output[0x389]=cave->intermission?1:0;
192 output[0x38a]=cave->level_ckdelay[level];
194 output[0x38b]=cave->slime_permeability_c64;
195 output[0x38c]=0; /* always "normal" intermission, scrolling intermission is said to be buggy */
196 output[0x38d]=0xf1; /* magic wall sound on */
198 /* if changed direction, we swap the flags here. */
199 /* effects are already converted by element_to_crli */
200 if (cave->expanding_wall_changed) {
201 gboolean temp;
203 temp=has_horizontal;
204 has_horizontal=has_vertical;
205 has_vertical=temp;
207 output[0x38e]=0x2e;
208 if (has_vertical && !has_horizontal)
209 output[0x38e]=0x2f;
210 if (has_horizontal && has_vertical)
211 g_warning("a crli map cannot contain horizontal and vertical growing walls at the same time");
212 output[0x38f]=cave->creatures_backwards?0x2d:0x2c;
214 output[0x390]=cave->amoeba_max_count/256;
215 output[0x391]=cave->amoeba_max_count%256;
217 output[0x392]=cave->time_bonus;
218 output[0x393]=cave->time_penalty;
219 output[0x394]=cave->biter_delay_frame;
220 output[0x395]=cave->magic_wall_stops_amoeba?0:1; /* inverted! */
222 output[0x396]=element_to_crli(cave->bomb_explosion_effect|SCANNED, unknown);
223 output[0x397]=element_to_crli(cave->explosion_effect|SCANNED, unknown);
224 if (cave->stone_falling_effect!=O_STONE_F)
225 g_warning("crli does not support 'falling stone to' effect");
226 output[0x398]=element_to_crli(cave->stone_bouncing_effect|SCANNED, unknown);
227 output[0x399]=element_to_crli(cave->diamond_birth_effect|SCANNED, unknown);
228 output[0x39a]=element_to_crli(cave->magic_diamond_to|SCANNED, unknown);
229 if (cave->diamond_bouncing_effect!=O_DIAMOND)
230 g_warning("crli does not support 'bouncing diamond turns to' effect");
231 output[0x39b]=element_to_crli(cave->bladder_converts_by, unknown);
232 output[0x39c]=element_to_crli(cave->diamond_falling_effect|SCANNED, unknown);
233 if (cave->diamond_bouncing_effect!=O_DIAMOND)
234 g_warning("crli does not support 'bouncing diamond turns to' effect");
235 output[0x39d]=element_to_crli(cave->biter_eat, unknown);
236 output[0x39e]=element_to_crli(cave->slime_eats_1, unknown);
237 if (element_to_crli(cave->slime_eats_1, unknown)+3!=element_to_crli(cave->slime_converts_1|SCANNED, unknown))
238 g_warning("cannot convert slime setting: %s to %s", gd_elements[cave->slime_eats_1].name, gd_elements[cave->slime_converts_1].name);
239 output[0x39f]=element_to_crli(cave->slime_eats_2, unknown);
240 if (element_to_crli(cave->slime_eats_2, unknown)+3!=element_to_crli(cave->slime_converts_2|SCANNED, unknown))
241 g_warning("cannot convert slime setting: %s to %s", gd_elements[cave->slime_eats_2].name, gd_elements[cave->slime_converts_2].name);
243 output[0x3a0]='V'; /* version number */
244 output[0x3a1]='3';
245 output[0x3a2]='.';
246 output[0x3a3]='0';
248 output[0x3a4]=cave->diagonal_movements?1:0;
249 output[0x3a6]=element_to_crli(cave->amoeba_too_big_effect|SCANNED, unknown);
250 output[0x3a7]=element_to_crli(cave->amoeba_enclosed_effect|SCANNED, unknown);
251 output[0x3a8]=cave->acid_spread_ratio*255.0;
252 output[0x3a9]=element_to_crli(cave->acid_eats_this, unknown);
253 output[0x3ab]=element_to_crli(cave->expanding_wall_looks_like, unknown);
254 output[0x3ac]=element_to_crli(cave->dirt_looks_like, unknown);
256 prev=output[0];
257 i=1;
258 out=0;
259 count=1;
260 while (i<G_N_ELEMENTS(output)) {
261 if (output[i]==prev) { /* same as previous */
262 /* if it would be too much for the length to be fit in a byte, write now */
263 if (count>253) {
264 compressed[out++]=0xbf;
265 compressed[out++]=prev; /* which is == 0xbf */
266 compressed[out++]=count;
268 count=0;
270 count++;
271 } else {
272 /* not the same as the previous ones, so write out those */
273 if (count==1 && prev!=0xbf) /* output previous character */
274 compressed[out++]=prev;
275 else if (count==1 && prev==0xbf) { /* output previous character, but it is accidentally the escape code */
276 compressed[out++]=0xbf;
277 compressed[out++]=prev; /* which is == 0xbf */
278 compressed[out++]=1;
279 } else if (count==2) {
280 /* count=2 is not written as escape, byte, count, as it would make it longer */
281 if (prev!=0xbf) {
282 /* if the byte to write is not the escape byte */
283 compressed[out++]=prev; /* which is == 0xbf */
284 compressed[out++]=prev; /* which is == 0xbf */
285 } else {
286 /* we have two escape bytes */
287 compressed[out++]=0xbf;
288 compressed[out++]=prev; /* which is == 0xbf */
289 compressed[out++]=2;
291 } else {
292 /* count > 2 */
293 compressed[out++]=0xbf;
294 compressed[out++]=prev;
295 compressed[out++]=count;
298 /* and process this one. */
299 prev=output[i];
300 count=1;
303 i++; /* next byte to compress */
305 /* process remaining bytes; always write them as compressed with escape byte */
306 compressed[out++]=0xbf;
307 compressed[out++]=prev;
308 compressed[out++]=count;
310 gd_cave_free(cave);
311 g_hash_table_destroy(unknown);
313 /* return number of bytes */
314 return out;
317 void
318 gd_export_cave_to_crli_cavefile(GdCave *cave, int level, const char *filename)
320 guint8 data[1024];
321 int size;
322 GError *error=NULL;
324 data[0x0]=0x00;
325 data[0x1]=0xc4;
326 data[0x2]='D';
327 data[0x3]='L';
328 data[0x4]='P';
329 size=crli_export(cave, level, data+5);
330 gd_error_set_context(NULL);
331 if (!g_file_set_contents(filename, (gchar *)data, size+5, &error)) {
332 /* could not save properly */
333 g_warning("%s: %s", filename, error->message);
334 g_error_free(error);
339 void
340 gd_export_cave_list_to_crli_cavepack(GList *caveset, int level, const char *filename)
342 GError *error=NULL;
343 const int start=0x6ffa;
344 guint8 out[0xcc00-start+1024]; /* max number of cavepack + 1024 so dont worry about buffer overrun :P */
345 int pos;
346 GList *iter;
347 int i;
349 /* start address */
350 out[0]=0xfc;
351 out[1]=0x6f;
352 /* cavepack version number */
353 out[0x6ffc - start]='V';
354 out[0x6ffd - start]='3';
355 out[0x6ffe - start]='.';
356 out[0x6fff - start]='0';
358 for (i=0; i<48; i++) {
359 out[0x7000-start+i]=0xff;
360 out[0x7030-start+i]=0xff;
361 out[0x7060-start+i]=0xff; /* no cave present */
363 out[0x7090-start]=0xff;
364 out[0x7091-start]=0x60;
365 out[0x7092-start]=0x60;
366 out[0x7093-start]=0x60;
367 pos=0x70a2; /* first available byte; before that we have space for the name */
369 for (iter=caveset, i=0; iter!=NULL; iter=iter->next, i++) {
370 GdCave *cave=iter->data;
371 int bytes;
372 int c;
373 gunichar ch;
374 gchar *namepos;
375 gboolean exportname;
377 if (i>=48) {
378 g_warning("maximum of 48 caves in a crli cavepack");
379 break;
382 bytes=crli_export(cave, level, out+pos-start);
384 if (pos+bytes>0xcbff) {
385 g_warning("run out of data space, not writing this cave");
386 break;
389 out[0x7000-start+i]=pos%256; /* lsb */
390 out[0x7030-start+i]=pos/256; /* msb */
391 out[0x7060-start+i]=cave->selectable?0:1; /* selection table */
393 /* write name */
394 for (c=0; c<14; c++) /* fill with space */
395 out[pos-start-14+c]=0x20;
397 exportname=TRUE;
398 namepos=cave->name;
399 c=0;
400 ch=g_utf8_get_char(namepos);
401 while (c<14 && ch!=0) {
402 int j;
403 gboolean succ=FALSE;
405 out[pos-start-14+c]=' '; /* space is default, also for unknown characters */
406 if (ch==' ') /* treat space as different, as gd_bd_internal_chars has lots of spaces at unknown character positions */
407 succ=TRUE;
408 else
409 if (ch<256) {
410 for (j=0; j<strlen(gd_bd_internal_chars); j++)
411 if (gd_bd_internal_chars[j]==g_ascii_toupper(ch)) { /* search for the character */
412 out[pos-start-14+c]=j;
413 succ=TRUE;
417 if (!succ)
418 exportname=FALSE;
420 c++;
421 namepos=g_utf8_next_char(namepos);
422 ch=g_utf8_get_char(namepos);
424 if (!exportname)
425 g_warning("couldn't export cave name properly");
427 pos+=bytes+14; /* jump number of bytes + place for next cave name */
429 pos-=14; /* subtract 14 for place we left for next cave name, but there was no more cave */
431 if (!g_file_set_contents(filename, (gchar *)out, pos-start, &error)) {
432 /* could not save properly */
433 g_warning("%s: %s", filename, error->message);
434 g_error_free(error);
439 static void
440 string_printf_markup(GString *string, const char *format, ...)
442 char *text;
443 va_list ap;
445 va_start(ap, format);
446 text=g_markup_vprintf_escaped(format, ap);
447 va_end(ap);
448 g_string_append(string, text);
449 g_free(text);
452 /* save caveset as html gallery.
453 htmlname: filename
454 window: show progress bar in a small dialog, transient for this window, if not NULL
456 void
457 gd_save_html(char *htmlname, GtkWidget *window)
459 char *pngbasename; /* used as a base for img src= tags */
460 char *pngoutbasename; /* used as a base name for png output files */
461 int i;
462 GtkWidget *dialog=NULL, *progress=NULL;
463 GString *contents;
464 GError *error=NULL;
466 contents=g_string_sized_new(20000);
468 if (window) {
469 dialog=gtk_dialog_new ();
470 gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
471 progress=gtk_progress_bar_new ();
472 gtk_window_set_title (GTK_WINDOW (dialog), _("Saving HTML gallery"));
473 gtk_box_pack_start_defaults (GTK_BOX (GTK_DIALOG (dialog)->vbox), progress);
474 gtk_widget_show_all (dialog);
477 if (g_str_has_suffix (htmlname, ".html")) {
478 /* has html extension */
479 pngoutbasename=g_strdup (htmlname);
480 *g_strrstr(pngoutbasename, ".html")=0;
482 else {
483 /* has no html extension */
484 pngoutbasename=g_strdup(htmlname);
485 htmlname=g_strdup_printf("%s.html", pngoutbasename);
487 pngbasename=g_path_get_basename(pngoutbasename);
489 g_string_append(contents, "<HTML>\n");
490 g_string_append(contents, "<HEAD>\n");
491 string_printf_markup(contents, "<TITLE>%s</TITLE>\n", gd_caveset_data->name);
492 g_string_append(contents, "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">\n");
493 if (gd_html_stylesheet_filename)
494 g_string_append_printf(contents, "<link rel=\"stylesheet\" href=\"%s\">\n", gd_html_stylesheet_filename);
495 if (gd_html_favicon_filename)
496 g_string_append_printf(contents, "<link rel=\"shortcut icon\" href=\"%s\">\n", gd_html_favicon_filename);
497 g_string_append(contents, "</HEAD>\n\n");
499 g_string_append(contents, "<BODY>\n");
501 string_printf_markup(contents, "<H1>%s</H1>\n", gd_caveset_data->name);
502 /* if the game has its own title screen */
503 if (gd_caveset_data->title_screen->len!=0) {
504 GdkPixbuf *title_image;
505 char *pngname;
507 /* create the title image and save it */
508 title_image=gd_create_title_image();
509 pngname=g_strdup_printf("%s_%03d.png", pngoutbasename, 0); /* it is the "zeroth" image */
510 gdk_pixbuf_save(title_image, pngname, "png", &error, "compression", "9", NULL);
511 if (error) {
512 g_warning("%s", error->message);
513 g_error_free(error);
514 error=NULL;
516 g_free (pngname);
518 g_string_append_printf(contents, "<IMAGE SRC=\"%s_%03d.png\" WIDTH=\"%d\" HEIGHT=\"%d\">\n", pngbasename, 0, gdk_pixbuf_get_width(title_image), gdk_pixbuf_get_height (title_image));
519 g_string_append_printf(contents, "<BR>\n");
521 g_object_unref(title_image);
523 g_string_append(contents, "<TABLE>\n");
524 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Caves"), gd_caveset_count());
525 if (!g_str_equal(gd_caveset_data->author, ""))
526 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Author"), gd_caveset_data->author);
527 if (!g_str_equal(gd_caveset_data->description, ""))
528 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Description"), gd_caveset_data->description);
529 if (!g_str_equal(gd_caveset_data->www, ""))
530 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("WWW"), gd_caveset_data->www);
531 if (!g_str_equal(gd_caveset_data->remark->str, ""))
532 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Remark"), gd_caveset_data->remark->str);
533 if (!g_str_equal(gd_caveset_data->story->str, ""))
534 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Story"), gd_caveset_data->story->str);
535 g_string_append(contents, "</TABLE>\n");
537 /* cave names, descriptions, hrefs */
538 g_string_append(contents, "<DL>\n");
539 for (i=0; i<gd_caveset_count(); i++) {
540 GdCave *cave;
542 cave=gd_return_nth_cave(i);
543 string_printf_markup(contents, "<DT><A HREF=\"#cave%03d\">%s</A></DT>\n<DD>%s</DD>\n", i+1, cave->name, cave->description);
545 g_string_append(contents, "</DL>\n\n");
547 for (i=0; i<gd_caveset_count(); i++) {
548 GdkPixbuf *pixbuf;
549 GdCave *cave;
550 char *pngname;
551 char *text;
552 gboolean has_amoeba=FALSE, has_magic=FALSE;
553 int x, y;
554 GError *error=NULL;
556 if (progress) {
557 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR (progress), (float) i/gd_caveset_count());
558 text=g_strdup_printf("%d/%d", i, gd_caveset_count());
559 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), text);
560 g_free(text);
561 gdk_window_process_all_updates();
564 /* rendering cave for png: seed=0 */
565 cave=gd_cave_new_from_caveset(i, 0, 0);
566 pixbuf=gd_drawcave_to_pixbuf(cave, 0, 0, TRUE, FALSE);
568 /* check cave to see if we have amoeba or magic wall. properties will be shown in html, if so. */
569 for (y=0; y<cave->h; y++)
570 for (x=0; x<cave->w; x++)
571 if (gd_cave_get_rc(cave, x, y)==O_AMOEBA) {
572 has_amoeba=TRUE;
573 break;
575 for (y=0; y<cave->h; y++)
576 for (x=0; x<cave->w; x++)
577 if (gd_cave_get_rc(cave, x, y)==O_MAGIC_WALL) {
578 has_magic=TRUE;
579 break;
582 /* save image */
583 pngname=g_strdup_printf("%s_%03d.png", pngoutbasename, i + 1);
584 gdk_pixbuf_save(pixbuf, pngname, "png", &error, "compression", "9", NULL);
585 if (error) {
586 g_warning("%s", error->message);
587 g_error_free(error);
588 error=NULL;
590 g_free (pngname);
592 /* cave header */
593 string_printf_markup(contents, "<A NAME=\"cave%03d\"></A>\n<H2>%s</H2>\n", i+1, cave->name);
594 g_string_append_printf(contents, "<IMAGE SRC=\"%s_%03d.png\" WIDTH=\"%d\" HEIGHT=\"%d\">\n", pngbasename, i+1, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf));
595 g_string_append(contents, "<BR>\n");
596 g_string_append(contents, "<TABLE>\n");
597 if (!g_str_equal(cave->author, ""))
598 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Author"), cave->author);
599 if (!g_str_equal(cave->description, ""))
600 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Description"), cave->description);
601 if (!g_str_equal(cave->remark->str, "")) {
602 /* we must split the story into lines, and join them with html <br> */
603 char **spl;
604 char *join;
605 char *escaped;
607 escaped=g_markup_escape_text(cave->remark->str, -1);
608 spl=g_strsplit_set(escaped, "\n", -1);
609 g_free(escaped);
610 /* maintain line breaks */
611 join=g_strjoinv("<BR>\n", spl);
612 g_strfreev(spl);
613 g_string_append_printf(contents, "<TR><TH>%s<TD>%s\n", _("Remark"), join); /* string already escaped! */
614 g_free(join);
616 if (!g_str_equal(cave->story->str, "")) {
617 /* we must split the story into lines, and join them with html <br> */
618 char **spl;
619 char *join;
620 char *escaped;
622 escaped=g_markup_escape_text(cave->story->str, -1);
623 spl=g_strsplit_set(escaped, "\n", -1);
624 g_free(escaped);
625 /* maintain line breaks */
626 join=g_strjoinv("<BR>\n", spl);
627 g_strfreev(spl);
628 g_string_append_printf(contents, "<TR><TH>%s<TD>%s\n", _("Story"), join); /* string already escaped! */
629 g_free(join);
631 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Type"), cave->intermission ? _("Intermission") : _("Normal cave"));
632 string_printf_markup(contents, "<TR><TH>%s<TD>%s\n", _("Selectable as start"), cave->selectable ? _("Yes") : _("No"));
633 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Diamonds needed"), cave->diamonds_needed);
634 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Diamond value"), cave->diamond_value);
635 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Extra diamond value"), cave->extra_diamond_value);
636 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Time (s)"), cave->time);
637 if (has_amoeba)
638 string_printf_markup(contents, "<TR><TH>%s<TD>%d, %d\n", _("Amoeba threshold and time (s)"), cave->amoeba_max_count, cave->amoeba_time);
639 if (has_magic)
640 string_printf_markup(contents, "<TR><TH>%s<TD>%d\n", _("Magic wall milling time (s)"), cave->magic_wall_time);
641 g_string_append(contents, "</TABLE>\n");
643 g_string_append(contents, "\n");
645 gd_cave_free (cave);
646 g_object_unref (pixbuf);
648 g_string_append(contents, "</BODY>\n");
649 g_string_append(contents, "</HTML>\n");
650 g_free (pngoutbasename);
651 g_free (pngbasename);
653 if (!g_file_set_contents (htmlname, contents->str, contents->len, &error)) {
654 /* could not save properly */
655 g_warning("%s", error->message);
656 g_error_free(error);
658 g_string_free(contents, TRUE);
660 if (dialog)
661 gtk_widget_destroy(dialog);