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