/*---------------------------------------------------------------------------*/
+#define CURR 0
+#define PREV 1
+
static struct game_draw gd;
+static struct game_lerp gl;
static float timer = 0.0f; /* Clock time */
static int status = GAME_NONE; /* Outcome of the game */
static int coins = 0; /* Collected coins */
-static int ups; /* Updates per second */
-static int first_update; /* First update flag */
-static int curr_ball; /* Current ball index */
+static struct cmd_state cs; /* Command state */
struct
{
int x, y;
} version; /* Current map version */
-/*
- * Neverball <= 1.5.1 does not send explicit tilt axes, rotation
- * happens directly around view vectors. So for compatibility if at
- * the time of receiving tilt angles we have not yet received the tilt
- * axes, we use the view vectors.
- */
-
-static int got_tilt_axes;
-
/*---------------------------------------------------------------------------*/
static void game_run_cmd(const union cmd *cmd)
{
if (gd.state)
{
+ struct game_view *view = &gl.view[CURR];
+ struct game_tilt *tilt = &gl.tilt[CURR];
+
struct s_vary *vary = &gd.file.vary;
struct v_item *hp;
- struct v_ball *up;
float v[3];
float dt;
- int i;
+
+ if (cs.next_update)
+ {
+ game_lerp_copy(&gl);
+ cs.next_update = 0;
+ }
switch (cmd->type)
{
case CMD_END_OF_UPDATE:
+ cs.got_tilt_axes = 0;
+ cs.next_update = 1;
- got_tilt_axes = 0;
-
- if (first_update)
+ if (cs.first_update)
{
- first_update = 0;
+ game_lerp_copy(&gl);
+ /* Hack to sync state before the next update. */
+ game_lerp_apply(&gl, &gd);
+ cs.first_update = 0;
break;
}
/* Compute gravity for particle effects. */
if (status == GAME_GOAL)
- game_tilt_grav(v, GRAVITY_UP, &gd.tilt);
+ game_tilt_grav(v, GRAVITY_UP, tilt);
else
- game_tilt_grav(v, GRAVITY_DN, &gd.tilt);
+ game_tilt_grav(v, GRAVITY_DN, tilt);
/* Step particle, goal and jump effects. */
- if (ups > 0)
+ if (cs.ups > 0)
{
- dt = 1.0f / (float) ups;
+ dt = 1.0f / cs.ups;
- if (gd.goal_e && gd.goal_k < 1.0f)
- gd.goal_k += dt;
+ if (gd.goal_e && gl.goal_k[CURR] < 1.0f)
+ gl.goal_k[CURR] += dt;
if (gd.jump_b)
{
- gd.jump_dt += dt;
+ gl.jump_dt[CURR] += dt;
- if (1.0f < gd.jump_dt)
+ if (1.0f < gl.jump_dt[PREV])
gd.jump_b = 0;
}
case CMD_MAKE_BALL:
/* Allocate a new ball and mark it as the current ball. */
- if ((up = realloc(vary->uv, sizeof (*up) * (vary->uc + 1))))
- {
- vary->uv = up;
- curr_ball = vary->uc;
- vary->uc++;
- }
+ if (sol_lerp_cmd(&gl.lerp, &cs, cmd))
+ cs.curr_ball = gl.lerp.uc - 1;
+
break;
case CMD_MAKE_ITEM:
break;
case CMD_TILT_ANGLES:
- if (!got_tilt_axes)
- game_tilt_axes(&gd.tilt, gd.view.e);
+ if (!cs.got_tilt_axes)
+ {
+ /*
+ * Neverball <= 1.5.1 does not send explicit tilt
+ * axes, rotation happens directly around view
+ * vectors. So for compatibility if at the time of
+ * receiving tilt angles we have not yet received the
+ * tilt axes, we use the view vectors.
+ */
+
+ game_tilt_axes(tilt, view->e);
+ }
- gd.tilt.rx = cmd->tiltangles.x;
- gd.tilt.rz = cmd->tiltangles.z;
+ tilt->rx = cmd->tiltangles.x;
+ tilt->rz = cmd->tiltangles.z;
break;
case CMD_SOUND:
case CMD_JUMP_ENTER:
gd.jump_b = 1;
gd.jump_e = 0;
- gd.jump_dt = 0.0f;
+ gl.jump_dt[PREV] = 0.0f;
+ gl.jump_dt[CURR] = 0.0f;
break;
case CMD_JUMP_EXIT:
break;
case CMD_BODY_PATH:
- vary->bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BODY_TIME:
- vary->bv[cmd->bodytime.bi].t = cmd->bodytime.t;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_GOAL_OPEN:
if (!gd.goal_e)
{
gd.goal_e = 1;
- gd.goal_k = first_update ? 1.0f : 0.0f;
+ gl.goal_k[CURR] = cs.first_update ? 1.0f : 0.0f;
}
break;
break;
case CMD_UPDATES_PER_SECOND:
- ups = cmd->ups.n;
+ cs.ups = cmd->ups.n;
break;
case CMD_BALL_RADIUS:
- vary->uv[curr_ball].r = cmd->ballradius.r;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_CLEAR_ITEMS:
break;
case CMD_CLEAR_BALLS:
- if (vary->uv)
- {
- free(vary->uv);
- vary->uv = NULL;
- }
- vary->uc = 0;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BALL_POSITION:
- up = vary->uv + curr_ball;
-
- v_cpy(up->p, cmd->ballpos.p);
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BALL_BASIS:
- up = vary->uv + curr_ball;
-
- v_cpy(up->e[0], cmd->ballbasis.e[0]);
- v_cpy(up->e[1], cmd->ballbasis.e[1]);
- v_crs(up->e[2], up->e[0], up->e[1]);
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BALL_PEND_BASIS:
- up = vary->uv + curr_ball;
-
- v_cpy(up->E[0], cmd->ballpendbasis.E[0]);
- v_cpy(up->E[1], cmd->ballpendbasis.E[1]);
- v_crs(up->E[2], up->E[0], up->E[1]);
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_VIEW_POSITION:
- v_cpy(gd.view.p, cmd->viewpos.p);
+ v_cpy(view->p, cmd->viewpos.p);
break;
case CMD_VIEW_CENTER:
- v_cpy(gd.view.c, cmd->viewcenter.c);
+ v_cpy(view->c, cmd->viewcenter.c);
break;
case CMD_VIEW_BASIS:
- v_cpy(gd.view.e[0], cmd->viewbasis.e[0]);
- v_cpy(gd.view.e[1], cmd->viewbasis.e[1]);
- v_crs(gd.view.e[2], gd.view.e[0], gd.view.e[1]);
+ v_cpy(view->e[0], cmd->viewbasis.e[0]);
+ v_cpy(view->e[1], cmd->viewbasis.e[1]);
+ v_crs(view->e[2], view->e[0], view->e[1]);
break;
case CMD_CURRENT_BALL:
- curr_ball = cmd->currball.ui;
+ cs.curr_ball = cmd->currball.ui;
break;
case CMD_PATH_FLAG:
break;
case CMD_STEP_SIMULATION:
- /*
- * Simulate body motion.
- *
- * This is done on the client side due to replay file size
- * concerns and isn't done as part of CMD_END_OF_UPDATE to
- * match the server state as closely as possible. Body time
- * is still synchronized with the server on a semi-regular
- * basis and path indices are handled through CMD_BODY_PATH,
- * thus this code doesn't need to be as sophisticated as
- * sol_body_step.
- */
-
- dt = cmd->stepsim.dt;
-
- for (i = 0; i < vary->bc; i++)
- {
- struct v_body *bp = vary->bv + i;
- struct v_path *pp = vary->pv + bp->pi;
-
- if (bp->pi >= 0 && pp->f)
- bp->t += dt;
- }
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_MAP:
* Note a version (mis-)match between the loaded map and what
* the server has. (This doesn't actually load a map.)
*/
-
game_compat_map = version.x == cmd->map.version.x;
break;
case CMD_TILT_AXES:
- got_tilt_axes = 1;
- v_cpy(gd.tilt.x, cmd->tiltaxes.x);
- v_cpy(gd.tilt.z, cmd->tiltaxes.z);
+ cs.got_tilt_axes = 1;
+ v_cpy(tilt->x, cmd->tiltaxes.x);
+ v_cpy(tilt->z, cmd->tiltaxes.z);
break;
case CMD_NONE:
}
}
}
+
void game_client_sync(fs_file demo_fp)
{
union cmd *cmdp;
if (!sol_load_full(&gd.file, file_name, config_get_d(CONFIG_SHADOW)))
return (gd.state = 0);
- gd.reflective = sol_reflective(&gd.file.draw);
-
gd.state = 1;
- game_tilt_init(&gd.tilt);
+ /* Initialize game state. */
- /* Initialize jump and goal states. */
+ game_tilt_init(&gd.tilt);
+ game_view_init(&gd.view);
- gd.jump_e = 1;
- gd.jump_b = 0;
+ gd.jump_e = 1;
+ gd.jump_b = 0;
+ gd.jump_dt = 0.0f;
gd.goal_e = 0;
gd.goal_k = 0.0f;
- /* Initialise the level, background, particles, fade, and view. */
+ /* Initialize interpolation. */
+
+ game_lerp_init(&gl, &gd);
+
+ /* Initialize fade. */
gd.fade_k = 1.0f;
gd.fade_d = -2.0f;
+ /* Load level info. */
version.x = 0;
version.y = 0;
game_compat_map = version.x == 1;
+ /* Initialize particles. */
+
part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
- ups = 0;
- first_update = 1;
+ /* Initialize command state. */
+
+ cmd_state_init(&cs);
+
+ /* Initialize background. */
back_init(grad_name);
sol_load_full(&gd.back, back_name, 0);
if (gd.state)
{
game_proxy_clr();
+ game_lerp_free(&gl);
sol_free_full(&gd.file);
sol_free_full(&gd.back);
back_free();
/*---------------------------------------------------------------------------*/
+void game_client_blend(float a)
+{
+ gl.alpha = a;
+}
+
void game_client_draw(int pose, float t)
{
+ game_lerp_apply(&gl, &gd);
game_draw(&gd, pose, t);
}
/*---------------------------------------------------------------------------*/
-
void game_look(float phi, float theta)
{
- gd.view.c[0] = gd.view.p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
- gd.view.c[1] = gd.view.p[1] + fsinf(V_RAD(phi));
- gd.view.c[2] = gd.view.p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
+ struct game_view *view = &gl.view[CURR];
+
+ view->c[0] = view->p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
+ view->c[1] = view->p[1] + fsinf(V_RAD(phi));
+ view->c[2] = view->p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
+
+ gl.view[PREV] = gl.view[CURR];
}
/*---------------------------------------------------------------------------*/
void game_client_fly(float k)
{
- game_view_fly(&gd.view, &gd.file.vary, k);
+ game_view_fly(&gl.view[CURR], &gd.file.vary, k);
+
+ gl.view[PREV] = gl.view[CURR];
}
/*---------------------------------------------------------------------------*/