20100212
[gdash.git] / src / sdlreplay.c
blobc97e312bf6c5f952ec0b544110062bb161773d8e
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.
16 #include <SDL/SDL.h>
17 #include <SDL/SDL_mixer.h>
18 #include "IMG_savepng.h"
19 #include <glib.h>
20 #include "config.h"
21 #include "cave.h"
22 #include "caveobject.h"
23 #include "caveengine.h"
24 #include "cavesound.h"
25 #include "cavedb.h"
26 #include "caveset.h"
27 #include "c64import.h"
28 #include "settings.h"
29 #include "util.h"
30 #include "gameplay.h"
31 #include "sdlgfx.h"
32 #include "sdlui.h"
33 #include "sound.h"
34 #include "about.h"
37 /* for saving the wav file */
38 static unsigned int wavlen;
39 static unsigned int frame;
40 static Uint16 format;
41 static int frequency, channels, bits;
43 /* this function saves the wav file,
44 and also does the timing! */
45 static void
46 mixfunc(void *udata, Uint8 *stream, int len)
48 SDL_Event ev;
50 if (fwrite(stream, 1, len, (FILE *)udata)!=len) {
51 g_critical("Cannot write to wav file!");
54 ev.type=SDL_USEREVENT;
55 SDL_PushEvent(&ev);
57 wavlen+=len;
60 /* the game itself */
61 static void
62 play_game_func(GdGame *game, const char *filename_prefix)
64 gboolean toggle=FALSE; /* this is used to divide the rate of the user interrupt by 2, if no fine scrolling requested */
65 gboolean exit_game;
66 int statusbar_since=0; /* count number of frames from when the outoftime or paused event happened. we need it for timeout header flash */
67 SDL_Event event;
68 GdStatusBarColors cols_struct;
69 GdStatusBarColors *cols=&cols_struct;
70 Uint32 out32;
71 Uint16 out16;
72 FILE *wavfile;
73 char *filename, *text;
74 int i;
75 gboolean show;
77 /* start the wave file */
78 filename=g_strdup_printf("%s.wav", filename_prefix);
79 wavfile=fopen(filename, "wb");
80 if (!wavfile) {
81 g_critical("Cannot open %s for sound output", filename);
82 return;
84 g_free(filename);
86 fseek(wavfile, 44, SEEK_SET); /* 44bytes offset: start of data in a wav file */
87 wavlen=0;
88 frame=0;
89 Mix_SetPostMix(mixfunc, wavfile);
91 /* these are not important, but otherwise the "saved xxxx.png" line in the first frame could not be seen. */
92 cols->background=GD_GDASH_BLACK;
93 cols->default_color=GD_GDASH_WHITE;
95 /* now do the replay */
96 exit_game=FALSE;
97 while (!exit_game && SDL_WaitEvent(&event)) {
98 GdGameState state;
100 switch(event.type) {
101 case SDL_QUIT: /* application closed by window manager */
102 gd_quit=TRUE;
103 exit_game=TRUE;
104 break;
106 case SDL_KEYDOWN:
107 switch(event.key.keysym.sym) {
108 case SDLK_ESCAPE:
109 case SDLK_F1:
110 exit_game=TRUE;
111 break;
113 case SDLK_q:
114 if (gd_keystate[SDLK_LCTRL]||gd_keystate[SDLK_RCTRL]) {
115 gd_quit=TRUE;
116 exit_game=TRUE;
118 break;
119 default:
120 break;
122 break;
124 case SDL_USEREVENT:
125 /* get movement */
126 /* tell the interrupt "20ms has passed" */
127 /* no movement, no fire, no suicide, no restart, no pause, no fast movement */
128 state=gd_game_main_int(game, 20, MV_STILL, FALSE, FALSE, FALSE, !game->out_of_window, FALSE, FALSE);
129 show=FALSE;
131 /* state of game, returned by gd_game_main_int */
132 switch (state) {
133 case GD_GAME_INVALID_STATE:
134 g_assert_not_reached();
135 break;
137 case GD_GAME_SHOW_STORY:
138 case GD_GAME_SHOW_STORY_WAIT:
139 /* should not happen */
140 break;
142 case GD_GAME_CAVE_LOADED:
143 /* select colors, prepare drawing etc. */
144 gd_select_pixbuf_colors(game->cave->color0, game->cave->color1, game->cave->color2, game->cave->color3, game->cave->color4, game->cave->color5);
145 gd_scroll_to_origin();
146 SDL_FillRect(gd_screen, NULL, SDL_MapRGB(gd_screen->format, 0, 0, 0)); /* fill whole gd_screen with black - cave might be smaller than previous! */
147 /* select status bar colors here, as some depend on actual cave colors */
148 gd_play_game_select_status_bar_colors(cols, game->cave);
149 gd_showheader_uncover(game, cols, FALSE); /* false=do not say "playing replay" in the status bar */
150 show=TRUE;
151 break;
153 case GD_GAME_NOTHING:
154 /* normally continue. */
155 break;
157 case GD_GAME_LABELS_CHANGED:
158 gd_showheader_game(game, statusbar_since, cols, FALSE); /* false=not showing "playing replay" as it would be ugly in the saved video */
159 show=TRUE;
160 break;
162 case GD_GAME_TIMEOUT_NOW:
163 statusbar_since=0;
164 gd_showheader_game(game, statusbar_since, cols, FALSE); /* also update the status bar here. */
165 show=TRUE;
166 break;
168 case GD_GAME_NO_MORE_LIVES:
169 /* should not reach */
170 break;
172 case GD_GAME_STOP:
173 exit_game=TRUE; /* game stopped, this could be a replay or a snapshot */
174 break;
176 case GD_GAME_GAME_OVER:
177 exit_game=TRUE;
178 /* ... but should not reach. */
179 break;
182 statusbar_since++;
184 /* for the sdl version, it seems nicer if we first scroll, and then draw. */
185 /* scrolling for the sdl version will merely invalidate the whole gfx buffer. */
186 /* if drawcave was before scrolling, it would draw, scroll would invalidate, and then it should be drawn again */
187 /* only do the drawing if the cave already exists. */
188 toggle=!toggle;
189 if (game->gfx_buffer) {
190 /* if fine scrolling, scroll at 50hz. if not, only scroll at every second call, so 25hz. */
191 if (game->cave && (toggle || gd_fine_scroll))
192 game->out_of_window=gd_scroll(game, game->cave->player_state==GD_PL_NOT_YET); /* do the scrolling. scroll exactly, if player is not yet alive */
194 gd_drawcave(gd_screen, game); /* draw the cave. */
197 /* before incrementing frame number, check if to save the frame to disk. */
198 if (frame%2==0) {
199 /* save every second frame, so 25hz */
200 filename=g_strdup_printf("%s_%08d.png", filename_prefix, frame/2);
201 IMG_SavePNG(filename, gd_screen, -1);
202 #if 0
203 /* now we can ruin the screen */
204 if (game->cave && game->replay_from) {
205 gd_clear_header(cols->background);
206 gd_blittext_printf_n(gd_screen, -1, gd_statusbar_y1, cols->default_color, "Movement %d of %d", game->replay_from->current_playing_pos, game->replay_from->movements->len);
207 gd_blittext_printf_n(gd_screen, -1, gd_statusbar_y2, cols->default_color, filename, game->replay_from->current_playing_pos, game->replay_from->movements->len);
209 #endif
210 g_free(filename);
214 /* once per iteration, show it to the user. */
215 if (show || frame%10==0)
216 SDL_Flip(gd_screen);
218 frame++;
219 break;
223 Mix_SetPostMix(NULL, NULL); /* remove wav file saver */
224 gd_sound_off(); /* we stop sounds. gd_game_free would do it, but we need the game struct for highscore */
226 /* write wav header, as now we now its final size. */
227 fseek(wavfile, 0, SEEK_SET);
229 i=0;
230 out32=GUINT32_TO_BE(0x52494646); i+=fwrite(&out32, 1, 4, wavfile); /* "RIFF" */
231 out32=GUINT32_TO_LE(wavlen+36); i+=fwrite(&out32, 1, 4, wavfile); /* 4 + 8+subchunk1size + 8+subchunk2size */
232 out32=GUINT32_TO_BE(0x57415645); i+=fwrite(&out32, 1, 4, wavfile); /* "WAVE" */
234 out32=GUINT32_TO_BE(0x666d7420); i+=fwrite(&out32, 1, 4, wavfile); /* "fmt " */
235 out32=GUINT32_TO_LE(16); i+=fwrite(&out32, 1, 4, wavfile); /* fmt chunk size=16 bytes */
236 out16=GUINT16_TO_LE(1); i+=fwrite(&out16, 1, 2, wavfile); /* 1=pcm */
237 out16=GUINT16_TO_LE(channels); i+=fwrite(&out16, 1, 2, wavfile);
238 out32=GUINT32_TO_LE(frequency); i+=fwrite(&out32, 1, 4, wavfile);
239 out32=GUINT32_TO_LE(frequency*bits/8*channels); i+=fwrite(&out32, 1, 4, wavfile); /* byterate */
240 out16=GUINT16_TO_LE(bits/8*channels); i+=fwrite(&out16, 1, 2, wavfile); /* blockalign */
241 out16=GUINT16_TO_LE(bits); i+=fwrite(&out16, 1, 2, wavfile); /* bitspersample */
243 out32=GUINT32_TO_BE(0x64617461); i+=fwrite(&out32, 1, 4, wavfile); /* "data" */
244 out32=GUINT32_TO_LE(wavlen); i+=fwrite(&out32, 1, 4, wavfile); /* actual data length */
246 if (i!=44)
247 g_critical("Could not write wav header to file!");
249 text=g_strdup_printf("Saved %d video frames and %dMiB of audio data to %s_*.png and %s.wav.", frame/2, wavlen/1048576, filename_prefix, filename_prefix);
250 gd_message(text);
251 g_free(text);
253 fclose(wavfile);
256 /* draws the title screen (maybe that of the game) */
257 static void
258 draw_title_screen()
260 SDL_Surface **animation;
261 SDL_Rect r;
262 int x;
264 animation=gd_get_title_animation(TRUE); /* true=we need only the first frame */
266 SDL_FillRect(gd_screen, NULL, SDL_MapRGB(gd_screen->format, 0, 0, 0));
267 r.x=(gd_screen->w-animation[0]->w)/2;
268 r.y=(gd_screen->h-animation[0]->h)/2;
269 SDL_BlitSurface(animation[0], NULL, gd_screen, &r);
270 SDL_Flip(gd_screen);
272 /* forget animation */
273 for (x=0; animation[x]!=NULL; x++)
274 SDL_FreeSurface(animation[x]);
275 g_free(animation);
279 static void
280 play_replay(GdCave *cave, GdReplay *replay)
282 GdGame *game;
283 char *prefix;
284 char *prefix_rec;
286 gd_wait_for_key_releases();
287 prefix_rec=g_strdup_printf("%s%sout", gd_last_folder?gd_last_folder:g_get_home_dir(), G_DIR_SEPARATOR_S); /* recommended */
288 prefix=gd_input_string("OUTPUT FILENAME PREFIX", prefix_rec);
289 g_free(prefix_rec);
290 if (!prefix)
291 return;
293 /* draw the title screen, so it will be the first frame of the movie */
294 draw_title_screen();
296 game=gd_game_new_replay(cave, replay);
297 play_game_func(game, prefix);
298 gd_game_free(game);
299 g_free(prefix);
301 /* wait for keys, as for example escape may be pressed at this time */
302 gd_wait_for_key_releases();
313 static GdMainMenuSelected
314 main_menu()
316 const int image_centered_threshold=164*gd_scale;
317 SDL_Surface **animation;
318 int animcycle;
319 int count;
320 GdMainMenuSelected s;
321 int x;
322 int y_gameline;
323 int image_h;
324 gboolean show_status;
325 gboolean title_image_shown;
327 animation=gd_get_title_animation(FALSE);
328 animcycle=0;
329 /* count number of frames */
330 count=0;
331 while(animation[count]!=NULL)
332 count++;
334 /* height of title screen, then decide which lines to show and where */
335 image_h=animation[0]->h;
336 if (gd_screen->h-image_h < 2*gd_font_height()) {
337 /* less than 2 lines - place for only one line of text. */
338 y_gameline=image_h + (gd_screen->h-image_h-gd_font_height())/2; /* centered in the small place */
339 show_status=FALSE;
340 } else
341 if (gd_screen->h-image_h < 3*gd_font_height()) {
342 /* more than 2, less than 3 - place for status bar. game name is not shown, as this will */
343 /* only be true for a game with its own title screen, and i decided that in that case it */
344 /* would make more sense. */
345 y_gameline=image_h + (gd_screen->h-image_h-gd_font_height()*2)/2; /* centered there */
346 show_status=TRUE;
347 } else {
348 image_h=image_centered_threshold; /* "minimum" height for the image, and it will be centered */
349 /* more than 3, less than 4 - place for everything. */
350 y_gameline=image_h + (gd_screen->h-image_h-gd_font_height()-gd_font_height())/2;
351 show_status=TRUE;
354 /* fill whole gd_screen with black */
355 SDL_FillRect(gd_screen, NULL, SDL_MapRGB(gd_screen->format, 0, 0, 0));
356 /* then fill with the tile, so if the title image is very small, there is no black border */
357 /* only do that if the image is significantly smaller */
358 if (animation[0]->w < gd_screen->w*9/10 || animation[0]->h < image_centered_threshold*9/10) {
359 SDL_Rect rect;
361 rect.x=0;
362 rect.y=0;
363 rect.w=gd_screen->w;
364 rect.h=image_centered_threshold;
365 SDL_SetClipRect(gd_screen, &rect);
366 gd_dark_screen();
367 SDL_SetClipRect(gd_screen, NULL);
370 if (y_gameline!=-1) {
371 x=gd_blittext_n(gd_screen, 0, y_gameline, GD_GDASH_WHITE, "GAME: ");
372 x=gd_blittext_n(gd_screen, x, y_gameline, GD_GDASH_YELLOW, gd_caveset_data->name);
374 if (show_status)
375 gd_status_line("L: LOAD R: REPLAYS ESC: EXIT");
377 if (gd_has_new_error())
378 /* show error flag */
379 gd_blittext_n(gd_screen, gd_screen->w-gd_font_width(), gd_screen->h-gd_font_height(), GD_GDASH_RED, "E");
381 s=M_NONE;
383 title_image_shown=FALSE;
384 while(!gd_quit && s==M_NONE) {
385 SDL_Event event;
387 /* play animation. if more frames, always draw. if only one frame, draw only once */
388 if (!title_image_shown || count>1) {
389 SDL_Rect dest_pos;
390 animcycle=(animcycle+1)%count;
391 dest_pos.x=(gd_screen->w-animation[animcycle]->w)/2; /* centered horizontally */
392 if (animation[animcycle]->h<image_centered_threshold)
393 dest_pos.y=(image_centered_threshold-animation[animcycle]->h)/2; /* centered vertically */
394 else
395 dest_pos.y=0; /* top of screen, as not too much space left for info lines */
396 SDL_BlitSurface(animation[animcycle], 0, gd_screen, &dest_pos);
397 title_image_shown=TRUE; /* shown at least once, so if not animated, we do not waste cpu */
399 SDL_Flip(gd_screen);
401 while (SDL_PollEvent(&event)) {
402 switch(event.type) {
403 case SDL_QUIT: /* application closed by window manager */
404 gd_quit=TRUE;
405 s=M_QUIT;
406 break;
408 case SDL_KEYDOWN:
409 switch(event.key.keysym.sym) {
410 case SDLK_ESCAPE: /* escape: quit app */
411 case SDLK_q:
412 s=M_EXIT;
413 break;
415 case SDLK_F1: /* f1: help */
416 s=M_HELP;
417 break;
419 case SDLK_l: /* load file */
420 s=M_LOAD;
421 break;
422 case SDLK_c:
423 s=M_LOAD_FROM_INSTALLED;
424 break;
426 case SDLK_r:
427 s=M_REPLAYS;
428 break;
429 case SDLK_i: /* caveset info */
430 s=M_INFO;
431 break;
432 case SDLK_h: /* h: highscore */
433 s=M_HIGHSCORE;
434 break;
436 case SDLK_a:
437 s=M_ABOUT;
438 break;
440 case SDLK_b:
441 s=M_LICENSE;
442 break;
444 case SDLK_e: /* show error console */
445 s=M_ERRORS;
446 break;
448 default: /* other keys do nothing */
449 break;
451 default: /* other events we don't care */
452 break;
455 SDL_Delay(40); /* 25 fps - we need exactly this for the animation */
458 gd_wait_for_key_releases();
460 /* forget animation */
461 for (x=0; x<count; x++)
462 SDL_FreeSurface(animation[x]);
463 g_free(animation);
465 return s;
469 static void
470 main_help()
472 const char* strings_menu[]={
473 "R", "SHOW REPLAYS",
474 "I", "SHOW CAVESET INFO",
475 "H", "SHOW HALL OF FAME",
476 "", "",
477 "L", "LOAD CAVESET",
478 "C", "LOAD FROM INSTALLED CAVES",
479 "", "",
480 "E", "ERROR CONSOLE",
481 "A", "ABOUT GDASH",
482 "B", "LICENSE",
483 "", "",
484 "ESCAPE", "QUIT",
485 NULL
488 gd_help(strings_menu);
494 main(int argc, char *argv[])
496 GOptionContext *context;
497 GError *error=NULL;
499 /* command line parsing */
500 context=gd_option_context_new();
501 g_option_context_parse (context, &argc, &argv, &error);
502 g_option_context_free (context);
503 if (error) {
504 g_warning("%s", error->message);
505 g_error_free(error);
508 /* show license? */
509 if (gd_param_license) {
510 char *wrapped=gd_wrap_text(gd_about_license, 72);
512 /* print license and quit. */
513 g_print("%s", wrapped);
514 g_free(wrapped);
515 return 0;
518 gd_settings_init_dirs();
520 gd_install_log_handler();
522 gd_cave_init();
523 gd_cave_db_init();
524 gd_cave_sound_db_init();
525 gd_c64_import_init_tables();
527 gd_load_settings();
529 gd_caveset_clear();
531 gd_clear_error_flag();
533 gd_wait_before_game_over=TRUE;
535 /* we setup mixing and other parameters for our own needs. */
536 /* this is why settings cannot be saved on exit! */
537 #ifdef GD_SOUND
538 gd_sdl_sound=TRUE;
539 gd_sdl_44khz_mixing=TRUE;
540 gd_sdl_16bit_mixing=TRUE;
541 #endif
542 gd_fine_scroll=FALSE;
543 gd_sdl_fullscreen=FALSE;
544 gd_sdl_scale=GD_SCALING_ORIGINAL;
545 gd_sdl_pal_emulation=FALSE;
546 gd_even_line_pal_emu_vertical_scroll=FALSE;
547 gd_random_colors=FALSE;
549 /* and after tweaking settings to our own need, we can init sdl. */
550 putenv("SDL_AUDIODRIVER=dummy"); /* do not output audio; also this will make sure sdl accepts 44khz 16bit. */
551 gd_sdl_init(gd_sdl_scale);
552 gd_create_dark_background();
554 gd_sound_init(44100/50); /* so the buffer will be 1/50th of a second. and we get our favourite 50hz interrupt from the mixer. */
555 /* query audio format from sdl, and start the wave file */
556 Mix_QuerySpec(&frequency, &format, &channels);
557 if (frequency!=44100) {
558 /* something must be really going wrong. */
559 g_critical("Cannot initialize mixer to 44100Hz mixing. The application will not work correctly!");
561 switch(format) {
562 case AUDIO_U8: bits=8; break;
563 case AUDIO_S8: bits=8; break;
564 case AUDIO_U16LSB: bits=16; break;
565 case AUDIO_S16LSB: bits=16; break;
566 case AUDIO_U16MSB: bits=16; break;
567 case AUDIO_S16MSB: bits=16; break;
568 default:
569 g_assert_not_reached();
571 #ifdef GD_SOUND
572 gd_sound_set_music_volume(gd_sound_music_volume_percent);
573 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent);
574 #endif
576 gd_loadfont_default();
577 gd_load_theme();
579 /* LOAD A CAVESET FROM A FILE */
580 if (gd_param_cavenames && gd_param_cavenames[0]) {
581 /* load caveset, "ignore" errors. */
582 if (!gd_caveset_load_from_file (gd_param_cavenames[0], gd_user_config_dir)) {
583 g_critical ("Errors during loading caveset from file '%s'", gd_param_cavenames[0]);
586 else
587 /* set caveset name to this, otherwise it would look ugly */
588 gd_strcpy(gd_caveset_data->name, ("No caveset loaded"));
590 while (!gd_quit) {
591 GdMainMenuSelected s;
593 s=main_menu();
595 switch(s) {
596 case M_NONE:
597 break;
599 case M_INSTALL_THEME:
600 case M_OPTIONS:
601 case M_PLAY:
602 case M_SAVE:
603 case M_SAVE_AS_NEW:
604 g_assert_not_reached();
605 break;
607 case M_REPLAYS:
608 gd_replays_menu(play_replay, FALSE);
609 break;
610 case M_HIGHSCORE:
611 gd_show_highscore(NULL, 0);
612 break;
613 case M_INFO:
614 gd_show_cave_info(NULL);
615 break;
617 /* FILES */
618 case M_LOAD:
619 gd_open_caveset(NULL);
620 break;
621 case M_LOAD_FROM_INSTALLED:
622 gd_open_caveset(gd_system_caves_dir);
623 break;
625 /* INFO */
626 case M_ABOUT:
627 gd_about();
628 break;
629 case M_LICENSE:
630 gd_show_license();
631 break;
632 case M_HELP:
633 main_help();
634 break;
636 /* SETUP */
637 case M_ERRORS:
638 gd_error_console();
639 break;
641 /* EXIT */
642 case M_EXIT:
643 case M_QUIT:
644 gd_quit=TRUE;
645 break;
649 SDL_Quit();
651 /* MUST NOT SAVE SETTINGS */
653 return 0;