Split SOL data structures into base, varying and rendering parts
[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 file;
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, file.uv[0].p);
189     game_proxy_enq(&cmd);
190
191     cmd.type = CMD_BALL_BASIS;
192     v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
193     v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
194     game_proxy_enq(&cmd);
195
196     cmd.type = CMD_BALL_PEND_BASIS;
197     v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
198     v_cpy(cmd.ballpendbasis.E[1], file.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 = file.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 < file.hc; i++)
245     {
246         cmd.type = CMD_MAKE_ITEM;
247
248         v_cpy(cmd.mkitem.p, file.hv[i].p);
249
250         cmd.mkitem.t = file.hv[i].t;
251         cmd.mkitem.n = file.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
425     {
426         int x, y;
427     } version;
428
429     int i;
430
431     timer      = (float) t / 100.f;
432     timer_down = (t > 0);
433     coins      = 0;
434     status     = GAME_NONE;
435
436     if (server_state)
437         game_server_free();
438
439     if (!sol_load_base(&base, file_name))
440         return (server_state = 0);
441
442     if (!sol_load_vary(&file, &base))
443         return (server_state = 0);
444
445     server_state = 1;
446
447     version.x = 0;
448     version.y = 0;
449
450     for (i = 0; i < file.base->dc; i++)
451     {
452         char *k = file.base->av + file.base->dv[i].ai;
453         char *v = file.base->av + file.base->dv[i].aj;
454
455         if (strcmp(k, "version") == 0)
456             sscanf(v, "%d.%d", &version.x, &version.y);
457     }
458
459     input_init();
460
461     game_tilt_init(&tilt);
462
463     /* Initialize jump and goal states. */
464
465     jump_e = 1;
466     jump_b = 0;
467
468     goal_e = e ? 1    : 0;
469     goal_k = e ? 1.0f : 0.0f;
470
471     /* Initialize the view. */
472
473     game_view_init(&view);
474
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(&file);
485
486     sol_cmd_enq_func(game_proxy_enq);
487
488     /* Queue client commands. */
489
490     game_cmd_map(file_name, version.x, version.y);
491     game_cmd_ups();
492     game_cmd_timer();
493
494     if (goal_e) game_cmd_goalopen();
495
496     game_cmd_init_balls();
497     game_cmd_init_items();
498
499     /* Store the initial view. */
500
501     game_server_fly(0.0f);
502
503     /* End first update. */
504
505     game_cmd_eou();
506
507     /* Reset lockstep state. */
508
509     lockstep_clr(&server_step);
510
511     return server_state;
512 }
513
514 void game_server_free(void)
515 {
516     if (server_state)
517     {
518         sol_quit_sim();
519         sol_free_vary(&file);
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, file.uv->p);
538
539     view_v[0] = -file.uv->v[0];
540     view_v[1] =  0.0f;
541     view_v[2] = -file.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, file.uv->p);
592
593     /* Compute the new view center. */
594
595     v_cpy(view.c, file.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 s_vary *vary = &file;
630     struct b_goal *zp;
631     int hi;
632
633     float p[3];
634
635     /* Test for an item. */
636
637     if (bt && (hi = sol_item_test(vary, p, ITEM_RADIUS)) != -1)
638     {
639         struct v_item *hp = file.hv + hi;
640
641         game_cmd_pkitem(hi);
642
643         grow_init(vary, hp->t);
644
645         if (hp->t == ITEM_COIN)
646         {
647             coins += hp->n;
648             game_cmd_coins();
649         }
650
651         audio_play(AUD_COIN, 1.f);
652
653         /* Discard item. */
654
655         hp->t = ITEM_NONE;
656     }
657
658     /* Test for a switch. */
659
660     if (sol_swch_test(vary, 0) == SWCH_TRIGGER)
661         audio_play(AUD_SWITCH, 1.f);
662
663     /* Test for a jump. */
664
665     if (jump_e == 1 && jump_b == 0 && (sol_jump_test(vary, jump_p, 0) ==
666                                        JUMP_TRIGGER))
667     {
668         jump_b  = 1;
669         jump_e  = 0;
670         jump_dt = 0.f;
671
672         v_sub(jump_w, jump_p, vary->uv->p);
673         v_add(jump_w, view.p, jump_w);
674
675         audio_play(AUD_JUMP, 1.f);
676
677         game_cmd_jump(1);
678     }
679     if (jump_e == 0 && jump_b == 0 && (sol_jump_test(vary, jump_p, 0) ==
680                                        JUMP_OUTSIDE))
681     {
682         jump_e = 1;
683         game_cmd_jump(0);
684     }
685
686     /* Test for a goal. */
687
688     if (bt && goal_e && (zp = sol_goal_test(vary, p, 0)))
689     {
690         audio_play(AUD_GOAL, 1.0f);
691         return GAME_GOAL;
692     }
693
694     /* Test for time-out. */
695
696     if (bt && timer_down && timer <= 0.f)
697     {
698         audio_play(AUD_TIME, 1.0f);
699         return GAME_TIME;
700     }
701
702     /* Test for fall-out. */
703
704     if (bt && vary->uv[0].p[1] < vary->base->vv[0].p[1])
705     {
706         audio_play(AUD_FALL, 1.0f);
707         return GAME_FALL;
708     }
709
710     return GAME_NONE;
711 }
712
713 static int game_step(const float g[3], float dt, int bt)
714 {
715     if (server_state)
716     {
717         struct s_vary *vary = &file;
718
719         float h[3];
720
721         /* Smooth jittery or discontinuous input. */
722
723         tilt.rx += (input_get_x() - tilt.rx) * dt / RESPONSE;
724         tilt.rz += (input_get_z() - tilt.rz) * dt / RESPONSE;
725
726         game_tilt_axes(&tilt, view.e);
727
728         game_cmd_tiltaxes();
729         game_cmd_tiltangles();
730
731         grow_step(vary, dt);
732
733         game_tilt_grav(h, g, &tilt);
734
735         if (jump_b)
736         {
737             jump_dt += dt;
738
739             /* Handle a jump. */
740
741             if (0.5f < jump_dt)
742             {
743                 v_cpy(vary->uv->p, jump_p);
744                 v_cpy(view.p,      jump_w);
745             }
746             if (1.0f < jump_dt)
747                 jump_b = 0;
748         }
749         else
750         {
751             /* Run the sim. */
752
753             float b = sol_step(vary, h, dt, 0, NULL);
754
755             /* Mix the sound of a ball bounce. */
756
757             if (b > 0.5f)
758             {
759                 float k = (b - 0.5f) * 2.0f;
760
761                 if (got_orig)
762                 {
763                     if      (vary->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
764                     else if (vary->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
765                     else                              audio_play(AUD_BUMPM, k);
766                 }
767                 else audio_play(AUD_BUMPM, k);
768             }
769         }
770
771         game_cmd_updball();
772
773         game_update_view(dt);
774         game_update_time(dt, bt);
775
776         return game_update_state(bt);
777     }
778     return GAME_NONE;
779 }
780
781 static void game_server_iter(float dt)
782 {
783     switch (status)
784     {
785     case GAME_GOAL: game_step(GRAVITY_UP, dt, 0); break;
786     case GAME_FALL: game_step(GRAVITY_DN, dt, 0); break;
787
788     case GAME_NONE:
789         if ((status = game_step(GRAVITY_DN, dt, 1)) != GAME_NONE)
790             game_cmd_status();
791         break;
792     }
793
794     game_cmd_eou();
795 }
796
797 static struct lockstep server_step = { game_server_iter, DT };
798
799 void game_server_step(float dt)
800 {
801     lockstep_run(&server_step, dt);
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 void game_clr_goal(void)
815 {
816     goal_e = 0;
817 }
818
819 /*---------------------------------------------------------------------------*/
820
821 void game_set_x(float k)
822 {
823     input_set_x(-ANGLE_BOUND * k);
824 }
825
826 void game_set_z(float k)
827 {
828     input_set_z(+ANGLE_BOUND * k);
829 }
830
831 void game_set_ang(int x, int z)
832 {
833     input_set_x(x);
834     input_set_z(z);
835 }
836
837 void game_set_pos(int x, int y)
838 {
839     const float range = ANGLE_BOUND * 2;
840
841     input_set_x(input_get_x() + range * y / config_get_d(CONFIG_MOUSE_SENSE));
842     input_set_z(input_get_z() + range * x / config_get_d(CONFIG_MOUSE_SENSE));
843 }
844
845 void game_set_cam(int c)
846 {
847     input_set_c(c);
848 }
849
850 void game_set_rot(float r)
851 {
852     input_set_r(r);
853 }
854
855 void game_server_fly(float k)
856 {
857     game_view_fly(&view, &file, k);
858     game_cmd_updview();
859 }
860
861 /*---------------------------------------------------------------------------*/