Fix lookaround mode without changing lerp alpha, also fix fly-by this way
[neverball] / ball / game_client.c
index 631d95b..4140a3c 100644 (file)
@@ -43,77 +43,80 @@ int game_compat_map;                    /* Client/server map compat flag     */
 
 /*---------------------------------------------------------------------------*/
 
+#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;
                 }
 
@@ -125,12 +128,9 @@ static void game_run_cmd(const union cmd *cmd)
         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:
@@ -167,11 +167,21 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -197,7 +207,8 @@ static void game_run_cmd(const union cmd *cmd)
         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:
@@ -205,11 +216,11 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -221,7 +232,7 @@ static void game_run_cmd(const union cmd *cmd)
             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;
 
@@ -238,11 +249,11 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -255,52 +266,37 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -308,28 +304,7 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -337,14 +312,13 @@ static void game_run_cmd(const union cmd *cmd)
              * 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:
@@ -353,6 +327,7 @@ static void game_run_cmd(const union cmd *cmd)
         }
     }
 }
+
 void game_client_sync(fs_file demo_fp)
 {
     union cmd *cmdp;
@@ -384,25 +359,30 @@ int  game_client_init(const char *file_name)
     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;
@@ -428,10 +408,15 @@ int  game_client_init(const char *file_name)
 
     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);
@@ -444,6 +429,7 @@ void game_client_free(void)
     if (gd.state)
     {
         game_proxy_clr();
+        game_lerp_free(&gl);
         sol_free_full(&gd.file);
         sol_free_full(&gd.back);
         back_free();
@@ -453,8 +439,14 @@ void game_client_free(void)
 
 /*---------------------------------------------------------------------------*/
 
+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);
 }
 
@@ -477,12 +469,15 @@ int curr_status(void)
 
 /*---------------------------------------------------------------------------*/
 
-
 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];
 }
 
 /*---------------------------------------------------------------------------*/
@@ -520,7 +515,9 @@ void game_fade(float d)
 
 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];
 }
 
 /*---------------------------------------------------------------------------*/