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