36322879014d995fbfd9796a1a3ce97852ff2a66
[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     v_cpy(cmd.ballpos.p, file.uv[0].p);
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     v_cpy(cmd.viewpos.p, view.p);
245     game_proxy_enq(&cmd);
246
247     cmd.type = CMD_VIEW_CENTER;
248     v_cpy(cmd.viewcenter.c, view.c);
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 static struct lockstep server_step;
460
461 int game_server_init(const char *file_name, int t, int e)
462 {
463     struct
464     {
465         int x, y;
466     } version;
467
468     int i;
469
470     timer      = (float) t / 100.f;
471     timer_down = (t > 0);
472     coins      = 0;
473     status     = GAME_NONE;
474
475     if (server_state)
476         game_server_free();
477
478     if (!sol_load_only_file(&file, file_name))
479         return (server_state = 0);
480
481     server_state = 1;
482
483     version.x = 0;
484     version.y = 0;
485
486     for (i = 0; i < file.dc; i++)
487     {
488         char *k = file.av + file.dv[i].ai;
489         char *v = file.av + file.dv[i].aj;
490
491         if (strcmp(k, "version") == 0)
492             sscanf(v, "%d.%d", &version.x, &version.y);
493     }
494
495     input_init();
496
497     game_tilt_init(&tilt);
498
499     /* Initialize jump and goal states. */
500
501     jump_e = 1;
502     jump_b = 0;
503
504     goal_e = e ? 1    : 0;
505     goal_k = e ? 1.0f : 0.0f;
506
507     /* Initialize the view. */
508
509     game_view_init(&view);
510
511     view_k = 1.0f;
512
513     /* Initialize ball size tracking... */
514
515     got_orig = 0;
516     grow = 0;
517
518     /* Initialize simulation. */
519
520     sol_init_sim(&file);
521
522     sol_cmd_enq_func(game_proxy_enq);
523
524     /* Queue client commands. */
525
526     game_cmd_map(file_name, version.x, version.y);
527     game_cmd_ups();
528     game_cmd_timer();
529
530     if (goal_e) game_cmd_goalopen();
531
532     game_cmd_init_balls();
533     game_cmd_init_items();
534
535     lockstep_clr(&server_step);
536
537     return server_state;
538 }
539
540 void game_server_free(void)
541 {
542     if (server_state)
543     {
544         sol_quit_sim();
545         sol_free(&file);
546         server_state = 0;
547     }
548 }
549
550 /*---------------------------------------------------------------------------*/
551
552 static void game_update_view(float dt)
553 {
554     float dc = view.dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
555     float da = input_get_r() * dt * 90.0f;
556     float k;
557
558     float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
559     float view_v[3];
560
561     /* Center the view about the ball. */
562
563     v_cpy(view.c, file.uv->p);
564
565     view_v[0] = -file.uv->v[0];
566     view_v[1] =  0.0f;
567     view_v[2] = -file.uv->v[2];
568
569     switch (input_get_c())
570     {
571     case VIEW_LAZY: /* Viewpoint chases the ball position. */
572
573         v_sub(view.e[2], view.p, view.c);
574
575         break;
576
577     case VIEW_MANUAL:  /* View vector is given by view angle. */
578
579         view.e[2][0] = fsinf(V_RAD(view.a));
580         view.e[2][1] = 0.0;
581         view.e[2][2] = fcosf(V_RAD(view.a));
582
583         break;
584
585     case VIEW_CHASE: /* View vector approaches the ball velocity vector. */
586
587         v_sub(view.e[2], view.p, view.c);
588         v_nrm(view.e[2], view.e[2]);
589         v_mad(view.e[2], view.e[2], view_v, v_dot(view_v, view_v) * dt / 4);
590
591         break;
592     }
593
594     /* Apply manual rotation. */
595
596     m_rot(M, Y, V_RAD(da));
597     m_vxfm(v, M, view.e[2]);
598     v_cpy(view.e[2], v);
599
600     /* Orthonormalize the new view reference frame. */
601
602     v_crs(view.e[0], view.e[1], view.e[2]);
603     v_crs(view.e[2], view.e[0], view.e[1]);
604     v_nrm(view.e[0], view.e[0]);
605     v_nrm(view.e[2], view.e[2]);
606
607     /* Compute the new view position. */
608
609     k = 1.0f + v_dot(view.e[2], view_v) / 10.0f;
610
611     view_k = view_k + (k - view_k) * dt;
612
613     if (view_k < 0.5) view_k = 0.5;
614
615     v_scl(v,    view.e[1], view.dp * view_k);
616     v_mad(v, v, view.e[2], view.dz * view_k);
617     v_add(view.p, v, file.uv->p);
618
619     /* Compute the new view center. */
620
621     v_cpy(view.c, file.uv->p);
622     v_mad(view.c, view.c, view.e[1], dc);
623
624     /* Note the current view angle. */
625
626     view.a = V_DEG(fatan2f(view.e[2][0], view.e[2][2]));
627
628     game_cmd_updview();
629 }
630
631 static void game_update_time(float dt, int b)
632 {
633     if (goal_e && goal_k < 1.0f)
634         goal_k += dt;
635
636    /* The ticking clock. */
637
638     if (b && timer_down)
639     {
640         if (timer < 600.f)
641             timer -= dt;
642         if (timer < 0.f)
643             timer = 0.f;
644     }
645     else if (b)
646     {
647         timer += dt;
648     }
649
650     if (b) game_cmd_timer();
651 }
652
653 static int game_update_state(int bt)
654 {
655     struct s_file *fp = &file;
656     struct s_goal *zp;
657     int hi;
658
659     float p[3];
660
661     /* Test for an item. */
662
663     if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
664     {
665         struct s_item *hp = &file.hv[hi];
666
667         game_cmd_pkitem(hi);
668
669         grow_init(fp, hp->t);
670
671         if (hp->t == ITEM_COIN)
672         {
673             coins += hp->n;
674             game_cmd_coins();
675         }
676
677         audio_play(AUD_COIN, 1.f);
678
679         /* Discard item. */
680
681         hp->t = ITEM_NONE;
682     }
683
684     /* Test for a switch. */
685
686     if (sol_swch_test(fp, 0))
687         audio_play(AUD_SWITCH, 1.f);
688
689     /* Test for a jump. */
690
691     if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
692     {
693         jump_b  = 1;
694         jump_e  = 0;
695         jump_dt = 0.f;
696
697         v_sub(jump_w, jump_p, fp->uv->p);
698         v_add(jump_w, view.p, jump_w);
699
700         audio_play(AUD_JUMP, 1.f);
701
702         game_cmd_jump(1);
703     }
704     if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
705     {
706         jump_e = 1;
707         game_cmd_jump(0);
708     }
709
710     /* Test for a goal. */
711
712     if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
713     {
714         audio_play(AUD_GOAL, 1.0f);
715         return GAME_GOAL;
716     }
717
718     /* Test for time-out. */
719
720     if (bt && timer_down && timer <= 0.f)
721     {
722         audio_play(AUD_TIME, 1.0f);
723         return GAME_TIME;
724     }
725
726     /* Test for fall-out. */
727
728     if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
729     {
730         audio_play(AUD_FALL, 1.0f);
731         return GAME_FALL;
732     }
733
734     return GAME_NONE;
735 }
736
737 static int game_step(const float g[3], float dt, int bt)
738 {
739     if (server_state)
740     {
741         struct s_file *fp = &file;
742
743         float h[3];
744
745         /* Smooth jittery or discontinuous input. */
746
747         tilt.rx += (input_get_x() - tilt.rx) * dt / RESPONSE;
748         tilt.rz += (input_get_z() - tilt.rz) * dt / RESPONSE;
749
750         game_tilt_axes(&tilt, view.e);
751
752         game_cmd_tiltaxes();
753         game_cmd_tiltangles();
754
755         grow_step(fp, dt);
756
757         game_tilt_grav(h, g, &tilt);
758
759         if (jump_b)
760         {
761             jump_dt += dt;
762
763             /* Handle a jump. */
764
765             if (0.5f < jump_dt)
766             {
767                 v_cpy(fp->uv->p, jump_p);
768                 v_cpy(view.p,    jump_w);
769             }
770             if (1.0f < jump_dt)
771                 jump_b = 0;
772         }
773         else
774         {
775             /* Run the sim. */
776
777             float b = sol_step(fp, h, dt, 0, NULL);
778
779             /* Mix the sound of a ball bounce. */
780
781             if (b > 0.5f)
782             {
783                 float k = (b - 0.5f) * 2.0f;
784
785                 if (got_orig)
786                 {
787                     if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
788                     else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
789                     else                            audio_play(AUD_BUMPM, k);
790                 }
791                 else audio_play(AUD_BUMPM, k);
792             }
793         }
794
795         game_cmd_updball();
796
797         game_update_view(dt);
798         game_update_time(dt, bt);
799
800         return game_update_state(bt);
801     }
802     return GAME_NONE;
803 }
804
805 static void game_server_iter(float dt)
806 {
807     switch (status)
808     {
809     case GAME_GOAL: game_step(GRAVITY_UP, dt, 0); break;
810     case GAME_FALL: game_step(GRAVITY_DN, dt, 0); break;
811
812     case GAME_NONE:
813         if ((status = game_step(GRAVITY_DN, dt, 1)) != GAME_NONE)
814             game_cmd_status();
815         break;
816     }
817
818     game_cmd_eou();
819 }
820
821 static struct lockstep server_step = { game_server_iter, DT };
822
823 void game_server_step(float dt)
824 {
825     lockstep_run(&server_step, dt);
826 }
827
828 /*---------------------------------------------------------------------------*/
829
830 void game_set_goal(void)
831 {
832     audio_play(AUD_SWITCH, 1.0f);
833     goal_e = 1;
834
835     game_cmd_goalopen();
836 }
837
838 void game_clr_goal(void)
839 {
840     goal_e = 0;
841 }
842
843 /*---------------------------------------------------------------------------*/
844
845 void game_set_x(int k)
846 {
847     input_set_x(-ANGLE_BOUND * k / JOY_MAX);
848 }
849
850 void game_set_z(int k)
851 {
852     input_set_z(+ANGLE_BOUND * k / JOY_MAX);
853 }
854
855 void game_set_ang(int x, int z)
856 {
857     input_set_x(x);
858     input_set_z(z);
859 }
860
861 void game_set_pos(int x, int y)
862 {
863     const float range = ANGLE_BOUND * 2;
864
865     input_set_x(input_get_x() + range * y / config_get_d(CONFIG_MOUSE_SENSE));
866     input_set_z(input_get_z() + range * x / config_get_d(CONFIG_MOUSE_SENSE));
867 }
868
869 void game_set_cam(int c)
870 {
871     input_set_c(c);
872 }
873
874 void game_set_rot(float r)
875 {
876     input_set_r(r);
877 }
878
879 void game_server_fly(float k)
880 {
881     game_view_fly(&view, &file, k);
882     game_cmd_updview();
883 }
884
885 /*---------------------------------------------------------------------------*/