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_vary vary;
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
76 static struct input input_current;
78 static void input_init(void)
86 static void input_set_x(float x)
88 if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
89 if (x > ANGLE_BOUND) x = ANGLE_BOUND;
94 static void input_set_z(float z)
96 if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
97 if (z > ANGLE_BOUND) z = ANGLE_BOUND;
102 static void input_set_r(float r)
104 if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
105 if (r > VIEWR_BOUND) r = VIEWR_BOUND;
110 static void input_set_c(int c)
115 static float input_get_x(void)
117 return input_current.x;
120 static float input_get_z(void)
122 return input_current.z;
125 static float input_get_r(void)
127 return input_current.r;
130 static int input_get_c(void)
132 return input_current.c;
135 /*---------------------------------------------------------------------------*/
138 * Utility functions for preparing the "server" state and events for
139 * consumption by the "client".
142 static union cmd cmd;
144 static void game_cmd_map(const char *name, int ver_x, int ver_y)
147 cmd.map.name = strdup(name);
148 cmd.map.version.x = ver_x;
149 cmd.map.version.y = ver_y;
150 game_proxy_enq(&cmd);
153 static void game_cmd_eou(void)
155 cmd.type = CMD_END_OF_UPDATE;
156 game_proxy_enq(&cmd);
159 static void game_cmd_ups(void)
161 cmd.type = CMD_UPDATES_PER_SECOND;
163 game_proxy_enq(&cmd);
166 static void game_cmd_sound(const char *filename, float a)
168 cmd.type = CMD_SOUND;
170 cmd.sound.n = strdup(filename);
173 game_proxy_enq(&cmd);
176 #define audio_play(s, f) game_cmd_sound((s), (f))
178 static void game_cmd_goalopen(void)
180 cmd.type = CMD_GOAL_OPEN;
181 game_proxy_enq(&cmd);
184 static void game_cmd_updball(void)
186 cmd.type = CMD_BALL_POSITION;
187 v_cpy(cmd.ballpos.p, vary.uv[0].p);
188 game_proxy_enq(&cmd);
190 cmd.type = CMD_BALL_BASIS;
191 v_cpy(cmd.ballbasis.e[0], vary.uv[0].e[0]);
192 v_cpy(cmd.ballbasis.e[1], vary.uv[0].e[1]);
193 game_proxy_enq(&cmd);
195 cmd.type = CMD_BALL_PEND_BASIS;
196 v_cpy(cmd.ballpendbasis.E[0], vary.uv[0].E[0]);
197 v_cpy(cmd.ballpendbasis.E[1], vary.uv[0].E[1]);
198 game_proxy_enq(&cmd);
201 static void game_cmd_updview(void)
203 cmd.type = CMD_VIEW_POSITION;
204 v_cpy(cmd.viewpos.p, view.p);
205 game_proxy_enq(&cmd);
207 cmd.type = CMD_VIEW_CENTER;
208 v_cpy(cmd.viewcenter.c, view.c);
209 game_proxy_enq(&cmd);
211 cmd.type = CMD_VIEW_BASIS;
212 v_cpy(cmd.viewbasis.e[0], view.e[0]);
213 v_cpy(cmd.viewbasis.e[1], view.e[1]);
214 game_proxy_enq(&cmd);
217 static void game_cmd_ballradius(void)
219 cmd.type = CMD_BALL_RADIUS;
220 cmd.ballradius.r = vary.uv[0].r;
221 game_proxy_enq(&cmd);
224 static void game_cmd_init_balls(void)
226 cmd.type = CMD_CLEAR_BALLS;
227 game_proxy_enq(&cmd);
229 cmd.type = CMD_MAKE_BALL;
230 game_proxy_enq(&cmd);
233 game_cmd_ballradius();
236 static void game_cmd_init_items(void)
240 cmd.type = CMD_CLEAR_ITEMS;
241 game_proxy_enq(&cmd);
243 for (i = 0; i < vary.hc; i++)
245 cmd.type = CMD_MAKE_ITEM;
247 v_cpy(cmd.mkitem.p, vary.hv[i].p);
249 cmd.mkitem.t = vary.hv[i].t;
250 cmd.mkitem.n = vary.hv[i].n;
252 game_proxy_enq(&cmd);
256 static void game_cmd_pkitem(int hi)
258 cmd.type = CMD_PICK_ITEM;
260 game_proxy_enq(&cmd);
263 static void game_cmd_jump(int e)
265 cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
266 game_proxy_enq(&cmd);
269 static void game_cmd_tiltangles(void)
271 cmd.type = CMD_TILT_ANGLES;
273 cmd.tiltangles.x = tilt.rx;
274 cmd.tiltangles.z = tilt.rz;
276 game_proxy_enq(&cmd);
279 static void game_cmd_tiltaxes(void)
281 cmd.type = CMD_TILT_AXES;
283 v_cpy(cmd.tiltaxes.x, tilt.x);
284 v_cpy(cmd.tiltaxes.z, tilt.z);
286 game_proxy_enq(&cmd);
289 static void game_cmd_timer(void)
291 cmd.type = CMD_TIMER;
293 game_proxy_enq(&cmd);
296 static void game_cmd_coins(void)
298 cmd.type = CMD_COINS;
300 game_proxy_enq(&cmd);
303 static void game_cmd_status(void)
305 cmd.type = CMD_STATUS;
306 cmd.status.t = status;
307 game_proxy_enq(&cmd);
310 /*---------------------------------------------------------------------------*/
312 static int grow = 0; /* Should the ball be changing size? */
313 static float grow_orig = 0; /* the original ball size */
314 static float grow_goal = 0; /* how big or small to get! */
315 static float grow_t = 0.0; /* timer for the ball to grow... */
316 static float grow_strt = 0; /* starting value for growth */
317 static int got_orig = 0; /* Do we know original ball size? */
319 #define GROW_TIME 0.5f /* sec for the ball to get to size. */
320 #define GROW_BIG 1.5f /* large factor */
321 #define GROW_SMALL 0.5f /* small factor */
323 static int grow_state = 0; /* Current state (values -1, 0, +1) */
325 static void grow_init(const struct s_vary *vary, int type)
329 grow_orig = vary->uv->r;
330 grow_goal = grow_orig;
331 grow_strt = grow_orig;
338 if (type == ITEM_SHRINK)
346 audio_play(AUD_SHRINK, 1.f);
347 grow_goal = grow_orig * GROW_SMALL;
353 audio_play(AUD_SHRINK, 1.f);
354 grow_goal = grow_orig;
360 else if (type == ITEM_GROW)
365 audio_play(AUD_GROW, 1.f);
366 grow_goal = grow_orig;
372 audio_play(AUD_GROW, 1.f);
373 grow_goal = grow_orig * GROW_BIG;
386 grow_strt = vary->uv->r;
390 static void grow_step(const struct s_vary *vary, float dt)
397 /* Calculate new size based on how long since you touched the coin... */
401 if (grow_t >= GROW_TIME)
407 dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
409 /* No sinking through the floor! Keeps ball's bottom constant. */
411 vary->uv->p[1] += (dr - vary->uv->r);
414 game_cmd_ballradius();
417 /*---------------------------------------------------------------------------*/
419 static struct lockstep server_step;
421 int game_server_init(const char *file_name, int t, int e)
423 struct { int x, y; } version;
426 timer = (float) t / 100.f;
427 timer_down = (t > 0);
431 game_server_free(file_name);
435 if (!game_base_load(file_name))
436 return (server_state = 0);
438 if (!sol_load_vary(&vary, &game_base))
440 game_base_free(NULL);
441 return (server_state = 0);
446 /* Get SOL version. */
451 for (i = 0; i < vary.base->dc; i++)
453 char *k = vary.base->av + vary.base->dv[i].ai;
454 char *v = vary.base->av + vary.base->dv[i].aj;
456 if (strcmp(k, "version") == 0)
457 sscanf(v, "%d.%d", &version.x, &version.y);
462 game_tilt_init(&tilt);
464 /* Initialize jump and goal states. */
470 goal_k = e ? 1.0f : 0.0f;
472 /* Initialize the view (and put it at the ball). */
474 game_view_fly(&view, &vary, 0.0f);
477 /* Initialize ball size tracking. */
482 /* Initialize simulation. */
485 sol_cmd_enq_func(game_proxy_enq);
487 /* Send initial update. */
489 game_cmd_map(file_name, version.x, version.y);
496 game_cmd_init_balls();
497 game_cmd_init_items();
502 /* Reset lockstep state. */
504 lockstep_clr(&server_step);
509 void game_server_free(const char *next)
514 sol_free_vary(&vary);
516 game_base_free(next);
522 /*---------------------------------------------------------------------------*/
524 static void game_update_view(float dt)
526 float dc = view.dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
527 float da = input_get_r() * dt * 90.0f;
530 float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
533 /* Center the view about the ball. */
535 v_cpy(view.c, vary.uv->p);
537 view_v[0] = -vary.uv->v[0];
539 view_v[2] = -vary.uv->v[2];
541 switch (input_get_c())
543 case VIEW_LAZY: /* Viewpoint chases the ball position. */
545 v_sub(view.e[2], view.p, view.c);
549 case VIEW_MANUAL: /* View vector is given by view angle. */
551 view.e[2][0] = fsinf(V_RAD(view.a));
553 view.e[2][2] = fcosf(V_RAD(view.a));
557 case VIEW_CHASE: /* View vector approaches the ball velocity vector. */
559 v_sub(view.e[2], view.p, view.c);
560 v_nrm(view.e[2], view.e[2]);
561 v_mad(view.e[2], view.e[2], view_v, v_dot(view_v, view_v) * dt / 4);
566 /* Apply manual rotation. */
568 m_rot(M, Y, V_RAD(da));
569 m_vxfm(v, M, view.e[2]);
572 /* Orthonormalize the new view reference frame. */
574 v_crs(view.e[0], view.e[1], view.e[2]);
575 v_crs(view.e[2], view.e[0], view.e[1]);
576 v_nrm(view.e[0], view.e[0]);
577 v_nrm(view.e[2], view.e[2]);
579 /* Compute the new view position. */
581 k = 1.0f + v_dot(view.e[2], view_v) / 10.0f;
583 view_k = view_k + (k - view_k) * dt;
585 if (view_k < 0.5) view_k = 0.5;
587 v_scl(v, view.e[1], view.dp * view_k);
588 v_mad(v, v, view.e[2], view.dz * view_k);
589 v_add(view.p, v, vary.uv->p);
591 /* Compute the new view center. */
593 v_cpy(view.c, vary.uv->p);
594 v_mad(view.c, view.c, view.e[1], dc);
596 /* Note the current view angle. */
598 view.a = V_DEG(fatan2f(view.e[2][0], view.e[2][2]));
603 static void game_update_time(float dt, int b)
605 if (goal_e && goal_k < 1.0f)
608 /* The ticking clock. */
622 if (b) game_cmd_timer();
625 static int game_update_state(int bt)
632 /* Test for an item. */
634 if (bt && (hi = sol_item_test(&vary, p, ITEM_RADIUS)) != -1)
636 struct v_item *hp = vary.hv + hi;
640 grow_init(&vary, hp->t);
642 if (hp->t == ITEM_COIN)
648 audio_play(AUD_COIN, 1.f);
655 /* Test for a switch. */
657 if (sol_swch_test(&vary, 0) == SWCH_TRIGGER)
658 audio_play(AUD_SWITCH, 1.f);
660 /* Test for a jump. */
662 if (jump_e == 1 && jump_b == 0 && (sol_jump_test(&vary, jump_p, 0) ==
669 v_sub(jump_w, jump_p, vary.uv->p);
670 v_add(jump_w, view.p, jump_w);
672 audio_play(AUD_JUMP, 1.f);
676 if (jump_e == 0 && jump_b == 0 && (sol_jump_test(&vary, jump_p, 0) ==
683 /* Test for a goal. */
685 if (bt && goal_e && (zp = sol_goal_test(&vary, p, 0)))
687 audio_play(AUD_GOAL, 1.0f);
691 /* Test for time-out. */
693 if (bt && timer_down && timer <= 0.f)
695 audio_play(AUD_TIME, 1.0f);
699 /* Test for fall-out. */
701 if (bt && vary.uv[0].p[1] < vary.base->vv[0].p[1])
703 audio_play(AUD_FALL, 1.0f);
710 static int game_step(const float g[3], float dt, int bt)
716 /* Smooth jittery or discontinuous input. */
718 tilt.rx += (input_get_x() - tilt.rx) * dt / RESPONSE;
719 tilt.rz += (input_get_z() - tilt.rz) * dt / RESPONSE;
721 game_tilt_axes(&tilt, view.e);
724 game_cmd_tiltangles();
726 grow_step(&vary, dt);
728 game_tilt_grav(h, g, &tilt);
738 v_cpy(vary.uv->p, jump_p);
739 v_cpy(view.p, jump_w);
748 float b = sol_step(&vary, h, dt, 0, NULL);
750 /* Mix the sound of a ball bounce. */
754 float k = (b - 0.5f) * 2.0f;
758 if (vary.uv->r > grow_orig) audio_play(AUD_BUMPL, k);
759 else if (vary.uv->r < grow_orig) audio_play(AUD_BUMPS, k);
760 else audio_play(AUD_BUMPM, k);
762 else audio_play(AUD_BUMPM, k);
768 game_update_view(dt);
769 game_update_time(dt, bt);
771 return game_update_state(bt);
776 static void game_server_iter(float dt)
780 case GAME_GOAL: game_step(GRAVITY_UP, dt, 0); break;
781 case GAME_FALL: game_step(GRAVITY_DN, dt, 0); break;
784 if ((status = game_step(GRAVITY_DN, dt, 1)) != GAME_NONE)
792 static struct lockstep server_step = { game_server_iter, DT };
794 void game_server_step(float dt)
796 lockstep_run(&server_step, dt);
799 float game_server_blend(void)
801 return lockstep_blend(&server_step);
804 /*---------------------------------------------------------------------------*/
806 void game_set_goal(void)
808 audio_play(AUD_SWITCH, 1.0f);
814 /*---------------------------------------------------------------------------*/
816 void game_set_x(float k)
818 input_set_x(-ANGLE_BOUND * k);
821 void game_set_z(float k)
823 input_set_z(+ANGLE_BOUND * k);
826 void game_set_ang(float x, float z)
832 void game_set_pos(int x, int y)
834 const float range = ANGLE_BOUND * 2;
836 input_set_x(input_get_x() + range * y / config_get_d(CONFIG_MOUSE_SENSE));
837 input_set_z(input_get_z() + range * x / config_get_d(CONFIG_MOUSE_SENSE));
840 void game_set_cam(int c)
845 void game_set_rot(float r)
850 /*---------------------------------------------------------------------------*/