/*---------------------------------------------------------------------------*/
#include <SDL.h>
-#include <SDL_image.h>
#include <stdio.h>
#include <string.h>
-#include <errno.h>
#include "glext.h"
#include "config.h"
+#include "video.h"
#include "image.h"
#include "audio.h"
#include "demo.h"
-#include "levels.h"
-#include "game.h"
+#include "progress.h"
#include "gui.h"
#include "set.h"
+#include "tilt.h"
+#include "fs.h"
+#include "common.h"
#include "st_conf.h"
#include "st_title.h"
#include "st_level.h"
#include "st_pause.h"
-#define TITLE "Neverball"
+const char TITLE[] = "Neverball " VERSION;
+const char ICON[] = "icon/neverball.png";
/*---------------------------------------------------------------------------*/
-static void shot(void)
+static int shot_pending;
+
+static void shot_prep(void)
{
- static char filename[MAXSTR];
- static int num = 0;
+ shot_pending = 1;
+}
- sprintf(filename, "screen%02d.png", num++);
+static void shot_take(void)
+{
+ static char filename[MAXSTR];
- image_snap(filename);
+ if (shot_pending)
+ {
+ sprintf(filename, "Screenshots/screen%05d.png", config_screenshot());
+ image_snap(filename);
+ shot_pending = 0;
+ }
}
/*---------------------------------------------------------------------------*/
static void toggle_wire(void)
{
+#if !ENABLE_OPENGLES
static int wire = 0;
if (wire)
glDisable(GL_LIGHTING);
wire = 1;
}
+#endif
}
-static void toggle_fullscreen(void)
+/*---------------------------------------------------------------------------*/
+
+static int handle_key_dn(SDL_Event *e)
{
- int x, y;
+ int d = 1;
+ int c;
+
+ c = e->key.keysym.sym;
+
+ if (config_tst_d(CONFIG_KEY_FORWARD, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), -1.0f);
+
+ else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), +1.0f);
+
+ else if (config_tst_d(CONFIG_KEY_LEFT, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), -1.0f);
- SDL_GetMouseState(&x, &y);
- config_mode(!config_get_d(CONFIG_FULLSCREEN), config_get_d(CONFIG_WIDTH),
- config_get_d(CONFIG_HEIGHT));
- SDL_WarpMouse(x, y);
+ else if (config_tst_d(CONFIG_KEY_RIGHT, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), +1.0f);
+
+ else switch (c)
+ {
+ case SDLK_F10: shot_prep(); break;
+ case SDLK_F9: config_tgl_d(CONFIG_FPS); break;
+ case SDLK_F8: config_tgl_d(CONFIG_NICE); break;
+
+ case SDLK_F7:
+ if (config_cheat())
+ toggle_wire();
+ break;
+ case SDLK_RETURN:
+ d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 1);
+ break;
+ case SDLK_ESCAPE:
+ d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 1);
+ break;
+
+ default:
+ if (SDL_EnableUNICODE(-1))
+ d = st_keybd(e->key.keysym.unicode, 1);
+ else
+ d = st_keybd(e->key.keysym.sym, 1);
+ }
+
+ return d;
}
-/*---------------------------------------------------------------------------*/
+static int handle_key_up(SDL_Event *e)
+{
+ int d = 1;
+ int c;
+
+ c = e->key.keysym.sym;
+
+ if (config_tst_d(CONFIG_KEY_FORWARD, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 0);
+
+ else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 0);
+
+ else if (config_tst_d(CONFIG_KEY_LEFT, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 0);
+
+ else if (config_tst_d(CONFIG_KEY_RIGHT, c))
+ st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 0);
+
+ else switch (c)
+ {
+ case SDLK_RETURN:
+ d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 0);
+ break;
+ case SDLK_ESCAPE:
+ d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 0);
+ break;
+
+ default:
+ d = st_keybd(e->key.keysym.sym, 0);
+ }
+
+ return d;
+}
static int loop(void)
{
SDL_Event e;
int d = 1;
- int c;
+
+ /* Process SDL events. */
while (d && SDL_PollEvent(&e))
{
break;
case SDL_MOUSEBUTTONDOWN:
- d = st_click((e.button.button == SDL_BUTTON_LEFT) ? -1 : 1, 1);
+ d = st_click(e.button.button, 1);
break;
case SDL_MOUSEBUTTONUP:
- d = st_click((e.button.button == SDL_BUTTON_LEFT) ? -1 : 1, 0);
+ d = st_click(e.button.button, 0);
break;
case SDL_KEYDOWN:
-
- c = e.key.keysym.sym;
-
- if (config_tst_d(CONFIG_KEY_FORWARD, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), -JOY_MAX);
-
- else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), +JOY_MAX);
-
- else if (config_tst_d(CONFIG_KEY_LEFT, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), -JOY_MAX);
-
- else if (config_tst_d(CONFIG_KEY_RIGHT, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), +JOY_MAX);
-
- else switch (c)
- {
- case SDLK_F11: toggle_fullscreen(); break;
- case SDLK_F10: shot(); break;
- case SDLK_F9: config_tgl_d(CONFIG_FPS); break;
- case SDLK_F8: config_tgl_d(CONFIG_NICE); break;
-
- case SDLK_F7:
- if (config_cheat())
- toggle_wire();
- break;
-
- case SDLK_RETURN:
- d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 1);
- break;
- case SDLK_ESCAPE:
- d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 1);
- break;
-
- default:
- if (SDL_EnableUNICODE(-1))
- d = st_keybd(e.key.keysym.unicode, 1);
- else
- d = st_keybd(e.key.keysym.sym, 1);
- }
-
+ d = handle_key_dn(&e);
break;
case SDL_KEYUP:
-
- c = e.key.keysym.sym;
-
- if (config_tst_d(CONFIG_KEY_FORWARD, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 1);
-
- else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 1);
-
- else if (config_tst_d(CONFIG_KEY_LEFT, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 1);
-
- else if (config_tst_d(CONFIG_KEY_RIGHT, c))
- st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 1);
-
- else switch (c)
- {
- case SDLK_RETURN:
- d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 0);
- break;
- case SDLK_ESCAPE:
- d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 0);
- break;
-
- default:
- d = st_keybd(e.key.keysym.sym, 0);
- }
+ d = handle_key_up(&e);
+ break;
case SDL_ACTIVEEVENT:
if (e.active.state == SDL_APPINPUTFOCUS)
- if (e.active.gain == 0 && config_get_grab())
- goto_pause();
+ if (e.active.gain == 0 && video_get_grab())
+ goto_state(&st_pause);
break;
case SDL_JOYAXISMOTION:
- st_stick(e.jaxis.axis, e.jaxis.value);
+ st_stick(e.jaxis.axis, JOY_VALUE(e.jaxis.value));
break;
case SDL_JOYBUTTONDOWN:
break;
}
}
- return d;
-}
-/*---------------------------------------------------------------------------*/
+ /* Process events via the tilt sensor API. */
-static char *data_path = NULL;
-static char *demo_path = NULL;
+ if (tilt_stat())
+ {
+ int b;
+ int s;
-static unsigned int display_info = 0;
-static unsigned int replay_demo = 0;
+ st_angle((int) tilt_get_x(),
+ (int) tilt_get_z());
-#define argument_error(option) { \
- fprintf(stderr, _("Option '%s' requires an argument.\n"), option); \
+ while (tilt_get_button(&b, &s))
+ {
+ const int X = config_get_d(CONFIG_JOYSTICK_AXIS_X);
+ const int Y = config_get_d(CONFIG_JOYSTICK_AXIS_Y);
+ const int L = config_get_d(CONFIG_JOYSTICK_DPAD_L);
+ const int R = config_get_d(CONFIG_JOYSTICK_DPAD_R);
+ const int U = config_get_d(CONFIG_JOYSTICK_DPAD_U);
+ const int D = config_get_d(CONFIG_JOYSTICK_DPAD_D);
+
+ if (b == L || b == R || b == U || b == D)
+ {
+ static int pad[4] = { 0, 0, 0, 0 };
+
+ /* Track the state of the D-pad buttons. */
+
+ if (b == L) pad[0] = s;
+ else if (b == R) pad[1] = s;
+ else if (b == U) pad[2] = s;
+ else if (b == D) pad[3] = s;
+
+ /* Convert D-pad button events into joystick axis motion. */
+
+ if (pad[0] && !pad[1]) st_stick(X, -1.0f);
+ else if (pad[1] && !pad[0]) st_stick(X, +1.0f);
+ else st_stick(X, 0.0f);
+
+ if (pad[2] && !pad[3]) st_stick(Y, -1.0f);
+ else if (pad[3] && !pad[2]) st_stick(Y, +1.0f);
+ else st_stick(Y, 0.0f);
+ }
+ else d = st_buttn(b, s);
+ }
+ }
+
+ return d;
}
-static void parse_args(int argc, char **argv)
+/*---------------------------------------------------------------------------*/
+
+static char *opt_data;
+static char *opt_replay;
+static char *opt_level;
+
+#define opt_usage \
+ L_( \
+ "Usage: %s [options ...]\n" \
+ "Options:\n" \
+ " -h, --help show this usage message.\n" \
+ " -v, --version show version.\n" \
+ " -d, --data <dir> use 'dir' as game data directory.\n" \
+ " -r, --replay <file> play the replay 'file'.\n" \
+ " -l, --level <file> load the level 'file'\n" \
+ )
+
+#define opt_error(option) \
+ fprintf(stderr, L_("Option '%s' requires an argument.\n"), option)
+
+static void opt_parse(int argc, char **argv)
{
int i;
- const char *usage = _(
- "Usage: %s [options ...]\n"
- "Options:\n"
- " -h, --help show this usage message.\n"
- " -v, --version show version.\n"
- " -d, --data <dir> use 'dir' as game data directory.\n"
- " -r, --replay <file> play the replay 'file'.\n"
- " -i, --info display info about a replay.\n"
- );
-
/* Scan argument list. */
for (i = 1; i < argc; i++)
{
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
{
- printf(usage, argv[0]);
+ printf(opt_usage, argv[0]);
exit(EXIT_SUCCESS);
}
{
if (i + 1 == argc)
{
- argument_error(argv[i]);
+ opt_error(argv[i]);
exit(EXIT_FAILURE);
}
- data_path = argv[++i];
+ opt_data = argv[++i];
continue;
}
{
if (i + 1 == argc)
{
- argument_error(argv[i]);
+ opt_error(argv[i]);
exit(EXIT_FAILURE);
}
- demo_path = argv[++i];
+ opt_replay = argv[++i];
continue;
}
- if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--info") == 0)
+ if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--level") == 0)
{
- display_info = 1;
+ if (i + 1 == argc)
+ {
+ opt_error(argv[i]);
+ exit(EXIT_FAILURE);
+ }
+ opt_level = argv[++i];
continue;
}
- }
- /* Resolve conflicts. */
+ /* Perform magic on a single unrecognized argument. */
- if (demo_path)
- replay_demo = display_info ? 0 : 1;
- else
- if (display_info)
+ if (argc == 2)
{
- /* FIXME, I'm a required option. */
- fputs(_("Option '--info' requires '--replay'.\n"), stderr);
- exit(EXIT_FAILURE);
+ size_t len = strlen(argv[i]);
+ int level = 0;
+
+ if (len > 4)
+ {
+ char *ext = argv[i] + len - 4;
+
+ if (strcmp(ext, ".map") == 0)
+ strncpy(ext, ".sol", 4);
+
+ if (strcmp(ext, ".sol") == 0)
+ level = 1;
+ }
+
+ if (level)
+ opt_level = argv[i];
+ else
+ opt_replay = argv[i];
+
+ break;
}
+ }
}
-#undef argument_error
+#undef opt_usage
+#undef opt_error
/*---------------------------------------------------------------------------*/
-int main(int argc, char *argv[])
+static int is_replay(struct dir_item *item)
{
- SDL_Joystick *joy = NULL;
- SDL_Surface *icon;
+ return str_ends_with(item->path, ".nbr");
+}
- int t1, t0;
+static int is_score_file(struct dir_item *item)
+{
+ return str_starts_with(item->path, "neverballhs-");
+}
- lang_init("neverball", CONFIG_LOCALE);
+static void make_dirs_and_migrate(void)
+{
+ Array items;
+ int i;
- parse_args(argc, argv);
+ const char *src;
+ char *dst;
- if (!config_data_path(data_path, SET_FILE))
+ if (fs_mkdir("Replays"))
{
- fprintf(stderr, _("Failure to establish game data directory\n"));
- return 1;
+ if ((items = fs_dir_scan("", is_replay)))
+ {
+ for (i = 0; i < array_len(items); i++)
+ {
+ src = DIR_ITEM_GET(items, i)->path;
+ dst = concat_string("Replays/", src, NULL);
+ fs_rename(src, dst);
+ free(dst);
+ }
+
+ fs_dir_free(items);
+ }
}
- if (!config_user_path(NULL))
+ if (fs_mkdir("Scores"))
{
- fprintf(stderr, _("Failure to establish config directory\n"));
+ if ((items = fs_dir_scan("", is_score_file)))
+ {
+ for (i = 0; i < array_len(items); i++)
+ {
+ src = DIR_ITEM_GET(items, i)->path;
+ dst = concat_string("Scores/",
+ src + sizeof ("neverballhs-") - 1,
+ ".txt",
+ NULL);
+ fs_rename(src, dst);
+ free(dst);
+ }
+
+ fs_dir_free(items);
+ }
+ }
+
+ fs_mkdir("Screenshots");
+}
+
+/*---------------------------------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ SDL_Joystick *joy = NULL;
+ int t1, t0;
+
+ if (!fs_init(argv[0]))
+ {
+ fprintf(stderr, "Failure to initialize virtual file system: %s\n",
+ fs_error());
return 1;
}
- /* Initialize SDL system and subsystems */
+ lang_init("neverball");
+
+ opt_parse(argc, argv);
+
+ config_paths(opt_data);
+ make_dirs_and_migrate();
+
+ /* Initialize SDL. */
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) == -1)
{
return 1;
}
- /* Intitialize the configuration */
+ /* Intitialize configuration. */
config_init();
config_load();
- /* Dump replay information and exit. */
+ /* Initialize joystick. */
- if (display_info)
- {
- if (!level_replay(demo_path))
- {
- fprintf(stderr, _("Replay file '%s': %s\n"), demo_path,
- errno ? strerror(errno) : _("Not a replay file"));
- return 1;
- }
- demo_replay_dump_info();
- return 0;
- }
-
- /* Initialize the joystick. */
-
- if (SDL_NumJoysticks() > 0)
+ if (config_get_d(CONFIG_JOYSTICK) && SDL_NumJoysticks() > 0)
{
joy = SDL_JoystickOpen(config_get_d(CONFIG_JOYSTICK_DEVICE));
if (joy)
SDL_JoystickEventState(SDL_ENABLE);
}
- /* Initialize the audio. */
-
- audio_bind(AUD_MENU, 3, "snd/menu.wav");
- audio_bind(AUD_START, 1, _("snd/select.ogg"));
- audio_bind(AUD_READY, 1, _("snd/ready.ogg"));
- audio_bind(AUD_SET, 1, _("snd/set.ogg"));
- audio_bind(AUD_GO, 1, _("snd/go.ogg"));
- audio_bind(AUD_BALL, 2, "snd/ball.ogg");
- audio_bind(AUD_BUMPS, 3, "snd/bumplil.ogg");
- audio_bind(AUD_BUMPM, 3, "snd/bump.ogg");
- audio_bind(AUD_BUMPL, 3, "snd/bumpbig.ogg");
- audio_bind(AUD_COIN, 2, "snd/coin.wav");
- audio_bind(AUD_TICK, 4, "snd/tick.ogg");
- audio_bind(AUD_TOCK, 4, "snd/tock.ogg");
- audio_bind(AUD_SWITCH, 5, "snd/switch.wav");
- audio_bind(AUD_JUMP, 5, "snd/jump.ogg");
- audio_bind(AUD_GOAL, 5, "snd/goal.wav");
- audio_bind(AUD_SCORE, 1, _("snd/record.ogg"));
- audio_bind(AUD_FALL, 1, _("snd/fall.ogg"));
- audio_bind(AUD_TIME, 1, _("snd/time.ogg"));
- audio_bind(AUD_OVER, 1, _("snd/over.ogg"));
- audio_bind(AUD_GROW, 5, "snd/grow.ogg");
- audio_bind(AUD_SHRINK, 5, "snd/shrink.ogg");
+ /* Initialize audio. */
audio_init();
+ tilt_init();
- /* Require 16-bit double buffer with 16-bit depth buffer. */
+ /* Initialize video. */
- SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
- SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
- SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
- SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
- SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ if (!video_init(TITLE, ICON))
+ return 1;
-#ifndef __APPLE__
- /* Set the WM icon */
+ init_state(&st_null);
- icon = IMG_Load(config_data("icon/neverball.png"));
+ /* Initialize demo playback or load the level. */
- if (icon)
+ if (opt_replay &&
+ fs_add_path(dir_name(opt_replay)) &&
+ progress_replay(base_name(opt_replay)))
{
- SDL_WM_SetIcon(icon, NULL);
- SDL_FreeSurface(icon);
+ demo_play_goto(1);
+ goto_state(&st_demo_play);
}
-#endif /* __APPLE__ */
-
- /* Initialize the video. */
-
- if (!config_mode(config_get_d(CONFIG_FULLSCREEN),
- config_get_d(CONFIG_WIDTH), config_get_d(CONFIG_HEIGHT)))
+ else if (opt_level)
{
- fprintf(stderr, "%s\n", SDL_GetError());
- return 1;
- }
+ const char *path = fs_resolve(opt_level);
+ int loaded = 0;
- SDL_WM_SetCaption(TITLE, TITLE);
+ if (path)
+ {
+ /* HACK: must be around for the duration of the game. */
+ static struct level level;
- init_state(&st_null);
+ if (level_load(path, &level))
+ {
+ progress_init(MODE_STANDALONE);
- /* Initialise demo playback. */
+ if (progress_play(&level))
+ {
+ goto_state(&st_level);
+ loaded = 1;
+ }
+ }
+ }
+ else fprintf(stderr, "%s: file is not in game path\n", opt_level);
- if (replay_demo)
- {
- level_replay(demo_path);
- demo_play_goto(1);
- goto_state(&st_demo_play);
+ if (!loaded)
+ goto_state(&st_title);
}
else
goto_state(&st_title);
/* Run the main game loop. */
t0 = SDL_GetTicks();
+
while (loop())
+ {
if ((t1 = SDL_GetTicks()) > t0)
{
- st_timer((t1 - t0) / 1000.f);
- st_paint();
- SDL_GL_SwapBuffers();
+ /* Step the game state. */
+
+ st_timer(0.001f * (t1 - t0));
t0 = t1;
+ /* Render. */
+
+ st_paint(0.001f * t0);
+ shot_take();
+ video_swap();
+
if (config_get_d(CONFIG_NICE))
SDL_Delay(1);
}
+ }
- /* Gracefully close the game */
+ config_save();
- if (SDL_JoystickOpened(0))
+ if (joy)
SDL_JoystickClose(joy);
+ tilt_free();
SDL_Quit();
- config_save();
-
return 0;
}