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