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.
25 #include "solid_sim.h"
26 #include "solid_all.h"
27 #include "solid_cmd.h"
29 #include "game_common.h"
30 #include "game_server.h"
31 #include "game_proxy.h"
35 /*---------------------------------------------------------------------------*/
37 static int server_state = 0;
39 static struct s_file file;
41 static float timer = 0.f; /* Clock time */
42 static int timer_down = 1; /* Timer go up or down? */
44 static int status = GAME_NONE; /* Outcome of the game */
46 static struct game_tilt tilt; /* Floor rotation */
47 static struct game_view view; /* Current view */
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 */
60 /*---------------------------------------------------------------------------*/
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. TODO: This used to have the effect of homogenizing
66 * input for use in replay recording and playback, but it's not clear
67 * how relevant this approach is with the introduction of the command
71 * -32767 = -ANGLE_BOUND
72 * +32767 = +ANGLE_BOUND
75 * -32767 = -VIEWR_BOUND
76 * +32767 = +VIEWR_BOUND
88 static struct input input_current;
90 static void input_init(void)
98 static void input_set_x(float x)
100 if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
101 if (x > ANGLE_BOUND) x = ANGLE_BOUND;
103 input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
106 static void input_set_z(float z)
108 if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
109 if (z > ANGLE_BOUND) z = ANGLE_BOUND;
111 input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
114 static void input_set_r(float r)
116 if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
117 if (r > VIEWR_BOUND) r = VIEWR_BOUND;
119 input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
122 static void input_set_c(int c)
124 input_current.c = (short) c;
127 static float input_get_x(void)
129 return ANGLE_BOUND * (float) input_current.x / 32767.0f;
132 static float input_get_z(void)
134 return ANGLE_BOUND * (float) input_current.z / 32767.0f;
137 static float input_get_r(void)
139 return VIEWR_BOUND * (float) input_current.r / 32767.0f;
142 static int input_get_c(void)
144 return (int) input_current.c;
147 int input_put(fs_file fout)
151 put_short(fout, &input_current.x);
152 put_short(fout, &input_current.z);
153 put_short(fout, &input_current.r);
154 put_short(fout, &input_current.c);
161 int input_get(fs_file fin)
165 get_short(fin, &input_current.x);
166 get_short(fin, &input_current.z);
167 get_short(fin, &input_current.r);
168 get_short(fin, &input_current.c);
170 return (fs_eof(fin) ? 0 : 1);
175 /*---------------------------------------------------------------------------*/
178 * Utility functions for preparing the "server" state and events for
179 * consumption by the "client".
182 static union cmd cmd;
184 static void game_cmd_map(const char *name, int ver_x, int ver_y)
187 cmd.map.name = strdup(name);
188 cmd.map.version.x = ver_x;
189 cmd.map.version.y = ver_y;
190 game_proxy_enq(&cmd);
193 static void game_cmd_eou(void)
195 cmd.type = CMD_END_OF_UPDATE;
196 game_proxy_enq(&cmd);
199 static void game_cmd_ups(void)
201 cmd.type = CMD_UPDATES_PER_SECOND;
203 game_proxy_enq(&cmd);
206 static void game_cmd_sound(const char *filename, float a)
208 cmd.type = CMD_SOUND;
210 cmd.sound.n = strdup(filename);
213 game_proxy_enq(&cmd);
216 #define audio_play(s, f) game_cmd_sound((s), (f))
218 static void game_cmd_goalopen(void)
220 cmd.type = CMD_GOAL_OPEN;
221 game_proxy_enq(&cmd);
224 static void game_cmd_updball(void)
226 cmd.type = CMD_BALL_POSITION;
227 memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
228 game_proxy_enq(&cmd);
230 cmd.type = CMD_BALL_BASIS;
231 v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
232 v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
233 game_proxy_enq(&cmd);
235 cmd.type = CMD_BALL_PEND_BASIS;
236 v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
237 v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
238 game_proxy_enq(&cmd);
241 static void game_cmd_updview(void)
243 cmd.type = CMD_VIEW_POSITION;
244 memcpy(cmd.viewpos.p, view.p, sizeof (float) * 3);
245 game_proxy_enq(&cmd);
247 cmd.type = CMD_VIEW_CENTER;
248 memcpy(cmd.viewcenter.c, view.c, sizeof (float) * 3);
249 game_proxy_enq(&cmd);
251 cmd.type = CMD_VIEW_BASIS;
252 v_cpy(cmd.viewbasis.e[0], view.e[0]);
253 v_cpy(cmd.viewbasis.e[1], view.e[1]);
254 game_proxy_enq(&cmd);
257 static void game_cmd_ballradius(void)
259 cmd.type = CMD_BALL_RADIUS;
260 cmd.ballradius.r = file.uv[0].r;
261 game_proxy_enq(&cmd);
264 static void game_cmd_init_balls(void)
266 cmd.type = CMD_CLEAR_BALLS;
267 game_proxy_enq(&cmd);
269 cmd.type = CMD_MAKE_BALL;
270 game_proxy_enq(&cmd);
273 game_cmd_ballradius();
276 static void game_cmd_init_items(void)
280 cmd.type = CMD_CLEAR_ITEMS;
281 game_proxy_enq(&cmd);
283 for (i = 0; i < file.hc; i++)
285 cmd.type = CMD_MAKE_ITEM;
287 v_cpy(cmd.mkitem.p, file.hv[i].p);
289 cmd.mkitem.t = file.hv[i].t;
290 cmd.mkitem.n = file.hv[i].n;
292 game_proxy_enq(&cmd);
296 static void game_cmd_pkitem(int hi)
298 cmd.type = CMD_PICK_ITEM;
300 game_proxy_enq(&cmd);
303 static void game_cmd_jump(int e)
305 cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
306 game_proxy_enq(&cmd);
309 static void game_cmd_tiltangles(void)
311 cmd.type = CMD_TILT_ANGLES;
313 cmd.tiltangles.x = tilt.rx;
314 cmd.tiltangles.z = tilt.rz;
316 game_proxy_enq(&cmd);
319 static void game_cmd_tiltaxes(void)
321 cmd.type = CMD_TILT_AXES;
323 v_cpy(cmd.tiltaxes.x, tilt.x);
324 v_cpy(cmd.tiltaxes.z, tilt.z);
326 game_proxy_enq(&cmd);
329 static void game_cmd_timer(void)
331 cmd.type = CMD_TIMER;
333 game_proxy_enq(&cmd);
336 static void game_cmd_coins(void)
338 cmd.type = CMD_COINS;
340 game_proxy_enq(&cmd);
343 static void game_cmd_status(void)
345 cmd.type = CMD_STATUS;
346 cmd.status.t = status;
347 game_proxy_enq(&cmd);
350 /*---------------------------------------------------------------------------*/
352 static int grow = 0; /* Should the ball be changing size? */
353 static float grow_orig = 0; /* the original ball size */
354 static float grow_goal = 0; /* how big or small to get! */
355 static float grow_t = 0.0; /* timer for the ball to grow... */
356 static float grow_strt = 0; /* starting value for growth */
357 static int got_orig = 0; /* Do we know original ball size? */
359 #define GROW_TIME 0.5f /* sec for the ball to get to size. */
360 #define GROW_BIG 1.5f /* large factor */
361 #define GROW_SMALL 0.5f /* small factor */
363 static int grow_state = 0; /* Current state (values -1, 0, +1) */
365 static void grow_init(const struct s_file *fp, int type)
369 grow_orig = fp->uv->r;
370 grow_goal = grow_orig;
371 grow_strt = grow_orig;
378 if (type == ITEM_SHRINK)
386 audio_play(AUD_SHRINK, 1.f);
387 grow_goal = grow_orig * GROW_SMALL;
393 audio_play(AUD_SHRINK, 1.f);
394 grow_goal = grow_orig;
400 else if (type == ITEM_GROW)
405 audio_play(AUD_GROW, 1.f);
406 grow_goal = grow_orig;
412 audio_play(AUD_GROW, 1.f);
413 grow_goal = grow_orig * GROW_BIG;
426 grow_strt = fp->uv->r;
430 static void grow_step(const struct s_file *fp, float dt)
437 /* Calculate new size based on how long since you touched the coin... */
441 if (grow_t >= GROW_TIME)
447 dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
449 /* No sinking through the floor! Keeps ball's bottom constant. */
451 fp->uv->p[1] += (dr - fp->uv->r);
454 game_cmd_ballradius();
457 /*---------------------------------------------------------------------------*/
459 int game_server_init(const char *file_name, int t, int e)
468 timer = (float) t / 100.f;
469 timer_down = (t > 0);
476 if (!sol_load_only_file(&file, file_name))
477 return (server_state = 0);
484 for (i = 0; i < file.dc; i++)
486 char *k = file.av + file.dv[i].ai;
487 char *v = file.av + file.dv[i].aj;
489 if (strcmp(k, "version") == 0)
490 sscanf(v, "%d.%d", &version.x, &version.y);
495 game_tilt_init(&tilt);
497 /* Initialize jump and goal states. */
503 goal_k = e ? 1.0f : 0.0f;
505 /* Initialize the view. */
507 game_view_init(&view);
511 /* Initialize ball size tracking... */
516 /* Initialize simulation. */
520 sol_cmd_enq_func(game_proxy_enq);
522 /* Queue client commands. */
524 game_cmd_map(file_name, version.x, version.y);
528 if (goal_e) game_cmd_goalopen();
530 game_cmd_init_balls();
531 game_cmd_init_items();
536 void game_server_free(void)
546 /*---------------------------------------------------------------------------*/
548 static void game_update_view(float dt)
550 float dc = view.dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
551 float da = input_get_r() * dt * 90.0f;
554 float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
557 /* Center the view about the ball. */
559 v_cpy(view.c, file.uv->p);
561 view_v[0] = -file.uv->v[0];
563 view_v[2] = -file.uv->v[2];
565 switch (input_get_c())
567 case VIEW_LAZY: /* Viewpoint chases the ball position. */
569 v_sub(view.e[2], view.p, view.c);
573 case VIEW_MANUAL: /* View vector is given by view angle. */
575 view.e[2][0] = fsinf(V_RAD(view.a));
577 view.e[2][2] = fcosf(V_RAD(view.a));
581 case VIEW_CHASE: /* View vector approaches the ball velocity vector. */
583 v_sub(view.e[2], view.p, view.c);
584 v_nrm(view.e[2], view.e[2]);
585 v_mad(view.e[2], view.e[2], view_v, v_dot(view_v, view_v) * dt / 4);
590 /* Apply manual rotation. */
592 m_rot(M, Y, V_RAD(da));
593 m_vxfm(v, M, view.e[2]);
596 /* Orthonormalize the new view reference frame. */
598 v_crs(view.e[0], view.e[1], view.e[2]);
599 v_crs(view.e[2], view.e[0], view.e[1]);
600 v_nrm(view.e[0], view.e[0]);
601 v_nrm(view.e[2], view.e[2]);
603 /* Compute the new view position. */
605 k = 1.0f + v_dot(view.e[2], view_v) / 10.0f;
607 view_k = view_k + (k - view_k) * dt;
609 if (view_k < 0.5) view_k = 0.5;
611 v_scl(v, view.e[1], view.dp * view_k);
612 v_mad(v, v, view.e[2], view.dz * view_k);
613 v_add(view.p, v, file.uv->p);
615 /* Compute the new view center. */
617 v_cpy(view.c, file.uv->p);
618 v_mad(view.c, view.c, view.e[1], dc);
620 /* Note the current view angle. */
622 view.a = V_DEG(fatan2f(view.e[2][0], view.e[2][2]));
627 static void game_update_time(float dt, int b)
629 if (goal_e && goal_k < 1.0f)
632 /* The ticking clock. */
646 if (b) game_cmd_timer();
649 static int game_update_state(int bt)
651 struct s_file *fp = &file;
657 /* Test for an item. */
659 if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
661 struct s_item *hp = &file.hv[hi];
665 grow_init(fp, hp->t);
667 if (hp->t == ITEM_COIN)
673 audio_play(AUD_COIN, 1.f);
680 /* Test for a switch. */
682 if (sol_swch_test(fp, 0))
683 audio_play(AUD_SWITCH, 1.f);
685 /* Test for a jump. */
687 if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
693 v_sub(jump_w, jump_p, fp->uv->p);
694 v_add(jump_w, view.p, jump_w);
696 audio_play(AUD_JUMP, 1.f);
700 if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
706 /* Test for a goal. */
708 if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
710 audio_play(AUD_GOAL, 1.0f);
714 /* Test for time-out. */
716 if (bt && timer_down && timer <= 0.f)
718 audio_play(AUD_TIME, 1.0f);
722 /* Test for fall-out. */
724 if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
726 audio_play(AUD_FALL, 1.0f);
733 static int game_step(const float g[3], float dt, int bt)
737 struct s_file *fp = &file;
741 /* Smooth jittery or discontinuous input. */
743 tilt.rx += (input_get_x() - tilt.rx) * dt / RESPONSE;
744 tilt.rz += (input_get_z() - tilt.rz) * dt / RESPONSE;
746 game_tilt_axes(&tilt, view.e);
749 game_cmd_tiltangles();
753 game_tilt_grav(h, g, &tilt);
763 v_cpy(fp->uv->p, jump_p);
764 v_cpy(view.p, jump_w);
773 float b = sol_step(fp, h, dt, 0, NULL);
775 /* Mix the sound of a ball bounce. */
779 float k = (b - 0.5f) * 2.0f;
783 if (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
784 else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
785 else audio_play(AUD_BUMPM, k);
787 else audio_play(AUD_BUMPM, k);
793 game_update_view(dt);
794 game_update_time(dt, bt);
796 return game_update_state(bt);
801 void game_server_step(float dt)
803 static const float gup[] = { 0.0f, +9.8f, 0.0f };
804 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
808 case GAME_GOAL: game_step(gup, dt, 0); break;
809 case GAME_FALL: game_step(gdn, dt, 0); break;
812 if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
820 /*---------------------------------------------------------------------------*/
822 void game_set_goal(void)
824 audio_play(AUD_SWITCH, 1.0f);
830 void game_clr_goal(void)
835 /*---------------------------------------------------------------------------*/
837 void game_set_x(int k)
839 input_set_x(-ANGLE_BOUND * k / JOY_MAX);
842 void game_set_z(int k)
844 input_set_z(+ANGLE_BOUND * k / JOY_MAX);
847 void game_set_ang(int x, int z)
853 void game_set_pos(int x, int y)
855 input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
856 input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
859 void game_set_cam(int c)
864 void game_set_rot(float r)
869 void game_server_fly(float k)
871 game_view_fly(&view, &file, k);
875 /*---------------------------------------------------------------------------*/