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