Treat angle values as floating point numbers
[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
21 #include "glext.h"
22 #include "config.h"
23 #include "video.h"
24 #include "image.h"
25 #include "audio.h"
26 #include "demo.h"
27 #include "progress.h"
28 #include "gui.h"
29 #include "set.h"
30 #include "tilt.h"
31 #include "fs.h"
32 #include "common.h"
33
34 #include "st_conf.h"
35 #include "st_title.h"
36 #include "st_demo.h"
37 #include "st_level.h"
38 #include "st_pause.h"
39
40 const char TITLE[] = "Neverball " VERSION;
41 const char ICON[] = "icon/neverball.png";
42
43 /*---------------------------------------------------------------------------*/
44
45 static int shot_pending;
46
47 static void shot_prep(void)
48 {
49     shot_pending = 1;
50 }
51
52 static void shot_take(void)
53 {
54     static char filename[MAXSTR];
55
56     if (shot_pending)
57     {
58         sprintf(filename, "Screenshots/screen%05d.png", config_screenshot());
59         image_snap(filename);
60         shot_pending = 0;
61     }
62 }
63
64 /*---------------------------------------------------------------------------*/
65
66 static void toggle_wire(void)
67 {
68 #if !ENABLE_OPENGLES
69     static int wire = 0;
70
71     if (wire)
72     {
73         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
74         glEnable(GL_TEXTURE_2D);
75         glEnable(GL_LIGHTING);
76         wire = 0;
77     }
78     else
79     {
80         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
81         glDisable(GL_TEXTURE_2D);
82         glDisable(GL_LIGHTING);
83         wire = 1;
84     }
85 #endif
86 }
87
88 /*---------------------------------------------------------------------------*/
89
90 static int handle_key_dn(SDL_Event *e)
91 {
92     int d = 1;
93     int c;
94
95     c = e->key.keysym.sym;
96
97     if (config_tst_d(CONFIG_KEY_FORWARD, c))
98         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), -1.0f);
99
100     else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
101         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), +1.0f);
102
103     else if (config_tst_d(CONFIG_KEY_LEFT, c))
104         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), -1.0f);
105
106     else if (config_tst_d(CONFIG_KEY_RIGHT, c))
107         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), +1.0f);
108
109     else switch (c)
110     {
111     case SDLK_F10:   shot_prep();               break;
112     case SDLK_F9:    config_tgl_d(CONFIG_FPS);  break;
113     case SDLK_F8:    config_tgl_d(CONFIG_NICE); break;
114
115     case SDLK_F7:
116         if (config_cheat())
117             toggle_wire();
118         break;
119     case SDLK_RETURN:
120         d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 1);
121         break;
122     case SDLK_ESCAPE:
123         d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 1);
124         break;
125
126     default:
127         if (SDL_EnableUNICODE(-1))
128             d = st_keybd(e->key.keysym.unicode, 1);
129         else
130             d = st_keybd(e->key.keysym.sym, 1);
131     }
132
133     return d;
134 }
135
136 static int handle_key_up(SDL_Event *e)
137 {
138     int d = 1;
139     int c;
140
141     c = e->key.keysym.sym;
142
143     if (config_tst_d(CONFIG_KEY_FORWARD, c))
144         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 0);
145
146     else if (config_tst_d(CONFIG_KEY_BACKWARD, c))
147         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_Y), 0);
148
149     else if (config_tst_d(CONFIG_KEY_LEFT, c))
150         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 0);
151
152     else if (config_tst_d(CONFIG_KEY_RIGHT, c))
153         st_stick(config_get_d(CONFIG_JOYSTICK_AXIS_X), 0);
154
155     else switch (c)
156     {
157     case SDLK_RETURN:
158         d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_A), 0);
159         break;
160     case SDLK_ESCAPE:
161         d = st_buttn(config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT), 0);
162         break;
163
164     default:
165         d = st_keybd(e->key.keysym.sym, 0);
166     }
167
168     return d;
169 }
170
171 static int loop(void)
172 {
173     SDL_Event e;
174     int d = 1;
175
176     /* Process SDL events. */
177
178     while (d && SDL_PollEvent(&e))
179     {
180         switch (e.type)
181         {
182         case SDL_QUIT:
183             return 0;
184
185         case SDL_MOUSEMOTION:
186             st_point(+e.motion.x,
187                      -e.motion.y + config_get_d(CONFIG_HEIGHT),
188                      +e.motion.xrel,
189                      config_get_d(CONFIG_MOUSE_INVERT)
190                      ? +e.motion.yrel : -e.motion.yrel);
191             break;
192
193         case SDL_MOUSEBUTTONDOWN:
194             d = st_click(e.button.button, 1);
195             break;
196
197         case SDL_MOUSEBUTTONUP:
198             d = st_click(e.button.button, 0);
199             break;
200
201         case SDL_KEYDOWN:
202             d = handle_key_dn(&e);
203             break;
204
205         case SDL_KEYUP:
206             d = handle_key_up(&e);
207             break;
208
209         case SDL_ACTIVEEVENT:
210             if (e.active.state == SDL_APPINPUTFOCUS)
211                 if (e.active.gain == 0 && video_get_grab())
212                     goto_state(&st_pause);
213             break;
214
215         case SDL_JOYAXISMOTION:
216             st_stick(e.jaxis.axis, JOY_VALUE(e.jaxis.value));
217             break;
218
219         case SDL_JOYBUTTONDOWN:
220             d = st_buttn(e.jbutton.button, 1);
221             break;
222
223         case SDL_JOYBUTTONUP:
224             d = st_buttn(e.jbutton.button, 0);
225             break;
226         }
227     }
228
229     /* Process events via the tilt sensor API. */
230
231     if (tilt_stat())
232     {
233         int b;
234         int s;
235
236         st_angle(tilt_get_x(),
237                  tilt_get_z());
238
239         while (tilt_get_button(&b, &s))
240         {
241             const int X = config_get_d(CONFIG_JOYSTICK_AXIS_X);
242             const int Y = config_get_d(CONFIG_JOYSTICK_AXIS_Y);
243             const int L = config_get_d(CONFIG_JOYSTICK_DPAD_L);
244             const int R = config_get_d(CONFIG_JOYSTICK_DPAD_R);
245             const int U = config_get_d(CONFIG_JOYSTICK_DPAD_U);
246             const int D = config_get_d(CONFIG_JOYSTICK_DPAD_D);
247
248             if (b == L || b == R || b == U || b == D)
249             {
250                 static int pad[4] = { 0, 0, 0, 0 };
251
252                 /* Track the state of the D-pad buttons. */
253
254                 if      (b == L) pad[0] = s;
255                 else if (b == R) pad[1] = s;
256                 else if (b == U) pad[2] = s;
257                 else if (b == D) pad[3] = s;
258
259                 /* Convert D-pad button events into joystick axis motion. */
260
261                 if      (pad[0] && !pad[1]) st_stick(X, -1.0f);
262                 else if (pad[1] && !pad[0]) st_stick(X, +1.0f);
263                 else                        st_stick(X,  0.0f);
264
265                 if      (pad[2] && !pad[3]) st_stick(Y, -1.0f);
266                 else if (pad[3] && !pad[2]) st_stick(Y, +1.0f);
267                 else                        st_stick(Y,  0.0f);
268             }
269             else d = st_buttn(b, s);
270         }
271     }
272
273     return d;
274 }
275
276 /*---------------------------------------------------------------------------*/
277
278 static char *opt_data;
279 static char *opt_replay;
280 static char *opt_level;
281
282 #define opt_usage \
283     L_(                                                                   \
284         "Usage: %s [options ...]\n"                                       \
285         "Options:\n"                                                      \
286         "  -h, --help                show this usage message.\n"          \
287         "  -v, --version             show version.\n"                     \
288         "  -d, --data <dir>          use 'dir' as game data directory.\n" \
289         "  -r, --replay <file>       play the replay 'file'.\n"           \
290         "  -l, --level <file>        load the level 'file'\n"             \
291     )
292
293 #define opt_error(option) \
294     fprintf(stderr, L_("Option '%s' requires an argument.\n"), option)
295
296 static void opt_parse(int argc, char **argv)
297 {
298     int i;
299
300     /* Scan argument list. */
301
302     for (i = 1; i < argc; i++)
303     {
304         if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help")    == 0)
305         {
306             printf(opt_usage, argv[0]);
307             exit(EXIT_SUCCESS);
308         }
309
310         if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0)
311         {
312             printf("%s\n", VERSION);
313             exit(EXIT_SUCCESS);
314         }
315
316         if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--data")    == 0)
317         {
318             if (i + 1 == argc)
319             {
320                 opt_error(argv[i]);
321                 exit(EXIT_FAILURE);
322             }
323             opt_data = argv[++i];
324             continue;
325         }
326
327         if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--replay")  == 0)
328         {
329             if (i + 1 == argc)
330             {
331                 opt_error(argv[i]);
332                 exit(EXIT_FAILURE);
333             }
334             opt_replay = argv[++i];
335             continue;
336         }
337
338         if (strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--level")  == 0)
339         {
340             if (i + 1 == argc)
341             {
342                 opt_error(argv[i]);
343                 exit(EXIT_FAILURE);
344             }
345             opt_level = argv[++i];
346             continue;
347         }
348
349         /* Perform magic on a single unrecognized argument. */
350
351         if (argc == 2)
352         {
353             size_t len = strlen(argv[i]);
354             int level = 0;
355
356             if (len > 4)
357             {
358                 char *ext = argv[i] + len - 4;
359
360                 if (strcmp(ext, ".map") == 0)
361                     strncpy(ext, ".sol", 4);
362
363                 if (strcmp(ext, ".sol") == 0)
364                     level = 1;
365             }
366
367             if (level)
368                 opt_level = argv[i];
369             else
370                 opt_replay = argv[i];
371
372             break;
373         }
374     }
375 }
376
377 #undef opt_usage
378 #undef opt_error
379
380 /*---------------------------------------------------------------------------*/
381
382 static int is_replay(struct dir_item *item)
383 {
384     return str_ends_with(item->path, ".nbr");
385 }
386
387 static int is_score_file(struct dir_item *item)
388 {
389     return str_starts_with(item->path, "neverballhs-");
390 }
391
392 static void make_dirs_and_migrate(void)
393 {
394     Array items;
395     int i;
396
397     const char *src;
398     char *dst;
399
400     if (fs_mkdir("Replays"))
401     {
402         if ((items = fs_dir_scan("", is_replay)))
403         {
404             for (i = 0; i < array_len(items); i++)
405             {
406                 src = DIR_ITEM_GET(items, i)->path;
407                 dst = concat_string("Replays/", src, NULL);
408                 fs_rename(src, dst);
409                 free(dst);
410             }
411
412             fs_dir_free(items);
413         }
414     }
415
416     if (fs_mkdir("Scores"))
417     {
418         if ((items = fs_dir_scan("", is_score_file)))
419         {
420             for (i = 0; i < array_len(items); i++)
421             {
422                 src = DIR_ITEM_GET(items, i)->path;
423                 dst = concat_string("Scores/",
424                                     src + sizeof ("neverballhs-") - 1,
425                                     ".txt",
426                                     NULL);
427                 fs_rename(src, dst);
428                 free(dst);
429             }
430
431             fs_dir_free(items);
432         }
433     }
434
435     fs_mkdir("Screenshots");
436 }
437
438 /*---------------------------------------------------------------------------*/
439
440 int main(int argc, char *argv[])
441 {
442     SDL_Joystick *joy = NULL;
443     int t1, t0;
444
445     if (!fs_init(argv[0]))
446     {
447         fprintf(stderr, "Failure to initialize virtual file system: %s\n",
448                 fs_error());
449         return 1;
450     }
451
452     lang_init("neverball");
453
454     opt_parse(argc, argv);
455
456     config_paths(opt_data);
457     make_dirs_and_migrate();
458
459     /* Initialize SDL. */
460
461     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) == -1)
462     {
463         fprintf(stderr, "%s\n", SDL_GetError());
464         return 1;
465     }
466
467     /* Intitialize configuration. */
468
469     config_init();
470     config_load();
471
472     /* Initialize joystick. */
473
474     if (config_get_d(CONFIG_JOYSTICK) && SDL_NumJoysticks() > 0)
475     {
476         joy = SDL_JoystickOpen(config_get_d(CONFIG_JOYSTICK_DEVICE));
477         if (joy)
478             SDL_JoystickEventState(SDL_ENABLE);
479     }
480
481     /* Initialize audio. */
482
483     audio_init();
484     tilt_init();
485
486     /* Initialize video. */
487
488     if (!video_init(TITLE, ICON))
489         return 1;
490
491     init_state(&st_null);
492
493     /* Initialize demo playback or load the level. */
494
495     if (opt_replay &&
496         fs_add_path(dir_name(opt_replay)) &&
497         progress_replay(base_name(opt_replay)))
498     {
499         demo_play_goto(1);
500         goto_state(&st_demo_play);
501     }
502     else if (opt_level)
503     {
504         const char *path = fs_resolve(opt_level);
505         int loaded = 0;
506
507         if (path)
508         {
509             /* HACK: must be around for the duration of the game. */
510             static struct level level;
511
512             if (level_load(path, &level))
513             {
514                 progress_init(MODE_STANDALONE);
515
516                 if (progress_play(&level))
517                 {
518                     goto_state(&st_level);
519                     loaded = 1;
520                 }
521             }
522         }
523         else fprintf(stderr, "%s: file is not in game path\n", opt_level);
524
525         if (!loaded)
526             goto_state(&st_title);
527     }
528     else
529         goto_state(&st_title);
530
531     /* Run the main game loop. */
532
533     t0 = SDL_GetTicks();
534
535     while (loop())
536     {
537         if ((t1 = SDL_GetTicks()) > t0)
538         {
539             /* Step the game state. */
540
541             st_timer(0.001f * (t1 - t0));
542
543             t0 = t1;
544
545             /* Render. */
546
547             st_paint(0.001f * t0);
548             shot_take();
549             video_swap();
550
551             if (config_get_d(CONFIG_NICE))
552                 SDL_Delay(1);
553         }
554     }
555
556     config_save();
557
558     if (joy)
559         SDL_JoystickClose(joy);
560
561     tilt_free();
562     SDL_Quit();
563
564     return 0;
565 }
566
567 /*---------------------------------------------------------------------------*/
568