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