Exclude vertical ball velocity from view computations
[neverball] / ball / game_server.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 "vec3.h"
20 #include "item.h"
21 #include "solid_phys.h"
22 #include "config.h"
23 #include "binary.h"
24 #include "common.h"
25
26 #include "game_common.h"
27 #include "game_server.h"
28 #include "game_proxy.h"
29
30 #include "cmd.h"
31
32 /*---------------------------------------------------------------------------*/
33
34 static int server_state = 0;
35
36 static struct s_file file;
37
38 static float timer      = 0.f;          /* Clock time                        */
39 static int   timer_down = 1;            /* Timer go up or down?              */
40
41 static int status = GAME_NONE;          /* Outcome of the game               */
42
43 static float game_rx;                   /* Floor rotation about X axis       */
44 static float game_rz;                   /* Floor rotation about Z axis       */
45
46 static float view_a;                    /* Ideal view rotation about Y axis  */
47 static float view_dc;                   /* Ideal view distance above ball    */
48 static float view_dp;                   /* Ideal view distance above ball    */
49 static float view_dz;                   /* Ideal view distance behind ball   */
50
51 static float view_c[3];                 /* Current view center               */
52 static float view_v[3];                 /* Current view vector               */
53 static float view_p[3];                 /* Current view position             */
54 static float view_e[3][3];              /* Current view reference frame      */
55 static float view_k;
56
57 static int   coins  = 0;                /* Collected coins                   */
58 static int   goal_e = 0;                /* Goal enabled flag                 */
59 static float goal_k = 0;                /* Goal animation                    */
60 static int   jump_e = 1;                /* Jumping enabled flag              */
61 static int   jump_b = 0;                /* Jump-in-progress flag             */
62 static float jump_dt;                   /* Jump duration                     */
63 static float jump_p[3];                 /* Jump destination                  */
64
65 /*---------------------------------------------------------------------------*/
66
67 /*
68  * This is an abstraction of the game's input state.  All input is
69  * encapsulated here, and all references to the input by the game are
70  * made here.  TODO: This used to have the effect of homogenizing
71  * input for use in replay recording and playback, but it's not clear
72  * how relevant this approach is with the introduction of the command
73  * pipeline.
74  *
75  * x and z:
76  *     -32767 = -ANGLE_BOUND
77  *     +32767 = +ANGLE_BOUND
78  *
79  * r:
80  *     -32767 = -VIEWR_BOUND
81  *     +32767 = +VIEWR_BOUND
82  *
83  */
84
85 struct input
86 {
87     short x;
88     short z;
89     short r;
90     short c;
91 };
92
93 static struct input input_current;
94
95 static void input_init(void)
96 {
97     input_current.x = 0;
98     input_current.z = 0;
99     input_current.r = 0;
100     input_current.c = 0;
101 }
102
103 static void input_set_x(float x)
104 {
105     if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
106     if (x >  ANGLE_BOUND) x =  ANGLE_BOUND;
107
108     input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
109 }
110
111 static void input_set_z(float z)
112 {
113     if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
114     if (z >  ANGLE_BOUND) z =  ANGLE_BOUND;
115
116     input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
117 }
118
119 static void input_set_r(float r)
120 {
121     if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
122     if (r >  VIEWR_BOUND) r =  VIEWR_BOUND;
123
124     input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
125 }
126
127 static void input_set_c(int c)
128 {
129     input_current.c = (short) c;
130 }
131
132 static float input_get_x(void)
133 {
134     return ANGLE_BOUND * (float) input_current.x / 32767.0f;
135 }
136
137 static float input_get_z(void)
138 {
139     return ANGLE_BOUND * (float) input_current.z / 32767.0f;
140 }
141
142 static float input_get_r(void)
143 {
144     return VIEWR_BOUND * (float) input_current.r / 32767.0f;
145 }
146
147 static int input_get_c(void)
148 {
149     return (int) input_current.c;
150 }
151
152 int input_put(fs_file fout)
153 {
154     if (server_state)
155     {
156         put_short(fout, &input_current.x);
157         put_short(fout, &input_current.z);
158         put_short(fout, &input_current.r);
159         put_short(fout, &input_current.c);
160
161         return 1;
162     }
163     return 0;
164 }
165
166 int input_get(fs_file fin)
167 {
168     if (server_state)
169     {
170         get_short(fin, &input_current.x);
171         get_short(fin, &input_current.z);
172         get_short(fin, &input_current.r);
173         get_short(fin, &input_current.c);
174
175         return (fs_eof(fin) ? 0 : 1);
176     }
177     return 0;
178 }
179
180 /*---------------------------------------------------------------------------*/
181
182 /*
183  * Utility functions for preparing the "server" state and events for
184  * consumption by the "client".
185  */
186
187 static union cmd cmd;
188
189 static void game_cmd_map(const char *name, int ver_x, int ver_y)
190 {
191     cmd.type          = CMD_MAP;
192     cmd.map.name      = strdup(name);
193     cmd.map.version.x = ver_x;
194     cmd.map.version.y = ver_y;
195     game_proxy_enq(&cmd);
196 }
197
198 static void game_cmd_eou(void)
199 {
200     cmd.type = CMD_END_OF_UPDATE;
201     game_proxy_enq(&cmd);
202 }
203
204 static void game_cmd_ups(void)
205 {
206     cmd.type  = CMD_UPDATES_PER_SECOND;
207     cmd.ups.n = UPS;
208     game_proxy_enq(&cmd);
209 }
210
211 static void game_cmd_sound(const char *filename, float a)
212 {
213     cmd.type = CMD_SOUND;
214
215     cmd.sound.n = strdup(filename);
216     cmd.sound.a = a;
217
218     game_proxy_enq(&cmd);
219 }
220
221 #define audio_play(s, f) game_cmd_sound((s), (f))
222
223 static void game_cmd_goalopen(void)
224 {
225     cmd.type = CMD_GOAL_OPEN;
226     game_proxy_enq(&cmd);
227 }
228
229 static void game_cmd_updball(void)
230 {
231     cmd.type = CMD_BALL_POSITION;
232     memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
233     game_proxy_enq(&cmd);
234
235     cmd.type = CMD_BALL_BASIS;
236     v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
237     v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
238     game_proxy_enq(&cmd);
239
240     cmd.type = CMD_BALL_PEND_BASIS;
241     v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
242     v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
243     game_proxy_enq(&cmd);
244 }
245
246 static void game_cmd_updview(void)
247 {
248     cmd.type = CMD_VIEW_POSITION;
249     memcpy(cmd.viewpos.p, view_p, sizeof (float) * 3);
250     game_proxy_enq(&cmd);
251
252     cmd.type = CMD_VIEW_CENTER;
253     memcpy(cmd.viewcenter.c, view_c, sizeof (float) * 3);
254     game_proxy_enq(&cmd);
255
256     cmd.type = CMD_VIEW_BASIS;
257     v_cpy(cmd.viewbasis.e[0], view_e[0]);
258     v_cpy(cmd.viewbasis.e[1], view_e[1]);
259     game_proxy_enq(&cmd);
260 }
261
262 static void game_cmd_ballradius(void)
263 {
264     cmd.type         = CMD_BALL_RADIUS;
265     cmd.ballradius.r = file.uv[0].r;
266     game_proxy_enq(&cmd);
267 }
268
269 static void game_cmd_init_balls(void)
270 {
271     cmd.type = CMD_CLEAR_BALLS;
272     game_proxy_enq(&cmd);
273
274     cmd.type = CMD_MAKE_BALL;
275     game_proxy_enq(&cmd);
276
277     game_cmd_updball();
278     game_cmd_ballradius();
279 }
280
281 static void game_cmd_init_items(void)
282 {
283     int i;
284
285     cmd.type = CMD_CLEAR_ITEMS;
286     game_proxy_enq(&cmd);
287
288     for (i = 0; i < file.hc; i++)
289     {
290         cmd.type = CMD_MAKE_ITEM;
291
292         v_cpy(cmd.mkitem.p, file.hv[i].p);
293
294         cmd.mkitem.t = file.hv[i].t;
295         cmd.mkitem.n = file.hv[i].n;
296
297         game_proxy_enq(&cmd);
298     }
299 }
300
301 static void game_cmd_pkitem(int hi)
302 {
303     cmd.type      = CMD_PICK_ITEM;
304     cmd.pkitem.hi = hi;
305     game_proxy_enq(&cmd);
306 }
307
308 static void game_cmd_jump(int e)
309 {
310     cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
311     game_proxy_enq(&cmd);
312 }
313
314 static void game_cmd_rotate(void)
315 {
316     cmd.type = CMD_ROTATE;
317
318     cmd.rotate.x = game_rx;
319     cmd.rotate.z = game_rz;
320
321     game_proxy_enq(&cmd);
322 }
323
324 static void game_cmd_timer(void)
325 {
326     cmd.type    = CMD_TIMER;
327     cmd.timer.t = timer;
328     game_proxy_enq(&cmd);
329 }
330
331 static void game_cmd_coins(void)
332 {
333     cmd.type    = CMD_COINS;
334     cmd.coins.n = coins;
335     game_proxy_enq(&cmd);
336 }
337
338 static void game_cmd_status(void)
339 {
340     cmd.type     = CMD_STATUS;
341     cmd.status.t = status;
342     game_proxy_enq(&cmd);
343 }
344
345 /*---------------------------------------------------------------------------*/
346
347 static int   grow = 0;                  /* Should the ball be changing size? */
348 static float grow_orig = 0;             /* the original ball size            */
349 static float grow_goal = 0;             /* how big or small to get!          */
350 static float grow_t = 0.0;              /* timer for the ball to grow...     */
351 static float grow_strt = 0;             /* starting value for growth         */
352 static int   got_orig = 0;              /* Do we know original ball size?    */
353
354 #define GROW_TIME  0.5f                 /* sec for the ball to get to size.  */
355 #define GROW_BIG   1.5f                 /* large factor                      */
356 #define GROW_SMALL 0.5f                 /* small factor                      */
357
358 static int   grow_state = 0;            /* Current state (values -1, 0, +1)  */
359
360 static void grow_init(const struct s_file *fp, int type)
361 {
362     if (!got_orig)
363     {
364         grow_orig  = fp->uv->r;
365         grow_goal  = grow_orig;
366         grow_strt  = grow_orig;
367
368         grow_state = 0;
369
370         got_orig   = 1;
371     }
372
373     if (type == ITEM_SHRINK)
374     {
375         switch (grow_state)
376         {
377         case -1:
378             break;
379
380         case  0:
381             audio_play(AUD_SHRINK, 1.f);
382             grow_goal = grow_orig * GROW_SMALL;
383             grow_state = -1;
384             grow = 1;
385             break;
386
387         case +1:
388             audio_play(AUD_SHRINK, 1.f);
389             grow_goal = grow_orig;
390             grow_state = 0;
391             grow = 1;
392             break;
393         }
394     }
395     else if (type == ITEM_GROW)
396     {
397         switch (grow_state)
398         {
399         case -1:
400             audio_play(AUD_GROW, 1.f);
401             grow_goal = grow_orig;
402             grow_state = 0;
403             grow = 1;
404             break;
405
406         case  0:
407             audio_play(AUD_GROW, 1.f);
408             grow_goal = grow_orig * GROW_BIG;
409             grow_state = +1;
410             grow = 1;
411             break;
412
413         case +1:
414             break;
415         }
416     }
417
418     if (grow)
419     {
420         grow_t = 0.0;
421         grow_strt = fp->uv->r;
422     }
423 }
424
425 static void grow_step(const struct s_file *fp, float dt)
426 {
427     float dr;
428
429     if (!grow)
430         return;
431
432     /* Calculate new size based on how long since you touched the coin... */
433
434     grow_t += dt;
435
436     if (grow_t >= GROW_TIME)
437     {
438         grow = 0;
439         grow_t = GROW_TIME;
440     }
441
442     dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
443
444     /* No sinking through the floor! Keeps ball's bottom constant. */
445
446     fp->uv->p[1] += (dr - fp->uv->r);
447     fp->uv->r     =  dr;
448
449     game_cmd_ballradius();
450 }
451
452 /*---------------------------------------------------------------------------*/
453
454 static void view_init(void)
455 {
456     view_dp  = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
457     view_dc  = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
458     view_dz  = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
459     view_k   = 1.0f;
460     view_a   = 0.0f;
461
462     view_c[0] = 0.f;
463     view_c[1] = view_dc;
464     view_c[2] = 0.f;
465
466     view_p[0] =     0.f;
467     view_p[1] = view_dp;
468     view_p[2] = view_dz;
469
470     view_e[0][0] = 1.f;
471     view_e[0][1] = 0.f;
472     view_e[0][2] = 0.f;
473     view_e[1][0] = 0.f;
474     view_e[1][1] = 1.f;
475     view_e[1][2] = 0.f;
476     view_e[2][0] = 0.f;
477     view_e[2][1] = 0.f;
478     view_e[2][2] = 1.f;
479 }
480
481 int game_server_init(const char *file_name, int t, int e)
482 {
483     struct
484     {
485         int x, y;
486     } version;
487
488     int i;
489
490     timer      = (float) t / 100.f;
491     timer_down = (t > 0);
492     coins      = 0;
493     status     = GAME_NONE;
494
495     if (server_state)
496         game_server_free();
497
498     if (!sol_load_only_file(&file, file_name))
499         return (server_state = 0);
500
501     server_state = 1;
502
503     version.x = 0;
504     version.y = 0;
505
506     for (i = 0; i < file.dc; i++)
507     {
508         char *k = file.av + file.dv[i].ai;
509         char *v = file.av + file.dv[i].aj;
510
511         if (strcmp(k, "version") == 0)
512             sscanf(v, "%d.%d", &version.x, &version.y);
513     }
514
515     input_init();
516
517     game_rx = 0.0f;
518     game_rz = 0.0f;
519
520     /* Initialize jump and goal states. */
521
522     jump_e = 1;
523     jump_b = 0;
524
525     goal_e = e ? 1    : 0;
526     goal_k = e ? 1.0f : 0.0f;
527
528     /* Initialize the view. */
529
530     view_init();
531
532     /* Initialize ball size tracking... */
533
534     got_orig = 0;
535     grow = 0;
536
537     sol_cmd_enq_func(game_proxy_enq);
538
539     /* Queue client commands. */
540
541     game_cmd_map(file_name, version.x, version.y);
542     game_cmd_ups();
543     game_cmd_timer();
544
545     if (goal_e) game_cmd_goalopen();
546
547     game_cmd_init_balls();
548     game_cmd_init_items();
549     game_cmd_eou();
550
551     return server_state;
552 }
553
554 void game_server_free(void)
555 {
556     if (server_state)
557     {
558         sol_free(&file);
559         server_state = 0;
560     }
561 }
562
563 /*---------------------------------------------------------------------------*/
564
565 static void game_update_view(float dt)
566 {
567     static int view_prev;
568
569     float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
570     float da = input_get_r() * dt * 90.0f;
571     float k;
572
573     float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
574
575     /* Center the view about the ball. */
576
577     v_cpy(view_c, file.uv->p);
578
579     view_v[0] = -file.uv->v[0];
580     view_v[1] =  0.0f;
581     view_v[2] = -file.uv->v[2];
582
583     /* Restore usable vectors. */
584
585     if (view_prev == VIEW_TOPDOWN)
586     {
587         /* View basis. */
588
589         v_inv(view_e[2], view_e[1]);
590         v_cpy(view_e[1], Y);
591
592         /* View position. */
593
594         v_scl(v,    view_e[1], view_dp);
595         v_mad(v, v, view_e[2], view_dz);
596         v_add(view_p, v, file.uv->p);
597     }
598
599     view_prev = input_get_c();
600
601     switch (input_get_c())
602     {
603     case VIEW_LAZY: /* Viewpoint chases the ball position. */
604
605         v_sub(view_e[2], view_p, view_c);
606
607         break;
608
609     case VIEW_MANUAL:  /* View vector is given by view angle. */
610     case VIEW_TOPDOWN: /* Crude top-down view. */
611
612         view_e[2][0] = fsinf(V_RAD(view_a));
613         view_e[2][1] = 0.0;
614         view_e[2][2] = fcosf(V_RAD(view_a));
615
616         break;
617
618     case VIEW_CHASE: /* View vector approaches the ball velocity vector. */
619
620         v_sub(view_e[2], view_p, view_c);
621         v_nrm(view_e[2], view_e[2]);
622         v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
623
624         break;
625     }
626
627     /* Apply manual rotation. */
628
629     m_rot(M, Y, V_RAD(da));
630     m_vxfm(view_e[2], M, view_e[2]);
631
632     /* Orthonormalize the new view reference frame. */
633
634     v_crs(view_e[0], view_e[1], view_e[2]);
635     v_crs(view_e[2], view_e[0], view_e[1]);
636     v_nrm(view_e[0], view_e[0]);
637     v_nrm(view_e[2], view_e[2]);
638
639     /* Compute the new view position. */
640
641     k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
642
643     view_k = view_k + (k - view_k) * dt;
644
645     if (view_k < 0.5) view_k = 0.5;
646
647     v_scl(v,    view_e[1], view_dp * view_k);
648     v_mad(v, v, view_e[2], view_dz * view_k);
649     v_add(view_p, v, file.uv->p);
650
651     /* Compute the new view center. */
652
653     v_cpy(view_c, file.uv->p);
654     v_mad(view_c, view_c, view_e[1], dc);
655
656     /* Note the current view angle. */
657
658     view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
659
660     /* Override vectors for top-down view. */
661
662     if (input_get_c() == VIEW_TOPDOWN)
663     {
664         v_inv(view_e[1], view_e[2]);
665         v_cpy(view_e[2], Y);
666
667         v_cpy(view_c, file.uv->p);
668         v_mad(view_p, view_c, view_e[2], view_dz * 1.5f);
669     }
670
671     game_cmd_updview();
672 }
673
674 static void game_update_time(float dt, int b)
675 {
676     if (goal_e && goal_k < 1.0f)
677         goal_k += dt;
678
679    /* The ticking clock. */
680
681     if (b && timer_down)
682     {
683         if (timer < 600.f)
684             timer -= dt;
685         if (timer < 0.f)
686             timer = 0.f;
687     }
688     else if (b)
689     {
690         timer += dt;
691     }
692
693     if (b) game_cmd_timer();
694 }
695
696 static int game_update_state(int bt)
697 {
698     struct s_file *fp = &file;
699     struct s_goal *zp;
700     int hi;
701
702     float p[3];
703
704     /* Test for an item. */
705
706     if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
707     {
708         struct s_item *hp = &file.hv[hi];
709
710         game_cmd_pkitem(hi);
711
712         grow_init(fp, hp->t);
713
714         if (hp->t == ITEM_COIN)
715         {
716             coins += hp->n;
717             game_cmd_coins();
718         }
719
720         audio_play(AUD_COIN, 1.f);
721
722         /* Discard item. */
723
724         hp->t = ITEM_NONE;
725     }
726
727     /* Test for a switch. */
728
729     if (sol_swch_test(fp, 0))
730         audio_play(AUD_SWITCH, 1.f);
731
732     /* Test for a jump. */
733
734     if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
735     {
736         jump_b  = 1;
737         jump_e  = 0;
738         jump_dt = 0.f;
739
740         audio_play(AUD_JUMP, 1.f);
741
742         game_cmd_jump(1);
743     }
744     if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
745     {
746         jump_e = 1;
747         game_cmd_jump(0);
748     }
749
750     /* Test for a goal. */
751
752     if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
753     {
754         audio_play(AUD_GOAL, 1.0f);
755         return GAME_GOAL;
756     }
757
758     /* Test for time-out. */
759
760     if (bt && timer_down && timer <= 0.f)
761     {
762         audio_play(AUD_TIME, 1.0f);
763         return GAME_TIME;
764     }
765
766     /* Test for fall-out. */
767
768     if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
769     {
770         audio_play(AUD_FALL, 1.0f);
771         return GAME_FALL;
772     }
773
774     return GAME_NONE;
775 }
776
777 static int game_step(const float g[3], float dt, int bt)
778 {
779     if (server_state)
780     {
781         struct s_file *fp = &file;
782
783         float h[3];
784
785         /* Smooth jittery or discontinuous input. */
786
787         game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
788         game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
789
790         game_cmd_rotate();
791
792         grow_step(fp, dt);
793
794         game_comp_grav(h, g, view_a, game_rx, game_rz);
795
796         if (jump_b)
797         {
798             jump_dt += dt;
799
800             /* Handle a jump. */
801
802             if (0.5f < jump_dt)
803             {
804                 fp->uv[0].p[0] = jump_p[0];
805                 fp->uv[0].p[1] = jump_p[1];
806                 fp->uv[0].p[2] = jump_p[2];
807             }
808             if (1.0f < jump_dt)
809                 jump_b = 0;
810         }
811         else
812         {
813             /* Run the sim. */
814
815             float b = sol_step(fp, h, dt, 0, NULL);
816
817             /* Mix the sound of a ball bounce. */
818
819             if (b > 0.5f)
820             {
821                 float k = (b - 0.5f) * 2.0f;
822
823                 if (got_orig)
824                 {
825                     if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
826                     else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
827                     else                            audio_play(AUD_BUMPM, k);
828                 }
829                 else audio_play(AUD_BUMPM, k);
830             }
831         }
832
833         game_cmd_updball();
834
835         game_update_view(dt);
836         game_update_time(dt, bt);
837
838         return game_update_state(bt);
839     }
840     return GAME_NONE;
841 }
842
843 void game_server_step(float dt)
844 {
845     static const float gup[] = { 0.0f, +9.8f, 0.0f };
846     static const float gdn[] = { 0.0f, -9.8f, 0.0f };
847
848     switch (status)
849     {
850     case GAME_GOAL: game_step(gup, dt, 0); break;
851     case GAME_FALL: game_step(gdn, dt, 0); break;
852
853     case GAME_NONE:
854         if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
855             game_cmd_status();
856         break;
857     }
858
859     game_cmd_eou();
860 }
861
862 /*---------------------------------------------------------------------------*/
863
864 void game_set_goal(void)
865 {
866     audio_play(AUD_SWITCH, 1.0f);
867     goal_e = 1;
868
869     game_cmd_goalopen();
870 }
871
872 void game_clr_goal(void)
873 {
874     goal_e = 0;
875 }
876
877 /*---------------------------------------------------------------------------*/
878
879 void game_set_x(int k)
880 {
881     input_set_x(-ANGLE_BOUND * k / JOY_MAX);
882 }
883
884 void game_set_z(int k)
885 {
886     input_set_z(+ANGLE_BOUND * k / JOY_MAX);
887 }
888
889 void game_set_ang(int x, int z)
890 {
891     input_set_x(x);
892     input_set_z(z);
893 }
894
895 void game_set_pos(int x, int y)
896 {
897     input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
898     input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
899 }
900
901 void game_set_cam(int c)
902 {
903     input_set_c(c);
904 }
905
906 void game_set_rot(float r)
907 {
908     input_set_r(r);
909 }
910
911 void game_set_fly(float k, const struct s_file *fp)
912 {
913     float  x[3] = { 1.f, 0.f, 0.f };
914     float  y[3] = { 0.f, 1.f, 0.f };
915     float  z[3] = { 0.f, 0.f, 1.f };
916     float c0[3] = { 0.f, 0.f, 0.f };
917     float p0[3] = { 0.f, 0.f, 0.f };
918     float c1[3] = { 0.f, 0.f, 0.f };
919     float p1[3] = { 0.f, 0.f, 0.f };
920     float  v[3];
921
922     if (!fp) fp = &file;
923
924     view_init();
925
926     z[0] = fsinf(V_RAD(view_a));
927     z[2] = fcosf(V_RAD(view_a));
928
929     v_cpy(view_e[0], x);
930     v_cpy(view_e[1], y);
931     v_cpy(view_e[2], z);
932
933     /* k = 0.0 view is at the ball. */
934
935     if (fp->uc > 0)
936     {
937         v_cpy(c0, fp->uv[0].p);
938         v_cpy(p0, fp->uv[0].p);
939     }
940
941     v_mad(p0, p0, y, view_dp);
942     v_mad(p0, p0, z, view_dz);
943     v_mad(c0, c0, y, view_dc);
944
945     /* k = +1.0 view is s_view 0 */
946
947     if (k >= 0 && fp->wc > 0)
948     {
949         v_cpy(p1, fp->wv[0].p);
950         v_cpy(c1, fp->wv[0].q);
951     }
952
953     /* k = -1.0 view is s_view 1 */
954
955     if (k <= 0 && fp->wc > 1)
956     {
957         v_cpy(p1, fp->wv[1].p);
958         v_cpy(c1, fp->wv[1].q);
959     }
960
961     /* Interpolate the views. */
962
963     v_sub(v, p1, p0);
964     v_mad(view_p, p0, v, k * k);
965
966     v_sub(v, c1, c0);
967     v_mad(view_c, c0, v, k * k);
968
969     /* Orthonormalize the view basis. */
970
971     v_sub(view_e[2], view_p, view_c);
972     v_crs(view_e[0], view_e[1], view_e[2]);
973     v_crs(view_e[2], view_e[0], view_e[1]);
974     v_nrm(view_e[0], view_e[0]);
975     v_nrm(view_e[2], view_e[2]);
976
977     game_cmd_updview();
978 }
979
980 /*---------------------------------------------------------------------------*/