#include "ball.h"
#include "image.h"
#include "audio.h"
-#include "solid_gl.h"
#include "config.h"
#include "video.h"
+#include "solid_draw.h"
+
#include "game_client.h"
#include "game_common.h"
#include "game_proxy.h"
/*---------------------------------------------------------------------------*/
-static struct game_draw dr;
+#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 (dr.state)
+ if (gd.state)
{
- s_file *fp = &dr.file;
- s_item *hp;
- s_ball *up;
+ 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;
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, &dr.tilt);
+ game_tilt_grav(v, GRAVITY_UP, tilt);
else
- game_tilt_grav(v, GRAVITY_DN, &dr.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 (dr.goal_e && dr.goal_k < 1.0f)
- dr.goal_k += dt;
+ if (gd.goal_e && gl.goal_k[CURR] < 1.0f)
+ gl.goal_k[CURR] += dt;
- if (dr.jump_b)
+ if (gd.jump_b)
{
- dr.jump_dt += dt;
+ gl.jump_dt[CURR] += dt;
- if (1.0f < dr.jump_dt)
- dr.jump_b = 0;
+ if (1.0f < gl.jump_dt[PREV])
+ gd.jump_b = 0;
}
part_step(v, dt);
case CMD_MAKE_BALL:
/* Allocate a new ball and mark it as the current ball. */
- if ((up = realloc(fp->uv, sizeof (*up) * (fp->uc + 1))))
- {
- fp->uv = up;
- curr_ball = fp->uc;
- fp->uc++;
- }
+ if (sol_lerp_cmd(&gl.lerp, &cs, cmd))
+ cs.curr_ball = gl.lerp.uc - 1;
+
break;
case CMD_MAKE_ITEM:
/* Allocate and initialise a new item. */
- if ((hp = realloc(fp->hv, sizeof (*hp) * (fp->hc + 1))))
+ if ((hp = realloc(vary->hv, sizeof (*hp) * (vary->hc + 1))))
{
- s_item h;
+ struct v_item h;
v_cpy(h.p, cmd->mkitem.p);
h.t = cmd->mkitem.t;
h.n = cmd->mkitem.n;
- fp->hv = hp;
- fp->hv[fp->hc] = h;
- fp->hc++;
+ vary->hv = hp;
+ vary->hv[vary->hc] = h;
+ vary->hc++;
}
break;
case CMD_PICK_ITEM:
/* Set up particle effects and discard the item. */
- assert(cmd->pkitem.hi < fp->hc);
+ assert(cmd->pkitem.hi < vary->hc);
- hp = fp->hv + cmd->pkitem.hi;
+ hp = vary->hv + cmd->pkitem.hi;
item_color(hp, v);
part_burst(hp->p, v);
break;
case CMD_TILT_ANGLES:
- if (!got_tilt_axes)
- game_tilt_axes(&dr.tilt, dr.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);
+ }
- dr.tilt.rx = cmd->tiltangles.x;
- dr.tilt.rz = cmd->tiltangles.z;
+ tilt->rx = cmd->tiltangles.x;
+ tilt->rz = cmd->tiltangles.z;
break;
case CMD_SOUND:
break;
case CMD_JUMP_ENTER:
- dr.jump_b = 1;
- dr.jump_e = 0;
- dr.jump_dt = 0.0f;
+ gd.jump_b = 1;
+ gd.jump_e = 0;
+ gl.jump_dt[PREV] = 0.0f;
+ gl.jump_dt[CURR] = 0.0f;
break;
case CMD_JUMP_EXIT:
- dr.jump_e = 1;
+ gd.jump_e = 1;
break;
case CMD_BODY_PATH:
- fp->bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BODY_TIME:
- fp->bv[cmd->bodytime.bi].t = cmd->bodytime.t;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_GOAL_OPEN:
* this is the first update.
*/
- if (!dr.goal_e)
+ if (!gd.goal_e)
{
- dr.goal_e = 1;
- dr.goal_k = first_update ? 1.0f : 0.0f;
+ gd.goal_e = 1;
+ gl.goal_k[CURR] = cs.first_update ? 1.0f : 0.0f;
}
break;
case CMD_SWCH_ENTER:
- fp->xv[cmd->swchenter.xi].e = 1;
+ vary->xv[cmd->swchenter.xi].e = 1;
break;
case CMD_SWCH_TOGGLE:
- fp->xv[cmd->swchtoggle.xi].f = !fp->xv[cmd->swchtoggle.xi].f;
+ vary->xv[cmd->swchtoggle.xi].f = !vary->xv[cmd->swchtoggle.xi].f;
break;
case CMD_SWCH_EXIT:
- fp->xv[cmd->swchexit.xi].e = 0;
+ vary->xv[cmd->swchexit.xi].e = 0;
break;
case CMD_UPDATES_PER_SECOND:
- ups = cmd->ups.n;
+ cs.ups = cmd->ups.n;
break;
case CMD_BALL_RADIUS:
- fp->uv[curr_ball].r = cmd->ballradius.r;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_CLEAR_ITEMS:
- if (fp->hv)
+ if (vary->hv)
{
- free(fp->hv);
- fp->hv = NULL;
+ free(vary->hv);
+ vary->hv = NULL;
}
- fp->hc = 0;
+ vary->hc = 0;
break;
case CMD_CLEAR_BALLS:
- if (fp->uv)
- {
- free(fp->uv);
- fp->uv = NULL;
- }
- fp->uc = 0;
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BALL_POSITION:
- up = fp->uv + curr_ball;
-
- v_cpy(up->p, cmd->ballpos.p);
+ sol_lerp_cmd(&gl.lerp, &cs, cmd);
break;
case CMD_BALL_BASIS:
- up = fp->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 = fp->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(dr.view.p, cmd->viewpos.p);
+ v_cpy(view->p, cmd->viewpos.p);
break;
case CMD_VIEW_CENTER:
- v_cpy(dr.view.c, cmd->viewcenter.c);
+ v_cpy(view->c, cmd->viewcenter.c);
break;
case CMD_VIEW_BASIS:
- v_cpy(dr.view.e[0], cmd->viewbasis.e[0]);
- v_cpy(dr.view.e[1], cmd->viewbasis.e[1]);
- v_crs(dr.view.e[2], dr.view.e[0], dr.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:
- fp->pv[cmd->pathflag.pi].f = cmd->pathflag.f;
+ vary->pv[cmd->pathflag.pi].f = cmd->pathflag.f;
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 < fp->bc; i++)
- {
- s_body *bp = fp->bv + i;
- s_path *pp = fp->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(dr.tilt.x, cmd->tiltaxes.x);
- v_cpy(dr.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;
coins = 0;
status = GAME_NONE;
- if (dr.state)
+ if (gd.state)
game_client_free();
- if (!sol_load_gl(&dr.file, file_name, config_get_d(CONFIG_SHADOW)))
- return (dr.state = 0);
+ if (!sol_load_full(&gd.file, file_name, config_get_d(CONFIG_SHADOW)))
+ return (gd.state = 0);
+
+ gd.state = 1;
- dr.reflective = sol_reflective(&dr.file);
+ /* Initialize game state. */
- dr.state = 1;
+ game_tilt_init(&gd.tilt);
+ game_view_init(&gd.view);
- game_tilt_init(&dr.tilt);
+ gd.jump_e = 1;
+ gd.jump_b = 0;
+ gd.jump_dt = 0.0f;
- /* Initialize jump and goal states. */
+ gd.goal_e = 0;
+ gd.goal_k = 0.0f;
- dr.jump_e = 1;
- dr.jump_b = 0;
+ /* Initialize interpolation. */
- dr.goal_e = 0;
- dr.goal_k = 0.0f;
+ game_lerp_init(&gl, &gd);
- /* Initialise the level, background, particles, fade, and view. */
+ /* Initialize fade. */
- dr.fade_k = 1.0f;
- dr.fade_d = -2.0f;
+ gd.fade_k = 1.0f;
+ gd.fade_d = -2.0f;
+ /* Load level info. */
version.x = 0;
version.y = 0;
- for (i = 0; i < dr.file.dc; i++)
+ for (i = 0; i < gd.file.base.dc; i++)
{
- char *k = dr.file.av + dr.file.dv[i].ai;
- char *v = dr.file.av + dr.file.dv[i].aj;
+ char *k = gd.file.base.av + gd.file.base.dv[i].ai;
+ char *v = gd.file.base.av + gd.file.base.dv[i].aj;
if (strcmp(k, "back") == 0) back_name = v;
if (strcmp(k, "grad") == 0) grad_name = v;
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_gl(&dr.back, back_name, 0);
+ sol_load_full(&gd.back, back_name, 0);
- return dr.state;
+ return gd.state;
}
void game_client_free(void)
{
- if (dr.state)
+ if (gd.state)
{
game_proxy_clr();
- sol_free_gl(&dr.file);
- sol_free_gl(&dr.back);
+ game_lerp_free(&gl);
+ sol_free_full(&gd.file);
+ sol_free_full(&gd.back);
back_free();
}
- dr.state = 0;
+ gd.state = 0;
}
/*---------------------------------------------------------------------------*/
+void game_client_blend(float a)
+{
+ gl.alpha = a;
+}
+
void game_client_draw(int pose, float t)
{
- game_draw(&dr, pose, t);
+ game_lerp_apply(&gl, &gd);
+ game_draw(&gd, pose, t);
}
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
-
void game_look(float phi, float theta)
{
- dr.view.c[0] = dr.view.p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
- dr.view.c[1] = dr.view.p[1] + fsinf(V_RAD(phi));
- dr.view.c[2] = dr.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_kill_fade(void)
{
- dr.fade_k = 0.0f;
- dr.fade_d = 0.0f;
+ gd.fade_k = 0.0f;
+ gd.fade_d = 0.0f;
}
void game_step_fade(float dt)
{
- if ((dr.fade_k < 1.0f && dr.fade_d > 0.0f) ||
- (dr.fade_k > 0.0f && dr.fade_d < 0.0f))
- dr.fade_k += dr.fade_d * dt;
+ if ((gd.fade_k < 1.0f && gd.fade_d > 0.0f) ||
+ (gd.fade_k > 0.0f && gd.fade_d < 0.0f))
+ gd.fade_k += gd.fade_d * dt;
- if (dr.fade_k < 0.0f)
+ if (gd.fade_k < 0.0f)
{
- dr.fade_k = 0.0f;
- dr.fade_d = 0.0f;
+ gd.fade_k = 0.0f;
+ gd.fade_d = 0.0f;
}
- if (dr.fade_k > 1.0f)
+ if (gd.fade_k > 1.0f)
{
- dr.fade_k = 1.0f;
- dr.fade_d = 0.0f;
+ gd.fade_k = 1.0f;
+ gd.fade_d = 0.0f;
}
}
void game_fade(float d)
{
- dr.fade_d = d;
+ gd.fade_d = d;
}
/*---------------------------------------------------------------------------*/
void game_client_fly(float k)
{
- game_view_fly(&dr.view, &dr.file, k);
+ game_view_fly(&gl.view[CURR], &gd.file.vary, k);
+
+ gl.view[PREV] = gl.view[CURR];
}
/*---------------------------------------------------------------------------*/