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