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.
18 #include <glib/gi18n.h>
21 #include "caveobject.h"
22 #include "caveengine.h"
23 #include "cavesound.h"
26 #include "c64import.h"
39 static char *username
;
46 const char* strings_menu
[]={
47 gd_key_name(gd_sdl_key_left
), "MOVE LEFT",
48 gd_key_name(gd_sdl_key_right
), "MOVE RIGHT",
49 gd_key_name(gd_sdl_key_up
), "MOVE UP",
50 gd_key_name(gd_sdl_key_down
), "MOVE DOWN",
51 gd_key_name(gd_sdl_key_fire_1
), "FIRE (SNAP)",
52 gd_key_name(gd_sdl_key_suicide
), "SUICIDE",
54 "F", "FAST FORWARD (HOLD)",
55 "SHIFT", "STATUS BAR (HOLD)",
58 "ESC", "RESTART LEVEL",
62 "CTRL-Q", "QUIT PROGRAM",
66 gd_help(strings_menu
);
74 showheader_pause(GdStatusBarColors
*cols
)
76 gd_clear_header(cols
->background
);
77 gd_blittext(gd_screen
, -1, gd_statusbar_mid
, cols
->default_color
, "SPACEBAR TO RESUME");
81 showheader_gameover(GdStatusBarColors
*cols
)
83 gd_clear_header(cols
->background
);
84 gd_blittext(gd_screen
, -1, gd_statusbar_mid
, cols
->default_color
, "G A M E O V E R");
88 /* generate an user event */
90 timer_callback(Uint32 interval
, void *param
)
94 ev
.type
=SDL_USEREVENT
;
101 /* the game itself */
103 play_game_func(GdGame
*game
)
105 gboolean toggle
=FALSE
; /* this is used to divide the rate of the user interrupt by 2, if no fine scrolling requested */
107 gboolean show_highscore
;
108 int statusbar_since
=0; /* count number of frames from when the outoftime or paused event happened. */
112 gboolean restart
, suicide
; /* for sdl key_downs */
113 GdStatusBarColors cols_struct
;
114 GdStatusBarColors
*cols
=&cols_struct
;
117 /* install the sdl timer which will generate events to control the speed of the game and drawing, at an 50hz rate; 1/50hz=20ms */
118 tim
=SDL_AddTimer(20, timer_callback
, NULL
);
120 suicide
=FALSE
; /* detected suicide and restart level keys */
123 show_highscore
=FALSE
;
125 while (!exit_game
&& SDL_WaitEvent(&event
)) {
127 GdDirection player_move
;
130 case SDL_QUIT
: /* application closed by window manager */
136 switch(event
.key
.keysym
.sym
) {
144 if (gd_keystate
[SDLK_LCTRL
]||gd_keystate
[SDLK_RCTRL
]) {
150 gd_show_cave_info(game
->original_cave
);
155 statusbar_since
=0; /* count frames "paused" */
156 gd_sound_off(); /* if paused, no sound. */
163 gd_sound_off(); /* switch off sounds when showing help. */
165 /* no need to turn on sounds; next cave iteration will restore them. */
166 /* also, do not worry about 25hz user events, as the help function will simply drop them. */
175 player_move
=gd_direction_from_keypress(gd_up(), gd_down(), gd_left(), gd_right());
176 /* tell the interrupt "20ms has passed" */
177 state
=gd_game_main_int(game
, 20, player_move
, gd_fire(), suicide
, restart
, !paused
&& !game
->out_of_window
, FALSE
, gd_keystate
[SDLK_f
]);
179 /* state of game, returned by gd_game_main_int */
181 case GD_GAME_INVALID_STATE
:
182 g_assert_not_reached();
185 case GD_GAME_SHOW_STORY
:
187 gd_title_line(game
->cave
->name
);
188 gd_status_line("FIRE: CONTINUE");
189 wrapped
=gd_wrap_text(game
->cave
->story
->str
, gd_screen
->w
/gd_font_width()-2);
190 gd_blittext_n(gd_screen
, gd_font_width(), gd_line_height()*2, GD_GDASH_LIGHTBLUE
, wrapped
);
194 case GD_GAME_CAVE_LOADED
:
195 /* select colors, prepare drawing etc. */
196 gd_select_pixbuf_colors(game
->cave
->color0
, game
->cave
->color1
, game
->cave
->color2
, game
->cave
->color3
, game
->cave
->color4
, game
->cave
->color5
);
197 gd_scroll_to_origin();
198 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! */
199 /* select status bar colors here, as some depend on actual cave colors */
200 gd_play_game_select_status_bar_colors(cols
, game
->cave
);
201 gd_showheader_uncover(game
, cols
, TRUE
); /* true = show "playing replay" if necessary */
202 suicide
=FALSE
; /* clear detected keypresses, so we do not "remember" them from previous cave runs */
206 case GD_GAME_NOTHING
:
207 /* normally continue. */
210 case GD_GAME_LABELS_CHANGED
:
211 gd_showheader_game(game
, statusbar_since
, cols
, TRUE
); /* true = show "playing replay" if necessary */
212 suicide
=FALSE
; /* clear detected keypresses, as cave was iterated and they were processed */
215 case GD_GAME_TIMEOUT_NOW
:
217 gd_showheader_game(game
, statusbar_since
, cols
, TRUE
); /* also update the status bar here. */ /* true = show "playing replay" if necessary */
218 suicide
=FALSE
; /* clear detected keypresses, as cave was iterated and they were processed */
221 case GD_GAME_NO_MORE_LIVES
:
222 showheader_gameover(cols
);
226 exit_game
=TRUE
; /* game stopped, this could be a replay or a snapshot */
229 case GD_GAME_GAME_OVER
:
231 show_highscore
=TRUE
; /* normal game stopped, may jump to highscore later. */
237 /* for the sdl version, it seems nicer if we first scroll, and then draw. */
238 /* scrolling for the sdl version will merely invalidate the whole gfx buffer. */
239 /* if drawcave was before scrolling, it would draw, scroll would invalidate, and then it should be drawn again */
240 /* only do the drawing if the cave already exists. */
242 if (game
->gfx_buffer
) {
243 /* if fine scrolling, scroll at 50hz. if not, only scroll at every second call, so 25hz. */
244 if (game
->cave
&& (toggle
|| gd_fine_scroll
))
245 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 */
247 gd_drawcave(gd_screen
, game
); /* draw the cave. */
249 /* may show pause header. but only if the cave already exists in a gfx buffer - or else we are seeing a story at the moment */
251 if (statusbar_since
/50%4==0)
252 showheader_pause(cols
);
254 gd_showheader_game(game
, statusbar_since
, cols
, TRUE
); /* true = show "playing replay" if necessary */
258 SDL_Flip(gd_screen
); /* can always be called, as it keeps track of dirty regions of the screen */
261 SDL_RemoveTimer(tim
);
262 gd_sound_off(); /* we stop sounds. gd_game_free would do it, but we need the game struct for highscore */
264 /* (if stopped because of a quit event, do not bother highscore at all) */
265 if (!gd_quit
&& show_highscore
&& gd_is_highscore(gd_caveset_data
->highscore
, game
->player_score
)) {
268 /* enter to highscore table */
269 rank
=gd_add_highscore(gd_caveset_data
->highscore
, game
->player_name
, game
->player_score
);
270 gd_show_highscore(NULL
, rank
);
279 play_game(int start_cave
, int start_level
)
284 /* ask name of player. */
285 name
=gd_input_string("ENTER YOUR NAME", username
);
293 game
=gd_game_new(username
, start_cave
, start_level
);
294 play_game_func(game
);
299 play_replay(GdCave
*cave
, GdReplay
*replay
)
305 game
=gd_game_new_replay(cave
, replay
);
306 play_game_func(game
);
308 /* wait for keys, as for example escape may be pressed at this time */
309 gd_wait_for_key_releases();
311 gd_music_play_random();
315 previous_selectable_cave(int cavenum
)
323 cave
=gd_return_nth_cave(cn
);
324 if (gd_all_caves_selectable
|| cave
->selectable
)
328 /* if not found any suitable, return current */
333 next_selectable_cave(int cavenum
)
337 while (cn
<gd_caveset_count()-1) {
341 cave
=gd_return_nth_cave(cn
);
342 if (gd_all_caves_selectable
|| cave
->selectable
)
346 /* if not found any suitable, return current */
358 static GdMainMenuSelected
361 const int image_centered_threshold
=164*gd_scale
;
362 SDL_Surface
**animation
;
365 GdMainMenuSelected s
;
368 int y_gameline
, y_caveline
;
370 gboolean show_status
;
371 gboolean title_image_shown
;
373 animation
=gd_get_title_animation(FALSE
);
375 /* count number of frames */
377 while(animation
[count
]!=NULL
)
380 /* start playing after creating title animation above */
381 gd_music_play_random();
383 /* height of title screen, then decide which lines to show and where */
384 image_h
=animation
[0]->h
;
385 if (gd_screen
->h
-image_h
< 2*gd_font_height()) {
386 /* less than 2 lines - place for only one line of text. */
388 y_caveline
=image_h
+ (gd_screen
->h
-image_h
-gd_font_height())/2; /* centered in the small place */
391 if (gd_screen
->h
-image_h
< 3*gd_font_height()) {
392 /* more than 2, less than 3 - place for status bar. game name is not shown, as this will */
393 /* only be true for a game with its own title screen, and i decided that in that case it */
394 /* would make more sense. */
396 y_caveline
=image_h
+ (gd_screen
->h
-image_h
-gd_font_height()*2)/2; /* centered there */
399 image_h
=image_centered_threshold
; /* "minimum" height for the image, and it will be centered */
400 /* more than 3, less than 4 - place for everything. */
401 y_gameline
=image_h
+ (gd_screen
->h
-image_h
-gd_font_height()-gd_font_height()*2)/2; /* centered with cave name */
402 y_caveline
=y_gameline
+gd_font_height();
403 /* if there is some place, move the cave line one pixel lower. */
404 if (y_caveline
+2*gd_font_height()<gd_screen
->h
)
405 y_caveline
+=1*gd_scale
;
409 /* fill whole gd_screen with black */
410 SDL_FillRect(gd_screen
, NULL
, SDL_MapRGB(gd_screen
->format
, 0, 0, 0));
411 /* then fill with the tile, so if the title image is very small, there is no black border */
412 /* only do that if the image is significantly smaller */
413 if (animation
[0]->w
< gd_screen
->w
*9/10 || animation
[0]->h
< image_centered_threshold
*9/10) {
419 rect
.h
=image_centered_threshold
;
420 SDL_SetClipRect(gd_screen
, &rect
);
422 SDL_SetClipRect(gd_screen
, NULL
);
425 if (y_gameline
!=-1) {
426 x
=gd_blittext_n(gd_screen
, 0, y_gameline
, GD_GDASH_WHITE
, "GAME: ");
427 x
=gd_blittext_n(gd_screen
, x
, y_gameline
, GD_GDASH_YELLOW
, gd_caveset_data
->name
);
428 if (gd_caveset_edited
) /* if edited (new replays), draw a sign XXX */
429 x
=gd_blittext_n(gd_screen
, x
, y_gameline
, GD_GDASH_RED
, " *");
432 if (gd_caveset_has_replays())
433 gd_status_line("CRSR: SELECT SPC: PLAY R: REPLAYS");
435 gd_status_line("CRSR: SELECT SPC: PLAY F1: HELP");
438 if (gd_has_new_error())
439 /* show error flag */
440 gd_blittext_n(gd_screen
, gd_screen
->w
-gd_font_width(), gd_screen
->h
-gd_font_height(), GD_GDASH_RED
, "E");
443 cavenum
=gd_caveset_last_selected
;
444 levelnum
=gd_caveset_last_selected_level
;
446 /* here we process the keys and joystick in an ugly way, so wait for releases first. */
447 gd_wait_for_key_releases();
449 title_image_shown
=FALSE
;
450 while(!gd_quit
&& s
==M_NONE
) {
453 static int i
=0; /* used to slow down cave selection, as this function must run at 25hz for animation */
456 if (!title_image_shown
|| count
>1) {
457 animcycle
=(animcycle
+1)%count
;
458 dest_pos
.x
=(gd_screen
->w
-animation
[animcycle
]->w
)/2; /* centered horizontally */
459 if (animation
[animcycle
]->h
<image_centered_threshold
)
460 dest_pos
.y
=(image_centered_threshold
-animation
[animcycle
]->h
)/2; /* centered vertically */
462 dest_pos
.y
=0; /* top of screen, as not too much space left for info lines */
463 SDL_BlitSurface(animation
[animcycle
], 0, gd_screen
, &dest_pos
);
464 title_image_shown
=TRUE
; /* shown at least once, so if not animated, we do not waste cpu */
468 gd_clear_line(gd_screen
, y_caveline
);
469 x
=gd_blittext_n(gd_screen
, 0, y_caveline
, GD_GDASH_WHITE
, "CAVE: ");
470 x
=gd_blittext_n(gd_screen
, 0, y_caveline
, GD_GDASH_WHITE
, "CAVE: ");
471 x
=gd_blittext_n(gd_screen
, x
, y_caveline
, GD_GDASH_YELLOW
, gd_return_nth_cave(cavenum
)->name
);
472 x
=gd_blittext_n(gd_screen
, x
, y_caveline
, GD_GDASH_WHITE
, "/");
473 x
=gd_blittext_printf_n(gd_screen
, x
, y_caveline
, GD_GDASH_YELLOW
, "%d", levelnum
+1);
479 while (SDL_PollEvent(&event
)) {
481 case SDL_QUIT
: /* application closed by window manager */
487 switch(event
.key
.keysym
.sym
) {
488 case SDLK_ESCAPE
: /* escape: quit app */
493 case SDLK_RETURN
: /* enter: start playing */
497 case SDLK_l
: /* load file */
501 case SDLK_s
: /* save file */
505 case SDLK_i
: /* caveset info */
509 case SDLK_n
: /* save file with new filename */
514 s
=M_LOAD_FROM_INSTALLED
;
529 case SDLK_o
: /* s: settings */
533 case SDLK_t
: /* t: install theme */
537 case SDLK_e
: /* show error console */
541 case SDLK_h
: /* h: highscore */
545 case SDLK_F1
: /* f1: help */
549 default: /* other keys do nothing */
552 default: /* other events we don't care */
557 /* not all frames process the joystick - otherwise it would be too fast. */
558 waitcycle
=(waitcycle
+1)%2;
560 /* we use gd_down() and functions like that, so the joystick also works */
562 /* joystick or keyboard up */
563 if (gd_up() && i
==0) {
569 /* joystick or keyboard down */
570 if (gd_down() && i
==0) {
576 /* joystick or keyboard left */
577 if (gd_left() && i
==0)
578 cavenum
=previous_selectable_cave(cavenum
);
580 /* joystick or keyboard right */
581 if (gd_right() && i
==0)
582 cavenum
=next_selectable_cave(cavenum
);
585 if (gd_space_or_enter_or_fire())
588 SDL_Delay(40); /* 25 fps - we need exactly this for the animation */
591 gd_wait_for_key_releases();
593 /* forget animation */
594 for (x
=0; x
<count
; x
++)
595 SDL_FreeSurface(animation
[x
]);
604 const char* strings_menu
[]={
605 "CURSOR", "SELECT CAVE&LEVEL",
606 "SPACE, RETURN", "PLAY GAME",
607 "I", "SHOW CAVESET INFO",
608 "H", "SHOW HALL OF FAME",
612 "C", "LOAD FROM INSTALLED CAVES",
613 "S", "SAVE CAVESET (REPLAYS)",
614 "N", "SAVE AS NEW FILE",
617 "T", "INSTALL THEME",
618 "E", "ERROR CONSOLE",
626 gd_help(strings_menu
);
631 /* save caveset to specified directory, and pop up error message if failed */
632 /* if not, call function to remember file name. */
634 caveset_save(const gchar
*filename
)
638 saved
=gd_caveset_save(filename
);
642 gd_caveset_file_operation_successful(filename
);
645 /* ask for new filename to save file to. then do the save. */
647 save_file_as(const char *directory
)
653 gd_last_folder
=g_strdup(g_get_home_dir());
655 filter
=g_strjoinv(";", gd_caveset_extensions
);
656 filename
=gd_select_file("SAVE CAVESET AS", directory
?directory
:gd_last_folder
, filter
, TRUE
);
659 /* if file selected */
661 /* remember last directory */
662 g_free(gd_last_folder
);
663 gd_last_folder
=g_path_get_dirname(filename
);
665 caveset_save(filename
);
671 /* if the caveset has a valid bdcff file name, save caves into that. if not, call the "save file as" function */
675 if (!gd_caveset_filename
)
676 /* if no filename remembered, rather start the save_as function, which asks for one. */
679 /* if given, save. */
680 gd_caveset_save(gd_caveset_filename
);
685 main(int argc
, char *argv
[])
687 GOptionContext
*context
;
690 /* command line parsing */
691 context
=gd_option_context_new();
692 g_option_context_parse (context
, &argc
, &argv
, &error
);
693 g_option_context_free (context
);
695 g_warning("%s", error
->message
);
700 if (gd_param_license
) {
701 char *wrapped
=gd_wrap_text(gd_about_license
, 72);
703 /* print license and quit. */
704 g_print("%s", wrapped
);
709 gd_settings_init_dirs();
711 gd_install_log_handler();
715 gd_cave_sound_db_init();
716 gd_c64_import_init_tables();
722 gd_clear_error_flag();
724 gd_wait_before_game_over
=TRUE
;
726 /* LOAD A CAVESET FROM A FILE, OR AN INTERNAL ONE */
727 /* if remaining arguments, they are filenames */
728 if (gd_param_cavenames
&& gd_param_cavenames
[0]) {
729 /* load caveset, "ignore" errors. */
730 if (!gd_caveset_load_from_file (gd_param_cavenames
[0], gd_user_config_dir
)) {
731 g_critical (_("Errors during loading caveset from file '%s'"), gd_param_cavenames
[0]);
733 gd_caveset_file_operation_successful(gd_param_cavenames
[0]);
735 else if (gd_param_internal
) {
736 /* if specified an internal caveset */
737 if (!gd_caveset_load_from_internal (gd_param_internal
-1, gd_user_config_dir
))
738 g_critical (_("%d: no such internal caveset"), gd_param_internal
);
741 /* if failed or nothing requested, load default */
742 if (gd_caveset
==NULL
)
743 gd_caveset_load_from_internal (0, gd_user_config_dir
);
745 /* if cave or level values given, check range */
747 if (gd_param_cave
<1 || gd_param_cave
>=gd_caveset_count() || gd_param_level
<1 || gd_param_level
>5) {
748 g_critical (_("Invalid cave or level number!\n"));
753 gd_sdl_init(gd_sdl_scale
);
754 gd_create_dark_background();
757 gd_sound_set_music_volume(gd_sound_music_volume_percent
);
758 gd_sound_set_chunk_volumes(gd_sound_chunks_volume_percent
);
761 gd_loadfont_default();
764 username
=g_strdup(g_get_real_name());
767 GdMainMenuSelected s
;
769 /* if a cavenum was given on the command line */
771 /* do as if it was selected from the menu */
772 cavenum
=gd_param_cave
-1;
773 levelnum
=gd_param_level
-1;
776 gd_param_cave
=0; /* and forget it */
787 /* get selected cave&level, then play */
788 gd_caveset_last_selected
=cavenum
;
789 gd_caveset_last_selected_level
=levelnum
;
790 play_game(cavenum
, levelnum
);
793 gd_replays_menu(play_replay
, TRUE
);
796 gd_show_highscore(NULL
, 0);
799 gd_show_cave_info(NULL
);
804 gd_open_caveset(NULL
);
806 case M_LOAD_FROM_INSTALLED
:
807 gd_open_caveset(gd_system_caves_dir
);
829 case M_INSTALL_THEME
:
846 /* if quit requested, check if the caveset is edited. */
847 /* and the user wants to save, ignore the quit request */
849 /* ugly hack. gd_ask_yes_no would not work if quit is true. so set to false */
851 /* and if discards, set to true */
852 if (gd_discard_changes())
859 gd_save_highscore(gd_user_config_dir
);