Define gravity constants only once
[neverball] / ball / game_client.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 #include <SDL.h>
16 #include <math.h>
17 #include <assert.h>
18
19 #include "glext.h"
20 #include "vec3.h"
21 #include "geom.h"
22 #include "item.h"
23 #include "back.h"
24 #include "part.h"
25 #include "ball.h"
26 #include "image.h"
27 #include "audio.h"
28 #include "solid_gl.h"
29 #include "config.h"
30 #include "video.h"
31
32 #include "game_client.h"
33 #include "game_common.h"
34 #include "game_proxy.h"
35 #include "game_draw.h"
36
37 #include "cmd.h"
38
39 /*---------------------------------------------------------------------------*/
40
41 int game_compat_map;                    /* Client/server map compat flag     */
42
43 /*---------------------------------------------------------------------------*/
44
45 static struct game_draw dr;
46
47 static float timer  = 0.0f;             /* Clock time                        */
48 static int   status = GAME_NONE;        /* Outcome of the game               */
49 static int   coins  = 0;                /* Collected coins                   */
50
51 static int ups;                         /* Updates per second                */
52 static int first_update;                /* First update flag                 */
53 static int curr_ball;                   /* Current ball index                */
54
55 struct
56 {
57     int x, y;
58 } version;                              /* Current map version               */
59
60 /*---------------------------------------------------------------------------*/
61
62 static void game_run_cmd(const union cmd *cmd)
63 {
64     /*
65      * Neverball <= 1.5.1 does not send explicit tilt axes, rotation
66      * happens directly around view vectors.  So for compatibility if
67      * at the time of receiving tilt angles we have not yet received
68      * the tilt axes, we use the view vectors.
69      */
70     static int got_tilt_axes;
71
72     float v[3];
73
74     if (dr.state)
75     {
76         struct s_item *hp;
77         struct s_ball *up;
78
79         float dt;
80         int i;
81
82         switch (cmd->type)
83         {
84         case CMD_END_OF_UPDATE:
85
86             got_tilt_axes = 0;
87
88             if (first_update)
89             {
90                 first_update = 0;
91                 break;
92             }
93
94             /* Compute gravity for particle effects. */
95
96             if (status == GAME_GOAL)
97                 game_tilt_grav(v, GRAVITY_UP, &dr.tilt);
98             else
99                 game_tilt_grav(v, GRAVITY_DN, &dr.tilt);
100
101             /* Step particle, goal and jump effects. */
102
103             if (ups > 0)
104             {
105                 dt = 1.0f / (float) ups;
106
107                 if (dr.goal_e && dr.goal_k < 1.0f)
108                     dr.goal_k += dt;
109
110                 if (dr.jump_b)
111                 {
112                     dr.jump_dt += dt;
113
114                     if (1.0f < dr.jump_dt)
115                         dr.jump_b = 0;
116                 }
117
118                 part_step(v, dt);
119             }
120
121             break;
122
123         case CMD_MAKE_BALL:
124             /* Allocate a new ball and mark it as the current ball. */
125
126             if ((up = realloc(dr.file.uv, sizeof (*up) * (dr.file.uc + 1))))
127             {
128                 dr.file.uv = up;
129                 curr_ball = dr.file.uc;
130                 dr.file.uc++;
131             }
132             break;
133
134         case CMD_MAKE_ITEM:
135             /* Allocate and initialise a new item. */
136
137             if ((hp = realloc(dr.file.hv, sizeof (*hp) * (dr.file.hc + 1))))
138             {
139                 struct s_item h;
140
141                 v_cpy(h.p, cmd->mkitem.p);
142
143                 h.t = cmd->mkitem.t;
144                 h.n = cmd->mkitem.n;
145
146                 dr.file.hv = hp;
147                 dr.file.hv[dr.file.hc] = h;
148                 dr.file.hc++;
149             }
150
151             break;
152
153         case CMD_PICK_ITEM:
154             /* Set up particle effects and discard the item. */
155
156             assert(cmd->pkitem.hi < dr.file.hc);
157
158             hp = &dr.file.hv[cmd->pkitem.hi];
159
160             item_color(hp, v);
161             part_burst(hp->p, v);
162
163             hp->t = ITEM_NONE;
164
165             break;
166
167         case CMD_TILT_ANGLES:
168             if (!got_tilt_axes)
169                 game_tilt_axes(&dr.tilt, dr.view.e);
170
171             dr.tilt.rx = cmd->tiltangles.x;
172             dr.tilt.rz = cmd->tiltangles.z;
173             break;
174
175         case CMD_SOUND:
176             /* Play the sound. */
177
178             if (cmd->sound.n)
179                 audio_play(cmd->sound.n, cmd->sound.a);
180
181             break;
182
183         case CMD_TIMER:
184             timer = cmd->timer.t;
185             break;
186
187         case CMD_STATUS:
188             status = cmd->status.t;
189             break;
190
191         case CMD_COINS:
192             coins = cmd->coins.n;
193             break;
194
195         case CMD_JUMP_ENTER:
196             dr.jump_b  = 1;
197             dr.jump_e  = 0;
198             dr.jump_dt = 0.0f;
199             break;
200
201         case CMD_JUMP_EXIT:
202             dr.jump_e = 1;
203             break;
204
205         case CMD_BODY_PATH:
206             dr.file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
207             break;
208
209         case CMD_BODY_TIME:
210             dr.file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
211             break;
212
213         case CMD_GOAL_OPEN:
214             /*
215              * Enable the goal and make sure it's fully visible if
216              * this is the first update.
217              */
218
219             if (!dr.goal_e)
220             {
221                 dr.goal_e = 1;
222                 dr.goal_k = first_update ? 1.0f : 0.0f;
223             }
224             break;
225
226         case CMD_SWCH_ENTER:
227             dr.file.xv[cmd->swchenter.xi].e = 1;
228             break;
229
230         case CMD_SWCH_TOGGLE:
231             dr.file.xv[cmd->swchtoggle.xi].f = !dr.file.xv[cmd->swchtoggle.xi].f;
232             break;
233
234         case CMD_SWCH_EXIT:
235             dr.file.xv[cmd->swchexit.xi].e = 0;
236             break;
237
238         case CMD_UPDATES_PER_SECOND:
239             ups = cmd->ups.n;
240             break;
241
242         case CMD_BALL_RADIUS:
243             dr.file.uv[curr_ball].r = cmd->ballradius.r;
244             break;
245
246         case CMD_CLEAR_ITEMS:
247             if (dr.file.hv)
248             {
249                 free(dr.file.hv);
250                 dr.file.hv = NULL;
251             }
252             dr.file.hc = 0;
253             break;
254
255         case CMD_CLEAR_BALLS:
256             if (dr.file.uv)
257             {
258                 free(dr.file.uv);
259                 dr.file.uv = NULL;
260             }
261             dr.file.uc = 0;
262             break;
263
264         case CMD_BALL_POSITION:
265             v_cpy(dr.file.uv[curr_ball].p, cmd->ballpos.p);
266             break;
267
268         case CMD_BALL_BASIS:
269             v_cpy(dr.file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
270             v_cpy(dr.file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
271
272             v_crs(dr.file.uv[curr_ball].e[2],
273                   dr.file.uv[curr_ball].e[0],
274                   dr.file.uv[curr_ball].e[1]);
275             break;
276
277         case CMD_BALL_PEND_BASIS:
278             v_cpy(dr.file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
279             v_cpy(dr.file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
280
281             v_crs(dr.file.uv[curr_ball].E[2],
282                   dr.file.uv[curr_ball].E[0],
283                   dr.file.uv[curr_ball].E[1]);
284             break;
285
286         case CMD_VIEW_POSITION:
287             v_cpy(dr.view.p, cmd->viewpos.p);
288             break;
289
290         case CMD_VIEW_CENTER:
291             v_cpy(dr.view.c, cmd->viewcenter.c);
292             break;
293
294         case CMD_VIEW_BASIS:
295             v_cpy(dr.view.e[0], cmd->viewbasis.e[0]);
296             v_cpy(dr.view.e[1], cmd->viewbasis.e[1]);
297
298             v_crs(dr.view.e[2], dr.view.e[0], dr.view.e[1]);
299
300             break;
301
302         case CMD_CURRENT_BALL:
303             curr_ball = cmd->currball.ui;
304             break;
305
306         case CMD_PATH_FLAG:
307             dr.file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
308             break;
309
310         case CMD_STEP_SIMULATION:
311             /*
312              * Simulate body motion.
313              *
314              * This is done on the client side due to replay file size
315              * concerns and isn't done as part of CMD_END_OF_UPDATE to
316              * match the server state as closely as possible.  Body
317              * time is still synchronised with the server on a
318              * semi-regular basis and path indices are handled through
319              * CMD_BODY_PATH, thus this code doesn't need to be as
320              * sophisticated as sol_body_step.
321              */
322
323             dt = cmd->stepsim.dt;
324
325             for (i = 0; i < dr.file.bc; i++)
326             {
327                 struct s_body *bp = dr.file.bv + i;
328                 struct s_path *pp = dr.file.pv + bp->pi;
329
330                 if (bp->pi >= 0 && pp->f)
331                     bp->t += dt;
332             }
333             break;
334
335         case CMD_MAP:
336             /*
337              * Note a version (mis-)match between the loaded map and
338              * what the server has. (This doesn't actually load a
339              * map.)
340              */
341
342             game_compat_map = version.x == cmd->map.version.x;
343             break;
344
345         case CMD_TILT_AXES:
346             got_tilt_axes = 1;
347             v_cpy(dr.tilt.x, cmd->tiltaxes.x);
348             v_cpy(dr.tilt.z, cmd->tiltaxes.z);
349             break;
350
351         case CMD_NONE:
352         case CMD_MAX:
353             break;
354         }
355     }
356 }
357
358 void game_client_sync(fs_file demo_fp)
359 {
360     union cmd *cmdp;
361
362     while ((cmdp = game_proxy_deq()))
363     {
364         if (demo_fp)
365             cmd_put(demo_fp, cmdp);
366
367         game_run_cmd(cmdp);
368
369         cmd_free(cmdp);
370     }
371 }
372
373 /*---------------------------------------------------------------------------*/
374
375 int  game_client_init(const char *file_name)
376 {
377     char *back_name = "", *grad_name = "";
378     int i;
379
380     coins  = 0;
381     status = GAME_NONE;
382
383     if (dr.state)
384         game_client_free();
385
386     if (!sol_load_gl(&dr.file, file_name, config_get_d(CONFIG_SHADOW)))
387         return (dr.state = 0);
388
389     dr.reflective = sol_reflective(&dr.file);
390
391     dr.state = 1;
392
393     game_tilt_init(&dr.tilt);
394
395     /* Initialize jump and goal states. */
396
397     dr.jump_e = 1;
398     dr.jump_b = 0;
399
400     dr.goal_e = 0;
401     dr.goal_k = 0.0f;
402
403     /* Initialise the level, background, particles, fade, and view. */
404
405     dr.fade_k =  1.0f;
406     dr.fade_d = -2.0f;
407
408
409     version.x = 0;
410     version.y = 0;
411
412     for (i = 0; i < dr.file.dc; i++)
413     {
414         char *k = dr.file.av + dr.file.dv[i].ai;
415         char *v = dr.file.av + dr.file.dv[i].aj;
416
417         if (strcmp(k, "back") == 0) back_name = v;
418         if (strcmp(k, "grad") == 0) grad_name = v;
419
420         if (strcmp(k, "version") == 0)
421             sscanf(v, "%d.%d", &version.x, &version.y);
422     }
423
424     /*
425      * If the version of the loaded map is 1, assume we have a version
426      * match with the server.  In this way 1.5.0 replays don't trigger
427      * bogus map compatibility warnings.  Post-1.5.0 replays will have
428      * CMD_MAP override this.
429      */
430
431     game_compat_map = version.x == 1;
432
433     part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
434
435     ups          = 0;
436     first_update = 1;
437
438     back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
439     sol_load_gl(&dr.back, back_name, 0);
440
441     return dr.state;
442 }
443
444 void game_client_free(void)
445 {
446     if (dr.state)
447     {
448         game_proxy_clr();
449         sol_free_gl(&dr.file);
450         sol_free_gl(&dr.back);
451         back_free();
452     }
453     dr.state = 0;
454 }
455
456 /*---------------------------------------------------------------------------*/
457
458 void game_client_draw(int pose, float t)
459 {
460     game_draw(&dr, pose, t);
461 }
462
463 /*---------------------------------------------------------------------------*/
464
465 int curr_clock(void)
466 {
467     return (int) (timer * 100.f);
468 }
469
470 int curr_coins(void)
471 {
472     return coins;
473 }
474
475 int curr_status(void)
476 {
477     return status;
478 }
479
480 /*---------------------------------------------------------------------------*/
481
482
483 void game_look(float phi, float theta)
484 {
485     dr.view.c[0] = dr.view.p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
486     dr.view.c[1] = dr.view.p[1] +                       fsinf(V_RAD(phi));
487     dr.view.c[2] = dr.view.p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
488 }
489
490 /*---------------------------------------------------------------------------*/
491
492 void game_kill_fade(void)
493 {
494     dr.fade_k = 0.0f;
495     dr.fade_d = 0.0f;
496 }
497
498 void game_step_fade(float dt)
499 {
500     if ((dr.fade_k < 1.0f && dr.fade_d > 0.0f) ||
501         (dr.fade_k > 0.0f && dr.fade_d < 0.0f))
502         dr.fade_k += dr.fade_d * dt;
503
504     if (dr.fade_k < 0.0f)
505     {
506         dr.fade_k = 0.0f;
507         dr.fade_d = 0.0f;
508     }
509     if (dr.fade_k > 1.0f)
510     {
511         dr.fade_k = 1.0f;
512         dr.fade_d = 0.0f;
513     }
514 }
515
516 void game_fade(float d)
517 {
518     dr.fade_d = d;
519 }
520
521 /*---------------------------------------------------------------------------*/
522
523 void game_client_fly(float k)
524 {
525     game_view_fly(&dr.view, &dr.file, k);
526 }
527
528 /*---------------------------------------------------------------------------*/