ball/main: simplify test for an open joystick
[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, "Screenshots/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 int is_score(struct dir_item *item)
352 {
353     return strncmp(item->path, "neverballhs-", sizeof ("neverballhs-") - 1) == 0;
354 }
355
356 static void make_dirs_and_migrate(void)
357 {
358     Array items;
359     int i;
360
361     const char *src;
362     char *dst;
363
364     if (fs_mkdir("Replays"))
365     {
366         if ((items = fs_dir_scan("", is_replay)))
367         {
368             for (i = 0; i < array_len(items); i++)
369             {
370                 src = DIR_ITEM_GET(items, i)->path;
371                 dst = concat_string("Replays/", src, NULL);
372                 fs_rename(src, dst);
373                 free(dst);
374             }
375
376             fs_dir_free(items);
377         }
378     }
379
380     if (fs_mkdir("Scores"))
381     {
382         if ((items = fs_dir_scan("", is_score)))
383         {
384             for (i = 0; i < array_len(items); i++)
385             {
386                 src = DIR_ITEM_GET(items, i)->path;
387                 dst = concat_string("Scores/",
388                                     src + sizeof ("neverballhs-") - 1,
389                                     ".txt",
390                                     NULL);
391                 fs_rename(src, dst);
392                 free(dst);
393             }
394
395             fs_dir_free(items);
396         }
397     }
398
399     fs_mkdir("Screenshots");
400 }
401
402 int main(int argc, char *argv[])
403 {
404     SDL_Joystick *joy = NULL;
405     int t1, t0, uniform;
406     Uint32 flags = 0;
407
408     if (!fs_init(argv[0]))
409     {
410         fputs("Failure to initialize virtual file system\n", stderr);
411         return 1;
412     }
413
414     lang_init("neverball");
415
416     parse_args(argc, argv);
417
418     config_paths(data_path);
419     make_dirs_and_migrate();
420
421     /* Initialize SDL system and subsystems */
422
423     flags |= SDL_INIT_VIDEO;
424     flags |= SDL_INIT_AUDIO;
425     flags |= config_get_d(CONFIG_JOYSTICK) ? SDL_INIT_JOYSTICK : 0;
426
427     if (SDL_Init(flags) == -1)
428     {
429         fprintf(stderr, "%s\n", SDL_GetError());
430         return 1;
431     }
432
433     /* Intitialize the configuration */
434
435     config_init();
436     config_load();
437
438     /* Dump replay information and exit. */
439
440     if (display_info && fs_add_path(dir_name(demo_path)))
441     {
442         if (!progress_replay(base_name(demo_path, NULL)))
443         {
444             fprintf(stderr, L_("Replay file '%s': %s\n"), demo_path,
445                     errno ?  strerror(errno) : L_("Not a replay file"));
446             return 1;
447         }
448         demo_replay_dump_info();
449         return 0;
450     }
451
452     /* Initialize the joystick. */
453
454     if (SDL_WasInit(SDL_INIT_JOYSTICK) && SDL_NumJoysticks() > 0)
455     {
456         joy = SDL_JoystickOpen(config_get_d(CONFIG_JOYSTICK_DEVICE));
457         if (joy)
458             SDL_JoystickEventState(SDL_ENABLE);
459     }
460
461     /* Initialize the audio. */
462
463     audio_init();
464     tilt_init();
465
466     /* Initialize the video. */
467
468     if (!video_init(TITLE, ICON))
469         return 1;
470
471     init_state(&st_null);
472
473     /* Initialise demo playback. */
474
475     if (replay_demo && fs_add_path(dir_name(demo_path)) &&
476         progress_replay(base_name(demo_path, NULL)))
477     {
478         demo_play_goto(1);
479         goto_state(&st_demo_play);
480     }
481     else
482         goto_state(&st_title);
483
484     /* Run the main game loop. */
485
486     uniform = config_get_d(CONFIG_UNIFORM);
487     t0 = SDL_GetTicks();
488
489     while (loop())
490     {
491         t1 = SDL_GetTicks();
492
493         if (uniform)
494         {
495             /* Step the game uniformly, as configured. */
496
497             int u;
498
499             for (u = 0; u < abs(uniform); ++u)
500             {
501                 st_timer(DT);
502                 t0 += (int) (DT * 1000);
503             }
504         }
505         else
506         {
507             /* Step the game state at least up to the current time. */
508
509             while (t1 > t0)
510             {
511                 st_timer(DT);
512                 t0 += (int) (DT * 1000);
513             }
514         }
515
516         /* Render. */
517
518         st_paint(0.001f * t0);
519         video_swap();
520
521         if (uniform < 0)
522             shot();
523
524         if (config_get_d(CONFIG_NICE))
525             SDL_Delay(1);
526     }
527
528     /* Gracefully close the game */
529
530     if (joy)
531         SDL_JoystickClose(joy);
532
533     tilt_free();
534     SDL_Quit();
535
536     config_save();
537
538     return 0;
539 }
540
541 /*---------------------------------------------------------------------------*/
542