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