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