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