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(fs_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(fs_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 (fs_eof(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_map(const char *name, int ver_x, int ver_y)
192 cmd.map.name = strdup(name);
193 cmd.map.version.x = ver_x;
194 cmd.map.version.y = ver_y;
195 game_proxy_enq(&cmd);
198 static void game_cmd_eou(void)
200 cmd.type = CMD_END_OF_UPDATE;
201 game_proxy_enq(&cmd);
204 static void game_cmd_ups(void)
206 cmd.type = CMD_UPDATES_PER_SECOND;
208 game_proxy_enq(&cmd);
211 static void game_cmd_sound(const char *filename, float a)
213 cmd.type = CMD_SOUND;
215 cmd.sound.n = strdup(filename);
218 game_proxy_enq(&cmd);
221 #define audio_play(s, f) game_cmd_sound((s), (f))
223 static void game_cmd_goalopen(void)
225 cmd.type = CMD_GOAL_OPEN;
226 game_proxy_enq(&cmd);
229 static void game_cmd_updball(void)
231 cmd.type = CMD_BALL_POSITION;
232 memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
233 game_proxy_enq(&cmd);
235 cmd.type = CMD_BALL_BASIS;
236 v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
237 v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
238 game_proxy_enq(&cmd);
240 cmd.type = CMD_BALL_PEND_BASIS;
241 v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
242 v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
243 game_proxy_enq(&cmd);
246 static void game_cmd_updview(void)
248 cmd.type = CMD_VIEW_POSITION;
249 memcpy(cmd.viewpos.p, view_p, sizeof (float) * 3);
250 game_proxy_enq(&cmd);
252 cmd.type = CMD_VIEW_CENTER;
253 memcpy(cmd.viewcenter.c, view_c, sizeof (float) * 3);
254 game_proxy_enq(&cmd);
256 cmd.type = CMD_VIEW_BASIS;
257 v_cpy(cmd.viewbasis.e[0], view_e[0]);
258 v_cpy(cmd.viewbasis.e[1], view_e[1]);
259 game_proxy_enq(&cmd);
262 static void game_cmd_ballradius(void)
264 cmd.type = CMD_BALL_RADIUS;
265 cmd.ballradius.r = file.uv[0].r;
266 game_proxy_enq(&cmd);
269 static void game_cmd_init_balls(void)
271 cmd.type = CMD_CLEAR_BALLS;
272 game_proxy_enq(&cmd);
274 cmd.type = CMD_MAKE_BALL;
275 game_proxy_enq(&cmd);
278 game_cmd_ballradius();
281 static void game_cmd_init_items(void)
285 cmd.type = CMD_CLEAR_ITEMS;
286 game_proxy_enq(&cmd);
288 for (i = 0; i < file.hc; i++)
290 cmd.type = CMD_MAKE_ITEM;
292 v_cpy(cmd.mkitem.p, file.hv[i].p);
294 cmd.mkitem.t = file.hv[i].t;
295 cmd.mkitem.n = file.hv[i].n;
297 game_proxy_enq(&cmd);
301 static void game_cmd_pkitem(int hi)
303 cmd.type = CMD_PICK_ITEM;
305 game_proxy_enq(&cmd);
308 static void game_cmd_jump(int e)
310 cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
311 game_proxy_enq(&cmd);
314 static void game_cmd_rotate(void)
316 cmd.type = CMD_ROTATE;
318 cmd.rotate.x = game_rx;
319 cmd.rotate.z = game_rz;
321 game_proxy_enq(&cmd);
324 static void game_cmd_timer(void)
326 cmd.type = CMD_TIMER;
328 game_proxy_enq(&cmd);
331 static void game_cmd_coins(void)
333 cmd.type = CMD_COINS;
335 game_proxy_enq(&cmd);
338 static void game_cmd_status(void)
340 cmd.type = CMD_STATUS;
341 cmd.status.t = status;
342 game_proxy_enq(&cmd);
345 /*---------------------------------------------------------------------------*/
347 static int grow = 0; /* Should the ball be changing size? */
348 static float grow_orig = 0; /* the original ball size */
349 static float grow_goal = 0; /* how big or small to get! */
350 static float grow_t = 0.0; /* timer for the ball to grow... */
351 static float grow_strt = 0; /* starting value for growth */
352 static int got_orig = 0; /* Do we know original ball size? */
354 #define GROW_TIME 0.5f /* sec for the ball to get to size. */
355 #define GROW_BIG 1.5f /* large factor */
356 #define GROW_SMALL 0.5f /* small factor */
358 static int grow_state = 0; /* Current state (values -1, 0, +1) */
360 static void grow_init(const struct s_file *fp, int type)
364 grow_orig = fp->uv->r;
365 grow_goal = grow_orig;
366 grow_strt = grow_orig;
373 if (type == ITEM_SHRINK)
381 audio_play(AUD_SHRINK, 1.f);
382 grow_goal = grow_orig * GROW_SMALL;
388 audio_play(AUD_SHRINK, 1.f);
389 grow_goal = grow_orig;
395 else if (type == ITEM_GROW)
400 audio_play(AUD_GROW, 1.f);
401 grow_goal = grow_orig;
407 audio_play(AUD_GROW, 1.f);
408 grow_goal = grow_orig * GROW_BIG;
421 grow_strt = fp->uv->r;
425 static void grow_step(const struct s_file *fp, float dt)
432 /* Calculate new size based on how long since you touched the coin... */
436 if (grow_t >= GROW_TIME)
442 dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
444 /* No sinking through the floor! Keeps ball's bottom constant. */
446 fp->uv->p[1] += (dr - fp->uv->r);
449 game_cmd_ballradius();
452 /*---------------------------------------------------------------------------*/
454 static void view_init(void)
456 view_dp = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
457 view_dc = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
458 view_dz = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
481 int game_server_init(const char *file_name, int t, int e)
490 timer = (float) t / 100.f;
491 timer_down = (t > 0);
498 if (!sol_load_only_file(&file, file_name))
499 return (server_state = 0);
506 for (i = 0; i < file.dc; i++)
508 char *k = file.av + file.dv[i].ai;
509 char *v = file.av + file.dv[i].aj;
511 if (strcmp(k, "version") == 0)
512 sscanf(v, "%d.%d", &version.x, &version.y);
520 /* Initialize jump and goal states. */
526 goal_k = e ? 1.0f : 0.0f;
528 /* Initialize the view. */
532 /* Initialize ball size tracking... */
537 sol_cmd_enq_func(game_proxy_enq);
539 /* Queue client commands. */
541 game_cmd_map(file_name, version.x, version.y);
545 if (goal_e) game_cmd_goalopen();
547 game_cmd_init_balls();
548 game_cmd_init_items();
554 void game_server_free(void)
563 /*---------------------------------------------------------------------------*/
565 static void game_update_view(float dt)
567 static int view_prev;
569 float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
570 float da = input_get_r() * dt * 90.0f;
573 float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
575 /* Center the view about the ball. */
577 v_cpy(view_c, file.uv->p);
579 view_v[0] = -file.uv->v[0];
581 view_v[2] = -file.uv->v[2];
583 /* Restore usable vectors. */
585 if (view_prev == VIEW_TOPDOWN)
589 v_inv(view_e[2], view_e[1]);
594 v_scl(v, view_e[1], view_dp);
595 v_mad(v, v, view_e[2], view_dz);
596 v_add(view_p, v, file.uv->p);
599 view_prev = input_get_c();
601 switch (input_get_c())
603 case VIEW_LAZY: /* Viewpoint chases the ball position. */
605 v_sub(view_e[2], view_p, view_c);
609 case VIEW_MANUAL: /* View vector is given by view angle. */
610 case VIEW_TOPDOWN: /* Crude top-down view. */
612 view_e[2][0] = fsinf(V_RAD(view_a));
614 view_e[2][2] = fcosf(V_RAD(view_a));
618 case VIEW_CHASE: /* View vector approaches the ball velocity vector. */
620 v_sub(view_e[2], view_p, view_c);
621 v_nrm(view_e[2], view_e[2]);
622 v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
627 /* Apply manual rotation. */
629 m_rot(M, Y, V_RAD(da));
630 m_vxfm(view_e[2], M, view_e[2]);
632 /* Orthonormalize the new view reference frame. */
634 v_crs(view_e[0], view_e[1], view_e[2]);
635 v_crs(view_e[2], view_e[0], view_e[1]);
636 v_nrm(view_e[0], view_e[0]);
637 v_nrm(view_e[2], view_e[2]);
639 /* Compute the new view position. */
641 k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
643 view_k = view_k + (k - view_k) * dt;
645 if (view_k < 0.5) view_k = 0.5;
647 v_scl(v, view_e[1], view_dp * view_k);
648 v_mad(v, v, view_e[2], view_dz * view_k);
649 v_add(view_p, v, file.uv->p);
651 /* Compute the new view center. */
653 v_cpy(view_c, file.uv->p);
654 v_mad(view_c, view_c, view_e[1], dc);
656 /* Note the current view angle. */
658 view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
660 /* Override vectors for top-down view. */
662 if (input_get_c() == VIEW_TOPDOWN)
664 v_inv(view_e[1], view_e[2]);
667 v_cpy(view_c, file.uv->p);
668 v_mad(view_p, view_c, view_e[2], view_dz * 1.5f);
674 static void game_update_time(float dt, int b)
676 if (goal_e && goal_k < 1.0f)
679 /* The ticking clock. */
693 if (b) game_cmd_timer();
696 static int game_update_state(int bt)
698 struct s_file *fp = &file;
704 /* Test for an item. */
706 if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
708 struct s_item *hp = &file.hv[hi];
712 grow_init(fp, hp->t);
714 if (hp->t == ITEM_COIN)
720 audio_play(AUD_COIN, 1.f);
727 /* Test for a switch. */
729 if (sol_swch_test(fp, 0))
730 audio_play(AUD_SWITCH, 1.f);
732 /* Test for a jump. */
734 if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
740 audio_play(AUD_JUMP, 1.f);
744 if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
750 /* Test for a goal. */
752 if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
754 audio_play(AUD_GOAL, 1.0f);
758 /* Test for time-out. */
760 if (bt && timer_down && timer <= 0.f)
762 audio_play(AUD_TIME, 1.0f);
766 /* Test for fall-out. */
768 if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
770 audio_play(AUD_FALL, 1.0f);
777 static int game_step(const float g[3], float dt, int bt)
781 struct s_file *fp = &file;
785 /* Smooth jittery or discontinuous input. */
787 game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
788 game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
794 game_comp_grav(h, g, view_a, game_rx, game_rz);
804 fp->uv[0].p[0] = jump_p[0];
805 fp->uv[0].p[1] = jump_p[1];
806 fp->uv[0].p[2] = jump_p[2];
815 float b = sol_step(fp, h, dt, 0, NULL);
817 /* Mix the sound of a ball bounce. */
821 float k = (b - 0.5f) * 2.0f;
825 if (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
826 else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
827 else audio_play(AUD_BUMPM, k);
829 else audio_play(AUD_BUMPM, k);
835 game_update_view(dt);
836 game_update_time(dt, bt);
838 return game_update_state(bt);
843 void game_server_step(float dt)
845 static const float gup[] = { 0.0f, +9.8f, 0.0f };
846 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
850 case GAME_GOAL: game_step(gup, dt, 0); break;
851 case GAME_FALL: game_step(gdn, dt, 0); break;
854 if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
862 /*---------------------------------------------------------------------------*/
864 void game_set_goal(void)
866 audio_play(AUD_SWITCH, 1.0f);
872 void game_clr_goal(void)
877 /*---------------------------------------------------------------------------*/
879 void game_set_x(int k)
881 input_set_x(-ANGLE_BOUND * k / JOY_MAX);
884 void game_set_z(int k)
886 input_set_z(+ANGLE_BOUND * k / JOY_MAX);
889 void game_set_ang(int x, int z)
895 void game_set_pos(int x, int y)
897 input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
898 input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
901 void game_set_cam(int c)
906 void game_set_rot(float r)
911 void game_set_fly(float k, const struct s_file *fp)
913 float x[3] = { 1.f, 0.f, 0.f };
914 float y[3] = { 0.f, 1.f, 0.f };
915 float z[3] = { 0.f, 0.f, 1.f };
916 float c0[3] = { 0.f, 0.f, 0.f };
917 float p0[3] = { 0.f, 0.f, 0.f };
918 float c1[3] = { 0.f, 0.f, 0.f };
919 float p1[3] = { 0.f, 0.f, 0.f };
926 z[0] = fsinf(V_RAD(view_a));
927 z[2] = fcosf(V_RAD(view_a));
933 /* k = 0.0 view is at the ball. */
937 v_cpy(c0, fp->uv[0].p);
938 v_cpy(p0, fp->uv[0].p);
941 v_mad(p0, p0, y, view_dp);
942 v_mad(p0, p0, z, view_dz);
943 v_mad(c0, c0, y, view_dc);
945 /* k = +1.0 view is s_view 0 */
947 if (k >= 0 && fp->wc > 0)
949 v_cpy(p1, fp->wv[0].p);
950 v_cpy(c1, fp->wv[0].q);
953 /* k = -1.0 view is s_view 1 */
955 if (k <= 0 && fp->wc > 1)
957 v_cpy(p1, fp->wv[1].p);
958 v_cpy(c1, fp->wv[1].q);
961 /* Interpolate the views. */
964 v_mad(view_p, p0, v, k * k);
967 v_mad(view_c, c0, v, k * k);
969 /* Orthonormalize the view basis. */
971 v_sub(view_e[2], view_p, view_c);
972 v_crs(view_e[0], view_e[1], view_e[2]);
973 v_crs(view_e[2], view_e[0], view_e[1]);
974 v_nrm(view_e[0], view_e[0]);
975 v_nrm(view_e[2], view_e[2]);
980 /*---------------------------------------------------------------------------*/