Fixed current time computation in uniform mode.
[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 "image.h"
25 #include "audio.h"
26 #include "demo.h"
27 #include "levels.h"
28 #include "game.h"
29 #include "gui.h"
30 #include "set.h"
31 #include "text.h"
32 #include "tilt.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 #define TITLE "Neverball"
41
42 /*---------------------------------------------------------------------------*/
43
44 static void shot(void)
45 {
46     static char filename[MAXSTR];
47     static int  num = 0;
48
49     sprintf(filename, "screen%02d.png", num++);
50
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 == SDL_BUTTON_LEFT) ? -1 : 1, 1);
103             break;
104
105         case SDL_MOUSEBUTTONUP:
106             d = st_click((e.button.button == SDL_BUTTON_LEFT) ? -1 : 1, 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 && config_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
320     /* Resolve conflicts. */
321
322     if (demo_path)
323         replay_demo = display_info ? 0 : 1;
324     else
325         if (display_info)
326         {
327             /* FIXME, I'm a required option. */
328             fputs(L_("Option '--info' requires '--replay'.\n"), stderr);
329             exit(EXIT_FAILURE);
330         }
331 }
332
333 #undef usage
334 #undef argument_error
335
336 /*---------------------------------------------------------------------------*/
337
338 int main(int argc, char *argv[])
339 {
340     SDL_Joystick *joy = NULL;
341 #ifndef __APPLE__
342     SDL_Surface *icon;
343 #endif
344
345     int t1, t0, uniform;
346
347     lang_init("neverball", CONFIG_LOCALE);
348
349     text_init();
350
351     parse_args(argc, argv);
352
353     if (!config_data_path(data_path, SET_FILE))
354     {
355         fputs(L_("Failure to establish game data directory\n"), stderr);
356         return 1;
357     }
358
359     if (!config_user_path(NULL))
360     {
361         fputs(L_("Failure to establish config directory\n"), stderr);
362         return 1;
363     }
364
365     /* Initialize SDL system and subsystems */
366
367     if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) == -1)
368     {
369         fprintf(stderr, "%s\n", SDL_GetError());
370         return 1;
371     }
372
373     /* Intitialize the configuration */
374
375     config_init();
376     config_load();
377
378     /* Dump replay information and exit. */
379
380     if (display_info)
381     {
382         if (!level_replay(demo_path))
383         {
384             fprintf(stderr, L_("Replay file '%s': %s\n"), demo_path,
385                     errno ?  strerror(errno) : L_("Not a replay file"));
386             return 1;
387         }
388         demo_replay_dump_info();
389         return 0;
390     }
391
392     /* Initialize the joystick. */
393
394     if (SDL_NumJoysticks() > 0)
395     {
396         joy = SDL_JoystickOpen(config_get_d(CONFIG_JOYSTICK_DEVICE));
397         if (joy)
398             SDL_JoystickEventState(SDL_ENABLE);
399     }
400
401     /* Initialize the audio. */
402
403     audio_init();
404     tilt_init();
405
406     /* Require 16-bit double buffer with 16-bit depth buffer. */
407
408     SDL_GL_SetAttribute(SDL_GL_RED_SIZE,     5);
409     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,   5);
410     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,    5);
411     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  16);
412     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
413
414 #ifndef __APPLE__
415     if ((icon = load_surface("icon/neverball.png")))
416     {
417         SDL_WM_SetIcon(icon, NULL);
418         free(icon->pixels);
419         SDL_FreeSurface(icon);
420     }
421 #endif /* __APPLE__ */
422
423     /* Initialize the video. */
424
425     if (!config_mode(config_get_d(CONFIG_FULLSCREEN),
426                      config_get_d(CONFIG_WIDTH), config_get_d(CONFIG_HEIGHT)))
427     {
428         fprintf(stderr, "%s\n", SDL_GetError());
429         return 1;
430     }
431
432     SDL_WM_SetCaption(TITLE, TITLE);
433
434     init_state(&st_null);
435
436     /* Initialise demo playback. */
437
438     if (replay_demo)
439     {
440         level_replay(demo_path);
441         demo_play_goto(1);
442         goto_state(&st_demo_play);
443     }
444     else
445         goto_state(&st_title);
446
447     /* Run the main game loop. */
448
449     uniform = config_get_d(CONFIG_UNIFORM);
450     t0 = SDL_GetTicks();
451
452     while (loop())
453     {
454         t1 = SDL_GetTicks();
455
456         if (uniform)
457         {
458             /* Step the game uniformly, as configured. */
459
460             int u;
461
462             for (u = 0; u < abs(uniform); ++u)
463             {
464                 st_timer(DT);
465                 t0 += (int) (DT * 1000);
466             }
467         }
468         else
469         {
470             /* Step the game state at least up to the current time. */
471
472             while (t1 > t0)
473             {
474                 st_timer(DT);
475                 t0 += (int) (DT * 1000);
476             }
477         }
478
479         /* Render. */
480
481         st_paint(0.001f * t0);
482         config_swap();
483
484         if (uniform < 0)
485             shot();
486
487         if (config_get_d(CONFIG_NICE))
488             SDL_Delay(1);
489     }
490
491     /* Gracefully close the game */
492
493     if (SDL_JoystickOpened(0))
494         SDL_JoystickClose(joy);
495
496     tilt_free();
497     SDL_Quit();
498
499     config_save();
500
501     text_quit();
502
503     return 0;
504 }
505
506 /*---------------------------------------------------------------------------*/
507