Assume map compatibility by default if client's map version is 1
[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
36 #include "cmd.h"
37
38 /*---------------------------------------------------------------------------*/
39
40 int game_compat_map;                    /* Client/server map compat flag     */
41
42 /*---------------------------------------------------------------------------*/
43
44 static int client_state = 0;
45
46 static struct s_file file;
47 static struct s_file back;
48
49 static int   reflective;                /* Reflective geometry used?         */
50
51 static float timer      = 0.f;          /* Clock time                        */
52
53 static int status = GAME_NONE;          /* Outcome of the game               */
54
55 static float game_rx;                   /* Floor rotation about X axis       */
56 static float game_rz;                   /* Floor rotation about Z axis       */
57
58 static float view_a;                    /* Ideal view rotation about Y axis  */
59 static float view_fov;                  /* Field of view                     */
60
61 static float view_c[3];                 /* Current view center               */
62 static float view_p[3];                 /* Current view position             */
63 static float view_e[3][3];              /* Current view reference frame      */
64
65 static int   coins  = 0;                /* Collected coins                   */
66 static int   goal_e = 0;                /* Goal enabled flag                 */
67 static float goal_k = 0;                /* Goal animation                    */
68
69 static int   jump_e = 1;                /* Jumping enabled flag              */
70 static int   jump_b = 0;                /* Jump-in-progress flag             */
71 static float jump_dt;                   /* Jump duration                     */
72
73 static float fade_k = 0.0;              /* Fade in/out level                 */
74 static float fade_d = 0.0;              /* Fade in/out direction             */
75
76 static int ups;                         /* Updates per second                */
77 static int first_update;                /* First update flag                 */
78 static int curr_ball;                   /* Current ball index                */
79
80 struct
81 {
82     int x, y;
83 } version;                              /* Current map version               */
84
85 /*---------------------------------------------------------------------------*/
86
87 static void game_run_cmd(const union cmd *cmd)
88 {
89     static const float gup[] = { 0.0f, +9.8f, 0.0f };
90     static const float gdn[] = { 0.0f, -9.8f, 0.0f };
91
92     float f[3];
93
94     if (client_state)
95     {
96         struct s_item *hp;
97         struct s_ball *up;
98
99         float dt;
100         int i;
101
102         switch (cmd->type)
103         {
104         case CMD_END_OF_UPDATE:
105             if (first_update)
106             {
107                 first_update = 0;
108                 break;
109             }
110
111             /* Compute gravity for particle effects. */
112
113             if (status == GAME_GOAL)
114                 game_comp_grav(f, gup, view_a, game_rx, game_rz);
115             else
116                 game_comp_grav(f, gdn, view_a, game_rx, game_rz);
117
118             /* Step particle, goal and jump effects. */
119
120             if (ups > 0)
121             {
122                 dt = 1.0f / (float) ups;
123
124                 if (goal_e && goal_k < 1.0f)
125                     goal_k += dt;
126
127                 if (jump_b)
128                 {
129                     jump_dt += dt;
130
131                     if (1.0f < jump_dt)
132                         jump_b = 0;
133                 }
134
135                 part_step(f, dt);
136             }
137
138             break;
139
140         case CMD_MAKE_BALL:
141             /* Allocate a new ball and mark it as the current ball. */
142
143             if ((up = realloc(file.uv, sizeof (*up) * (file.uc + 1))))
144             {
145                 file.uv = up;
146                 curr_ball = file.uc;
147                 file.uc++;
148             }
149             break;
150
151         case CMD_MAKE_ITEM:
152             /* Allocate and initialise a new item. */
153
154             if ((hp = realloc(file.hv, sizeof (*hp) * (file.hc + 1))))
155             {
156                 struct s_item h;
157
158                 v_cpy(h.p, cmd->mkitem.p);
159
160                 h.t = cmd->mkitem.t;
161                 h.n = cmd->mkitem.n;
162
163                 file.hv          = hp;
164                 file.hv[file.hc] = h;
165                 file.hc++;
166             }
167
168             break;
169
170         case CMD_PICK_ITEM:
171             /* Set up particle effects and discard the item. */
172
173             assert(cmd->pkitem.hi < file.hc);
174
175             hp = &file.hv[cmd->pkitem.hi];
176
177             item_color(hp, f);
178             part_burst(hp->p, f);
179
180             hp->t = ITEM_NONE;
181
182             break;
183
184         case CMD_ROTATE:
185             game_rx = cmd->rotate.x;
186             game_rz = cmd->rotate.z;
187             break;
188
189         case CMD_SOUND:
190             /* Play the sound, then free its file name. */
191
192             if (cmd->sound.n)
193             {
194                 audio_play(cmd->sound.n, cmd->sound.a);
195
196                 /*
197                  * FIXME Command memory management should be done
198                  * elsewhere and done properly.
199                  */
200
201                 free(cmd->sound.n);
202             }
203             break;
204
205         case CMD_TIMER:
206             timer = cmd->timer.t;
207             break;
208
209         case CMD_STATUS:
210             status = cmd->status.t;
211             break;
212
213         case CMD_COINS:
214             coins = cmd->coins.n;
215             break;
216
217         case CMD_JUMP_ENTER:
218             jump_b  = 1;
219             jump_e  = 0;
220             jump_dt = 0.0f;
221             break;
222
223         case CMD_JUMP_EXIT:
224             jump_e = 1;
225             break;
226
227         case CMD_BODY_PATH:
228             file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
229             break;
230
231         case CMD_BODY_TIME:
232             file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
233             break;
234
235         case CMD_GOAL_OPEN:
236             /*
237              * Enable the goal and make sure it's fully visible if
238              * this is the first update.
239              */
240
241             if (!goal_e)
242             {
243                 goal_e = 1;
244                 goal_k = first_update ? 1.0f : 0.0f;
245             }
246             break;
247
248         case CMD_SWCH_ENTER:
249             file.xv[cmd->swchenter.xi].e = 1;
250             break;
251
252         case CMD_SWCH_TOGGLE:
253             file.xv[cmd->swchtoggle.xi].f = !file.xv[cmd->swchtoggle.xi].f;
254             break;
255
256         case CMD_SWCH_EXIT:
257             file.xv[cmd->swchexit.xi].e = 0;
258             break;
259
260         case CMD_UPDATES_PER_SECOND:
261             ups = cmd->ups.n;
262             break;
263
264         case CMD_BALL_RADIUS:
265             file.uv[curr_ball].r = cmd->ballradius.r;
266             break;
267
268         case CMD_CLEAR_ITEMS:
269             if (file.hv)
270             {
271                 free(file.hv);
272                 file.hv = NULL;
273             }
274             file.hc = 0;
275             break;
276
277         case CMD_CLEAR_BALLS:
278             if (file.uv)
279             {
280                 free(file.uv);
281                 file.uv = NULL;
282             }
283             file.uc = 0;
284             break;
285
286         case CMD_BALL_POSITION:
287             v_cpy(file.uv[curr_ball].p, cmd->ballpos.p);
288             break;
289
290         case CMD_BALL_BASIS:
291             v_cpy(file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
292             v_cpy(file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
293
294             v_crs(file.uv[curr_ball].e[2],
295                   file.uv[curr_ball].e[0],
296                   file.uv[curr_ball].e[1]);
297             break;
298
299         case CMD_BALL_PEND_BASIS:
300             v_cpy(file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
301             v_cpy(file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
302
303             v_crs(file.uv[curr_ball].E[2],
304                   file.uv[curr_ball].E[0],
305                   file.uv[curr_ball].E[1]);
306             break;
307
308         case CMD_VIEW_POSITION:
309             v_cpy(view_p, cmd->viewpos.p);
310             break;
311
312         case CMD_VIEW_CENTER:
313             v_cpy(view_c, cmd->viewcenter.c);
314             break;
315
316         case CMD_VIEW_BASIS:
317             v_cpy(view_e[0], cmd->viewbasis.e[0]);
318             v_cpy(view_e[1], cmd->viewbasis.e[1]);
319
320             v_crs(view_e[2], view_e[0], view_e[1]);
321
322             view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
323
324             break;
325
326         case CMD_CURRENT_BALL:
327             curr_ball = cmd->currball.ui;
328             break;
329
330         case CMD_PATH_FLAG:
331             file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
332             break;
333
334         case CMD_STEP_SIMULATION:
335             /*
336              * Simulate body motion.
337              *
338              * This is done on the client side due to replay file size
339              * concerns and isn't done as part of CMD_END_OF_UPDATE to
340              * match the server state as closely as possible.  Body
341              * time is still synchronised with the server on a
342              * semi-regular basis and path indices are handled through
343              * CMD_BODY_PATH, thus this code doesn't need to be as
344              * sophisticated as sol_body_step.
345              */
346
347             dt = cmd->stepsim.dt;
348
349             for (i = 0; i < file.bc; i++)
350             {
351                 struct s_body *bp = file.bv + i;
352                 struct s_path *pp = file.pv + bp->pi;
353
354                 if (bp->pi >= 0 && pp->f)
355                     bp->t += dt;
356             }
357             break;
358
359         case CMD_MAP:
360
361             /*
362              * Note if the loaded map matches the server's
363              * expectations. (No, this doesn't actually load a map,
364              * yet.  Something else somewhere else does.)
365              */
366
367             free(cmd->map.name);
368             game_compat_map = version.x == cmd->map.version.x;
369             break;
370
371         case CMD_NONE:
372         case CMD_MAX:
373             break;
374         }
375     }
376 }
377
378 void game_client_step(FILE *demo_fp)
379 {
380     union cmd *cmdp;
381
382     while ((cmdp = game_proxy_deq()))
383     {
384         /*
385          * Note: cmd_put is called first here because game_run_cmd
386          * frees some command struct members.
387          */
388
389         if (demo_fp)
390             cmd_put(demo_fp, cmdp);
391
392         game_run_cmd(cmdp);
393
394         free(cmdp);
395     }
396 }
397
398 /*---------------------------------------------------------------------------*/
399
400 int  game_client_init(const char *file_name)
401 {
402     char *back_name = NULL, *grad_name = NULL;
403     int i;
404
405     coins  = 0;
406     status = GAME_NONE;
407
408     if (client_state)
409         game_client_free();
410
411     if (!sol_load_gl(&file, config_data(file_name),
412                      config_get_d(CONFIG_TEXTURES),
413                      config_get_d(CONFIG_SHADOW)))
414         return (client_state = 0);
415
416     reflective = sol_reflective(&file);
417
418     client_state = 1;
419
420     game_rx = 0.0f;
421     game_rz = 0.0f;
422
423     /* Initialize jump and goal states. */
424
425     jump_e = 1;
426     jump_b = 0;
427
428     goal_e = 0;
429     goal_k = 0.0f;
430
431     /* Initialise the level, background, particles, fade, and view. */
432
433     fade_k =  1.0f;
434     fade_d = -2.0f;
435
436
437     version.x = 0;
438     version.y = 0;
439
440     for (i = 0; i < file.dc; i++)
441     {
442         char *k = file.av + file.dv[i].ai;
443         char *v = file.av + file.dv[i].aj;
444
445         if (strcmp(k, "back") == 0) back_name = v;
446         if (strcmp(k, "grad") == 0) grad_name = v;
447
448         if (strcmp(k, "version") == 0)
449             sscanf(v, "%d.%d", &version.x, &version.y);
450     }
451
452     /*
453      * Work around 1.5.0 replays that trigger bogus replay
454      * compatibility warnings: if the client map's version is 1,
455      * assume the map is compatible with the server.  Post-1.5.0
456      * replays will have CMD_MAP override this.
457      */
458
459     game_compat_map = version.x == 1;
460
461     part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
462
463     view_fov = (float) config_get_d(CONFIG_VIEW_FOV);
464
465     ups          = 0;
466     first_update = 1;
467
468     back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
469     sol_load_gl(&back, config_data(back_name),
470                 config_get_d(CONFIG_TEXTURES), 0);
471
472     return client_state;
473 }
474
475 void game_client_free(void)
476 {
477     if (client_state)
478     {
479         game_proxy_clr();
480         sol_free_gl(&file);
481         sol_free_gl(&back);
482         back_free();
483     }
484     client_state = 0;
485 }
486
487 /*---------------------------------------------------------------------------*/
488
489 int curr_clock(void)
490 {
491     return (int) (timer * 100.f);
492 }
493
494 int curr_coins(void)
495 {
496     return coins;
497 }
498
499 int curr_status(void)
500 {
501     return status;
502 }
503
504 /*---------------------------------------------------------------------------*/
505
506 static void game_draw_balls(const struct s_file *fp,
507                             const float *bill_M, float t)
508 {
509     float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
510
511     float ball_M[16];
512     float pend_M[16];
513
514     m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
515     m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
516
517     glPushAttrib(GL_LIGHTING_BIT);
518     glPushMatrix();
519     {
520         glTranslatef(fp->uv[0].p[0],
521                      fp->uv[0].p[1] + BALL_FUDGE,
522                      fp->uv[0].p[2]);
523         glScalef(fp->uv[0].r,
524                  fp->uv[0].r,
525                  fp->uv[0].r);
526
527         glColor4fv(c);
528         ball_draw(ball_M, pend_M, bill_M, t);
529     }
530     glPopMatrix();
531     glPopAttrib();
532 }
533
534 static void game_draw_items(const struct s_file *fp, float t)
535 {
536     float r = 360.f * t;
537     int hi;
538
539     glPushAttrib(GL_LIGHTING_BIT);
540     {
541         item_push(ITEM_COIN);
542         {
543             for (hi = 0; hi < fp->hc; hi++)
544
545                 if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
546                 {
547                     glPushMatrix();
548                     {
549                         glTranslatef(fp->hv[hi].p[0],
550                                      fp->hv[hi].p[1],
551                                      fp->hv[hi].p[2]);
552                         glRotatef(r, 0.0f, 1.0f, 0.0f);
553                         item_draw(&fp->hv[hi], r);
554                     }
555                     glPopMatrix();
556                 }
557         }
558         item_pull();
559
560         item_push(ITEM_SHRINK);
561         {
562             for (hi = 0; hi < fp->hc; hi++)
563
564                 if (fp->hv[hi].t == ITEM_SHRINK)
565                 {
566                     glPushMatrix();
567                     {
568                         glTranslatef(fp->hv[hi].p[0],
569                                      fp->hv[hi].p[1],
570                                      fp->hv[hi].p[2]);
571                         glRotatef(r, 0.0f, 1.0f, 0.0f);
572                         item_draw(&fp->hv[hi], r);
573                     }
574                     glPopMatrix();
575                 }
576         }
577         item_pull();
578
579         item_push(ITEM_GROW);
580         {
581             for (hi = 0; hi < fp->hc; hi++)
582
583                 if (fp->hv[hi].t == ITEM_GROW)
584                 {
585                     glPushMatrix();
586                     {
587                         glTranslatef(fp->hv[hi].p[0],
588                                      fp->hv[hi].p[1],
589                                      fp->hv[hi].p[2]);
590                         glRotatef(r, 0.0f, 1.0f, 0.0f);
591                         item_draw(&fp->hv[hi], r);
592                     }
593                     glPopMatrix();
594                 }
595         }
596         item_pull();
597     }
598     glPopAttrib();
599 }
600
601 static void game_draw_goals(const struct s_file *fp, const float *M, float t)
602 {
603     if (goal_e)
604     {
605         int zi;
606
607         /* Draw the goal particles. */
608
609         glEnable(GL_TEXTURE_2D);
610         {
611             for (zi = 0; zi < fp->zc; zi++)
612             {
613                 glPushMatrix();
614                 {
615                     glTranslatef(fp->zv[zi].p[0],
616                                  fp->zv[zi].p[1],
617                                  fp->zv[zi].p[2]);
618
619                     part_draw_goal(M, fp->zv[zi].r, goal_k, t);
620                 }
621                 glPopMatrix();
622             }
623         }
624         glDisable(GL_TEXTURE_2D);
625
626         /* Draw the goal column. */
627
628         for (zi = 0; zi < fp->zc; zi++)
629         {
630             glPushMatrix();
631             {
632                 glTranslatef(fp->zv[zi].p[0],
633                              fp->zv[zi].p[1],
634                              fp->zv[zi].p[2]);
635
636                 glScalef(fp->zv[zi].r,
637                          goal_k,
638                          fp->zv[zi].r);
639
640                 goal_draw();
641             }
642             glPopMatrix();
643         }
644     }
645 }
646
647 static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
648 {
649     int ji;
650
651     glEnable(GL_TEXTURE_2D);
652     {
653         for (ji = 0; ji < fp->jc; ji++)
654         {
655             glPushMatrix();
656             {
657                 glTranslatef(fp->jv[ji].p[0],
658                              fp->jv[ji].p[1],
659                              fp->jv[ji].p[2]);
660
661                 part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
662             }
663             glPopMatrix();
664         }
665     }
666     glDisable(GL_TEXTURE_2D);
667
668     for (ji = 0; ji < fp->jc; ji++)
669     {
670         glPushMatrix();
671         {
672             glTranslatef(fp->jv[ji].p[0],
673                          fp->jv[ji].p[1],
674                          fp->jv[ji].p[2]);
675             glScalef(fp->jv[ji].r,
676                      1.0f,
677                      fp->jv[ji].r);
678
679             jump_draw(!jump_e);
680         }
681         glPopMatrix();
682     }
683 }
684
685 static void game_draw_swchs(const struct s_file *fp)
686 {
687     int xi;
688
689     for (xi = 0; xi < fp->xc; xi++)
690     {
691         if (fp->xv[xi].i)
692             continue;
693
694         glPushMatrix();
695         {
696             glTranslatef(fp->xv[xi].p[0],
697                          fp->xv[xi].p[1],
698                          fp->xv[xi].p[2]);
699             glScalef(fp->xv[xi].r,
700                      1.0f,
701                      fp->xv[xi].r);
702
703             swch_draw(fp->xv[xi].f, fp->xv[xi].e);
704         }
705         glPopMatrix();
706     }
707 }
708
709 /*---------------------------------------------------------------------------*/
710
711 static void game_draw_tilt(int d)
712 {
713     const float *ball_p = file.uv->p;
714
715     /* Rotate the environment about the position of the ball. */
716
717     glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
718     glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
719     glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
720     glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
721 }
722
723 static void game_refl_all(void)
724 {
725     glPushMatrix();
726     {
727         game_draw_tilt(1);
728
729         /* Draw the floor. */
730
731         sol_refl(&file);
732     }
733     glPopMatrix();
734 }
735
736 /*---------------------------------------------------------------------------*/
737
738 static void game_draw_light(void)
739 {
740     const float light_p[2][4] = {
741         { -8.0f, +32.0f, -8.0f, 0.0f },
742         { +8.0f, +32.0f, +8.0f, 0.0f },
743     };
744     const float light_c[2][4] = {
745         { 1.0f, 0.8f, 0.8f, 1.0f },
746         { 0.8f, 1.0f, 0.8f, 1.0f },
747     };
748
749     /* Configure the lighting. */
750
751     glEnable(GL_LIGHT0);
752     glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
753     glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
754     glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
755
756     glEnable(GL_LIGHT1);
757     glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
758     glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
759     glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
760 }
761
762 static void game_draw_back(int pose, int d, float t)
763 {
764     glPushMatrix();
765     {
766         if (d < 0)
767         {
768             glRotatef(game_rz * 2, view_e[2][0], view_e[2][1], view_e[2][2]);
769             glRotatef(game_rx * 2, view_e[0][0], view_e[0][1], view_e[0][2]);
770         }
771
772         glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
773
774         if (config_get_d(CONFIG_BACKGROUND))
775         {
776             /* Draw all background layers back to front. */
777
778             sol_back(&back, BACK_DIST, FAR_DIST,  t);
779             back_draw(0);
780             sol_back(&back,         0, BACK_DIST, t);
781         }
782         else back_draw(0);
783     }
784     glPopMatrix();
785 }
786
787 static void game_clip_refl(int d)
788 {
789     /* Fudge to eliminate the floor from reflection. */
790
791     GLdouble e[4], k = -0.00001;
792
793     e[0] = 0;
794     e[1] = 1;
795     e[2] = 0;
796     e[3] = k;
797
798     glClipPlane(GL_CLIP_PLANE0, e);
799 }
800
801 static void game_clip_ball(int d, const float *p)
802 {
803     GLdouble r, c[3], pz[4], nz[4];
804
805     /* Compute the plane giving the front of the ball, as seen from view_p. */
806
807     c[0] = p[0];
808     c[1] = p[1] * d;
809     c[2] = p[2];
810
811     pz[0] = view_p[0] - c[0];
812     pz[1] = view_p[1] - c[1];
813     pz[2] = view_p[2] - c[2];
814
815     r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
816
817     pz[0] /= r;
818     pz[1] /= r;
819     pz[2] /= r;
820     pz[3] = -(pz[0] * c[0] +
821               pz[1] * c[1] +
822               pz[2] * c[2]);
823
824     /* Find the plane giving the back of the ball, as seen from view_p. */
825
826     nz[0] = -pz[0];
827     nz[1] = -pz[1];
828     nz[2] = -pz[2];
829     nz[3] = -pz[3];
830
831     /* Reflect these planes as necessary, and store them in the GL state. */
832
833     pz[1] *= d;
834     nz[1] *= d;
835
836     glClipPlane(GL_CLIP_PLANE1, nz);
837     glClipPlane(GL_CLIP_PLANE2, pz);
838 }
839
840 static void game_draw_fore(int pose, const float *M, int d, float t)
841 {
842     const float *ball_p = file.uv->p;
843     const float  ball_r = file.uv->r;
844
845     glPushMatrix();
846     {
847         /* Rotate the environment about the position of the ball. */
848
849         game_draw_tilt(d);
850
851         /* Compute clipping planes for reflection and ball facing. */
852
853         game_clip_refl(d);
854         game_clip_ball(d, ball_p);
855
856         if (d < 0)
857             glEnable(GL_CLIP_PLANE0);
858
859         if (pose)
860             sol_draw(&file, 0, 1);
861         else
862         {
863             /* Draw the coins. */
864
865             game_draw_items(&file, t);
866
867             /* Draw the floor. */
868
869             sol_draw(&file, 0, 1);
870
871             /* Draw the ball shadow. */
872
873             if (d > 0 && config_get_d(CONFIG_SHADOW))
874             {
875                 shad_draw_set(ball_p, ball_r);
876                 sol_shad(&file);
877                 shad_draw_clr();
878             }
879
880             /* Draw the ball. */
881
882             game_draw_balls(&file, M, t);
883         }
884
885         /* Draw the particles and light columns. */
886
887         glEnable(GL_COLOR_MATERIAL);
888         glDisable(GL_LIGHTING);
889         glDepthMask(GL_FALSE);
890         {
891             glColor3f(1.0f, 1.0f, 1.0f);
892
893             sol_bill(&file, M, t);
894             part_draw_coin(M, t);
895
896             glDisable(GL_TEXTURE_2D);
897             {
898                 game_draw_goals(&file, M, t);
899                 game_draw_jumps(&file, M, t);
900                 game_draw_swchs(&file);
901             }
902             glEnable(GL_TEXTURE_2D);
903
904             glColor3f(1.0f, 1.0f, 1.0f);
905         }
906         glDepthMask(GL_TRUE);
907         glEnable(GL_LIGHTING);
908         glDisable(GL_COLOR_MATERIAL);
909
910         if (d < 0)
911             glDisable(GL_CLIP_PLANE0);
912     }
913     glPopMatrix();
914 }
915
916 void game_draw(int pose, float t)
917 {
918     float fov = view_fov;
919
920     if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
921
922     if (client_state)
923     {
924         video_push_persp(fov, 0.1f, FAR_DIST);
925         glPushMatrix();
926         {
927             float T[16], U[16], M[16], v[3];
928
929             /* Compute direct and reflected view bases. */
930
931             v[0] = +view_p[0];
932             v[1] = -view_p[1];
933             v[2] = +view_p[2];
934
935             m_view(T, view_c, view_p, view_e[1]);
936             m_view(U, view_c, v,      view_e[1]);
937
938             m_xps(M, T);
939
940             /* Apply current the view. */
941
942             v_sub(v, view_c, view_p);
943
944             glTranslatef(0.f, 0.f, -v_len(v));
945             glMultMatrixf(M);
946             glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
947
948             if (reflective && config_get_d(CONFIG_REFLECTION))
949             {
950                 glEnable(GL_STENCIL_TEST);
951                 {
952                     /* Draw the mirrors only into the stencil buffer. */
953
954                     glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
955                     glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
956                     glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
957                     glDepthMask(GL_FALSE);
958
959                     game_refl_all();
960
961                     glDepthMask(GL_TRUE);
962                     glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
963                     glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
964                     glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
965
966                     /* Draw the scene reflected into color and depth buffers. */
967
968                     glFrontFace(GL_CW);
969                     glPushMatrix();
970                     {
971                         glScalef(+1.0f, -1.0f, +1.0f);
972
973                         game_draw_light();
974                         game_draw_back(pose,    -1, t);
975                         game_draw_fore(pose, U, -1, t);
976                     }
977                     glPopMatrix();
978                     glFrontFace(GL_CCW);
979                 }
980                 glDisable(GL_STENCIL_TEST);
981             }
982
983             /* Draw the scene normally. */
984
985             game_draw_light();
986             game_refl_all();
987             game_draw_back(pose,    +1, t);
988             game_draw_fore(pose, T, +1, t);
989         }
990         glPopMatrix();
991         video_pop_matrix();
992
993         /* Draw the fade overlay. */
994
995         fade_draw(fade_k);
996     }
997 }
998
999 /*---------------------------------------------------------------------------*/
1000
1001 void game_look(float phi, float theta)
1002 {
1003     view_c[0] = view_p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
1004     view_c[1] = view_p[1] +                       fsinf(V_RAD(phi));
1005     view_c[2] = view_p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
1006 }
1007
1008 /*---------------------------------------------------------------------------*/
1009
1010 void game_kill_fade(void)
1011 {
1012     fade_k = 0.0f;
1013     fade_d = 0.0f;
1014 }
1015
1016 void game_step_fade(float dt)
1017 {
1018     if ((fade_k < 1.0f && fade_d > 0.0f) ||
1019         (fade_k > 0.0f && fade_d < 0.0f))
1020         fade_k += fade_d * dt;
1021
1022     if (fade_k < 0.0f)
1023     {
1024         fade_k = 0.0f;
1025         fade_d = 0.0f;
1026     }
1027     if (fade_k > 1.0f)
1028     {
1029         fade_k = 1.0f;
1030         fade_d = 0.0f;
1031     }
1032 }
1033
1034 void game_fade(float d)
1035 {
1036     fade_d = d;
1037 }
1038
1039 /*---------------------------------------------------------------------------*/
1040
1041 const struct s_file *game_client_file(void)
1042 {
1043     return &file;
1044 }
1045
1046 /*---------------------------------------------------------------------------*/