Move replays found at the top of the user dir into Replays
[neverball] / ball / main.c
1 /*
2  * Copyright (C) 2003 Robert Kooima
3  *
4  * NEVERBALL is  free software; you can redistribute  it and/or modify
5  * it under the  terms of the GNU General  Public License as published
6  * by the Free  Software Foundation; either version 2  of the License,
7  * or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
11  * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
12  * General Public License for more details.
13  */
14
15 /*---------------------------------------------------------------------------*/
16
17 #include <SDL.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <errno.h>
21
22 #include "glext.h"
23 #include "config.h"
24 #include "video.h"
25 #include "image.h"
26 #include "audio.h"
27 #include "demo.h"
28 #include "progress.h"
29 #include "gui.h"
30 #include "set.h"
31 #include "tilt.h"
32 #include "fs.h"
33 #include "common.h"
34
35 #include "st_conf.h"
36 #include "st_title.h"
37 #include "st_demo.h"
38 #include "st_level.h"
39 #include "st_pause.h"
40
41 const char TITLE[] = "Neverball " VERSION;
42 const char ICON[] = "icon/neverball.png";
43
44 /*---------------------------------------------------------------------------*/
45
46 static void shot(void)
47 {
48     static char filename[MAXSTR];
49
50     sprintf(filename, "screen%05d.png", config_screenshot());
51     image_snap(filename);
52 }
53
54 /*---------------------------------------------------------------------------*/
55
56 static void toggle_wire(void)
57 {
58     static int wire = 0;
59
60     if (wire)
61     {
62         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
63         glEnable(GL_TEXTURE_2D);
64         glEnable(GL_LIGHTING);
65         wire = 0;
66     }
67     else
68     {
69         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
70         glDisable(GL_TEXTURE_2D);
71         glDisable(GL_LIGHTING);
72         wire = 1;
73     }
74 }
75
76 /*---------------------------------------------------------------------------*/
77
78 static int loop(void)
79 {
80     SDL_Event e;
81     int d = 1;
82     int c;
83
84     /* Process SDL events. */
85
86     while (d && SDL_PollEvent(&e))
87     {
88         switch (e.type)
89         {
90         case SDL_QUIT:
91             return 0;
92
93         case SDL_MOUSEMOTION:
94             st_point(+e.motion.x,
95                      -e.motion.y + config_get_d(CONFIG_HEIGHT),
96                      +e.motion.xrel,
97                      config_get_d(CONFIG_MOUSE_INVERT)
98                      ? +e.motion.yrel : -e.motion.yrel);
99             break;
100
101         case SDL_MOUSEBUTTONDOWN:
102             d = st_click(e.button.button, 1);
103             break;
104
105         case SDL_MOUSEBUTTONUP:
106             d = st_click(e.button.button, 0);
107             break;
108
109         case SDL_KEYDOWN:
110
111             c = e.key.keysym.sym;
112
113             if (config_tst_d(CONFIG_KEY_FORWARD, c))
114                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), -JOY_MAX);
115
116             else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
117                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), +JOY_MAX);
118
119             else if (config_tst_d(CONFIG_KEY_LEFT, c))
120                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), -JOY_MAX);
121
122             else if (config_tst_d(CONFIG_KEY_RIGHT, c))
123                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), +JOY_MAX);
124
125             else switch (c)
126             {
127             case SDLK_F10:   shot();                    break;
128             case SDLK_F9:    config_tgl_d(CONFIG_FPS);  break;
129             case SDLK_F8:    config_tgl_d(CONFIG_NICE); break;
130
131             case SDLK_F7:
132                 if (config_cheat())
133                     toggle_wire();
134                 break;
135
136             case SDLK_RETURN:
137                 d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 1);
138                 break;
139             case SDLK_ESCAPE:
140                 d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 1);
141                 break;
142
143             default:
144                 if (SDL_EnableUNICODE(-1))
145                     d = st_keybd(e.key.keysym.unicode, 1);
146                 else
147                     d = st_keybd(e.key.keysym.sym, 1);
148             }
149
150             break;
151
152         case SDL_KEYUP:
153
154             c = e.key.keysym.sym;
155
156             if      (config_tst_d(CONFIG_KEY_FORWARD, c))
157                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 1);
158
159             else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
160                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 1);
161
162             else if (config_tst_d(CONFIG_KEY_LEFT, c))
163                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 1);
164
165             else if (config_tst_d(CONFIG_KEY_RIGHT, c))
166                 st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 1);
167
168             else switch (c)
169             {
170             case SDLK_RETURN:
171                 d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 0);
172                 break;
173             case SDLK_ESCAPE:
174                 d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 0);
175                 break;
176
177             default:
178                 d = st_keybd(e.key.keysym.sym, 0);
179             }
180
181         case SDL_ACTIVEEVENT:
182             if (e.active.state == SDL_APPINPUTFOCUS)
183                 if (e.active.gain == 0 && video_get_grab())
184                     goto_pause();
185             break;
186
187         case SDL_JOYAXISMOTION:
188             st_stick(e.jaxis.axis, e.jaxis.value);
189             break;
190
191         case SDL_JOYBUTTONDOWN:
192             d = st_buttn(e.jbutton.button, 1);
193             break;
194
195         case SDL_JOYBUTTONUP:
196             d = st_buttn(e.jbutton.button, 0);
197             break;
198         }
199     }
200
201     /* Process events via the tilt sensor API. */
202
203     if (tilt_stat())
204     {
205         int b;
206         int s;
207
208         st_angle((int) tilt_get_x(),
209                  (int) tilt_get_z());
210
211         while (tilt_get_button(&b, &s))
212         {
213             const int X = config_get_d(CONFIG_JOYSTICK_AXIS_X);
214             const int Y = config_get_d(CONFIG_JOYSTICK_AXIS_Y);
215             const int L = config_get_d(CONFIG_JOYSTICK_DPAD_L);
216             const int R = config_get_d(CONFIG_JOYSTICK_DPAD_R);
217             const int U = config_get_d(CONFIG_JOYSTICK_DPAD_U);
218             const int D = config_get_d(CONFIG_JOYSTICK_DPAD_D);
219
220             if (b == L || b == R || b == U || b == D)
221             {
222                 static int pad[4] = { 0, 0, 0, 0 };
223
224                 /* Track the state of the D-pad buttons. */
225
226                 if      (b == L) pad[0] = s;
227                 else if (b == R) pad[1] = s;
228                 else if (b == U) pad[2] = s;
229                 else if (b == D) pad[3] = s;
230
231                 /* Convert D-pad button events into joystick axis motion. */
232
233                 if      (pad[0] && !pad[1]) st_stick(X, -JOY_MAX);
234                 else if (pad[1] && !pad[0]) st_stick(X, +JOY_MAX);
235                 else                        st_stick(X,        1);
236
237                 if      (pad[2] && !pad[3]) st_stick(Y, -JOY_MAX);
238                 else if (pad[3] && !pad[2]) st_stick(Y, +JOY_MAX);
239                 else                        st_stick(Y,        1);
240             }
241             else d = st_buttn(b, s);
242         }
243     }
244
245     return d;
246 }
247
248 /*---------------------------------------------------------------------------*/
249
250 static char *data_path = NULL;
251 static char *demo_path = NULL;
252
253 static unsigned int display_info = 0;
254 static unsigned int replay_demo  = 0;
255
256 #define usage \
257     L_(                                                                   \
258         "Usage: %s [options ...]\n"                                       \
259         "Options:\n"                                                      \
260         "  -h, --help                show this usage message.\n"          \
261         "  -v, --version             show version.\n"                     \
262         "  -d, --data <dir>          use 'dir' as game data directory.\n" \
263         "  -r, --replay <file>       play the replay 'file'.\n"           \
264         "  -i, --info                display info about a replay.\n"      \
265     )
266
267 #define argument_error(option) { \
268     fprintf(stderr, L_("Option '%s' requires an argument.\n"),  option); \
269 }
270
271 static void parse_args(int argc, char **argv)
272 {
273     int i;
274
275     /* Scan argument list. */
276
277     for (i = 1; i < argc; i++)
278     {
279         if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help")    == 0)
280         {
281             printf(usage, argv[0]);
282             exit(EXIT_SUCCESS);
283         }
284
285         if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0)
286         {
287             printf("%s\n", VERSION);
288             exit(EXIT_SUCCESS);
289         }
290
291         if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--data")    == 0)
292         {
293             if (i + 1 == argc)
294             {
295                 argument_error(argv[i]);
296                 exit(EXIT_FAILURE);
297             }
298             data_path = argv[++i];
299             continue;
300         }
301
302         if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--replay")  == 0)
303         {
304             if (i + 1 == argc)
305             {
306                 argument_error(argv[i]);
307                 exit(EXIT_FAILURE);
308             }
309             demo_path = argv[++i];
310             continue;
311         }
312
313         if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--info")    == 0)
314         {
315             display_info = 1;
316             continue;
317         }
318
319         /* Assume a single unrecognised argument is a replay name. */
320
321         if (argc == 2)
322         {
323             demo_path = argv[i];
324             break;
325         }
326     }
327
328     /* Resolve conflicts. */
329
330     if (demo_path)
331         replay_demo = display_info ? 0 : 1;
332     else
333         if (display_info)
334         {
335             /* FIXME, I'm a required option. */
336             fputs(L_("Option '--info' requires '--replay'.\n"), stderr);
337             exit(EXIT_FAILURE);
338         }
339 }
340
341 #undef usage
342 #undef argument_error
343
344 /*---------------------------------------------------------------------------*/
345
346 static int is_replay(struct dir_item *item)
347 {
348     return strcmp(item->path + strlen(item->path) - 4, ".nbr") == 0;
349 }
350
351 static void make_dirs(void)
352 {
353     Array items;
354     int i;
355
356     const char *src;
357     char *dst;
358
359     if (fs_mkdir("Replays"))
360     {
361         if ((items = fs_dir_scan("", is_replay)))
362         {
363             for (i = 0; i < array_len(items); i++)
364             {
365                 src = DIR_ITEM_GET(items, i)->path;
366                 dst = concat_string("Replays/", src, NULL);
367                 fs_rename(src, dst);
368                 free(dst);
369             }
370
371             fs_dir_free(items);
372         }
373     }
374 }
375
376 int main(int argc, char *argv[])
377 {
378     SDL_Joystick *joy = NULL;
379     int t1, t0, uniform;
380
381     if (!fs_init(argv[0]))
382     {
383         fputs("Failure to initialize virtual file system\n", stderr);
384         return 1;
385     }
386
387     lang_init("neverball");
388
389     parse_args(argc, argv);
390
391     config_paths(data_path);
392     make_dirs();
393
394     /* Initialize SDL system and subsystems */
395
396     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) == -1)
397     {
398         fprintf(stderr, "%s\n", SDL_GetError());
399         return 1;
400     }
401
402     /* Intitialize the configuration */
403
404     config_init();
405     config_load();
406
407     /* Dump replay information and exit. */
408
409     if (display_info && fs_add_path(dir_name(demo_path)))
410     {
411         if (!progress_replay(base_name(demo_path, NULL)))
412         {
413             fprintf(stderr, L_("Replay file '%s': %s\n"), demo_path,
414                     errno ?  strerror(errno) : L_("Not a replay file"));
415             return 1;
416         }
417         demo_replay_dump_info();
418         return 0;
419     }
420
421     /* Initialize the joystick. */
422
423     if (SDL_NumJoysticks() > 0)
424     {
425         joy = SDL_JoystickOpen(config_get_d(CONFIG_JOYSTICK_DEVICE));
426         if (joy)
427             SDL_JoystickEventState(SDL_ENABLE);
428     }
429
430     /* Initialize the audio. */
431
432     audio_init();
433     tilt_init();
434
435     /* Initialize the video. */
436
437     if (!video_init(TITLE, ICON))
438         return 1;
439
440     init_state(&st_null);
441
442     /* Initialise demo playback. */
443
444     if (replay_demo && fs_add_path(dir_name(demo_path)) &&
445         progress_replay(base_name(demo_path, NULL)))
446     {
447         demo_play_goto(1);
448         goto_state(&st_demo_play);
449     }
450     else
451         goto_state(&st_title);
452
453     /* Run the main game loop. */
454
455     uniform = config_get_d(CONFIG_UNIFORM);
456     t0 = SDL_GetTicks();
457
458     while (loop())
459     {
460         t1 = SDL_GetTicks();
461
462         if (uniform)
463         {
464             /* Step the game uniformly, as configured. */
465
466             int u;
467
468             for (u = 0; u < abs(uniform); ++u)
469             {
470                 st_timer(DT);
471                 t0 += (int) (DT * 1000);
472             }
473         }
474         else
475         {
476             /* Step the game state at least up to the current time. */
477
478             while (t1 > t0)
479             {
480                 st_timer(DT);
481                 t0 += (int) (DT * 1000);
482             }
483         }
484
485         /* Render. */
486
487         st_paint(0.001f * t0);
488         video_swap();
489
490         if (uniform < 0)
491             shot();
492
493         if (config_get_d(CONFIG_NICE))
494             SDL_Delay(1);
495     }
496
497     /* Gracefully close the game */
498
499     if (SDL_JoystickOpened(0))
500         SDL_JoystickClose(joy);
501
502     tilt_free();
503     SDL_Quit();
504
505     config_save();
506
507     return 0;
508 }
509
510 /*---------------------------------------------------------------------------*/
511