2 * Copyright (C) 2003 Robert Kooima
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.
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.
21 #include "solid_phys.h"
26 #include "game_common.h"
27 #include "game_server.h"
28 #include "game_proxy.h"
32 /*---------------------------------------------------------------------------*/
34 static int server_state = 0;
36 static struct s_file file;
38 static float timer = 0.f; /* Clock time */
39 static int timer_down = 1; /* Timer go up or down? */
41 static int status = GAME_NONE; /* Outcome of the game */
43 static float game_rx; /* Floor rotation about X axis */
44 static float game_rz; /* Floor rotation about Z axis */
46 static float view_a; /* Ideal view rotation about Y axis */
47 static float view_dc; /* Ideal view distance above ball */
48 static float view_dp; /* Ideal view distance above ball */
49 static float view_dz; /* Ideal view distance behind ball */
51 static float view_c[3]; /* Current view center */
52 static float view_v[3]; /* Current view vector */
53 static float view_p[3]; /* Current view position */
54 static float view_e[3][3]; /* Current view reference frame */
57 static int coins = 0; /* Collected coins */
58 static int goal_e = 0; /* Goal enabled flag */
59 static float goal_k = 0; /* Goal animation */
60 static int jump_e = 1; /* Jumping enabled flag */
61 static int jump_b = 0; /* Jump-in-progress flag */
62 static float jump_dt; /* Jump duration */
63 static float jump_p[3]; /* Jump destination */
65 /*---------------------------------------------------------------------------*/
68 * This is an abstraction of the game's input state. All input is
69 * encapsulated here, and all references to the input by the game are
70 * made here. TODO: This used to have the effect of homogenizing
71 * input for use in replay recording and playback, but it's not clear
72 * how relevant this approach is with the introduction of the command
76 * -32767 = -ANGLE_BOUND
77 * +32767 = +ANGLE_BOUND
80 * -32767 = -VIEWR_BOUND
81 * +32767 = +VIEWR_BOUND
93 static struct input input_current;
95 static void input_init(void)
103 static void input_set_x(float x)
105 if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
106 if (x > ANGLE_BOUND) x = ANGLE_BOUND;
108 input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
111 static void input_set_z(float z)
113 if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
114 if (z > ANGLE_BOUND) z = ANGLE_BOUND;
116 input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
119 static void input_set_r(float r)
121 if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
122 if (r > VIEWR_BOUND) r = VIEWR_BOUND;
124 input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
127 static void input_set_c(int c)
129 input_current.c = (short) c;
132 static float input_get_x(void)
134 return ANGLE_BOUND * (float) input_current.x / 32767.0f;
137 static float input_get_z(void)
139 return ANGLE_BOUND * (float) input_current.z / 32767.0f;
142 static float input_get_r(void)
144 return VIEWR_BOUND * (float) input_current.r / 32767.0f;
147 static int input_get_c(void)
149 return (int) input_current.c;
152 int input_put(FILE *fout)
156 put_short(fout, &input_current.x);
157 put_short(fout, &input_current.z);
158 put_short(fout, &input_current.r);
159 put_short(fout, &input_current.c);
166 int input_get(FILE *fin)
170 get_short(fin, &input_current.x);
171 get_short(fin, &input_current.z);
172 get_short(fin, &input_current.r);
173 get_short(fin, &input_current.c);
175 return (feof(fin) ? 0 : 1);
180 /*---------------------------------------------------------------------------*/
183 * Utility functions for preparing the "server" state and events for
184 * consumption by the "client".
187 static union cmd cmd;
189 static void game_cmd_eou(void)
191 cmd.type = CMD_END_OF_UPDATE;
192 game_proxy_enq(&cmd);
195 static void game_cmd_ups(void)
197 cmd.type = CMD_UPDATES_PER_SECOND;
199 game_proxy_enq(&cmd);
202 static void game_cmd_sound(const char *filename, float a)
204 cmd.type = CMD_SOUND;
206 cmd.sound.n = strdup(filename);
209 game_proxy_enq(&cmd);
212 #define audio_play(s, f) game_cmd_sound((s), (f))
214 static void game_cmd_goalopen(void)
216 cmd.type = CMD_GOAL_OPEN;
217 game_proxy_enq(&cmd);
220 static void game_cmd_updball(void)
222 cmd.type = CMD_BALL_POSITION;
223 memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
224 game_proxy_enq(&cmd);
226 cmd.type = CMD_BALL_BASIS;
227 v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
228 v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
229 game_proxy_enq(&cmd);
231 cmd.type = CMD_BALL_PEND_BASIS;
232 v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
233 v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
234 game_proxy_enq(&cmd);
237 static void game_cmd_updview(void)
239 cmd.type = CMD_VIEW_POSITION;
240 memcpy(cmd.viewpos.p, view_p, sizeof (float) * 3);
241 game_proxy_enq(&cmd);
243 cmd.type = CMD_VIEW_CENTER;
244 memcpy(cmd.viewcenter.c, view_c, sizeof (float) * 3);
245 game_proxy_enq(&cmd);
247 cmd.type = CMD_VIEW_BASIS;
248 v_cpy(cmd.viewbasis.e[0], view_e[0]);
249 v_cpy(cmd.viewbasis.e[1], view_e[1]);
250 game_proxy_enq(&cmd);
253 static void game_cmd_ballradius(void)
255 cmd.type = CMD_BALL_RADIUS;
256 cmd.ballradius.r = file.uv[0].r;
257 game_proxy_enq(&cmd);
260 static void game_cmd_init_balls(void)
262 cmd.type = CMD_CLEAR_BALLS;
263 game_proxy_enq(&cmd);
265 cmd.type = CMD_MAKE_BALL;
266 game_proxy_enq(&cmd);
269 game_cmd_ballradius();
272 static void game_cmd_init_items(void)
276 cmd.type = CMD_CLEAR_ITEMS;
277 game_proxy_enq(&cmd);
279 for (i = 0; i < file.hc; i++)
281 cmd.type = CMD_MAKE_ITEM;
283 v_cpy(cmd.mkitem.p, file.hv[i].p);
285 cmd.mkitem.t = file.hv[i].t;
286 cmd.mkitem.n = file.hv[i].n;
288 game_proxy_enq(&cmd);
292 static void game_cmd_pkitem(int hi)
294 cmd.type = CMD_PICK_ITEM;
296 game_proxy_enq(&cmd);
299 static void game_cmd_jump(int e)
301 cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
302 game_proxy_enq(&cmd);
305 static void game_cmd_rotate(void)
307 cmd.type = CMD_ROTATE;
309 cmd.rotate.x = game_rx;
310 cmd.rotate.z = game_rz;
312 game_proxy_enq(&cmd);
315 static void game_cmd_timer(void)
317 cmd.type = CMD_TIMER;
319 game_proxy_enq(&cmd);
322 static void game_cmd_coins(void)
324 cmd.type = CMD_COINS;
326 game_proxy_enq(&cmd);
329 static void game_cmd_status(void)
331 cmd.type = CMD_STATUS;
332 cmd.status.t = status;
333 game_proxy_enq(&cmd);
336 /*---------------------------------------------------------------------------*/
338 static int grow = 0; /* Should the ball be changing size? */
339 static float grow_orig = 0; /* the original ball size */
340 static float grow_goal = 0; /* how big or small to get! */
341 static float grow_t = 0.0; /* timer for the ball to grow... */
342 static float grow_strt = 0; /* starting value for growth */
343 static int got_orig = 0; /* Do we know original ball size? */
345 #define GROW_TIME 0.5f /* sec for the ball to get to size. */
346 #define GROW_BIG 1.5f /* large factor */
347 #define GROW_SMALL 0.5f /* small factor */
349 static int grow_state = 0; /* Current state (values -1, 0, +1) */
351 static void grow_init(const struct s_file *fp, int type)
355 grow_orig = fp->uv->r;
356 grow_goal = grow_orig;
357 grow_strt = grow_orig;
364 if (type == ITEM_SHRINK)
372 audio_play(AUD_SHRINK, 1.f);
373 grow_goal = grow_orig * GROW_SMALL;
379 audio_play(AUD_SHRINK, 1.f);
380 grow_goal = grow_orig;
386 else if (type == ITEM_GROW)
391 audio_play(AUD_GROW, 1.f);
392 grow_goal = grow_orig;
398 audio_play(AUD_GROW, 1.f);
399 grow_goal = grow_orig * GROW_BIG;
412 grow_strt = fp->uv->r;
416 static void grow_step(const struct s_file *fp, float dt)
423 /* Calculate new size based on how long since you touched the coin... */
427 if (grow_t >= GROW_TIME)
433 dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
435 /* No sinking through the floor! Keeps ball's bottom constant. */
437 fp->uv->p[1] += (dr - fp->uv->r);
440 game_cmd_ballradius();
443 /*---------------------------------------------------------------------------*/
445 static void view_init(void)
447 view_dp = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
448 view_dc = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
449 view_dz = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
472 int game_server_init(const char *file_name, int t, int e)
474 timer = (float) t / 100.f;
475 timer_down = (t > 0);
482 if (!sol_load_only_file(&file, config_data(file_name)))
483 return (server_state = 0);
492 /* Initialize jump and goal states. */
498 goal_k = e ? 1.0f : 0.0f;
500 /* Initialize the view. */
504 /* Initialize ball size tracking... */
509 sol_cmd_enq_func(game_proxy_enq);
511 /* Queue client commands. */
516 if (goal_e) game_cmd_goalopen();
518 game_cmd_init_balls();
519 game_cmd_init_items();
525 void game_server_free(void)
534 /*---------------------------------------------------------------------------*/
536 static void game_update_view(float dt)
538 float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
539 float da = input_get_r() * dt * 90.0f;
542 float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
544 /* Center the view about the ball. */
546 v_cpy(view_c, file.uv->p);
547 v_inv(view_v, file.uv->v);
549 switch (input_get_c())
551 case 1: /* Camera 1: Viewpoint chases the ball position. */
553 v_sub(view_e[2], view_p, view_c);
557 case 2: /* Camera 2: View vector is given by view angle. */
559 view_e[2][0] = fsinf(V_RAD(view_a));
561 view_e[2][2] = fcosf(V_RAD(view_a));
565 default: /* Default: View vector approaches the ball velocity vector. */
567 v_sub(view_e[2], view_p, view_c);
568 v_nrm(view_e[2], view_e[2]);
569 v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
574 /* Apply manual rotation. */
576 m_rot(M, Y, V_RAD(da));
577 m_vxfm(view_e[2], M, view_e[2]);
579 /* Orthonormalize the new view reference frame. */
581 v_crs(view_e[0], view_e[1], view_e[2]);
582 v_crs(view_e[2], view_e[0], view_e[1]);
583 v_nrm(view_e[0], view_e[0]);
584 v_nrm(view_e[2], view_e[2]);
586 /* Compute the new view position. */
588 k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
590 view_k = view_k + (k - view_k) * dt;
592 if (view_k < 0.5) view_k = 0.5;
594 v_scl(v, view_e[1], view_dp * view_k);
595 v_mad(v, v, view_e[2], view_dz * view_k);
596 v_add(view_p, v, file.uv->p);
598 /* Compute the new view center. */
600 v_cpy(view_c, file.uv->p);
601 v_mad(view_c, view_c, view_e[1], dc);
603 /* Note the current view angle. */
605 view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
610 static void game_update_time(float dt, int b)
612 if (goal_e && goal_k < 1.0f)
615 /* The ticking clock. */
629 if (b) game_cmd_timer();
632 static int game_update_state(int bt)
634 struct s_file *fp = &file;
640 /* Test for an item. */
642 if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
644 struct s_item *hp = &file.hv[hi];
648 grow_init(fp, hp->t);
650 if (hp->t == ITEM_COIN)
656 audio_play(AUD_COIN, 1.f);
663 /* Test for a switch. */
665 if (sol_swch_test(fp, 0))
666 audio_play(AUD_SWITCH, 1.f);
668 /* Test for a jump. */
670 if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
676 audio_play(AUD_JUMP, 1.f);
680 if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
686 /* Test for a goal. */
688 if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
690 audio_play(AUD_GOAL, 1.0f);
694 /* Test for time-out. */
696 if (bt && timer_down && timer <= 0.f)
698 audio_play(AUD_TIME, 1.0f);
702 /* Test for fall-out. */
704 if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
706 audio_play(AUD_FALL, 1.0f);
713 static int game_step(const float g[3], float dt, int bt)
717 struct s_file *fp = &file;
721 /* Smooth jittery or discontinuous input. */
723 game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
724 game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
730 game_comp_grav(h, g, view_a, game_rx, game_rz);
740 fp->uv[0].p[0] = jump_p[0];
741 fp->uv[0].p[1] = jump_p[1];
742 fp->uv[0].p[2] = jump_p[2];
751 float b = sol_step(fp, h, dt, 0, NULL);
753 /* Mix the sound of a ball bounce. */
757 float k = (b - 0.5f) * 2.0f;
761 if (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
762 else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
763 else audio_play(AUD_BUMPM, k);
765 else audio_play(AUD_BUMPM, k);
771 game_update_view(dt);
772 game_update_time(dt, bt);
774 return game_update_state(bt);
779 void game_server_step(float dt)
781 static const float gup[] = { 0.0f, +9.8f, 0.0f };
782 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
786 case GAME_GOAL: game_step(gup, dt, 0); break;
787 case GAME_FALL: game_step(gdn, dt, 0); break;
790 if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
798 /*---------------------------------------------------------------------------*/
800 void game_set_goal(void)
802 audio_play(AUD_SWITCH, 1.0f);
808 void game_clr_goal(void)
813 /*---------------------------------------------------------------------------*/
815 void game_set_x(int k)
817 input_set_x(-ANGLE_BOUND * k / JOY_MAX);
820 void game_set_z(int k)
822 input_set_z(+ANGLE_BOUND * k / JOY_MAX);
825 void game_set_ang(int x, int z)
831 void game_set_pos(int x, int y)
833 input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
834 input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
837 void game_set_cam(int c)
842 void game_set_rot(float r)
847 void game_set_fly(float k, const struct s_file *fp)
849 float x[3] = { 1.f, 0.f, 0.f };
850 float y[3] = { 0.f, 1.f, 0.f };
851 float z[3] = { 0.f, 0.f, 1.f };
852 float c0[3] = { 0.f, 0.f, 0.f };
853 float p0[3] = { 0.f, 0.f, 0.f };
854 float c1[3] = { 0.f, 0.f, 0.f };
855 float p1[3] = { 0.f, 0.f, 0.f };
862 z[0] = fsinf(V_RAD(view_a));
863 z[2] = fcosf(V_RAD(view_a));
869 /* k = 0.0 view is at the ball. */
873 v_cpy(c0, fp->uv[0].p);
874 v_cpy(p0, fp->uv[0].p);
877 v_mad(p0, p0, y, view_dp);
878 v_mad(p0, p0, z, view_dz);
879 v_mad(c0, c0, y, view_dc);
881 /* k = +1.0 view is s_view 0 */
883 if (k >= 0 && fp->wc > 0)
885 v_cpy(p1, fp->wv[0].p);
886 v_cpy(c1, fp->wv[0].q);
889 /* k = -1.0 view is s_view 1 */
891 if (k <= 0 && fp->wc > 1)
893 v_cpy(p1, fp->wv[1].p);
894 v_cpy(c1, fp->wv[1].q);
897 /* Interpolate the views. */
900 v_mad(view_p, p0, v, k * k);
903 v_mad(view_c, c0, v, k * k);
905 /* Orthonormalize the view basis. */
907 v_sub(view_e[2], view_p, view_c);
908 v_crs(view_e[0], view_e[1], view_e[2]);
909 v_crs(view_e[2], view_e[0], view_e[1]);
910 v_nrm(view_e[0], view_e[0]);
911 v_nrm(view_e[2], view_e[2]);
916 /*---------------------------------------------------------------------------*/