Fix lookaround mode without changing lerp alpha, also fix fly-by this way
[neverball] / ball / game_client.c
index 8094f38..4140a3c 100644 (file)
 #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"
@@ -42,78 +43,81 @@ int game_compat_map;                    /* Client/server map compat flag     */
 
 /*---------------------------------------------------------------------------*/
 
-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);
@@ -124,29 +128,26 @@ 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(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;
@@ -154,9 +155,9 @@ static void game_run_cmd(const union cmd *cmd)
         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);
@@ -166,11 +167,21 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -194,21 +205,22 @@ static void game_run_cmd(const union cmd *cmd)
             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:
@@ -217,118 +229,82 @@ static void game_run_cmd(const union cmd *cmd)
              * 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:
@@ -336,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(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:
@@ -352,6 +327,7 @@ static void game_run_cmd(const union cmd *cmd)
         }
     }
 }
+
 void game_client_sync(fs_file demo_fp)
 {
     union cmd *cmdp;
@@ -377,39 +353,44 @@ int  game_client_init(const char *file_name)
     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;
@@ -427,34 +408,46 @@ 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_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);
 }
 
 /*---------------------------------------------------------------------------*/
@@ -476,50 +469,55 @@ int curr_status(void)
 
 /*---------------------------------------------------------------------------*/
 
-
 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];
 }
 
 /*---------------------------------------------------------------------------*/