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 <SDL/SDL_mixer.h>
18 #include "IMG_savepng.h"
22 #include "caveobject.h"
23 #include "caveengine.h"
24 #include "cavesound.h"
27 #include "c64import.h"
37 /* for saving the wav file */
38 static unsigned int wavlen
;
39 static unsigned int frame
;
41 static int frequency
, channels
, bits
;
43 /* this function saves the wav file,
44 and also does the timing! */
46 mixfunc(void *udata
, Uint8
*stream
, int len
)
50 if (fwrite(stream
, 1, len
, (FILE *)udata
)!=len
) {
51 g_critical("Cannot write to wav file!");
54 ev
.type
=SDL_USEREVENT
;
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 */
66 int statusbar_since
=0; /* count number of frames from when the outoftime or paused event happened. we need it for timeout header flash */
68 GdStatusBarColors cols_struct
;
69 GdStatusBarColors
*cols
=&cols_struct
;
73 char *filename
, *text
;
77 /* start the wave file */
78 filename
=g_strdup_printf("%s.wav", filename_prefix
);
79 wavfile
=fopen(filename
, "wb");
81 g_critical("Cannot open %s for sound output", filename
);
86 fseek(wavfile
, 44, SEEK_SET
); /* 44bytes offset: start of data in a wav file */
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 */
97 while (!exit_game
&& SDL_WaitEvent(&event
)) {
101 case SDL_QUIT
: /* application closed by window manager */
107 switch(event
.key
.keysym
.sym
) {
114 if (gd_keystate
[SDLK_LCTRL
]||gd_keystate
[SDLK_RCTRL
]) {
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
);
131 /* state of game, returned by gd_game_main_int */
133 case GD_GAME_INVALID_STATE
:
134 g_assert_not_reached();
137 case GD_GAME_SHOW_STORY
:
138 case GD_GAME_SHOW_STORY_WAIT
:
139 /* should not happen */
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 */
153 case GD_GAME_NOTHING
:
154 /* normally continue. */
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 */
162 case GD_GAME_TIMEOUT_NOW
:
164 gd_showheader_game(game
, statusbar_since
, cols
, FALSE
); /* also update the status bar here. */
168 case GD_GAME_NO_MORE_LIVES
:
169 /* should not reach */
173 exit_game
=TRUE
; /* game stopped, this could be a replay or a snapshot */
176 case GD_GAME_GAME_OVER
:
178 /* ... but should not reach. */
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. */
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. */
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);
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
);
214 /* once per iteration, show it to the user. */
215 if (show
|| frame
%10==0)
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
);
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 */
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
);
256 /* draws the title screen (maybe that of the game) */
260 SDL_Surface
**animation
;
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
);
272 /* forget animation */
273 for (x
=0; animation
[x
]!=NULL
; x
++)
274 SDL_FreeSurface(animation
[x
]);
280 play_replay(GdCave
*cave
, GdReplay
*replay
)
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
);
293 /* draw the title screen, so it will be the first frame of the movie */
296 game
=gd_game_new_replay(cave
, replay
);
297 play_game_func(game
, prefix
);
301 /* wait for keys, as for example escape may be pressed at this time */
302 gd_wait_for_key_releases();
313 static GdMainMenuSelected
316 const int image_centered_threshold
=164*gd_scale
;
317 SDL_Surface
**animation
;
320 GdMainMenuSelected s
;
324 gboolean show_status
;
325 gboolean title_image_shown
;
327 animation
=gd_get_title_animation(FALSE
);
329 /* count number of frames */
331 while(animation
[count
]!=NULL
)
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 */
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 */
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;
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) {
364 rect
.h
=image_centered_threshold
;
365 SDL_SetClipRect(gd_screen
, &rect
);
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
);
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");
383 title_image_shown
=FALSE
;
384 while(!gd_quit
&& s
==M_NONE
) {
387 /* play animation. if more frames, always draw. if only one frame, draw only once */
388 if (!title_image_shown
|| count
>1) {
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 */
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 */
401 while (SDL_PollEvent(&event
)) {
403 case SDL_QUIT
: /* application closed by window manager */
409 switch(event
.key
.keysym
.sym
) {
410 case SDLK_ESCAPE
: /* escape: quit app */
415 case SDLK_F1
: /* f1: help */
419 case SDLK_l
: /* load file */
423 s
=M_LOAD_FROM_INSTALLED
;
429 case SDLK_i
: /* caveset info */
432 case SDLK_h
: /* h: highscore */
444 case SDLK_e
: /* show error console */
448 default: /* other keys do nothing */
451 default: /* other events we don't care */
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
]);
472 const char* strings_menu
[]={
474 "I", "SHOW CAVESET INFO",
475 "H", "SHOW HALL OF FAME",
478 "C", "LOAD FROM INSTALLED CAVES",
480 "E", "ERROR CONSOLE",
488 gd_help(strings_menu
);
494 main(int argc
, char *argv
[])
496 GOptionContext
*context
;
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
);
504 g_warning("%s", error
->message
);
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
);
518 gd_settings_init_dirs();
520 gd_install_log_handler();
524 gd_cave_sound_db_init();
525 gd_c64_import_init_tables();
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! */
539 gd_sdl_44khz_mixing
=TRUE
;
540 gd_sdl_16bit_mixing
=TRUE
;
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!");
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;
569 g_assert_not_reached();
572 gd_sound_set_music_volume(gd_sound_music_volume_percent
);
573 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent
);
576 gd_loadfont_default();
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]);
587 /* set caveset name to this, otherwise it would look ugly */
588 gd_strcpy(gd_caveset_data
->name
, ("No caveset loaded"));
591 GdMainMenuSelected s
;
599 case M_INSTALL_THEME
:
604 g_assert_not_reached();
608 gd_replays_menu(play_replay
, FALSE
);
611 gd_show_highscore(NULL
, 0);
614 gd_show_cave_info(NULL
);
619 gd_open_caveset(NULL
);
621 case M_LOAD_FROM_INSTALLED
:
622 gd_open_caveset(gd_system_caves_dir
);
651 /* MUST NOT SAVE SETTINGS */