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