Implement a server/client-like game/replay architecture
authorparasti <parasti@78b8d119-cf0a-0410-b17c-f493084dd1d7>
Fri, 9 Jan 2009 21:52:24 +0000 (21:52 +0000)
committerparasti <parasti@78b8d119-cf0a-0410-b17c-f493084dd1d7>
Fri, 9 Jan 2009 21:52:24 +0000 (21:52 +0000)
The primary focus of this patch is splitting the ball/game module into
a "client" module and a "server" module.

The server module is responsible for running the game simulation,
while the client module mostly deals with drawing the game state to
the screen. The server communicates its state to the client through a
"proxy" module, which is simply a queue of "commands" encapsulating
the server state and events. The client reads the commands off the
proxy and runs them, updating its own state for display.  It can also
write them to a file, from which the same exact client state can then
be reconstructed by simply reading the commands back and adding them
to the proxy queue for the client to handle as described.

The primary purpose of the change was to eliminate game simulation
from the replay "operation chain", in order to eliminate most possible
causes for unreliable replays.  In ASCII art, the old scheme basically
looked like this:

    ...    -> input -> sim. -> display
    replay -> input -> sim. -> display

while the new one looks like this:

    ...    -> input -> server (sim.) -> proxy -> client -> display
    ...    -> ...   -> replay        -> proxy -> client -> display

In-detail description of the change:

    http://www.nevercorner.net/forum/viewtopic.php?pid=18842#p18842

git-svn-id: https://s.snth.net/svn/neverball/trunk@2714 78b8d119-cf0a-0410-b17c-f493084dd1d7

38 files changed:
Makefile
ball/demo.c
ball/demo.h
ball/game.c [deleted file]
ball/game.h [deleted file]
ball/game_client.c [new file with mode: 0644]
ball/game_client.h [new file with mode: 0644]
ball/game_common.c [new file with mode: 0644]
ball/game_common.h [new file with mode: 0644]
ball/game_proxy.c [new file with mode: 0644]
ball/game_proxy.h [new file with mode: 0644]
ball/game_server.c [new file with mode: 0644]
ball/game_server.h [new file with mode: 0644]
ball/hud.c
ball/main.c
ball/progress.c
ball/set.c
ball/st_conf.c
ball/st_demo.c
ball/st_done.c
ball/st_fall_out.c
ball/st_goal.c
ball/st_help.c
ball/st_level.c
ball/st_name.c
ball/st_over.c
ball/st_pause.c
ball/st_play.c
ball/st_save.c
ball/st_set.c
ball/st_shared.c
ball/st_start.c
ball/st_time_out.c
ball/st_title.c
share/cmd.c [new file with mode: 0644]
share/cmd.h [new file with mode: 0644]
share/solid_phys.c
share/solid_phys.h

index b5d17b4..ceca64f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -160,8 +160,14 @@ BALL_OBJS := \
        share/common.o      \
        share/keynames.o    \
        share/syswm.o       \
+       share/list.o        \
+       share/queue.o       \
+       share/cmd.o         \
        ball/hud.o          \
-       ball/game.o         \
+       ball/game_common.o  \
+       ball/game_client.o  \
+       ball/game_server.o  \
+       ball/game_proxy.o   \
        ball/score.o        \
        ball/level.o        \
        ball/progress.o     \
@@ -209,6 +215,7 @@ PUTT_OBJS := \
        share/sync.o        \
        share/common.o      \
        share/syswm.o       \
+       share/list.o        \
        putt/hud.o          \
        putt/game.o         \
        putt/hole.o         \
index 2c5f5de..b95e328 100644 (file)
@@ -19,7 +19,6 @@
 #include <assert.h>
 
 #include "demo.h"
-#include "game.h"
 #include "audio.h"
 #include "solid.h"
 #include "config.h"
 #include "common.h"
 #include "level.h"
 
+#include "game_server.h"
+#include "game_client.h"
+#include "game_proxy.h"
+
 /*---------------------------------------------------------------------------*/
 
 #define MAGIC           0x52424EAF
-#define DEMO_VERSION    6
+#define DEMO_VERSION    9
 
 #define DATELEN 20
 
@@ -370,17 +373,23 @@ int demo_play_init(const char *name, const struct level *level,
     {
         demo_header_write(demo_fp, &demo);
         audio_music_fade_to(2.0f, level->song);
-        return game_init(level->file, t, e);
+
+        /*
+         * Init both client and server, then process the first batch
+         * of commands generated by the server to sync client to
+         * server.
+         */
+
+        if (game_client_init(level->file) &&
+            game_server_init(level->file, t, e))
+        {
+            game_client_step(demo_fp);
+            return 1;
+        }
     }
     return 0;
 }
 
-void demo_play_step()
-{
-    if (demo_fp)
-        input_put(demo_fp);
-}
-
 void demo_play_stat(int status, int coins, int timer)
 {
     if (demo_fp)
@@ -510,12 +519,9 @@ const struct demo *curr_demo_replay(void)
     return &demo_replay;
 }
 
-static int demo_status = GAME_NONE;
-
 int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
 {
-    demo_status = GAME_NONE;
-    demo_fp     = fopen(name, FMODE_RB);
+    demo_fp = fopen(name, FMODE_RB);
 
     if (demo_fp && demo_header_read(demo_fp, &demo_replay))
     {
@@ -538,42 +544,56 @@ int demo_replay_init(const char *name, int *g, int *m, int *b, int *s, int *tt)
         if (s)  *s  = demo_replay.score;
         if (tt) *tt = demo_replay.times;
 
+        /*
+         * Init client and then read and process the first batch of
+         * commands from the replay file.
+         */
+
         if (g)
         {
             audio_music_fade_to(0.5f, demo_level_replay.song);
 
-            return game_init(demo_level_replay.file,
-                             demo_level_replay.time,
-                             demo_replay.goal_e);
+            return (game_client_init(demo_level_replay.file) &&
+                    demo_replay_step(0.0f));
+        }
+
+        /* Likewise, but also queue a command to open the goal. */
+
+        else if (game_client_init(demo_level_replay.file))
+        {
+            union cmd cmd;
+
+            cmd.type = CMD_GOAL_OPEN;
+            game_proxy_enq(&cmd);
+
+            return demo_replay_step(0.0f);
         }
-        else
-            return game_init(demo_level_replay.file,
-                             demo_level_replay.time, 1);
     }
     return 0;
 }
 
+/*
+ * Read and enqueue commands from the replay file, up to and including
+ * CMD_END_OF_UPDATE.  Return 0 on EOF.  Otherwise, run the commands
+ * on the client and return 1.
+ */
 int demo_replay_step(float dt)
 {
-    const float gdn[3] = { 0.0f, -9.8f, 0.0f };
-    const float gup[3] = { 0.0f, +9.8f, 0.0f };
-
     if (demo_fp)
     {
-        if (input_get(demo_fp))
+        union cmd cmd;
+
+        while (cmd_get(demo_fp, &cmd))
         {
-            /* Play out current game state. */
+            game_proxy_enq(&cmd);
 
-            switch (demo_status)
-            {
-            case GAME_NONE:
-                demo_status = game_step(gdn, dt, 1); break;
-            case GAME_GOAL:
-                (void)        game_step(gup, dt, 0); break;
-            default:
-                (void)        game_step(gdn, dt, 0); break;
-            }
+            if (cmd.type == CMD_END_OF_UPDATE)
+                break;
+        }
 
+        if (!feof(demo_fp))
+        {
+            game_client_step(NULL);
             return 1;
         }
     }
@@ -597,3 +617,7 @@ void demo_replay_dump_info(void)
 }
 
 /*---------------------------------------------------------------------------*/
+
+FILE *demo_file(void) { return demo_fp; }
+
+/*---------------------------------------------------------------------------*/
index 0dcb0c5..ae52bc1 100644 (file)
@@ -2,6 +2,7 @@
 #define DEMO_H
 
 #include <time.h>
+#include <stdio.h>
 
 #include "level.h"
 
@@ -69,4 +70,8 @@ const struct demo *curr_demo_replay(void);
 
 /*---------------------------------------------------------------------------*/
 
+FILE *demo_file(void);
+
+/*---------------------------------------------------------------------------*/
+
 #endif
diff --git a/ball/game.c b/ball/game.c
deleted file mode 100644 (file)
index 18101e4..0000000
+++ /dev/null
@@ -1,1320 +0,0 @@
-/*
- * Copyright (C) 2003 Robert Kooima
- *
- * NEVERBALL is  free software; you can redistribute  it and/or modify
- * it under the  terms of the GNU General  Public License as published
- * by the Free  Software Foundation; either version 2  of the License,
- * or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
- * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
- * General Public License for more details.
- */
-
-#include <SDL.h>
-#include <math.h>
-
-#include "glext.h"
-#include "game.h"
-#include "vec3.h"
-#include "geom.h"
-#include "item.h"
-#include "back.h"
-#include "part.h"
-#include "ball.h"
-#include "image.h"
-#include "audio.h"
-#include "solid_gl.h"
-#include "solid_phys.h"
-#include "config.h"
-#include "binary.h"
-
-/*---------------------------------------------------------------------------*/
-
-static int game_state = 0;
-
-static struct s_file file;
-static struct s_file back;
-
-static int   reflective;                /* Reflective geometry used?         */
-
-static float timer      = 0.f;          /* Clock time                        */
-static int   timer_down = 1;            /* Timer go up or down?              */
-
-static float game_rx;                   /* Floor rotation about X axis       */
-static float game_rz;                   /* Floor rotation about Z axis       */
-
-static float view_a;                    /* Ideal view rotation about Y axis  */
-static float view_dc;                   /* Ideal view distance above ball    */
-static float view_dp;                   /* Ideal view distance above ball    */
-static float view_dz;                   /* Ideal view distance behind ball   */
-static float view_fov;                  /* Field of view                     */
-
-static float view_c[3];                 /* Current view center               */
-static float view_v[3];                 /* Current view vector               */
-static float view_p[3];                 /* Current view position             */
-static float view_e[3][3];              /* Current view reference frame      */
-static float view_k;
-
-static int   coins  = 0;                /* Collected coins                   */
-static int   goal_e = 0;                /* Goal enabled flag                 */
-static float goal_k = 0;                /* Goal animation                    */
-static int   jump_e = 1;                /* Jumping enabled flag              */
-static int   jump_b = 0;                /* Jump-in-progress flag             */
-static float jump_dt;                   /* Jump duration                     */
-static float jump_p[3];                 /* Jump destination                  */
-static float fade_k = 0.0;              /* Fade in/out level                 */
-static float fade_d = 0.0;              /* Fade in/out direction             */
-
-/*---------------------------------------------------------------------------*/
-
-/*
- * This is an abstraction of the game's input state.  All input is
- * encapsulated here, and all references to the input by the game are made
- * here.  This has the effect of homogenizing input for use in replay
- * recording and playback.
- *
- * x and z:
- *     -32767 = -ANGLE_BOUND
- *     +32767 = +ANGLE_BOUND
- *
- * r:
- *     -32767 = -VIEWR_BOUND
- *     +32767 = +VIEWR_BOUND
- *
- */
-
-struct input
-{
-    short x;
-    short z;
-    short r;
-    short c;
-};
-
-static struct input input_current;
-
-static void input_init(void)
-{
-    input_current.x = 0;
-    input_current.z = 0;
-    input_current.r = 0;
-    input_current.c = 0;
-}
-
-static void input_set_x(float x)
-{
-    if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
-    if (x >  ANGLE_BOUND) x =  ANGLE_BOUND;
-
-    input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
-}
-
-static void input_set_z(float z)
-{
-    if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
-    if (z >  ANGLE_BOUND) z =  ANGLE_BOUND;
-
-    input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
-}
-
-static void input_set_r(float r)
-{
-    if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
-    if (r >  VIEWR_BOUND) r =  VIEWR_BOUND;
-
-    input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
-}
-
-static void input_set_c(int c)
-{
-    input_current.c = (short) c;
-}
-
-static float input_get_x(void)
-{
-    return ANGLE_BOUND * (float) input_current.x / 32767.0f;
-}
-
-static float input_get_z(void)
-{
-    return ANGLE_BOUND * (float) input_current.z / 32767.0f;
-}
-
-static float input_get_r(void)
-{
-    return VIEWR_BOUND * (float) input_current.r / 32767.0f;
-}
-
-static int input_get_c(void)
-{
-    return (int) input_current.c;
-}
-
-int input_put(FILE *fout)
-{
-    if (game_state)
-    {
-        put_short(fout, &input_current.x);
-        put_short(fout, &input_current.z);
-        put_short(fout, &input_current.r);
-        put_short(fout, &input_current.c);
-
-        return 1;
-    }
-    return 0;
-}
-
-int input_get(FILE *fin)
-{
-    if (game_state)
-    {
-        get_short(fin, &input_current.x);
-        get_short(fin, &input_current.z);
-        get_short(fin, &input_current.r);
-        get_short(fin, &input_current.c);
-
-        return (feof(fin) ? 0 : 1);
-    }
-    return 0;
-}
-
-/*---------------------------------------------------------------------------*/
-
-static int   grow = 0;                  /* Should the ball be changing size? */
-static float grow_orig = 0;             /* the original ball size            */
-static float grow_goal = 0;             /* how big or small to get!          */
-static float grow_t = 0.0;              /* timer for the ball to grow...     */
-static float grow_strt = 0;             /* starting value for growth         */
-static int   got_orig = 0;              /* Do we know original ball size?    */
-
-#define GROW_TIME  0.5f                 /* sec for the ball to get to size.  */
-#define GROW_BIG   1.5f                 /* large factor                      */
-#define GROW_SMALL 0.5f                 /* small factor                      */
-
-static int   grow_state = 0;            /* Current state (values -1, 0, +1)  */
-
-static void grow_init(const struct s_file *fp, int type)
-{
-    if (!got_orig)
-    {
-        grow_orig  = fp->uv->r;
-        grow_goal  = grow_orig;
-        grow_strt  = grow_orig;
-
-        grow_state = 0;
-
-        got_orig   = 1;
-    }
-
-    if (type == ITEM_SHRINK)
-    {
-        switch (grow_state)
-        {
-        case -1:
-            break;
-
-        case  0:
-            audio_play(AUD_SHRINK, 1.f);
-            grow_goal = grow_orig * GROW_SMALL;
-            grow_state = -1;
-            grow = 1;
-            break;
-
-        case +1:
-            audio_play(AUD_SHRINK, 1.f);
-            grow_goal = grow_orig;
-            grow_state = 0;
-            grow = 1;
-            break;
-        }
-    }
-    else if (type == ITEM_GROW)
-    {
-        switch (grow_state)
-        {
-        case -1:
-            audio_play(AUD_GROW, 1.f);
-            grow_goal = grow_orig;
-            grow_state = 0;
-            grow = 1;
-            break;
-
-        case  0:
-            audio_play(AUD_GROW, 1.f);
-            grow_goal = grow_orig * GROW_BIG;
-            grow_state = +1;
-            grow = 1;
-            break;
-
-        case +1:
-            break;
-        }
-    }
-
-    if (grow)
-    {
-        grow_t = 0.0;
-        grow_strt = fp->uv->r;
-    }
-}
-
-static void grow_step(const struct s_file *fp, float dt)
-{
-    float dr;
-
-    if (!grow)
-        return;
-
-    /* Calculate new size based on how long since you touched the coin... */
-
-    grow_t += dt;
-
-    if (grow_t >= GROW_TIME)
-    {
-        grow = 0;
-        grow_t = GROW_TIME;
-    }
-
-    dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
-
-    /* No sinking through the floor! Keeps ball's bottom constant. */
-
-    fp->uv->p[1] += (dr - fp->uv->r);
-    fp->uv->r     =  dr;
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void view_init(void)
-{
-    view_fov = (float) config_get_d(CONFIG_VIEW_FOV);
-    view_dp  = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
-    view_dc  = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
-    view_dz  = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
-    view_k   = 1.0f;
-    view_a   = 0.0f;
-
-    view_c[0] = 0.f;
-    view_c[1] = view_dc;
-    view_c[2] = 0.f;
-
-    view_p[0] =     0.f;
-    view_p[1] = view_dp;
-    view_p[2] = view_dz;
-
-    view_e[0][0] = 1.f;
-    view_e[0][1] = 0.f;
-    view_e[0][2] = 0.f;
-    view_e[1][0] = 0.f;
-    view_e[1][1] = 1.f;
-    view_e[1][2] = 0.f;
-    view_e[2][0] = 0.f;
-    view_e[2][1] = 0.f;
-    view_e[2][2] = 1.f;
-}
-
-int game_init(const char *file_name, int t, int e)
-{
-    char *back_name = NULL, *grad_name = NULL;
-
-    int i;
-
-    timer      = (float) t / 100.f;
-    timer_down = (t > 0);
-    coins      = 0;
-
-    if (game_state)
-        game_free();
-
-    if (!sol_load_gl(&file, config_data(file_name),
-                     config_get_d(CONFIG_TEXTURES),
-                     config_get_d(CONFIG_SHADOW)))
-        return (game_state = 0);
-
-    reflective = sol_reflective(&file);
-
-    game_state = 1;
-
-    input_init();
-
-    game_rx = 0.0f;
-    game_rz = 0.0f;
-
-    /* Initialize jump and goal states. */
-
-    jump_e = 1;
-    jump_b = 0;
-
-    goal_e = e ? 1    : 0;
-    goal_k = e ? 1.0f : 0.0f;
-
-    /* Initialise the level, background, particles, fade, and view. */
-
-    fade_k =  1.0f;
-    fade_d = -2.0f;
-
-    for (i = 0; i < file.dc; i++)
-    {
-        char *k = file.av + file.dv[i].ai;
-        char *v = file.av + file.dv[i].aj;
-
-        if (strcmp(k, "back") == 0) back_name = v;
-        if (strcmp(k, "grad") == 0) grad_name = v;
-    }
-
-    part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
-    view_init();
-    back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
-
-    sol_load_gl(&back, config_data(back_name),
-                config_get_d(CONFIG_TEXTURES), 0);
-
-    /* Initialize ball size tracking... */
-
-    got_orig = 0;
-    grow = 0;
-
-    return game_state;
-}
-
-void game_free(void)
-{
-    if (game_state)
-    {
-        sol_free_gl(&file);
-        sol_free_gl(&back);
-        back_free();
-    }
-    game_state = 0;
-}
-
-/*---------------------------------------------------------------------------*/
-
-int curr_clock(void)
-{
-    return (int) (timer * 100.f);
-}
-
-int curr_coins(void)
-{
-    return coins;
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void game_draw_balls(const struct s_file *fp,
-                            const float *bill_M, float t)
-{
-    float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
-
-    float ball_M[16];
-    float pend_M[16];
-
-    m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
-    m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
-
-    glPushAttrib(GL_LIGHTING_BIT);
-    glPushMatrix();
-    {
-        glTranslatef(fp->uv[0].p[0],
-                     fp->uv[0].p[1] + BALL_FUDGE,
-                     fp->uv[0].p[2]);
-        glScalef(fp->uv[0].r,
-                 fp->uv[0].r,
-                 fp->uv[0].r);
-
-        glColor4fv(c);
-        ball_draw(ball_M, pend_M, bill_M, t);
-    }
-    glPopMatrix();
-    glPopAttrib();
-}
-
-static void game_draw_items(const struct s_file *fp, float t)
-{
-    float r = 360.f * t;
-    int hi;
-
-    glPushAttrib(GL_LIGHTING_BIT);
-    {
-        item_push(ITEM_COIN);
-        {
-            for (hi = 0; hi < fp->hc; hi++)
-
-                if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
-                {
-                    glPushMatrix();
-                    {
-                        glTranslatef(fp->hv[hi].p[0],
-                                     fp->hv[hi].p[1],
-                                     fp->hv[hi].p[2]);
-                        glRotatef(r, 0.0f, 1.0f, 0.0f);
-                        item_draw(&fp->hv[hi], r);
-                    }
-                    glPopMatrix();
-                }
-        }
-        item_pull();
-
-        item_push(ITEM_SHRINK);
-        {
-            for (hi = 0; hi < fp->hc; hi++)
-
-                if (fp->hv[hi].t == ITEM_SHRINK)
-                {
-                    glPushMatrix();
-                    {
-                        glTranslatef(fp->hv[hi].p[0],
-                                     fp->hv[hi].p[1],
-                                     fp->hv[hi].p[2]);
-                        glRotatef(r, 0.0f, 1.0f, 0.0f);
-                        item_draw(&fp->hv[hi], r);
-                    }
-                    glPopMatrix();
-                }
-        }
-        item_pull();
-
-        item_push(ITEM_GROW);
-        {
-            for (hi = 0; hi < fp->hc; hi++)
-
-                if (fp->hv[hi].t == ITEM_GROW)
-                {
-                    glPushMatrix();
-                    {
-                        glTranslatef(fp->hv[hi].p[0],
-                                     fp->hv[hi].p[1],
-                                     fp->hv[hi].p[2]);
-                        glRotatef(r, 0.0f, 1.0f, 0.0f);
-                        item_draw(&fp->hv[hi], r);
-                    }
-                    glPopMatrix();
-                }
-        }
-        item_pull();
-    }
-    glPopAttrib();
-}
-
-static void game_draw_goals(const struct s_file *fp, const float *M, float t)
-{
-    if (goal_e)
-    {
-        int zi;
-
-        /* Draw the goal particles. */
-
-        glEnable(GL_TEXTURE_2D);
-        {
-            for (zi = 0; zi < fp->zc; zi++)
-            {
-                glPushMatrix();
-                {
-                    glTranslatef(fp->zv[zi].p[0],
-                                 fp->zv[zi].p[1],
-                                 fp->zv[zi].p[2]);
-
-                    part_draw_goal(M, fp->zv[zi].r, goal_k, t);
-                }
-                glPopMatrix();
-            }
-        }
-        glDisable(GL_TEXTURE_2D);
-
-        /* Draw the goal column. */
-
-        for (zi = 0; zi < fp->zc; zi++)
-        {
-            glPushMatrix();
-            {
-                glTranslatef(fp->zv[zi].p[0],
-                             fp->zv[zi].p[1],
-                             fp->zv[zi].p[2]);
-
-                glScalef(fp->zv[zi].r,
-                         goal_k,
-                         fp->zv[zi].r);
-
-                goal_draw();
-            }
-            glPopMatrix();
-        }
-    }
-}
-
-static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
-{
-    int ji;
-
-    glEnable(GL_TEXTURE_2D);
-    {
-        for (ji = 0; ji < fp->jc; ji++)
-        {
-            glPushMatrix();
-            {
-                glTranslatef(fp->jv[ji].p[0],
-                             fp->jv[ji].p[1],
-                             fp->jv[ji].p[2]);
-
-                part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
-            }
-            glPopMatrix();
-        }
-    }
-    glDisable(GL_TEXTURE_2D);
-
-    for (ji = 0; ji < fp->jc; ji++)
-    {
-        glPushMatrix();
-        {
-            glTranslatef(fp->jv[ji].p[0],
-                         fp->jv[ji].p[1],
-                         fp->jv[ji].p[2]);
-            glScalef(fp->jv[ji].r,
-                     1.0f,
-                     fp->jv[ji].r);
-
-            jump_draw(!jump_e);
-        }
-        glPopMatrix();
-    }
-}
-
-static void game_draw_swchs(const struct s_file *fp)
-{
-    int xi;
-
-    for (xi = 0; xi < fp->xc; xi++)
-    {
-        if (fp->xv[xi].i)
-            continue;
-
-        glPushMatrix();
-        {
-            glTranslatef(fp->xv[xi].p[0],
-                         fp->xv[xi].p[1],
-                         fp->xv[xi].p[2]);
-            glScalef(fp->xv[xi].r,
-                     1.0f,
-                     fp->xv[xi].r);
-
-            swch_draw(fp->xv[xi].f, fp->xv[xi].e);
-        }
-        glPopMatrix();
-    }
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void game_draw_tilt(int d)
-{
-    const float *ball_p = file.uv->p;
-
-    /* Rotate the environment about the position of the ball. */
-
-    glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
-    glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
-    glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
-    glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
-}
-
-static void game_refl_all(void)
-{
-    glPushMatrix();
-    {
-        game_draw_tilt(1);
-
-        /* Draw the floor. */
-
-        sol_refl(&file);
-    }
-    glPopMatrix();
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void game_draw_light(void)
-{
-    const float light_p[2][4] = {
-        { -8.0f, +32.0f, -8.0f, 0.0f },
-        { +8.0f, +32.0f, +8.0f, 0.0f },
-    };
-    const float light_c[2][4] = {
-        { 1.0f, 0.8f, 0.8f, 1.0f },
-        { 0.8f, 1.0f, 0.8f, 1.0f },
-    };
-
-    /* Configure the lighting. */
-
-    glEnable(GL_LIGHT0);
-    glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
-    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
-    glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
-
-    glEnable(GL_LIGHT1);
-    glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
-    glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
-    glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
-}
-
-static void game_draw_back(int pose, int d, float t)
-{
-    glPushMatrix();
-    {
-        if (d < 0)
-        {
-            glRotatef(game_rz * 2, view_e[2][0], view_e[2][1], view_e[2][2]);
-            glRotatef(game_rx * 2, view_e[0][0], view_e[0][1], view_e[0][2]);
-        }
-
-        glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
-
-        if (config_get_d(CONFIG_BACKGROUND))
-        {
-            /* Draw all background layers back to front. */
-
-            sol_back(&back, BACK_DIST, FAR_DIST,  t);
-            back_draw(0);
-            sol_back(&back,         0, BACK_DIST, t);
-        }
-        else back_draw(0);
-    }
-    glPopMatrix();
-}
-
-static void game_clip_refl(int d)
-{
-    /* Fudge to eliminate the floor from reflection. */
-
-    GLdouble e[4], k = -0.00001;
-
-    e[0] = 0;
-    e[1] = 1;
-    e[2] = 0;
-    e[3] = k;
-
-    glClipPlane(GL_CLIP_PLANE0, e);
-}
-
-static void game_clip_ball(int d, const float *p)
-{
-    GLdouble r, c[3], pz[4], nz[4];
-
-    /* Compute the plane giving the front of the ball, as seen from view_p. */
-
-    c[0] = p[0];
-    c[1] = p[1] * d;
-    c[2] = p[2];
-
-    pz[0] = view_p[0] - c[0];
-    pz[1] = view_p[1] - c[1];
-    pz[2] = view_p[2] - c[2];
-
-    r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
-
-    pz[0] /= r;
-    pz[1] /= r;
-    pz[2] /= r;
-    pz[3] = -(pz[0] * c[0] +
-              pz[1] * c[1] +
-              pz[2] * c[2]);
-
-    /* Find the plane giving the back of the ball, as seen from view_p. */
-
-    nz[0] = -pz[0];
-    nz[1] = -pz[1];
-    nz[2] = -pz[2];
-    nz[3] = -pz[3];
-
-    /* Reflect these planes as necessary, and store them in the GL state. */
-
-    pz[1] *= d;
-    nz[1] *= d;
-
-    glClipPlane(GL_CLIP_PLANE1, nz);
-    glClipPlane(GL_CLIP_PLANE2, pz);
-}
-
-static void game_draw_fore(int pose, const float *M, int d, float t)
-{
-    const float *ball_p = file.uv->p;
-    const float  ball_r = file.uv->r;
-
-    glPushMatrix();
-    {
-        /* Rotate the environment about the position of the ball. */
-
-        game_draw_tilt(d);
-
-        /* Compute clipping planes for reflection and ball facing. */
-
-        game_clip_refl(d);
-        game_clip_ball(d, ball_p);
-
-        if (d < 0)
-            glEnable(GL_CLIP_PLANE0);
-
-        if (pose)
-            sol_draw(&file, 0, 1);
-        else
-        {
-            /* Draw the coins. */
-
-            game_draw_items(&file, t);
-
-            /* Draw the floor. */
-
-            sol_draw(&file, 0, 1);
-
-            /* Draw the ball shadow. */
-
-            if (d > 0 && config_get_d(CONFIG_SHADOW))
-            {
-                shad_draw_set(ball_p, ball_r);
-                sol_shad(&file);
-                shad_draw_clr();
-            }
-
-            /* Draw the ball. */
-
-            game_draw_balls(&file, M, t);
-        }
-
-        /* Draw the particles and light columns. */
-
-        glEnable(GL_COLOR_MATERIAL);
-        glDisable(GL_LIGHTING);
-        glDepthMask(GL_FALSE);
-        {
-            glColor3f(1.0f, 1.0f, 1.0f);
-
-            sol_bill(&file, M, t);
-            part_draw_coin(M, t);
-
-            glDisable(GL_TEXTURE_2D);
-            {
-                game_draw_goals(&file, M, t);
-                game_draw_jumps(&file, M, t);
-                game_draw_swchs(&file);
-            }
-            glEnable(GL_TEXTURE_2D);
-
-            glColor3f(1.0f, 1.0f, 1.0f);
-        }
-        glDepthMask(GL_TRUE);
-        glEnable(GL_LIGHTING);
-        glDisable(GL_COLOR_MATERIAL);
-
-        if (d < 0)
-            glDisable(GL_CLIP_PLANE0);
-    }
-    glPopMatrix();
-}
-
-void game_draw(int pose, float t)
-{
-    float fov = view_fov;
-
-    if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
-
-    if (game_state)
-    {
-        config_push_persp(fov, 0.1f, FAR_DIST);
-        glPushMatrix();
-        {
-            float T[16], U[16], M[16], v[3];
-
-            /* Compute direct and reflected view bases. */
-
-            v[0] = +view_p[0];
-            v[1] = -view_p[1];
-            v[2] = +view_p[2];
-
-            m_view(T, view_c, view_p, view_e[1]);
-            m_view(U, view_c, v,      view_e[1]);
-
-            m_xps(M, T);
-
-            /* Apply current the view. */
-
-            v_sub(v, view_c, view_p);
-
-            glTranslatef(0.f, 0.f, -v_len(v));
-            glMultMatrixf(M);
-            glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
-
-            if (reflective && config_get_d(CONFIG_REFLECTION))
-            {
-                glEnable(GL_STENCIL_TEST);
-                {
-                    /* Draw the mirrors only into the stencil buffer. */
-
-                    glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
-                    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
-                    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
-                    glDepthMask(GL_FALSE);
-
-                    game_refl_all();
-
-                    glDepthMask(GL_TRUE);
-                    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-                    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
-                    glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
-
-                    /* Draw the scene reflected into color and depth buffers. */
-
-                    glFrontFace(GL_CW);
-                    glPushMatrix();
-                    {
-                        glScalef(+1.0f, -1.0f, +1.0f);
-
-                        game_draw_light();
-                        game_draw_back(pose,    -1, t);
-                        game_draw_fore(pose, U, -1, t);
-                    }
-                    glPopMatrix();
-                    glFrontFace(GL_CCW);
-                }
-                glDisable(GL_STENCIL_TEST);
-            }
-
-            /* Draw the scene normally. */
-
-            game_draw_light();
-            game_refl_all();
-            game_draw_back(pose,    +1, t);
-            game_draw_fore(pose, T, +1, t);
-        }
-        glPopMatrix();
-        config_pop_matrix();
-
-        /* Draw the fade overlay. */
-
-        fade_draw(fade_k);
-    }
-}
-
-/*---------------------------------------------------------------------------*/
-
-static void game_update_grav(float h[3], const float g[3])
-{
-    float x[3];
-    float y[3] = { 0.0f, 1.0f, 0.0f };
-    float z[3];
-    float X[16];
-    float Z[16];
-    float M[16];
-
-    /* Compute the gravity vector from the given world rotations. */
-
-    z[0] = fsinf(V_RAD(view_a));
-    z[1] = 0.0f;
-    z[2] = fcosf(V_RAD(view_a));
-
-    v_crs(x, y, z);
-    v_crs(z, x, y);
-    v_nrm(x, x);
-    v_nrm(z, z);
-
-    m_rot (Z, z, V_RAD(game_rz));
-    m_rot (X, x, V_RAD(game_rx));
-    m_mult(M, Z, X);
-    m_vxfm(h, M, g);
-}
-
-static void game_update_view(float dt)
-{
-    float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
-    float da = input_get_r() * dt * 90.0f;
-    float k;
-
-    float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
-
-    view_a += da;
-
-    /* Center the view about the ball. */
-
-    v_cpy(view_c, file.uv->p);
-    v_inv(view_v, file.uv->v);
-
-    view_e[2][0] = fsinf(V_RAD(view_a));
-    view_e[2][1] = 0.0;
-    view_e[2][2] = fcosf(V_RAD(view_a));
-
-    switch (input_get_c())
-    {
-    case 1: /* Camera 1: Viewpoint chases the ball position. */
-
-        v_sub(view_e[2], view_p, view_c);
-
-        break;
-
-    case 2: /* Camera 2: View vector is given by view angle. */
-
-        break;
-
-    default: /* Default: View vector approaches the ball velocity vector. */
-
-        v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
-
-        break;
-    }
-
-    /* Orthonormalize the new view reference frame. */
-
-    v_crs(view_e[0], view_e[1], view_e[2]);
-    v_crs(view_e[2], view_e[0], view_e[1]);
-    v_nrm(view_e[0], view_e[0]);
-    v_nrm(view_e[2], view_e[2]);
-
-    /* Compute the new view position. */
-
-    k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
-
-    view_k = view_k + (k - view_k) * dt;
-
-    if (view_k < 0.5) view_k = 0.5;
-
-    v_scl(v,    view_e[1], view_dp * view_k);
-    v_mad(v, v, view_e[2], view_dz * view_k);
-    m_rot(M, Y, V_RAD(da));
-    m_vxfm(view_p, M, v);
-    v_add(view_p, view_p, file.uv->p);
-
-    /* Compute the new view center. */
-
-    v_cpy(view_c, file.uv->p);
-    v_mad(view_c, view_c, view_e[1], dc);
-
-    /* Note the current view angle. */
-
-    view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
-}
-
-static void game_update_time(float dt, int b)
-{
-    if (goal_e && goal_k < 1.0f)
-        goal_k += dt;
-
-   /* The ticking clock. */
-
-    if (b && timer_down)
-    {
-        if (timer < 600.f)
-            timer -= dt;
-        if (timer < 0.f)
-            timer = 0.f;
-    }
-    else if (b)
-    {
-        timer += dt;
-    }
-}
-
-static int game_update_state(int bt)
-{
-    struct s_file *fp = &file;
-    struct s_goal *zp;
-    struct s_item *hp;
-
-    float p[3];
-    float c[3];
-
-    /* Test for an item. */
-
-    if (bt && (hp = sol_item_test(fp, p, ITEM_RADIUS)))
-    {
-        item_color(hp, c);
-        part_burst(p, c);
-
-        grow_init(fp, hp->t);
-
-        if (hp->t == ITEM_COIN)
-            coins += hp->n;
-
-        audio_play(AUD_COIN, 1.f);
-
-        /* Discard item. */
-
-        hp->t = ITEM_NONE;
-    }
-
-    /* Test for a switch. */
-
-    if (sol_swch_test(fp, 0))
-        audio_play(AUD_SWITCH, 1.f);
-
-    /* Test for a jump. */
-
-    if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
-    {
-        jump_b  = 1;
-        jump_e  = 0;
-        jump_dt = 0.f;
-
-        audio_play(AUD_JUMP, 1.f);
-    }
-    if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
-        jump_e = 1;
-
-    /* Test for a goal. */
-
-    if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
-    {
-        audio_play(AUD_GOAL, 1.0f);
-        return GAME_GOAL;
-    }
-
-    /* Test for time-out. */
-
-    if (bt && timer_down && timer <= 0.f)
-    {
-        audio_play(AUD_TIME, 1.0f);
-        return GAME_TIME;
-    }
-
-    /* Test for fall-out. */
-
-    if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
-    {
-        audio_play(AUD_FALL, 1.0f);
-        return GAME_FALL;
-    }
-
-    return GAME_NONE;
-}
-
-int game_step(const float g[3], float dt, int bt)
-{
-    if (game_state)
-    {
-        struct s_file *fp = &file;
-
-        float h[3];
-
-        /* Smooth jittery or discontinuous input. */
-
-        game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
-        game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
-
-        grow_step(fp, dt);
-
-        game_update_grav(h, g);
-        part_step(h, dt);
-
-        if (jump_b)
-        {
-            jump_dt += dt;
-
-            /* Handle a jump. */
-
-            if (0.5f < jump_dt)
-            {
-                fp->uv[0].p[0] = jump_p[0];
-                fp->uv[0].p[1] = jump_p[1];
-                fp->uv[0].p[2] = jump_p[2];
-            }
-            if (1.0f < jump_dt)
-                jump_b = 0;
-        }
-        else
-        {
-            /* Run the sim. */
-
-            float b = sol_step(fp, h, dt, 0, NULL);
-
-            /* Mix the sound of a ball bounce. */
-
-            if (b > 0.5f)
-            {
-                float k = (b - 0.5f) * 2.0f;
-
-                if (got_orig)
-                {
-                    if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
-                    else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
-                    else                            audio_play(AUD_BUMPM, k);
-                }
-                else audio_play(AUD_BUMPM, k);
-            }
-        }
-
-        game_step_fade(dt);
-        game_update_view(dt);
-        game_update_time(dt, bt);
-
-        return game_update_state(bt);
-    }
-    return GAME_NONE;
-}
-
-/*---------------------------------------------------------------------------*/
-
-void game_set_goal(void)
-{
-    audio_play(AUD_SWITCH, 1.0f);
-    goal_e = 1;
-}
-
-void game_clr_goal(void)
-{
-    goal_e = 0;
-}
-
-/*---------------------------------------------------------------------------*/
-
-void game_set_x(int k)
-{
-    input_set_x(-ANGLE_BOUND * k / JOY_MAX);
-}
-
-void game_set_z(int k)
-{
-    input_set_z(+ANGLE_BOUND * k / JOY_MAX);
-}
-
-void game_set_ang(int x, int z)
-{
-    input_set_x(x);
-    input_set_z(z);
-}
-
-void game_set_pos(int x, int y)
-{
-    input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
-    input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
-}
-
-void game_set_cam(int c)
-{
-    input_set_c(c);
-}
-
-void game_set_rot(float r)
-{
-    input_set_r(r);
-}
-
-/*---------------------------------------------------------------------------*/
-
-void game_set_fly(float k)
-{
-    struct s_file *fp = &file;
-
-    float  x[3] = { 1.f, 0.f, 0.f };
-    float  y[3] = { 0.f, 1.f, 0.f };
-    float  z[3] = { 0.f, 0.f, 1.f };
-    float c0[3] = { 0.f, 0.f, 0.f };
-    float p0[3] = { 0.f, 0.f, 0.f };
-    float c1[3] = { 0.f, 0.f, 0.f };
-    float p1[3] = { 0.f, 0.f, 0.f };
-    float  v[3];
-
-    z[0] = fsinf(V_RAD(view_a));
-    z[2] = fcosf(V_RAD(view_a));
-
-    v_cpy(view_e[0], x);
-    v_cpy(view_e[1], y);
-    v_cpy(view_e[2], z);
-
-    /* k = 0.0 view is at the ball. */
-
-    if (fp->uc > 0)
-    {
-        v_cpy(c0, fp->uv[0].p);
-        v_cpy(p0, fp->uv[0].p);
-    }
-
-    v_mad(p0, p0, y, view_dp);
-    v_mad(p0, p0, z, view_dz);
-    v_mad(c0, c0, y, view_dc);
-
-    /* k = +1.0 view is s_view 0 */
-
-    if (k >= 0 && fp->wc > 0)
-    {
-        v_cpy(p1, fp->wv[0].p);
-        v_cpy(c1, fp->wv[0].q);
-    }
-
-    /* k = -1.0 view is s_view 1 */
-
-    if (k <= 0 && fp->wc > 1)
-    {
-        v_cpy(p1, fp->wv[1].p);
-        v_cpy(c1, fp->wv[1].q);
-    }
-
-    /* Interpolate the views. */
-
-    v_sub(v, p1, p0);
-    v_mad(view_p, p0, v, k * k);
-
-    v_sub(v, c1, c0);
-    v_mad(view_c, c0, v, k * k);
-
-    /* Orthonormalize the view basis. */
-
-    v_sub(view_e[2], view_p, view_c);
-    v_crs(view_e[0], view_e[1], view_e[2]);
-    v_crs(view_e[2], view_e[0], view_e[1]);
-    v_nrm(view_e[0], view_e[0]);
-    v_nrm(view_e[2], view_e[2]);
-}
-
-void game_look(float phi, float theta)
-{
-    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));
-}
-
-/*---------------------------------------------------------------------------*/
-
-void game_kill_fade(void)
-{
-    fade_k = 0.0f;
-    fade_d = 0.0f;
-}
-
-void game_step_fade(float dt)
-{
-    if ((fade_k < 1.0f && fade_d > 0.0f) ||
-        (fade_k > 0.0f && fade_d < 0.0f))
-        fade_k += fade_d * dt;
-
-    if (fade_k < 0.0f)
-    {
-        fade_k = 0.0f;
-        fade_d = 0.0f;
-    }
-    if (fade_k > 1.0f)
-    {
-        fade_k = 1.0f;
-        fade_d = 0.0f;
-    }
-}
-
-void game_fade(float d)
-{
-    fade_d = d;
-}
-
-/*---------------------------------------------------------------------------*/
-
-const char *status_to_str(int s)
-{
-    switch (s)
-    {
-    case GAME_NONE:    return _("Aborted");
-    case GAME_TIME:    return _("Time-out");
-    case GAME_GOAL:    return _("Success");
-    case GAME_FALL:    return _("Fall-out");
-    default:           return _("Unknown");
-    }
-}
-
-/*---------------------------------------------------------------------------*/
diff --git a/ball/game.h b/ball/game.h
deleted file mode 100644 (file)
index 88b15ff..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-#ifndef GAME_H
-#define GAME_H
-
-#include <stdio.h>
-
-#include "lang.h"
-
-/*---------------------------------------------------------------------------*/
-
-#define AUD_MENU    "snd/menu.ogg"
-#define AUD_START _("snd/select.ogg")
-#define AUD_READY _("snd/ready.ogg")
-#define AUD_SET   _("snd/set.ogg")
-#define AUD_GO    _("snd/go.ogg")
-#define AUD_BALL    "snd/ball.ogg"
-#define AUD_BUMPS   "snd/bumplil.ogg"
-#define AUD_BUMPM   "snd/bump.ogg"
-#define AUD_BUMPL   "snd/bumpbig.ogg"
-#define AUD_COIN    "snd/coin.ogg"
-#define AUD_TICK    "snd/tick.ogg"
-#define AUD_TOCK    "snd/tock.ogg"
-#define AUD_SWITCH  "snd/switch.ogg"
-#define AUD_JUMP    "snd/jump.ogg"
-#define AUD_GOAL    "snd/goal.ogg"
-#define AUD_SCORE _("snd/record.ogg")
-#define AUD_FALL  _("snd/fall.ogg")
-#define AUD_TIME  _("snd/time.ogg")
-#define AUD_OVER  _("snd/over.ogg")
-#define AUD_GROW    "snd/grow.ogg"
-#define AUD_SHRINK  "snd/shrink.ogg"
-
-/*---------------------------------------------------------------------------*/
-
-#define RESPONSE    0.05f              /* Input smoothing time               */
-#define ANGLE_BOUND 20.0f              /* Angle limit of floor tilting       */
-#define VIEWR_BOUND 10.0f              /* Maximum rate of view rotation      */
-
-/*---------------------------------------------------------------------------*/
-
-int   game_init(const char *, int, int);
-void  game_free(void);
-
-int   curr_clock(void);
-int   curr_coins(void);
-
-void  game_draw(int, float);
-int   game_step(const float[3], float, int);
-
-void  game_set_goal(void);
-void  game_clr_goal(void);
-
-void  game_set_ang(int, int);
-void  game_set_pos(int, int);
-void  game_set_x  (int);
-void  game_set_z  (int);
-void  game_set_cam(int);
-void  game_set_rot(float);
-void  game_set_fly(float);
-
-void  game_look(float, float);
-
-void  game_kill_fade(void);
-void  game_step_fade(float);
-void  game_fade(float);
-
-/*---------------------------------------------------------------------------*/
-
-int input_put(FILE *);
-int input_get(FILE *);
-
-/*---------------------------------------------------------------------------*/
-
-#define GAME_NONE 0
-#define GAME_TIME 1
-#define GAME_GOAL 2
-#define GAME_FALL 3
-
-const char *status_to_str(int);
-
-/*---------------------------------------------------------------------------*/
-
-#endif
diff --git a/ball/game_client.c b/ball/game_client.c
new file mode 100644 (file)
index 0000000..3ca8d67
--- /dev/null
@@ -0,0 +1,1008 @@
+/*
+ * Copyright (C) 2003 Robert Kooima
+ *
+ * NEVERBALL is  free software; you can redistribute  it and/or modify
+ * it under the  terms of the GNU General  Public License as published
+ * by the Free  Software Foundation; either version 2  of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
+ * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
+ * General Public License for more details.
+ */
+
+#include <SDL.h>
+#include <math.h>
+#include <assert.h>
+
+#include "glext.h"
+#include "vec3.h"
+#include "geom.h"
+#include "item.h"
+#include "back.h"
+#include "part.h"
+#include "ball.h"
+#include "image.h"
+#include "audio.h"
+#include "solid_gl.h"
+#include "config.h"
+
+#include "game_client.h"
+#include "game_common.h"
+#include "game_proxy.h"
+
+#include "cmd.h"
+
+/*---------------------------------------------------------------------------*/
+
+static int client_state = 0;
+
+static struct s_file file;
+static struct s_file back;
+
+static int   reflective;                /* Reflective geometry used?         */
+
+static float timer      = 0.f;          /* Clock time                        */
+
+static int status = GAME_NONE;          /* Outcome of the game               */
+
+static float game_rx;                   /* Floor rotation about X axis       */
+static float game_rz;                   /* Floor rotation about Z axis       */
+
+static float view_a;                    /* Ideal view rotation about Y axis  */
+static float view_fov;                  /* Field of view                     */
+
+static float view_c[3];                 /* Current view center               */
+static float view_p[3];                 /* Current view position             */
+static float view_e[3][3];              /* Current view reference frame      */
+
+static int   coins  = 0;                /* Collected coins                   */
+static int   goal_e = 0;                /* Goal enabled flag                 */
+static float goal_k = 0;                /* Goal animation                    */
+
+static int   jump_e = 1;                /* Jumping enabled flag              */
+static int   jump_b = 0;                /* Jump-in-progress flag             */
+static float jump_dt;                   /* Jump duration                     */
+
+static float fade_k = 0.0;              /* Fade in/out level                 */
+static float fade_d = 0.0;              /* Fade in/out direction             */
+
+static int ups;                         /* Updates per second                */
+static int first_update;                /* First update flag                 */
+static int curr_ball;                   /* Current ball index                */
+
+/*---------------------------------------------------------------------------*/
+
+static void game_run_cmd(const union cmd *cmd)
+{
+    static const float gup[] = { 0.0f, +9.8f, 0.0f };
+    static const float gdn[] = { 0.0f, -9.8f, 0.0f };
+
+    float f[3];
+
+    if (client_state)
+    {
+        struct s_item *hp;
+        struct s_ball *up;
+
+        float dt;
+        int i;
+
+        switch (cmd->type)
+        {
+        case CMD_END_OF_UPDATE:
+            if (first_update)
+            {
+                first_update = 0;
+                break;
+            }
+
+            /* Compute gravity for particle effects. */
+
+            if (status == GAME_GOAL)
+                game_comp_grav(f, gup, view_a, game_rx, game_rz);
+            else
+                game_comp_grav(f, gdn, view_a, game_rx, game_rz);
+
+            /* Step particle, goal and jump effects. */
+
+            if (ups > 0)
+            {
+                dt = 1.0f / (float) ups;
+
+                if (goal_e && goal_k < 1.0f)
+                    goal_k += dt;
+
+                if (jump_b)
+                {
+                    jump_dt += dt;
+
+                    if (1.0f < jump_dt)
+                        jump_b = 0;
+                }
+
+                part_step(f, dt);
+            }
+
+            break;
+
+        case CMD_MAKE_BALL:
+            /* Allocate a new ball and mark it as the current ball. */
+
+            if ((up = realloc(file.uv, sizeof (*up) * (file.uc + 1))))
+            {
+                file.uv = up;
+                curr_ball = file.uc;
+                file.uc++;
+            }
+            break;
+
+        case CMD_MAKE_ITEM:
+            /* Allocate and initialise a new item. */
+
+            if ((hp = realloc(file.hv, sizeof (*hp) * (file.hc + 1))))
+            {
+                struct s_item h;
+
+                v_cpy(h.p, cmd->mkitem.p);
+
+                h.t = cmd->mkitem.t;
+                h.n = cmd->mkitem.n;
+
+                file.hv          = hp;
+                file.hv[file.hc] = h;
+                file.hc++;
+            }
+
+            break;
+
+        case CMD_PICK_ITEM:
+            /* Set up particle effects and discard the item. */
+
+            assert(cmd->pkitem.hi < file.hc);
+
+            hp = &file.hv[cmd->pkitem.hi];
+
+            item_color(hp, f);
+            part_burst(hp->p, f);
+
+            hp->t = ITEM_NONE;
+
+            break;
+
+        case CMD_ROTATE:
+            game_rx = cmd->rotate.x;
+            game_rz = cmd->rotate.z;
+            break;
+
+        case CMD_SOUND:
+            /* Play the sound, then free its file name. */
+
+            if (cmd->sound.n)
+            {
+                audio_play(cmd->sound.n, cmd->sound.a);
+
+                /*
+                 * FIXME Command memory management should be done
+                 * elsewhere and done properly.
+                 */
+
+                free(cmd->sound.n);
+            }
+            break;
+
+        case CMD_TIMER:
+            timer = cmd->timer.t;
+            break;
+
+        case CMD_STATUS:
+            status = cmd->status.t;
+            break;
+
+        case CMD_COINS:
+            coins = cmd->coins.n;
+            break;
+
+        case CMD_JUMP_ENTER:
+            jump_b  = 1;
+            jump_e  = 0;
+            jump_dt = 0.0f;
+            break;
+
+        case CMD_JUMP_EXIT:
+            jump_e = 1;
+            break;
+
+        case CMD_BODY_PATH:
+            file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
+            break;
+
+        case CMD_BODY_TIME:
+            file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
+            break;
+
+        case CMD_GOAL_OPEN:
+            /*
+             * Enable the goal and make sure it's fully visible if
+             * this is the first update.
+             */
+
+            if (!goal_e)
+            {
+                goal_e = 1;
+                goal_k = first_update ? 1.0f : 0.0f;
+            }
+            break;
+
+        case CMD_SWCH_ENTER:
+            file.xv[cmd->swchenter.xi].e = 1;
+            break;
+
+        case CMD_SWCH_TOGGLE:
+            file.xv[cmd->swchtoggle.xi].f = !file.xv[cmd->swchtoggle.xi].f;
+            break;
+
+        case CMD_SWCH_EXIT:
+            file.xv[cmd->swchexit.xi].e = 0;
+            break;
+
+        case CMD_UPDATES_PER_SECOND:
+            ups = cmd->ups.n;
+            break;
+
+        case CMD_BALL_RADIUS:
+            file.uv[curr_ball].r = cmd->ballradius.r;
+            break;
+
+        case CMD_CLEAR_ITEMS:
+            if (file.hv)
+            {
+                free(file.hv);
+                file.hv = NULL;
+            }
+            file.hc = 0;
+            break;
+
+        case CMD_CLEAR_BALLS:
+            if (file.uv)
+            {
+                free(file.uv);
+                file.uv = NULL;
+            }
+            file.uc = 0;
+            break;
+
+        case CMD_BALL_POSITION:
+            v_cpy(file.uv[curr_ball].p, cmd->ballpos.p);
+            break;
+
+        case CMD_BALL_BASIS:
+            v_cpy(file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
+            v_cpy(file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
+
+            v_crs(file.uv[curr_ball].e[2],
+                  file.uv[curr_ball].e[0],
+                  file.uv[curr_ball].e[1]);
+            break;
+
+        case CMD_BALL_PEND_BASIS:
+            v_cpy(file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
+            v_cpy(file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
+
+            v_crs(file.uv[curr_ball].E[2],
+                  file.uv[curr_ball].E[0],
+                  file.uv[curr_ball].E[1]);
+            break;
+
+        case CMD_VIEW_POSITION:
+            v_cpy(view_p, cmd->viewpos.p);
+            break;
+
+        case CMD_VIEW_CENTER:
+            v_cpy(view_c, cmd->viewcenter.c);
+            break;
+
+        case CMD_VIEW_BASIS:
+            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]);
+
+            view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
+
+            break;
+
+        case CMD_CURRENT_BALL:
+            curr_ball = cmd->currball.ui;
+            break;
+
+        case CMD_PATH_FLAG:
+            file.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 synchronised 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 < file.bc; i++)
+            {
+                struct s_body *bp = file.bv + i;
+                struct s_path *pp = file.pv + bp->pi;
+
+                if (bp->pi >= 0 && pp->f)
+                    bp->t += dt;
+            }
+            break;
+
+        case CMD_NONE:
+        case CMD_MAX:
+            break;
+        }
+    }
+}
+
+void game_client_step(FILE *demo_fp)
+{
+    union cmd *cmdp;
+
+    while ((cmdp = game_proxy_deq()))
+    {
+        /*
+         * Note: cmd_put is called first here because game_run_cmd
+         * frees the filename of CMD_SOUND.
+         */
+
+        if (demo_fp)
+            cmd_put(demo_fp, cmdp);
+
+        game_run_cmd(cmdp);
+
+        free(cmdp);
+    }
+}
+
+/*---------------------------------------------------------------------------*/
+
+int  game_client_init(const char *file_name)
+{
+    char *back_name = NULL, *grad_name = NULL;
+    int i;
+
+    coins  = 0;
+    status = GAME_NONE;
+
+    if (client_state)
+        game_client_free();
+
+    if (!sol_load_gl(&file, config_data(file_name),
+                     config_get_d(CONFIG_TEXTURES),
+                     config_get_d(CONFIG_SHADOW)))
+        return (client_state = 0);
+
+    reflective = sol_reflective(&file);
+
+    client_state = 1;
+
+    game_rx = 0.0f;
+    game_rz = 0.0f;
+
+    /* Initialize jump and goal states. */
+
+    jump_e = 1;
+    jump_b = 0;
+
+    goal_e = 0;
+    goal_k = 0.0f;
+
+    /* Initialise the level, background, particles, fade, and view. */
+
+    fade_k =  1.0f;
+    fade_d = -2.0f;
+
+    for (i = 0; i < file.dc; i++)
+    {
+        char *k = file.av + file.dv[i].ai;
+        char *v = file.av + file.dv[i].aj;
+
+        if (strcmp(k, "back") == 0) back_name = v;
+        if (strcmp(k, "grad") == 0) grad_name = v;
+    }
+
+    part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
+
+    view_fov = (float) config_get_d(CONFIG_VIEW_FOV);
+
+    ups          = 0;
+    first_update = 1;
+
+    back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
+    sol_load_gl(&back, config_data(back_name),
+                config_get_d(CONFIG_TEXTURES), 0);
+
+    return client_state;
+}
+
+void game_client_free(void)
+{
+    if (client_state)
+    {
+        game_proxy_clr();
+        sol_free_gl(&file);
+        sol_free_gl(&back);
+        back_free();
+    }
+    client_state = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+int curr_clock(void)
+{
+    return (int) (timer * 100.f);
+}
+
+int curr_coins(void)
+{
+    return coins;
+}
+
+int curr_status(void)
+{
+    return status;
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void game_draw_balls(const struct s_file *fp,
+                            const float *bill_M, float t)
+{
+    float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+
+    float ball_M[16];
+    float pend_M[16];
+
+    m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
+    m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
+
+    glPushAttrib(GL_LIGHTING_BIT);
+    glPushMatrix();
+    {
+        glTranslatef(fp->uv[0].p[0],
+                     fp->uv[0].p[1] + BALL_FUDGE,
+                     fp->uv[0].p[2]);
+        glScalef(fp->uv[0].r,
+                 fp->uv[0].r,
+                 fp->uv[0].r);
+
+        glColor4fv(c);
+        ball_draw(ball_M, pend_M, bill_M, t);
+    }
+    glPopMatrix();
+    glPopAttrib();
+}
+
+static void game_draw_items(const struct s_file *fp, float t)
+{
+    float r = 360.f * t;
+    int hi;
+
+    glPushAttrib(GL_LIGHTING_BIT);
+    {
+        item_push(ITEM_COIN);
+        {
+            for (hi = 0; hi < fp->hc; hi++)
+
+                if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
+                {
+                    glPushMatrix();
+                    {
+                        glTranslatef(fp->hv[hi].p[0],
+                                     fp->hv[hi].p[1],
+                                     fp->hv[hi].p[2]);
+                        glRotatef(r, 0.0f, 1.0f, 0.0f);
+                        item_draw(&fp->hv[hi], r);
+                    }
+                    glPopMatrix();
+                }
+        }
+        item_pull();
+
+        item_push(ITEM_SHRINK);
+        {
+            for (hi = 0; hi < fp->hc; hi++)
+
+                if (fp->hv[hi].t == ITEM_SHRINK)
+                {
+                    glPushMatrix();
+                    {
+                        glTranslatef(fp->hv[hi].p[0],
+                                     fp->hv[hi].p[1],
+                                     fp->hv[hi].p[2]);
+                        glRotatef(r, 0.0f, 1.0f, 0.0f);
+                        item_draw(&fp->hv[hi], r);
+                    }
+                    glPopMatrix();
+                }
+        }
+        item_pull();
+
+        item_push(ITEM_GROW);
+        {
+            for (hi = 0; hi < fp->hc; hi++)
+
+                if (fp->hv[hi].t == ITEM_GROW)
+                {
+                    glPushMatrix();
+                    {
+                        glTranslatef(fp->hv[hi].p[0],
+                                     fp->hv[hi].p[1],
+                                     fp->hv[hi].p[2]);
+                        glRotatef(r, 0.0f, 1.0f, 0.0f);
+                        item_draw(&fp->hv[hi], r);
+                    }
+                    glPopMatrix();
+                }
+        }
+        item_pull();
+    }
+    glPopAttrib();
+}
+
+static void game_draw_goals(const struct s_file *fp, const float *M, float t)
+{
+    if (goal_e)
+    {
+        int zi;
+
+        /* Draw the goal particles. */
+
+        glEnable(GL_TEXTURE_2D);
+        {
+            for (zi = 0; zi < fp->zc; zi++)
+            {
+                glPushMatrix();
+                {
+                    glTranslatef(fp->zv[zi].p[0],
+                                 fp->zv[zi].p[1],
+                                 fp->zv[zi].p[2]);
+
+                    part_draw_goal(M, fp->zv[zi].r, goal_k, t);
+                }
+                glPopMatrix();
+            }
+        }
+        glDisable(GL_TEXTURE_2D);
+
+        /* Draw the goal column. */
+
+        for (zi = 0; zi < fp->zc; zi++)
+        {
+            glPushMatrix();
+            {
+                glTranslatef(fp->zv[zi].p[0],
+                             fp->zv[zi].p[1],
+                             fp->zv[zi].p[2]);
+
+                glScalef(fp->zv[zi].r,
+                         goal_k,
+                         fp->zv[zi].r);
+
+                goal_draw();
+            }
+            glPopMatrix();
+        }
+    }
+}
+
+static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
+{
+    int ji;
+
+    glEnable(GL_TEXTURE_2D);
+    {
+        for (ji = 0; ji < fp->jc; ji++)
+        {
+            glPushMatrix();
+            {
+                glTranslatef(fp->jv[ji].p[0],
+                             fp->jv[ji].p[1],
+                             fp->jv[ji].p[2]);
+
+                part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
+            }
+            glPopMatrix();
+        }
+    }
+    glDisable(GL_TEXTURE_2D);
+
+    for (ji = 0; ji < fp->jc; ji++)
+    {
+        glPushMatrix();
+        {
+            glTranslatef(fp->jv[ji].p[0],
+                         fp->jv[ji].p[1],
+                         fp->jv[ji].p[2]);
+            glScalef(fp->jv[ji].r,
+                     1.0f,
+                     fp->jv[ji].r);
+
+            jump_draw(!jump_e);
+        }
+        glPopMatrix();
+    }
+}
+
+static void game_draw_swchs(const struct s_file *fp)
+{
+    int xi;
+
+    for (xi = 0; xi < fp->xc; xi++)
+    {
+        if (fp->xv[xi].i)
+            continue;
+
+        glPushMatrix();
+        {
+            glTranslatef(fp->xv[xi].p[0],
+                         fp->xv[xi].p[1],
+                         fp->xv[xi].p[2]);
+            glScalef(fp->xv[xi].r,
+                     1.0f,
+                     fp->xv[xi].r);
+
+            swch_draw(fp->xv[xi].f, fp->xv[xi].e);
+        }
+        glPopMatrix();
+    }
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void game_draw_tilt(int d)
+{
+    const float *ball_p = file.uv->p;
+
+    /* Rotate the environment about the position of the ball. */
+
+    glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
+    glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
+    glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
+    glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
+}
+
+static void game_refl_all(void)
+{
+    glPushMatrix();
+    {
+        game_draw_tilt(1);
+
+        /* Draw the floor. */
+
+        sol_refl(&file);
+    }
+    glPopMatrix();
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void game_draw_light(void)
+{
+    const float light_p[2][4] = {
+        { -8.0f, +32.0f, -8.0f, 0.0f },
+        { +8.0f, +32.0f, +8.0f, 0.0f },
+    };
+    const float light_c[2][4] = {
+        { 1.0f, 0.8f, 0.8f, 1.0f },
+        { 0.8f, 1.0f, 0.8f, 1.0f },
+    };
+
+    /* Configure the lighting. */
+
+    glEnable(GL_LIGHT0);
+    glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
+    glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_c[0]);
+    glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
+
+    glEnable(GL_LIGHT1);
+    glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
+    glLightfv(GL_LIGHT1, GL_DIFFUSE,  light_c[1]);
+    glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
+}
+
+static void game_draw_back(int pose, int d, float t)
+{
+    glPushMatrix();
+    {
+        if (d < 0)
+        {
+            glRotatef(game_rz * 2, view_e[2][0], view_e[2][1], view_e[2][2]);
+            glRotatef(game_rx * 2, view_e[0][0], view_e[0][1], view_e[0][2]);
+        }
+
+        glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
+
+        if (config_get_d(CONFIG_BACKGROUND))
+        {
+            /* Draw all background layers back to front. */
+
+            sol_back(&back, BACK_DIST, FAR_DIST,  t);
+            back_draw(0);
+            sol_back(&back,         0, BACK_DIST, t);
+        }
+        else back_draw(0);
+    }
+    glPopMatrix();
+}
+
+static void game_clip_refl(int d)
+{
+    /* Fudge to eliminate the floor from reflection. */
+
+    GLdouble e[4], k = -0.00001;
+
+    e[0] = 0;
+    e[1] = 1;
+    e[2] = 0;
+    e[3] = k;
+
+    glClipPlane(GL_CLIP_PLANE0, e);
+}
+
+static void game_clip_ball(int d, const float *p)
+{
+    GLdouble r, c[3], pz[4], nz[4];
+
+    /* Compute the plane giving the front of the ball, as seen from view_p. */
+
+    c[0] = p[0];
+    c[1] = p[1] * d;
+    c[2] = p[2];
+
+    pz[0] = view_p[0] - c[0];
+    pz[1] = view_p[1] - c[1];
+    pz[2] = view_p[2] - c[2];
+
+    r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
+
+    pz[0] /= r;
+    pz[1] /= r;
+    pz[2] /= r;
+    pz[3] = -(pz[0] * c[0] +
+              pz[1] * c[1] +
+              pz[2] * c[2]);
+
+    /* Find the plane giving the back of the ball, as seen from view_p. */
+
+    nz[0] = -pz[0];
+    nz[1] = -pz[1];
+    nz[2] = -pz[2];
+    nz[3] = -pz[3];
+
+    /* Reflect these planes as necessary, and store them in the GL state. */
+
+    pz[1] *= d;
+    nz[1] *= d;
+
+    glClipPlane(GL_CLIP_PLANE1, nz);
+    glClipPlane(GL_CLIP_PLANE2, pz);
+}
+
+static void game_draw_fore(int pose, const float *M, int d, float t)
+{
+    const float *ball_p = file.uv->p;
+    const float  ball_r = file.uv->r;
+
+    glPushMatrix();
+    {
+        /* Rotate the environment about the position of the ball. */
+
+        game_draw_tilt(d);
+
+        /* Compute clipping planes for reflection and ball facing. */
+
+        game_clip_refl(d);
+        game_clip_ball(d, ball_p);
+
+        if (d < 0)
+            glEnable(GL_CLIP_PLANE0);
+
+        if (pose)
+            sol_draw(&file, 0, 1);
+        else
+        {
+            /* Draw the coins. */
+
+            game_draw_items(&file, t);
+
+            /* Draw the floor. */
+
+            sol_draw(&file, 0, 1);
+
+            /* Draw the ball shadow. */
+
+            if (d > 0 && config_get_d(CONFIG_SHADOW))
+            {
+                shad_draw_set(ball_p, ball_r);
+                sol_shad(&file);
+                shad_draw_clr();
+            }
+
+            /* Draw the ball. */
+
+            game_draw_balls(&file, M, t);
+        }
+
+        /* Draw the particles and light columns. */
+
+        glEnable(GL_COLOR_MATERIAL);
+        glDisable(GL_LIGHTING);
+        glDepthMask(GL_FALSE);
+        {
+            glColor3f(1.0f, 1.0f, 1.0f);
+
+            sol_bill(&file, M, t);
+            part_draw_coin(M, t);
+
+            glDisable(GL_TEXTURE_2D);
+            {
+                game_draw_goals(&file, M, t);
+                game_draw_jumps(&file, M, t);
+                game_draw_swchs(&file);
+            }
+            glEnable(GL_TEXTURE_2D);
+
+            glColor3f(1.0f, 1.0f, 1.0f);
+        }
+        glDepthMask(GL_TRUE);
+        glEnable(GL_LIGHTING);
+        glDisable(GL_COLOR_MATERIAL);
+
+        if (d < 0)
+            glDisable(GL_CLIP_PLANE0);
+    }
+    glPopMatrix();
+}
+
+void game_draw(int pose, float t)
+{
+    float fov = view_fov;
+
+    if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
+
+    if (client_state)
+    {
+        config_push_persp(fov, 0.1f, FAR_DIST);
+        glPushMatrix();
+        {
+            float T[16], U[16], M[16], v[3];
+
+            /* Compute direct and reflected view bases. */
+
+            v[0] = +view_p[0];
+            v[1] = -view_p[1];
+            v[2] = +view_p[2];
+
+            m_view(T, view_c, view_p, view_e[1]);
+            m_view(U, view_c, v,      view_e[1]);
+
+            m_xps(M, T);
+
+            /* Apply current the view. */
+
+            v_sub(v, view_c, view_p);
+
+            glTranslatef(0.f, 0.f, -v_len(v));
+            glMultMatrixf(M);
+            glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
+
+            if (reflective && config_get_d(CONFIG_REFLECTION))
+            {
+                glEnable(GL_STENCIL_TEST);
+                {
+                    /* Draw the mirrors only into the stencil buffer. */
+
+                    glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
+                    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
+                    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+                    glDepthMask(GL_FALSE);
+
+                    game_refl_all();
+
+                    glDepthMask(GL_TRUE);
+                    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+                    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+                    glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
+
+                    /* Draw the scene reflected into color and depth buffers. */
+
+                    glFrontFace(GL_CW);
+                    glPushMatrix();
+                    {
+                        glScalef(+1.0f, -1.0f, +1.0f);
+
+                        game_draw_light();
+                        game_draw_back(pose,    -1, t);
+                        game_draw_fore(pose, U, -1, t);
+                    }
+                    glPopMatrix();
+                    glFrontFace(GL_CCW);
+                }
+                glDisable(GL_STENCIL_TEST);
+            }
+
+            /* Draw the scene normally. */
+
+            game_draw_light();
+            game_refl_all();
+            game_draw_back(pose,    +1, t);
+            game_draw_fore(pose, T, +1, t);
+        }
+        glPopMatrix();
+        config_pop_matrix();
+
+        /* Draw the fade overlay. */
+
+        fade_draw(fade_k);
+    }
+}
+
+/*---------------------------------------------------------------------------*/
+
+void game_look(float phi, float theta)
+{
+    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));
+}
+
+/*---------------------------------------------------------------------------*/
+
+void game_kill_fade(void)
+{
+    fade_k = 0.0f;
+    fade_d = 0.0f;
+}
+
+void game_step_fade(float dt)
+{
+    if ((fade_k < 1.0f && fade_d > 0.0f) ||
+        (fade_k > 0.0f && fade_d < 0.0f))
+        fade_k += fade_d * dt;
+
+    if (fade_k < 0.0f)
+    {
+        fade_k = 0.0f;
+        fade_d = 0.0f;
+    }
+    if (fade_k > 1.0f)
+    {
+        fade_k = 1.0f;
+        fade_d = 0.0f;
+    }
+}
+
+void game_fade(float d)
+{
+    fade_d = d;
+}
+
+/*---------------------------------------------------------------------------*/
+
+const struct s_file *game_client_file(void)
+{
+    return &file;
+}
+
+/*---------------------------------------------------------------------------*/
diff --git a/ball/game_client.h b/ball/game_client.h
new file mode 100644 (file)
index 0000000..7b563cd
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef GAME_CLIENT_H
+#define GAME_CLIENT_H
+
+#include <stdio.h>
+#include "solid.h"
+
+/*---------------------------------------------------------------------------*/
+
+int   game_client_init(const char *);
+void  game_client_free(void);
+void  game_client_step(FILE *);
+
+int   curr_clock(void);
+int   curr_coins(void);
+int   curr_status(void);
+
+void  game_draw(int, float);
+
+void  game_look(float, float);
+
+void  game_kill_fade(void);
+void  game_step_fade(float);
+void  game_fade(float);
+
+/*---------------------------------------------------------------------------*/
+
+const struct s_file *game_client_file(void);
+
+/*---------------------------------------------------------------------------*/
+
+#endif
diff --git a/ball/game_common.c b/ball/game_common.c
new file mode 100644 (file)
index 0000000..0588169
--- /dev/null
@@ -0,0 +1,43 @@
+#include "game_common.h"
+#include "vec3.h"
+
+const char *status_to_str(int s)
+{
+    switch (s)
+    {
+    case GAME_NONE:    return _("Aborted");
+    case GAME_TIME:    return _("Time-out");
+    case GAME_GOAL:    return _("Success");
+    case GAME_FALL:    return _("Fall-out");
+    default:           return _("Unknown");
+    }
+}
+
+void game_comp_grav(float h[3], const float g[3],
+                    float view_a,
+                    float game_rx,
+                    float game_rz)
+{
+    float x[3];
+    float y[3] = { 0.0f, 1.0f, 0.0f };
+    float z[3];
+    float X[16];
+    float Z[16];
+    float M[16];
+
+    /* Compute the gravity vector from the given world rotations. */
+
+    z[0] = fsinf(V_RAD(view_a));
+    z[1] = 0.0f;
+    z[2] = fcosf(V_RAD(view_a));
+
+    v_crs(x, y, z);
+    v_crs(z, x, y);
+    v_nrm(x, x);
+    v_nrm(z, z);
+
+    m_rot (Z, z, V_RAD(game_rz));
+    m_rot (X, x, V_RAD(game_rx));
+    m_mult(M, Z, X);
+    m_vxfm(h, M, g);
+}
diff --git a/ball/game_common.h b/ball/game_common.h
new file mode 100644 (file)
index 0000000..57fb7d9
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef GAME_COMMON_H
+#define GAME_COMMON_H
+
+#include "lang.h"
+
+/*---------------------------------------------------------------------------*/
+
+#define AUD_MENU    "snd/menu.ogg"
+#define AUD_START _("snd/select.ogg")
+#define AUD_READY _("snd/ready.ogg")
+#define AUD_SET   _("snd/set.ogg")
+#define AUD_GO    _("snd/go.ogg")
+#define AUD_BALL    "snd/ball.ogg"
+#define AUD_BUMPS   "snd/bumplil.ogg"
+#define AUD_BUMPM   "snd/bump.ogg"
+#define AUD_BUMPL   "snd/bumpbig.ogg"
+#define AUD_COIN    "snd/coin.ogg"
+#define AUD_TICK    "snd/tick.ogg"
+#define AUD_TOCK    "snd/tock.ogg"
+#define AUD_SWITCH  "snd/switch.ogg"
+#define AUD_JUMP    "snd/jump.ogg"
+#define AUD_GOAL    "snd/goal.ogg"
+#define AUD_SCORE _("snd/record.ogg")
+#define AUD_FALL  _("snd/fall.ogg")
+#define AUD_TIME  _("snd/time.ogg")
+#define AUD_OVER  _("snd/over.ogg")
+#define AUD_GROW    "snd/grow.ogg"
+#define AUD_SHRINK  "snd/shrink.ogg"
+
+/*---------------------------------------------------------------------------*/
+
+#define GAME_NONE 0
+#define GAME_TIME 1
+#define GAME_GOAL 2
+#define GAME_FALL 3
+
+const char *status_to_str(int);
+
+/*---------------------------------------------------------------------------*/
+
+void game_comp_grav(float h[3], const float g[3],
+                    float view_a,
+                    float game_rx,
+                    float game_rz);
+
+/*---------------------------------------------------------------------------*/
+
+#endif
diff --git a/ball/game_proxy.c b/ball/game_proxy.c
new file mode 100644 (file)
index 0000000..285339b
--- /dev/null
@@ -0,0 +1,54 @@
+#include <stdlib.h>
+
+#include "game_proxy.h"
+#include "queue.h"
+#include "cmd.h"
+
+static Queue cmd_queue;
+
+/*
+ * Enqueue SRC in the game's command queue.
+ */
+void game_proxy_enq(const union cmd *src)
+{
+    union cmd *dst;
+
+    /*
+     * Create the queue.  This is done only once during the life time
+     * of the program.  For simplicity's sake, the queue is never
+     * destroyed.
+     */
+
+    if (!cmd_queue)
+        cmd_queue = queue_new();
+
+    /*
+     * Add a copy of the command to the end of the queue.
+     */
+
+    if ((dst = malloc(sizeof (*dst))))
+    {
+        *dst = *src;
+        queue_enq(cmd_queue, dst);
+    }
+}
+
+/*
+ * Dequeue and return the head element in the game's command queue.
+ * The element must be freed after use.
+ */
+union cmd *game_proxy_deq(void)
+{
+    return cmd_queue ? queue_deq(cmd_queue) : NULL;
+}
+
+/*
+ * Clear the entire queue.
+ */
+void game_proxy_clr(void)
+{
+    union cmd *cmdp;
+
+    while ((cmdp = game_proxy_deq()))
+        free(cmdp);
+}
diff --git a/ball/game_proxy.h b/ball/game_proxy.h
new file mode 100644 (file)
index 0000000..4f3d30d
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef GAME_PROXY_H
+#define GAME_PROXY_H
+
+#include "cmd.h"
+
+void       game_proxy_enq(const union cmd *);
+union cmd *game_proxy_deq(void);
+void       game_proxy_clr(void);
+
+#endif
diff --git a/ball/game_server.c b/ball/game_server.c
new file mode 100644 (file)
index 0000000..2fa1e17
--- /dev/null
@@ -0,0 +1,913 @@
+/*
+ * Copyright (C) 2003 Robert Kooima
+ *
+ * NEVERBALL is  free software; you can redistribute  it and/or modify
+ * it under the  terms of the GNU General  Public License as published
+ * by the Free  Software Foundation; either version 2  of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
+ * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
+ * General Public License for more details.
+ */
+
+#include <SDL.h>
+#include <math.h>
+#include <assert.h>
+
+#include "vec3.h"
+#include "item.h"
+#include "solid_phys.h"
+#include "config.h"
+#include "binary.h"
+#include "common.h"
+
+#include "game_common.h"
+#include "game_server.h"
+#include "game_proxy.h"
+
+#include "cmd.h"
+
+/*---------------------------------------------------------------------------*/
+
+static int server_state = 0;
+
+static struct s_file file;
+
+static float timer      = 0.f;          /* Clock time                        */
+static int   timer_down = 1;            /* Timer go up or down?              */
+
+static int status = GAME_NONE;          /* Outcome of the game               */
+
+static float game_rx;                   /* Floor rotation about X axis       */
+static float game_rz;                   /* Floor rotation about Z axis       */
+
+static float view_a;                    /* Ideal view rotation about Y axis  */
+static float view_dc;                   /* Ideal view distance above ball    */
+static float view_dp;                   /* Ideal view distance above ball    */
+static float view_dz;                   /* Ideal view distance behind ball   */
+
+static float view_c[3];                 /* Current view center               */
+static float view_v[3];                 /* Current view vector               */
+static float view_p[3];                 /* Current view position             */
+static float view_e[3][3];              /* Current view reference frame      */
+static float view_k;
+
+static int   coins  = 0;                /* Collected coins                   */
+static int   goal_e = 0;                /* Goal enabled flag                 */
+static float goal_k = 0;                /* Goal animation                    */
+static int   jump_e = 1;                /* Jumping enabled flag              */
+static int   jump_b = 0;                /* Jump-in-progress flag             */
+static float jump_dt;                   /* Jump duration                     */
+static float jump_p[3];                 /* Jump destination                  */
+
+/*---------------------------------------------------------------------------*/
+
+/*
+ * This is an abstraction of the game's input state.  All input is
+ * encapsulated here, and all references to the input by the game are
+ * made here.  TODO: This used to have the effect of homogenizing
+ * input for use in replay recording and playback, but it's not clear
+ * how relevant this approach is with the introduction of the command
+ * pipeline.
+ *
+ * x and z:
+ *     -32767 = -ANGLE_BOUND
+ *     +32767 = +ANGLE_BOUND
+ *
+ * r:
+ *     -32767 = -VIEWR_BOUND
+ *     +32767 = +VIEWR_BOUND
+ *
+ */
+
+struct input
+{
+    short x;
+    short z;
+    short r;
+    short c;
+};
+
+static struct input input_current;
+
+static void input_init(void)
+{
+    input_current.x = 0;
+    input_current.z = 0;
+    input_current.r = 0;
+    input_current.c = 0;
+}
+
+static void input_set_x(float x)
+{
+    if (x < -ANGLE_BOUND) x = -ANGLE_BOUND;
+    if (x >  ANGLE_BOUND) x =  ANGLE_BOUND;
+
+    input_current.x = (short) (32767.0f * x / ANGLE_BOUND);
+}
+
+static void input_set_z(float z)
+{
+    if (z < -ANGLE_BOUND) z = -ANGLE_BOUND;
+    if (z >  ANGLE_BOUND) z =  ANGLE_BOUND;
+
+    input_current.z = (short) (32767.0f * z / ANGLE_BOUND);
+}
+
+static void input_set_r(float r)
+{
+    if (r < -VIEWR_BOUND) r = -VIEWR_BOUND;
+    if (r >  VIEWR_BOUND) r =  VIEWR_BOUND;
+
+    input_current.r = (short) (32767.0f * r / VIEWR_BOUND);
+}
+
+static void input_set_c(int c)
+{
+    input_current.c = (short) c;
+}
+
+static float input_get_x(void)
+{
+    return ANGLE_BOUND * (float) input_current.x / 32767.0f;
+}
+
+static float input_get_z(void)
+{
+    return ANGLE_BOUND * (float) input_current.z / 32767.0f;
+}
+
+static float input_get_r(void)
+{
+    return VIEWR_BOUND * (float) input_current.r / 32767.0f;
+}
+
+static int input_get_c(void)
+{
+    return (int) input_current.c;
+}
+
+int input_put(FILE *fout)
+{
+    if (server_state)
+    {
+        put_short(fout, &input_current.x);
+        put_short(fout, &input_current.z);
+        put_short(fout, &input_current.r);
+        put_short(fout, &input_current.c);
+
+        return 1;
+    }
+    return 0;
+}
+
+int input_get(FILE *fin)
+{
+    if (server_state)
+    {
+        get_short(fin, &input_current.x);
+        get_short(fin, &input_current.z);
+        get_short(fin, &input_current.r);
+        get_short(fin, &input_current.c);
+
+        return (feof(fin) ? 0 : 1);
+    }
+    return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+/*
+ * Utility functions for preparing the "server" state and events for
+ * consumption by the "client".
+ */
+
+static union cmd cmd;
+
+static void game_cmd_eou(void)
+{
+    cmd.type = CMD_END_OF_UPDATE;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_ups(void)
+{
+    cmd.type  = CMD_UPDATES_PER_SECOND;
+    cmd.ups.n = UPS;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_sound(const char *filename, float a)
+{
+    cmd.type = CMD_SOUND;
+
+    cmd.sound.n = strdup(filename);
+    cmd.sound.a = a;
+
+    game_proxy_enq(&cmd);
+}
+
+#define audio_play(s, f) game_cmd_sound((s), (f))
+
+static void game_cmd_goalopen(void)
+{
+    cmd.type = CMD_GOAL_OPEN;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_updball(void)
+{
+    cmd.type = CMD_BALL_POSITION;
+    memcpy(cmd.ballpos.p, file.uv[0].p, sizeof (float) * 3);
+    game_proxy_enq(&cmd);
+
+    cmd.type = CMD_BALL_BASIS;
+    v_cpy(cmd.ballbasis.e[0], file.uv[0].e[0]);
+    v_cpy(cmd.ballbasis.e[1], file.uv[0].e[1]);
+    game_proxy_enq(&cmd);
+
+    cmd.type = CMD_BALL_PEND_BASIS;
+    v_cpy(cmd.ballpendbasis.E[0], file.uv[0].E[0]);
+    v_cpy(cmd.ballpendbasis.E[1], file.uv[0].E[1]);
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_updview(void)
+{
+    cmd.type = CMD_VIEW_POSITION;
+    memcpy(cmd.viewpos.p, view_p, sizeof (float) * 3);
+    game_proxy_enq(&cmd);
+
+    cmd.type = CMD_VIEW_CENTER;
+    memcpy(cmd.viewcenter.c, view_c, sizeof (float) * 3);
+    game_proxy_enq(&cmd);
+
+    cmd.type = CMD_VIEW_BASIS;
+    v_cpy(cmd.viewbasis.e[0], view_e[0]);
+    v_cpy(cmd.viewbasis.e[1], view_e[1]);
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_ballradius(void)
+{
+    cmd.type         = CMD_BALL_RADIUS;
+    cmd.ballradius.r = file.uv[0].r;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_init_balls(void)
+{
+    cmd.type = CMD_CLEAR_BALLS;
+    game_proxy_enq(&cmd);
+
+    cmd.type = CMD_MAKE_BALL;
+    game_proxy_enq(&cmd);
+
+    game_cmd_updball();
+    game_cmd_ballradius();
+}
+
+static void game_cmd_init_items(void)
+{
+    int i;
+
+    cmd.type = CMD_CLEAR_ITEMS;
+    game_proxy_enq(&cmd);
+
+    for (i = 0; i < file.hc; i++)
+    {
+        cmd.type = CMD_MAKE_ITEM;
+
+        v_cpy(cmd.mkitem.p, file.hv[i].p);
+
+        cmd.mkitem.t = file.hv[i].t;
+        cmd.mkitem.n = file.hv[i].n;
+
+        game_proxy_enq(&cmd);
+    }
+}
+
+static void game_cmd_pkitem(int hi)
+{
+    cmd.type      = CMD_PICK_ITEM;
+    cmd.pkitem.hi = hi;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_jump(int e)
+{
+    cmd.type = e ? CMD_JUMP_ENTER : CMD_JUMP_EXIT;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_rotate(void)
+{
+    cmd.type = CMD_ROTATE;
+
+    cmd.rotate.x = game_rx;
+    cmd.rotate.z = game_rz;
+
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_timer(void)
+{
+    cmd.type    = CMD_TIMER;
+    cmd.timer.t = timer;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_coins(void)
+{
+    cmd.type    = CMD_COINS;
+    cmd.coins.n = coins;
+    game_proxy_enq(&cmd);
+}
+
+static void game_cmd_status(void)
+{
+    cmd.type     = CMD_STATUS;
+    cmd.status.t = status;
+    game_proxy_enq(&cmd);
+}
+
+/*---------------------------------------------------------------------------*/
+
+static int   grow = 0;                  /* Should the ball be changing size? */
+static float grow_orig = 0;             /* the original ball size            */
+static float grow_goal = 0;             /* how big or small to get!          */
+static float grow_t = 0.0;              /* timer for the ball to grow...     */
+static float grow_strt = 0;             /* starting value for growth         */
+static int   got_orig = 0;              /* Do we know original ball size?    */
+
+#define GROW_TIME  0.5f                 /* sec for the ball to get to size.  */
+#define GROW_BIG   1.5f                 /* large factor                      */
+#define GROW_SMALL 0.5f                 /* small factor                      */
+
+static int   grow_state = 0;            /* Current state (values -1, 0, +1)  */
+
+static void grow_init(const struct s_file *fp, int type)
+{
+    if (!got_orig)
+    {
+        grow_orig  = fp->uv->r;
+        grow_goal  = grow_orig;
+        grow_strt  = grow_orig;
+
+        grow_state = 0;
+
+        got_orig   = 1;
+    }
+
+    if (type == ITEM_SHRINK)
+    {
+        switch (grow_state)
+        {
+        case -1:
+            break;
+
+        case  0:
+            audio_play(AUD_SHRINK, 1.f);
+            grow_goal = grow_orig * GROW_SMALL;
+            grow_state = -1;
+            grow = 1;
+            break;
+
+        case +1:
+            audio_play(AUD_SHRINK, 1.f);
+            grow_goal = grow_orig;
+            grow_state = 0;
+            grow = 1;
+            break;
+        }
+    }
+    else if (type == ITEM_GROW)
+    {
+        switch (grow_state)
+        {
+        case -1:
+            audio_play(AUD_GROW, 1.f);
+            grow_goal = grow_orig;
+            grow_state = 0;
+            grow = 1;
+            break;
+
+        case  0:
+            audio_play(AUD_GROW, 1.f);
+            grow_goal = grow_orig * GROW_BIG;
+            grow_state = +1;
+            grow = 1;
+            break;
+
+        case +1:
+            break;
+        }
+    }
+
+    if (grow)
+    {
+        grow_t = 0.0;
+        grow_strt = fp->uv->r;
+    }
+}
+
+static void grow_step(const struct s_file *fp, float dt)
+{
+    float dr;
+
+    if (!grow)
+        return;
+
+    /* Calculate new size based on how long since you touched the coin... */
+
+    grow_t += dt;
+
+    if (grow_t >= GROW_TIME)
+    {
+        grow = 0;
+        grow_t = GROW_TIME;
+    }
+
+    dr = grow_strt + ((grow_goal-grow_strt) * (1.0f / (GROW_TIME / grow_t)));
+
+    /* No sinking through the floor! Keeps ball's bottom constant. */
+
+    fp->uv->p[1] += (dr - fp->uv->r);
+    fp->uv->r     =  dr;
+
+    game_cmd_ballradius();
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void view_init(void)
+{
+    view_dp  = (float) config_get_d(CONFIG_VIEW_DP) / 100.0f;
+    view_dc  = (float) config_get_d(CONFIG_VIEW_DC) / 100.0f;
+    view_dz  = (float) config_get_d(CONFIG_VIEW_DZ) / 100.0f;
+    view_k   = 1.0f;
+    view_a   = 0.0f;
+
+    view_c[0] = 0.f;
+    view_c[1] = view_dc;
+    view_c[2] = 0.f;
+
+    view_p[0] =     0.f;
+    view_p[1] = view_dp;
+    view_p[2] = view_dz;
+
+    view_e[0][0] = 1.f;
+    view_e[0][1] = 0.f;
+    view_e[0][2] = 0.f;
+    view_e[1][0] = 0.f;
+    view_e[1][1] = 1.f;
+    view_e[1][2] = 0.f;
+    view_e[2][0] = 0.f;
+    view_e[2][1] = 0.f;
+    view_e[2][2] = 1.f;
+}
+
+int game_server_init(const char *file_name, int t, int e)
+{
+    timer      = (float) t / 100.f;
+    timer_down = (t > 0);
+    coins      = 0;
+    status     = GAME_NONE;
+
+    if (server_state)
+        game_server_free();
+
+    if (!sol_load_only_file(&file, config_data(file_name)))
+        return (server_state = 0);
+
+    server_state = 1;
+
+    input_init();
+
+    game_rx = 0.0f;
+    game_rz = 0.0f;
+
+    /* Initialize jump and goal states. */
+
+    jump_e = 1;
+    jump_b = 0;
+
+    goal_e = e ? 1    : 0;
+    goal_k = e ? 1.0f : 0.0f;
+
+    /* Initialize the view. */
+
+    view_init();
+
+    /* Initialize ball size tracking... */
+
+    got_orig = 0;
+    grow = 0;
+
+    sol_cmd_enq_func(game_proxy_enq);
+
+    /* Queue client commands. */
+
+    game_cmd_ups();
+    game_cmd_timer();
+
+    if (goal_e) game_cmd_goalopen();
+
+    game_cmd_init_balls();
+    game_cmd_init_items();
+    game_cmd_eou();
+
+    return server_state;
+}
+
+void game_server_free(void)
+{
+    if (server_state)
+    {
+        sol_free(&file);
+        server_state = 0;
+    }
+}
+
+/*---------------------------------------------------------------------------*/
+
+static void game_update_view(float dt)
+{
+    float dc = view_dc * (jump_b ? 2.0f * fabsf(jump_dt - 0.5f) : 1.0f);
+    float da = input_get_r() * dt * 90.0f;
+    float k;
+
+    float M[16], v[3], Y[3] = { 0.0f, 1.0f, 0.0f };
+
+    view_a += da;
+
+    /* Center the view about the ball. */
+
+    v_cpy(view_c, file.uv->p);
+    v_inv(view_v, file.uv->v);
+
+    view_e[2][0] = fsinf(V_RAD(view_a));
+    view_e[2][1] = 0.0;
+    view_e[2][2] = fcosf(V_RAD(view_a));
+
+    switch (input_get_c())
+    {
+    case 1: /* Camera 1: Viewpoint chases the ball position. */
+
+        v_sub(view_e[2], view_p, view_c);
+
+        break;
+
+    case 2: /* Camera 2: View vector is given by view angle. */
+
+        break;
+
+    default: /* Default: View vector approaches the ball velocity vector. */
+
+        v_mad(view_e[2], view_e[2], view_v, v_dot(view_v, view_v) * dt / 4);
+
+        break;
+    }
+
+    /* Orthonormalize the new view reference frame. */
+
+    v_crs(view_e[0], view_e[1], view_e[2]);
+    v_crs(view_e[2], view_e[0], view_e[1]);
+    v_nrm(view_e[0], view_e[0]);
+    v_nrm(view_e[2], view_e[2]);
+
+    /* Compute the new view position. */
+
+    k = 1.0f + v_dot(view_e[2], view_v) / 10.0f;
+
+    view_k = view_k + (k - view_k) * dt;
+
+    if (view_k < 0.5) view_k = 0.5;
+
+    v_scl(v,    view_e[1], view_dp * view_k);
+    v_mad(v, v, view_e[2], view_dz * view_k);
+    m_rot(M, Y, V_RAD(da));
+    m_vxfm(view_p, M, v);
+    v_add(view_p, view_p, file.uv->p);
+
+    /* Compute the new view center. */
+
+    v_cpy(view_c, file.uv->p);
+    v_mad(view_c, view_c, view_e[1], dc);
+
+    /* Note the current view angle. */
+
+    view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
+
+    game_cmd_updview();
+}
+
+static void game_update_time(float dt, int b)
+{
+    if (goal_e && goal_k < 1.0f)
+        goal_k += dt;
+
+   /* The ticking clock. */
+
+    if (b && timer_down)
+    {
+        if (timer < 600.f)
+            timer -= dt;
+        if (timer < 0.f)
+            timer = 0.f;
+    }
+    else if (b)
+    {
+        timer += dt;
+    }
+
+    if (b) game_cmd_timer();
+}
+
+static int game_update_state(int bt)
+{
+    struct s_file *fp = &file;
+    struct s_goal *zp;
+    int hi;
+
+    float p[3];
+
+    /* Test for an item. */
+
+    if (bt && (hi = sol_item_test(fp, p, ITEM_RADIUS)) != -1)
+    {
+        struct s_item *hp = &file.hv[hi];
+
+        game_cmd_pkitem(hi);
+
+        grow_init(fp, hp->t);
+
+        if (hp->t == ITEM_COIN)
+        {
+            coins += hp->n;
+            game_cmd_coins();
+        }
+
+        audio_play(AUD_COIN, 1.f);
+
+        /* Discard item. */
+
+        hp->t = ITEM_NONE;
+    }
+
+    /* Test for a switch. */
+
+    if (sol_swch_test(fp, 0))
+        audio_play(AUD_SWITCH, 1.f);
+
+    /* Test for a jump. */
+
+    if (jump_e == 1 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 1)
+    {
+        jump_b  = 1;
+        jump_e  = 0;
+        jump_dt = 0.f;
+
+        audio_play(AUD_JUMP, 1.f);
+
+        game_cmd_jump(1);
+    }
+    if (jump_e == 0 && jump_b == 0 && sol_jump_test(fp, jump_p, 0) == 0)
+    {
+        jump_e = 1;
+        game_cmd_jump(0);
+    }
+
+    /* Test for a goal. */
+
+    if (bt && goal_e && (zp = sol_goal_test(fp, p, 0)))
+    {
+        audio_play(AUD_GOAL, 1.0f);
+        return GAME_GOAL;
+    }
+
+    /* Test for time-out. */
+
+    if (bt && timer_down && timer <= 0.f)
+    {
+        audio_play(AUD_TIME, 1.0f);
+        return GAME_TIME;
+    }
+
+    /* Test for fall-out. */
+
+    if (bt && fp->uv[0].p[1] < fp->vv[0].p[1])
+    {
+        audio_play(AUD_FALL, 1.0f);
+        return GAME_FALL;
+    }
+
+    return GAME_NONE;
+}
+
+static int game_step(const float g[3], float dt, int bt)
+{
+    if (server_state)
+    {
+        struct s_file *fp = &file;
+
+        float h[3];
+
+        /* Smooth jittery or discontinuous input. */
+
+        game_rx += (input_get_x() - game_rx) * dt / RESPONSE;
+        game_rz += (input_get_z() - game_rz) * dt / RESPONSE;
+
+        game_cmd_rotate();
+
+        grow_step(fp, dt);
+
+        game_comp_grav(h, g, view_a, game_rx, game_rz);
+
+        if (jump_b)
+        {
+            jump_dt += dt;
+
+            /* Handle a jump. */
+
+            if (0.5f < jump_dt)
+            {
+                fp->uv[0].p[0] = jump_p[0];
+                fp->uv[0].p[1] = jump_p[1];
+                fp->uv[0].p[2] = jump_p[2];
+            }
+            if (1.0f < jump_dt)
+                jump_b = 0;
+        }
+        else
+        {
+            /* Run the sim. */
+
+            float b = sol_step(fp, h, dt, 0, NULL);
+
+            /* Mix the sound of a ball bounce. */
+
+            if (b > 0.5f)
+            {
+                float k = (b - 0.5f) * 2.0f;
+
+                if (got_orig)
+                {
+                    if      (fp->uv->r > grow_orig) audio_play(AUD_BUMPL, k);
+                    else if (fp->uv->r < grow_orig) audio_play(AUD_BUMPS, k);
+                    else                            audio_play(AUD_BUMPM, k);
+                }
+                else audio_play(AUD_BUMPM, k);
+            }
+        }
+
+        game_cmd_updball();
+
+        game_update_view(dt);
+        game_update_time(dt, bt);
+
+        return game_update_state(bt);
+    }
+    return GAME_NONE;
+}
+
+void game_server_step(float dt)
+{
+    static const float gup[] = { 0.0f, +9.8f, 0.0f };
+    static const float gdn[] = { 0.0f, -9.8f, 0.0f };
+
+    switch (status)
+    {
+    case GAME_GOAL: game_step(gup, dt, 0); break;
+    case GAME_FALL: game_step(gdn, dt, 0); break;
+
+    case GAME_NONE:
+        if ((status = game_step(gdn, dt, 1)) != GAME_NONE)
+            game_cmd_status();
+        break;
+    }
+
+    game_cmd_eou();
+}
+
+/*---------------------------------------------------------------------------*/
+
+void game_set_goal(void)
+{
+    audio_play(AUD_SWITCH, 1.0f);
+    goal_e = 1;
+
+    game_cmd_goalopen();
+}
+
+void game_clr_goal(void)
+{
+    goal_e = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
+void game_set_x(int k)
+{
+    input_set_x(-ANGLE_BOUND * k / JOY_MAX);
+}
+
+void game_set_z(int k)
+{
+    input_set_z(+ANGLE_BOUND * k / JOY_MAX);
+}
+
+void game_set_ang(int x, int z)
+{
+    input_set_x(x);
+    input_set_z(z);
+}
+
+void game_set_pos(int x, int y)
+{
+    input_set_x(input_get_x() + 40.0f * y / config_get_d(CONFIG_MOUSE_SENSE));
+    input_set_z(input_get_z() + 40.0f * x / config_get_d(CONFIG_MOUSE_SENSE));
+}
+
+void game_set_cam(int c)
+{
+    input_set_c(c);
+}
+
+void game_set_rot(float r)
+{
+    input_set_r(r);
+}
+
+void game_set_fly(float k, const struct s_file *fp)
+{
+    float  x[3] = { 1.f, 0.f, 0.f };
+    float  y[3] = { 0.f, 1.f, 0.f };
+    float  z[3] = { 0.f, 0.f, 1.f };
+    float c0[3] = { 0.f, 0.f, 0.f };
+    float p0[3] = { 0.f, 0.f, 0.f };
+    float c1[3] = { 0.f, 0.f, 0.f };
+    float p1[3] = { 0.f, 0.f, 0.f };
+    float  v[3];
+
+    if (!fp) fp = &file;
+
+    view_init();
+
+    z[0] = fsinf(V_RAD(view_a));
+    z[2] = fcosf(V_RAD(view_a));
+
+    v_cpy(view_e[0], x);
+    v_cpy(view_e[1], y);
+    v_cpy(view_e[2], z);
+
+    /* k = 0.0 view is at the ball. */
+
+    if (fp->uc > 0)
+    {
+        v_cpy(c0, fp->uv[0].p);
+        v_cpy(p0, fp->uv[0].p);
+    }
+
+    v_mad(p0, p0, y, view_dp);
+    v_mad(p0, p0, z, view_dz);
+    v_mad(c0, c0, y, view_dc);
+
+    /* k = +1.0 view is s_view 0 */
+
+    if (k >= 0 && fp->wc > 0)
+    {
+        v_cpy(p1, fp->wv[0].p);
+        v_cpy(c1, fp->wv[0].q);
+    }
+
+    /* k = -1.0 view is s_view 1 */
+
+    if (k <= 0 && fp->wc > 1)
+    {
+        v_cpy(p1, fp->wv[1].p);
+        v_cpy(c1, fp->wv[1].q);
+    }
+
+    /* Interpolate the views. */
+
+    v_sub(v, p1, p0);
+    v_mad(view_p, p0, v, k * k);
+
+    v_sub(v, c1, c0);
+    v_mad(view_c, c0, v, k * k);
+
+    /* Orthonormalize the view basis. */
+
+    v_sub(view_e[2], view_p, view_c);
+    v_crs(view_e[0], view_e[1], view_e[2]);
+    v_crs(view_e[2], view_e[0], view_e[1]);
+    v_nrm(view_e[0], view_e[0]);
+    v_nrm(view_e[2], view_e[2]);
+
+    game_cmd_updview();
+}
+
+/*---------------------------------------------------------------------------*/
diff --git a/ball/game_server.h b/ball/game_server.h
new file mode 100644 (file)
index 0000000..5f3b52f
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef GAME_SERVER_H
+#define GAME_SERVER_H
+
+#include "solid.h"
+
+/*---------------------------------------------------------------------------*/
+
+#define RESPONSE    0.05f              /* Input smoothing time               */
+#define ANGLE_BOUND 20.0f              /* Angle limit of floor tilting       */
+#define VIEWR_BOUND 10.0f              /* Maximum rate of view rotation      */
+
+/*---------------------------------------------------------------------------*/
+
+int   game_server_init(const char *, int, int);
+void  game_server_free(void);
+void  game_server_step(float);
+
+void  game_set_goal(void);
+void  game_clr_goal(void);
+
+void  game_set_ang(int, int);
+void  game_set_pos(int, int);
+void  game_set_x  (int);
+void  game_set_z  (int);
+void  game_set_cam(int);
+void  game_set_rot(float);
+void  game_set_fly(float, const struct s_file *);
+
+/*---------------------------------------------------------------------------*/
+
+int input_put(FILE *);
+int input_get(FILE *);
+
+/*---------------------------------------------------------------------------*/
+
+#endif
index c339d4a..bcba457 100644 (file)
 #include "glext.h"
 #include "hud.h"
 #include "gui.h"
-#include "game.h"
 #include "progress.h"
 #include "config.h"
 #include "audio.h"
 
+#include "game_common.h"
+#include "game_client.h"
+
 /*---------------------------------------------------------------------------*/
 
 static int Lhud_id;
index b816409..a676532 100644 (file)
@@ -25,7 +25,6 @@
 #include "audio.h"
 #include "demo.h"
 #include "progress.h"
-#include "game.h"
 #include "gui.h"
 #include "set.h"
 #include "text.h"
index 7a8287d..a3588af 100644 (file)
 
 #include "progress.h"
 #include "config.h"
-#include "game.h"
 #include "demo.h"
 #include "level.h"
 #include "set.h"
 #include "lang.h"
 #include "score.h"
 
+#include "game_common.h"
+#include "game_client.h"
+#include "game_server.h"
+
 #include <assert.h>
 
 /*---------------------------------------------------------------------------*/
@@ -32,6 +35,8 @@ struct progress
     int times;
 };
 
+static int replay = 0;
+
 static int mode = MODE_NORMAL;
 
 static int level =  0;
@@ -72,6 +77,8 @@ void progress_init(int m)
     mode  = m;
     bonus = 0;
 
+    replay = 0;
+
     curr.balls = 2;
     curr.score = 0;
     curr.times = 0;
@@ -128,7 +135,7 @@ void progress_step(void)
 
         if (goal <= 0)
         {
-            game_set_goal();
+            if (!replay) game_set_goal();
             goal = 0;
         }
     }
@@ -239,6 +246,7 @@ int  progress_replay(const char *filename)
                          &curr.times))
     {
         goal_i = goal;
+        replay = 1;
         return 1;
     }
     else
index ea26ecd..1cc762e 100644 (file)
 #include "image.h"
 #include "text.h"
 #include "set.h"
-#include "game.h"
 #include "common.h"
 
+#include "game_server.h"
+#include "game_client.h"
+#include "game_proxy.h"
+
 /*---------------------------------------------------------------------------*/
 
 struct set
@@ -441,13 +444,19 @@ void level_snap(int i)
 
     /* Initialize the game for a snapshot. */
 
-    if (game_init(level_v[i].file, 0, 1))
+    if (game_client_init(level_v[i].file))
     {
+        union cmd cmd;
+
+        cmd.type = CMD_GOAL_OPEN;
+        game_proxy_enq(&cmd);
+
         /* Render the level and grab the screen. */
 
         config_clear();
-        game_set_fly(1.f);
+        game_set_fly(1.f, game_client_file());
         game_kill_fade();
+        game_client_step(NULL);
         game_draw(1, 0);
 
         image_snap(filename);
index 04c3299..081d104 100644 (file)
 #include "item.h"
 #include "ball.h"
 #include "part.h"
-#include "game.h"
 #include "audio.h"
 #include "config.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+#include "game_client.h"
+#include "game_server.h"
+
 #include "st_conf.h"
 #include "st_title.h"
 #include "st_resol.h"
@@ -180,7 +183,7 @@ static int conf_enter(void)
 {
     int id, jd, kd;
 
-    game_free();
+    game_client_free();
     back_init("back/gui.png", config_get_d(CONFIG_GEOMETRY));
 
     /* Initialize the configuration GUI. */
index 6c610c0..a71f46c 100644 (file)
@@ -17,7 +17,6 @@
 #include "gui.h"
 #include "hud.h"
 #include "set.h"
-#include "game.h"
 #include "demo.h"
 #include "progress.h"
 #include "audio.h"
 #include "util.h"
 #include "common.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_demo.h"
 #include "st_title.h"
 
@@ -352,7 +355,8 @@ static int demo_play_enter(void)
 
     show_hud = 1;
     hud_update(0);
-    game_set_fly(0.f);
+    game_set_fly(0.f, game_client_file());
+    game_client_step(NULL);
 
     return id;
 }
index e0f1768..82006be 100644 (file)
@@ -16,7 +16,6 @@
 
 #include "gui.h"
 #include "set.h"
-#include "game.h"
 #include "util.h"
 #include "demo.h"
 #include "progress.h"
@@ -24,6 +23,8 @@
 #include "config.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+
 #include "st_done.h"
 #include "st_start.h"
 #include "st_name.h"
index 0c26c18..d0a6675 100644 (file)
  */
 
 #include "gui.h"
-#include "game.h"
 #include "util.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "demo.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_fall_out.h"
 #include "st_save.h"
 #include "st_over.h"
@@ -115,12 +118,10 @@ static int fall_out_enter(void)
 
 static void fall_out_timer(int id, float dt)
 {
-    float g[3] = { 0.0f, -9.8f, 0.0f };
-
     if (time_state() < 2.f)
     {
-        demo_play_step();
-        game_step(g, dt, 0);
+        game_server_step(dt);
+        game_client_step(demo_file());
     }
 
     gui_timer(id, dt);
index 863b94c..226bab0 100644 (file)
 #include <stdio.h>
 
 #include "gui.h"
-#include "game.h"
 #include "util.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "demo.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_goal.h"
 #include "st_save.h"
 #include "st_over.h"
@@ -239,14 +242,12 @@ static void goal_timer(int id, float dt)
 {
     static float t = 0.0f;
 
-    float g[3] = { 0.0f, 9.8f, 0.0f };
-
     t += dt;
 
     if (time_state() < 1.f)
     {
-        demo_play_step();
-        game_step(g, dt, 0);
+        game_server_step(dt);
+        game_client_step(demo_file());
     }
     else if (t > 0.05f && coins_id)
     {
index 8618116..a931ed4 100644 (file)
  */
 
 #include "gui.h"
-#include "game.h"
 #include "audio.h"
 #include "config.h"
 #include "demo.h"
 #include "keynames.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_shared.h"
 #include "st_title.h"
 #include "st_help.h"
@@ -367,8 +370,8 @@ static int help_buttn(int b, int d)
 
 static int help_demo_enter(void)
 {
-    game_set_fly(0.f);
-
+    game_set_fly(0.f, game_client_file());
+    game_client_step(NULL);
     return 0;
 }
 
index 8050752..26bba56 100644 (file)
 #include <string.h>
 
 #include "gui.h"
-#include "game.h"
 #include "set.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "st_shared.h"
 
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_level.h"
 #include "st_play.h"
 #include "st_start.h"
@@ -73,7 +75,8 @@ static int level_enter(void)
         gui_layout(id, 0, 0);
     }
 
-    game_set_fly(1.f);
+    game_set_fly(1.f, NULL);
+    game_client_step(NULL);
 
     return id;
 }
index b454745..db59bef 100644 (file)
 #include "util.h"
 #include "audio.h"
 #include "config.h"
-#include "game.h"
 #include "text.h"
 #include "back.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_name.h"
 #include "st_shared.h"
 
@@ -94,7 +97,7 @@ static int name_enter(void)
 
     if (draw_back)
     {
-        game_free();
+        game_client_free();
         back_init("back/gui.png", config_get_d(CONFIG_GEOMETRY));
     }
 
index 4a141b4..842b8af 100644 (file)
 
 #include "gui.h"
 #include "set.h"
-#include "game.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "demo.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+
 #include "st_over.h"
 #include "st_start.h"
 
index fd8c03b..cdfac90 100644 (file)
 
 #include "gui.h"
 #include "config.h"
-#include "game.h"
 #include "progress.h"
 #include "level.h"
 #include "audio.h"
 #include "hud.h"
 
+#include "game_common.h"
+
 #include "st_play.h"
 #include "st_over.h"
 #include "st_shared.h"
index fbf6a73..f6d8079 100644 (file)
 
 #include "gui.h"
 #include "hud.h"
-#include "game.h"
 #include "demo.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_play.h"
 #include "st_goal.h"
 #include "st_fall_out.h"
@@ -71,7 +74,8 @@ static void play_ready_timer(int id, float dt)
 {
     float t = time_state();
 
-    game_set_fly(1.0f - 0.5f * t);
+    game_set_fly(1.0f - 0.5f * t, NULL);
+    game_client_step(NULL);
 
     if (dt > 0.0f && t > 1.0f)
         goto_state(&st_play_set);
@@ -128,7 +132,8 @@ static void play_set_timer(int id, float dt)
 {
     float t = time_state();
 
-    game_set_fly(0.5f - 0.5f * t);
+    game_set_fly(0.5f - 0.5f * t, NULL);
+    game_client_step(NULL);
 
     if (dt > 0.0f && t > 1.0f)
         goto_state(&st_play_loop);
@@ -141,7 +146,8 @@ static int play_set_click(int b, int d)
 {
     if (b < 0 && d == 1)
     {
-        game_set_fly(0.0f);
+        game_set_fly(0.0f, NULL);
+        game_client_step(NULL);
         return goto_state(&st_play_loop);
     }
     return 1;
@@ -190,7 +196,8 @@ static int play_loop_enter(void)
 
     audio_play(AUD_GO, 1.f);
 
-    game_set_fly(0.f);
+    game_set_fly(0.f, NULL);
+    game_client_step(NULL);
     view_rotate = 0;
 
     hud_view_pulse(config_get_d(CONFIG_CAMERA));
@@ -219,17 +226,17 @@ static void play_loop_timer(int id, float dt)
                (float) config_get_d(CONFIG_ROTATE_FAST) / 100.0f :
                (float) config_get_d(CONFIG_ROTATE_SLOW) / 100.0f);
 
-    float g[3] = { 0.0f, -9.8f, 0.0f };
-
     gui_timer(id, dt);
     hud_timer(dt);
     game_set_rot(view_rotate * k);
     game_set_cam(config_get_d(CONFIG_CAMERA));
 
     game_step_fade(dt);
-    demo_play_step();
 
-    switch (game_step(g, dt, 1))
+    game_server_step(dt);
+    game_client_step(demo_file());
+
+    switch (curr_status())
     {
     case GAME_GOAL:
         progress_stat(GAME_GOAL);
index 509eb94..9ac7440 100644 (file)
@@ -16,7 +16,6 @@
 #include <ctype.h>
 
 #include "gui.h"
-#include "game.h"
 #include "util.h"
 #include "audio.h"
 #include "config.h"
@@ -24,6 +23,8 @@
 #include "progress.h"
 #include "text.h"
 
+#include "game_common.h"
+
 #include "st_shared.h"
 #include "st_save.h"
 
index b3b6331..a9f39e3 100644 (file)
 #include "gui.h"
 #include "set.h"
 #include "progress.h"
-#include "game.h"
 #include "audio.h"
 #include "config.h"
 #include "util.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+
 #include "st_set.h"
 #include "st_title.h"
 #include "st_start.h"
index 44695c1..028c7ad 100644 (file)
 #include "gui.h"
 #include "config.h"
 #include "audio.h"
-#include "game.h"
 #include "state.h"
 
+#include "game_server.h"
+#include "game_client.h"
+
 #include "st_shared.h"
 
 void shared_leave(int id)
index 4feec74..877b366 100644 (file)
 #include "gui.h"
 #include "set.h"
 #include "util.h"
-#include "game.h"
 #include "progress.h"
 #include "audio.h"
 #include "config.h"
 #include "st_shared.h"
 
+#include "game_common.h"
+
 #include "st_set.h"
 #include "st_over.h"
 #include "st_level.h"
index e2711dc..7aded52 100644 (file)
@@ -12,7 +12,6 @@
  * General Public License for more details.
  */
 
-#include "game.h"
 #include "util.h"
 #include "progress.h"
 #include "demo.h"
@@ -20,6 +19,8 @@
 #include "gui.h"
 #include "config.h"
 
+#include "game_common.h"
+
 #include "st_over.h"
 #include "st_start.h"
 #include "st_save.h"
index cb581b2..8529ea0 100644 (file)
 #include "gui.h"
 #include "vec3.h"
 #include "demo.h"
-#include "game.h"
 #include "audio.h"
 #include "config.h"
 #include "st_shared.h"
+#include "cmd.h"
+
+#include "game_common.h"
+#include "game_server.h"
+#include "game_client.h"
+#include "game_proxy.h"
 
 #include "st_title.h"
 #include "st_help.h"
 
 /*---------------------------------------------------------------------------*/
 
+static int init_title_level(void)
+{
+    if (game_client_init("map-medium/title.sol"))
+    {
+        union cmd cmd;
+
+        cmd.type = CMD_GOAL_OPEN;
+        game_proxy_enq(&cmd);
+
+        game_client_step(NULL);
+
+        return 1;
+    }
+    return 0;
+}
+
+/*---------------------------------------------------------------------------*/
+
 static float real_time = 0.0f;
 static int   mode      = 0;
 
@@ -152,7 +175,7 @@ static int title_enter(void)
 
     /* Initialize the title level for display. */
 
-    game_init("map-medium/title.sol", 0, 1);
+    init_title_level();
 
     real_time = 0.0f;
     mode = 0;
@@ -180,7 +203,11 @@ static void title_timer(int id, float dt)
     case 0: /* Mode 0: Pan across title level. */
 
         if (real_time <= 20.0f)
-            game_set_fly(fcosf(V_PI * real_time / 20.0f));
+        {
+            game_set_fly(fcosf(V_PI * real_time / 20.0f),
+                         game_client_file());
+            game_client_step(NULL);
+        }
         else
         {
             game_fade(+1.0f);
@@ -196,7 +223,8 @@ static void title_timer(int id, float dt)
             if ((demo = demo_pick()))
             {
                 demo_replay_init(demo, NULL, NULL, NULL, NULL, NULL);
-                game_set_fly(0.0f);
+                game_set_fly(0.0f, game_client_file());
+                game_client_step(NULL);
                 real_time = 0.0f;
                 mode = 2;
             }
@@ -224,7 +252,7 @@ static void title_timer(int id, float dt)
 
         if (real_time > 1.0f)
         {
-            game_init("map-medium/title.sol", 0, 1);
+            init_title_level();
 
             real_time = 0.0f;
             mode = 0;
diff --git a/share/cmd.c b/share/cmd.c
new file mode 100644 (file)
index 0000000..6646f34
--- /dev/null
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2009 Neverball contributors
+ *
+ * NEVERBALL is  free software; you can redistribute  it and/or modify
+ * it under the  terms of the GNU General  Public License as published
+ * by the Free  Software Foundation; either version 2  of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
+ * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
+ * General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "cmd.h"
+#include "binary.h"
+#include "base_config.h"
+#include "common.h"
+
+/*---------------------------------------------------------------------------*/
+
+static int cmd_stats = 0;
+
+/*---------------------------------------------------------------------------*/
+
+/*
+ * Let's pretend these aren't Ridiculously Convoluted Macros from
+ * Hell, and that all this looks pretty straight-forward.  (In all
+ * fairness, the macros have paid off.)
+ *
+ * A command's "write" and "read" functions are defined by calling the
+ * PUT_FUNC or the GET_FUNC macro, respectively, with the command type
+ * as argument, followed by the body of the function (which has
+ * variables "fp" and "cmd" available), and finalised with the
+ * END_FUNC macro, which must be terminated with a semi-colon.  Before
+ * the function definitions, the BYTES macro must be redefined for
+ * each command to an expression evaluating to the number of bytes
+ * that the command will occupy in the file.  (See existing commands
+ * for examples.)
+ */
+
+#define PUT_FUNC(t)                                                     \
+    static void cmd_put_ ## t(FILE *fp, const union cmd *cmd) {         \
+    const char *cmd_name = #t;                                          \
+                                                                        \
+    /* This is a write, so BYTES should be safe to eval already. */     \
+    short cmd_bytes = BYTES;                                            \
+                                                                        \
+    /* Write command size info (right after the command type). */       \
+    put_short(fp, &cmd_bytes);                                          \
+                                                                        \
+    /* Start the stats output. */                                       \
+    if (cmd_stats) printf("put");                                       \
+
+#define GET_FUNC(t)                                             \
+    static void cmd_get_ ## t(FILE *fp, union cmd *cmd) {       \
+    const char *cmd_name = #t;                                  \
+                                                                \
+    /* This is a read, so we'll have to eval BYTES later. */    \
+    short cmd_bytes = -1;                                       \
+                                                                \
+    /* Start the stats output. */                               \
+    if (cmd_stats) printf("get");
+
+#define END_FUNC                                                        \
+    if (cmd_bytes < 0) cmd_bytes = BYTES;                               \
+                                                                        \
+    /* Finish the stats output. */                                      \
+    if (cmd_stats) printf("\t%s\t%d\n", cmd_name, cmd_bytes);           \
+    } struct dummy              /* Allows a trailing semi-colon. */
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_END_OF_UPDATE) { } END_FUNC;
+GET_FUNC(CMD_END_OF_UPDATE) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_MAKE_BALL) { } END_FUNC;
+GET_FUNC(CMD_MAKE_BALL) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (ARRAY_BYTES(3) + INDEX_BYTES + INDEX_BYTES)
+
+PUT_FUNC(CMD_MAKE_ITEM)
+{
+    put_array(fp, cmd->mkitem.p, 3);
+    put_index(fp, &cmd->mkitem.t);
+    put_index(fp, &cmd->mkitem.n);
+}
+END_FUNC;
+
+GET_FUNC(CMD_MAKE_ITEM)
+{
+    get_array(fp, cmd->mkitem.p, 3);
+    get_index(fp, &cmd->mkitem.t);
+    get_index(fp, &cmd->mkitem.n);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_PICK_ITEM)
+{
+    put_index(fp, &cmd->pkitem.hi);
+}
+END_FUNC;
+
+GET_FUNC(CMD_PICK_ITEM)
+{
+    get_index(fp, &cmd->pkitem.hi);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (FLOAT_BYTES + FLOAT_BYTES)
+
+PUT_FUNC(CMD_ROTATE)
+{
+    put_float(fp, &cmd->rotate.x);
+    put_float(fp, &cmd->rotate.z);
+}
+END_FUNC;
+
+GET_FUNC(CMD_ROTATE)
+{
+    get_float(fp, &cmd->rotate.x);
+    get_float(fp, &cmd->rotate.z);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (STRING_BYTES(cmd->sound.n) + FLOAT_BYTES)
+
+PUT_FUNC(CMD_SOUND)
+{
+    put_string(fp, cmd->sound.n);
+    put_float(fp, &cmd->sound.a);
+}
+END_FUNC;
+
+GET_FUNC(CMD_SOUND)
+{
+    static char buff[MAXSTR];
+
+    get_string(fp, buff, sizeof (buff));
+    get_float(fp, &cmd->sound.a);
+
+    cmd->sound.n = strdup(buff);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES FLOAT_BYTES
+
+PUT_FUNC(CMD_TIMER)
+{
+    put_float(fp, &cmd->timer.t);
+}
+END_FUNC;
+
+GET_FUNC(CMD_TIMER)
+{
+    get_float(fp, &cmd->timer.t);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_STATUS)
+{
+    put_index(fp, &cmd->status.t);
+}
+END_FUNC;
+
+GET_FUNC(CMD_STATUS)
+{
+    get_index(fp, &cmd->status.t);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_COINS)
+{
+    put_index(fp, &cmd->coins.n);
+}
+END_FUNC;
+
+GET_FUNC(CMD_COINS)
+{
+    get_index(fp, &cmd->coins.n);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_JUMP_ENTER) { } END_FUNC;
+GET_FUNC(CMD_JUMP_ENTER) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_JUMP_EXIT) { } END_FUNC;
+GET_FUNC(CMD_JUMP_EXIT) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (INDEX_BYTES + INDEX_BYTES)
+
+PUT_FUNC(CMD_BODY_PATH)
+{
+    put_index(fp, &cmd->bodypath.bi);
+    put_index(fp, &cmd->bodypath.pi);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BODY_PATH)
+{
+    get_index(fp, &cmd->bodypath.bi);
+    get_index(fp, &cmd->bodypath.pi);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (INDEX_BYTES + FLOAT_BYTES)
+
+PUT_FUNC(CMD_BODY_TIME)
+{
+    put_index(fp, &cmd->bodytime.bi);
+    put_float(fp, &cmd->bodytime.t);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BODY_TIME)
+{
+    get_index(fp, &cmd->bodytime.bi);
+    get_float(fp, &cmd->bodytime.t);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_GOAL_OPEN) { } END_FUNC;
+GET_FUNC(CMD_GOAL_OPEN) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_SWCH_ENTER)
+{
+    put_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+GET_FUNC(CMD_SWCH_ENTER)
+{
+    get_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_SWCH_TOGGLE)
+{
+    put_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+GET_FUNC(CMD_SWCH_TOGGLE)
+{
+    get_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_SWCH_EXIT)
+{
+    put_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+GET_FUNC(CMD_SWCH_EXIT)
+{
+    get_index(fp, &cmd->swchenter.xi);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_UPDATES_PER_SECOND)
+{
+    put_index(fp, &cmd->ups.n);
+}
+END_FUNC;
+
+GET_FUNC(CMD_UPDATES_PER_SECOND)
+{
+    get_index(fp, &cmd->ups.n);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES FLOAT_BYTES
+
+PUT_FUNC(CMD_BALL_RADIUS)
+{
+    put_float(fp, &cmd->ballradius.r);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BALL_RADIUS)
+{
+    get_float(fp, &cmd->ballradius.r);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_CLEAR_ITEMS) { } END_FUNC;
+GET_FUNC(CMD_CLEAR_ITEMS) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES 0
+
+PUT_FUNC(CMD_CLEAR_BALLS) { } END_FUNC;
+GET_FUNC(CMD_CLEAR_BALLS) { } END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES ARRAY_BYTES(3)
+
+PUT_FUNC(CMD_BALL_POSITION)
+{
+    put_array(fp, cmd->ballpos.p, 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BALL_POSITION)
+{
+    get_array(fp, cmd->ballpos.p, 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (ARRAY_BYTES(3) + ARRAY_BYTES(3))
+
+PUT_FUNC(CMD_BALL_BASIS)
+{
+    put_array(fp, cmd->ballbasis.e[0], 3);
+    put_array(fp, cmd->ballbasis.e[1], 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BALL_BASIS)
+{
+    get_array(fp, cmd->ballbasis.e[0], 3);
+    get_array(fp, cmd->ballbasis.e[1], 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (ARRAY_BYTES(3) + ARRAY_BYTES(3))
+
+PUT_FUNC(CMD_BALL_PEND_BASIS)
+{
+    put_array(fp, cmd->ballpendbasis.E[0], 3);
+    put_array(fp, cmd->ballpendbasis.E[1], 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_BALL_PEND_BASIS)
+{
+    get_array(fp, cmd->ballpendbasis.E[0], 3);
+    get_array(fp, cmd->ballpendbasis.E[1], 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES ARRAY_BYTES(3)
+
+PUT_FUNC(CMD_VIEW_POSITION)
+{
+    put_array(fp, cmd->viewpos.p, 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_VIEW_POSITION)
+{
+    get_array(fp, cmd->viewpos.p, 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES ARRAY_BYTES(3)
+
+PUT_FUNC(CMD_VIEW_CENTER)
+{
+    put_array(fp, cmd->viewcenter.c, 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_VIEW_CENTER)
+{
+    get_array(fp, cmd->viewcenter.c, 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (ARRAY_BYTES(3) + ARRAY_BYTES(3))
+
+PUT_FUNC(CMD_VIEW_BASIS)
+{
+    put_array(fp, cmd->viewbasis.e[0], 3);
+    put_array(fp, cmd->viewbasis.e[1], 3);
+}
+END_FUNC;
+
+GET_FUNC(CMD_VIEW_BASIS)
+{
+    get_array(fp, cmd->viewbasis.e[0], 3);
+    get_array(fp, cmd->viewbasis.e[1], 3);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES INDEX_BYTES
+
+PUT_FUNC(CMD_CURRENT_BALL)
+{
+    put_index(fp, &cmd->currball.ui);
+}
+END_FUNC;
+
+GET_FUNC(CMD_CURRENT_BALL)
+{
+    get_index(fp, &cmd->currball.ui);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES (INDEX_BYTES + INDEX_BYTES)
+
+PUT_FUNC(CMD_PATH_FLAG)
+{
+    put_index(fp, &cmd->pathflag.pi);
+    put_index(fp, &cmd->pathflag.f);
+}
+END_FUNC;
+
+GET_FUNC(CMD_PATH_FLAG)
+{
+    get_index(fp, &cmd->pathflag.pi);
+    get_index(fp, &cmd->pathflag.f);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#undef BYTES
+#define BYTES FLOAT_BYTES
+
+PUT_FUNC(CMD_STEP_SIMULATION)
+{
+    put_float(fp, &cmd->stepsim.dt);
+}
+END_FUNC;
+
+GET_FUNC(CMD_STEP_SIMULATION)
+{
+    get_float(fp, &cmd->stepsim.dt);
+}
+END_FUNC;
+
+/*---------------------------------------------------------------------------*/
+
+#define PUT_CASE(t) case t: cmd_put_ ## t(fp, cmd); break
+#define GET_CASE(t) case t: cmd_get_ ## t(fp, cmd); break
+
+int cmd_put(FILE *fp, const union cmd *cmd)
+{
+    if (!fp || !cmd)
+        return 0;
+
+    assert(cmd->type > CMD_NONE && cmd->type < CMD_MAX);
+
+    fputc(cmd->type, fp);
+
+    switch (cmd->type)
+    {
+        PUT_CASE(CMD_END_OF_UPDATE);
+        PUT_CASE(CMD_MAKE_BALL);
+        PUT_CASE(CMD_MAKE_ITEM);
+        PUT_CASE(CMD_PICK_ITEM);
+        PUT_CASE(CMD_ROTATE);
+        PUT_CASE(CMD_SOUND);
+        PUT_CASE(CMD_TIMER);
+        PUT_CASE(CMD_STATUS);
+        PUT_CASE(CMD_COINS);
+        PUT_CASE(CMD_JUMP_ENTER);
+        PUT_CASE(CMD_JUMP_EXIT);
+        PUT_CASE(CMD_BODY_PATH);
+        PUT_CASE(CMD_BODY_TIME);
+        PUT_CASE(CMD_GOAL_OPEN);
+        PUT_CASE(CMD_SWCH_ENTER);
+        PUT_CASE(CMD_SWCH_TOGGLE);
+        PUT_CASE(CMD_SWCH_EXIT);
+        PUT_CASE(CMD_UPDATES_PER_SECOND);
+        PUT_CASE(CMD_BALL_RADIUS);
+        PUT_CASE(CMD_CLEAR_ITEMS);
+        PUT_CASE(CMD_CLEAR_BALLS);
+        PUT_CASE(CMD_BALL_POSITION);
+        PUT_CASE(CMD_BALL_BASIS);
+        PUT_CASE(CMD_BALL_PEND_BASIS);
+        PUT_CASE(CMD_VIEW_POSITION);
+        PUT_CASE(CMD_VIEW_CENTER);
+        PUT_CASE(CMD_VIEW_BASIS);
+        PUT_CASE(CMD_CURRENT_BALL);
+        PUT_CASE(CMD_PATH_FLAG);
+        PUT_CASE(CMD_STEP_SIMULATION);
+
+    case CMD_NONE:
+    case CMD_MAX:
+        break;
+    }
+
+    return !feof(fp);
+}
+
+int cmd_get(FILE *fp, union cmd *cmd)
+{
+    int type;
+    short size;
+
+    if (!fp || !cmd)
+        return 0;
+
+    if ((type = fgetc(fp)) != EOF)
+    {
+        get_short(fp, &size);
+
+        /* Discard unrecognised commands. */
+
+        if (type >= CMD_MAX)
+        {
+            fseek(fp, size, SEEK_CUR);
+            type = CMD_NONE;
+        }
+
+        cmd->type = type;
+
+        switch (cmd->type)
+        {
+            GET_CASE(CMD_END_OF_UPDATE);
+            GET_CASE(CMD_MAKE_BALL);
+            GET_CASE(CMD_MAKE_ITEM);
+            GET_CASE(CMD_PICK_ITEM);
+            GET_CASE(CMD_ROTATE);
+            GET_CASE(CMD_SOUND);
+            GET_CASE(CMD_TIMER);
+            GET_CASE(CMD_STATUS);
+            GET_CASE(CMD_COINS);
+            GET_CASE(CMD_JUMP_ENTER);
+            GET_CASE(CMD_JUMP_EXIT);
+            GET_CASE(CMD_BODY_PATH);
+            GET_CASE(CMD_BODY_TIME);
+            GET_CASE(CMD_GOAL_OPEN);
+            GET_CASE(CMD_SWCH_ENTER);
+            GET_CASE(CMD_SWCH_TOGGLE);
+            GET_CASE(CMD_SWCH_EXIT);
+            GET_CASE(CMD_UPDATES_PER_SECOND);
+            GET_CASE(CMD_BALL_RADIUS);
+            GET_CASE(CMD_CLEAR_ITEMS);
+            GET_CASE(CMD_CLEAR_BALLS);
+            GET_CASE(CMD_BALL_POSITION);
+            GET_CASE(CMD_BALL_BASIS);
+            GET_CASE(CMD_BALL_PEND_BASIS);
+            GET_CASE(CMD_VIEW_POSITION);
+            GET_CASE(CMD_VIEW_CENTER);
+            GET_CASE(CMD_VIEW_BASIS);
+            GET_CASE(CMD_CURRENT_BALL);
+            GET_CASE(CMD_PATH_FLAG);
+            GET_CASE(CMD_STEP_SIMULATION);
+
+        case CMD_NONE:
+        case CMD_MAX:
+            break;
+        }
+
+        return !feof(fp);
+    }
+    return 0;
+}
+
+/*---------------------------------------------------------------------------*/
diff --git a/share/cmd.h b/share/cmd.h
new file mode 100644 (file)
index 0000000..ebfe28b
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2009 Neverball contributors
+ *
+ * NEVERBALL is  free software; you can redistribute  it and/or modify
+ * it under the  terms of the GNU General  Public License as published
+ * by the Free  Software Foundation; either version 2  of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
+ * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef CMD_H
+#define CMD_H
+
+/*
+ * In an attempt to improve replay compatibility, a few guidelines
+ * apply to command addition, removal, and modification:
+ *
+ * - New commands are added at the bottom of the list.
+ *
+ * - Existing commands are never modified nor removed.
+ *
+ * - The list is never reordered.  (It's tempting...)
+ *
+ * However, commands can be renamed (e.g., to add a "deprecated" tag,
+ * because it's superseded by another command).
+ */
+
+enum cmd_type
+{
+    CMD_NONE = 0,
+
+    CMD_END_OF_UPDATE,
+    CMD_MAKE_BALL,
+    CMD_MAKE_ITEM,
+    CMD_PICK_ITEM,
+    CMD_ROTATE,
+    CMD_SOUND,
+    CMD_TIMER,
+    CMD_STATUS,
+    CMD_COINS,
+    CMD_JUMP_ENTER,
+    CMD_JUMP_EXIT,
+    CMD_BODY_PATH,
+    CMD_BODY_TIME,
+    CMD_GOAL_OPEN,
+    CMD_SWCH_ENTER,
+    CMD_SWCH_TOGGLE,
+    CMD_SWCH_EXIT,
+    CMD_UPDATES_PER_SECOND,
+    CMD_BALL_RADIUS,
+    CMD_CLEAR_ITEMS,
+    CMD_CLEAR_BALLS,
+    CMD_BALL_POSITION,
+    CMD_BALL_BASIS,
+    CMD_BALL_PEND_BASIS,
+    CMD_VIEW_POSITION,
+    CMD_VIEW_CENTER,
+    CMD_VIEW_BASIS,
+    CMD_CURRENT_BALL,
+    CMD_PATH_FLAG,
+    CMD_STEP_SIMULATION,
+
+    CMD_MAX
+};
+
+/*
+ * Here are the members common to all structures.  Note that it
+ * explicitly says "enum cmd_type", not "int".  This allows GCC to
+ * catch and warn about unhandled command types in switch constructs
+ * (handy when adding new commands).
+ */
+
+#define HEADER                                  \
+    enum cmd_type type
+
+struct cmd_end_of_update
+{
+    HEADER;
+};
+
+struct cmd_make_ball
+{
+    HEADER;
+};
+
+struct cmd_make_item
+{
+    HEADER;
+    float p[3];
+    int   t;
+    int   n;
+};
+
+struct cmd_pick_item
+{
+    HEADER;
+    int   hi;
+};
+
+struct cmd_rotate
+{
+    HEADER;
+    short size;
+    float x;
+    float z;
+};
+
+struct cmd_sound
+{
+    HEADER;
+    char  *n;
+    float  a;
+};
+
+struct cmd_timer
+{
+    HEADER;
+    float t;
+};
+
+struct cmd_status
+{
+    HEADER;
+    int t;
+};
+
+struct cmd_coins
+{
+    HEADER;
+    int n;
+};
+
+struct cmd_jump_enter
+{
+    HEADER;
+};
+
+struct cmd_jump_exit
+{
+    HEADER;
+};
+
+struct cmd_body_path
+{
+    HEADER;
+    int bi;
+    int pi;
+};
+
+struct cmd_body_time
+{
+    HEADER;
+    int   bi;
+    float t;
+};
+
+struct cmd_goal_open
+{
+    HEADER;
+};
+
+struct cmd_swch_enter
+{
+    HEADER;
+    int xi;
+};
+
+struct cmd_swch_toggle
+{
+    HEADER;
+    int xi;
+};
+
+struct cmd_swch_exit
+{
+    HEADER;
+    int xi;
+};
+
+struct cmd_updates_per_second
+{
+    HEADER;
+    int n;
+};
+
+struct cmd_ball_radius
+{
+    HEADER;
+    float r;
+};
+
+struct cmd_clear_items
+{
+    HEADER;
+};
+
+struct cmd_clear_balls
+{
+    HEADER;
+};
+
+struct cmd_ball_position
+{
+    HEADER;
+    float p[3];
+};
+
+struct cmd_ball_basis
+{
+    HEADER;
+    float e[2][3];
+};
+
+struct cmd_ball_pend_basis
+{
+    HEADER;
+    float E[2][3];
+};
+
+struct cmd_view_position
+{
+    HEADER;
+    float p[3];
+};
+
+struct cmd_view_center
+{
+    HEADER;
+    float c[3];
+};
+
+struct cmd_view_basis
+{
+    HEADER;
+    float e[2][3];
+};
+
+struct cmd_current_ball
+{
+    HEADER;
+    int ui;
+};
+
+struct cmd_path_flag
+{
+    HEADER;
+    int pi;
+    int f;
+};
+
+struct cmd_step_simulation
+{
+    HEADER;
+    float dt;
+};
+
+union cmd
+{
+    HEADER;
+    struct cmd_end_of_update      eou;
+    struct cmd_make_ball          mkball;
+    struct cmd_make_item          mkitem;
+    struct cmd_pick_item          pkitem;
+    struct cmd_rotate             rotate;
+    struct cmd_sound              sound;
+    struct cmd_timer              timer;
+    struct cmd_status             status;
+    struct cmd_coins              coins;
+    struct cmd_jump_enter         jumpenter;
+    struct cmd_jump_exit          jumpexit;
+    struct cmd_body_path          bodypath;
+    struct cmd_body_time          bodytime;
+    struct cmd_goal_open          goalopen;
+    struct cmd_swch_enter         swchenter;
+    struct cmd_swch_toggle        swchtoggle;
+    struct cmd_swch_exit          swchexit;
+    struct cmd_updates_per_second ups;
+    struct cmd_ball_radius        ballradius;
+    struct cmd_clear_items        clritems;
+    struct cmd_clear_balls        clrballs;
+    struct cmd_ball_position      ballpos;
+    struct cmd_ball_basis         ballbasis;
+    struct cmd_ball_pend_basis    ballpendbasis;
+    struct cmd_view_position      viewpos;
+    struct cmd_view_center        viewcenter;
+    struct cmd_view_basis         viewbasis;
+    struct cmd_current_ball       currball;
+    struct cmd_path_flag          pathflag;
+    struct cmd_step_simulation    stepsim;
+};
+
+/* No module should see this. */
+#undef HEADER
+
+#include <stdio.h>
+
+int cmd_put(FILE *, const union cmd *);
+int cmd_get(FILE *, union cmd *);
+
+#endif
index 0e8d0ae..529c131 100644 (file)
 
 /*---------------------------------------------------------------------------*/
 
+#include "cmd.h"
+#include "list.h"
+
+static union cmd cmd;
+
+static void (*cmd_enq_fn)(const union cmd *);
+
+/*
+ * sol_step generates a few commands that supersede previous commands
+ * of the same type generated by the same sol_step invocation.  The
+ * code below provides the means to accumulate commands for later
+ * addition to the command queue, while making sure that commands that
+ * have been superseded during the accumulation are discarded.
+ */
+
+static int  defer_cmds;
+static List deferred_cmds;
+
+static void sol_cmd_enq_deferred(void)
+{
+    List l, r;
+
+    /* Reverse the list to preserve original command order. */
+
+    for (r = NULL, l = deferred_cmds;
+         l;
+         r = list_cons(l->data, r), l = list_rest(l));
+
+    /* Enqueue commands. */
+
+    for (; r; r = list_rest(r))
+    {
+        if (cmd_enq_fn)
+            cmd_enq_fn(r->data);
+
+        free(r->data);
+    }
+
+    deferred_cmds = NULL;
+}
+
+static void sol_cmd_enq(const union cmd *new)
+{
+    if (defer_cmds)
+    {
+        union cmd *copy;
+        List l, p;
+
+        for (p = NULL, l = deferred_cmds; l; p = l, l = l->next)
+        {
+            union cmd *cur = l->data;
+
+            /* Remove element made obsolete by the new command. */
+
+            if (new->type == cur->type &&
+                ((new->type == CMD_BODY_TIME &&
+                  new->bodytime.bi == cur->bodytime.bi) ||
+                 (new->type == CMD_BODY_PATH &&
+                  new->bodypath.bi == cur->bodypath.bi)))
+            {
+                free(cur);
+
+                if (p)
+                    p->next       = list_rest(l);
+                else
+                    deferred_cmds = list_rest(l);
+
+                /*
+                 * The operation above made the list pointer useless
+                 * for the variable update part of the loop, and it
+                 * seems a bit involved to recover from that in a
+                 * proper fashion.  Fortunately, this very block
+                 * ensures that there's only one element to remove, so
+                 * no more iterations are needed.
+                 */
+
+                l = NULL;
+                break;
+            }
+        }
+
+        if ((copy = malloc(sizeof (*copy))))
+        {
+            *copy = *new;
+            deferred_cmds = list_cons(copy, deferred_cmds);
+        }
+    }
+    else if (cmd_enq_fn)
+        cmd_enq_fn(new);
+}
+
+void sol_cmd_enq_func(void (*enq_fn) (const union cmd *))
+{
+    cmd_enq_fn = enq_fn;
+}
+
+/*---------------------------------------------------------------------------*/
+
 static float erp(float t)
 {
     return 3.0f * t * t - 2.0f * t * t * t;
@@ -388,6 +486,11 @@ static void sol_swch_step(struct s_file *fp, float dt)
                     fp->pv[pi].f = xp->f0;
                     fp->pv[pj].f = xp->f0;
 
+                    cmd.type        = CMD_PATH_FLAG;
+                    cmd.pathflag.pi = pi;
+                    cmd.pathflag.f  = fp->pv[pi].f;
+                    sol_cmd_enq(&cmd);
+
                     pi = fp->pv[pi].pi;
                     pj = fp->pv[pj].pi;
                     pj = fp->pv[pj].pi;
@@ -395,6 +498,10 @@ static void sol_swch_step(struct s_file *fp, float dt)
                 while (pi != pj);
 
                 xp->f = xp->f0;
+
+                cmd.type          = CMD_SWCH_TOGGLE;
+                cmd.swchtoggle.xi = xi;
+                sol_cmd_enq(&cmd);
             }
         }
     }
@@ -420,6 +527,16 @@ static void sol_body_step(struct s_file *fp, float dt)
             {
                 bp->t  = 0;
                 bp->pi = pp->pi;
+
+                cmd.type        = CMD_BODY_TIME;
+                cmd.bodytime.bi = i;
+                cmd.bodytime.t  = bp->t;
+                sol_cmd_enq(&cmd);
+
+                cmd.type        = CMD_BODY_PATH;
+                cmd.bodypath.bi = i;
+                cmd.bodypath.pi = bp->pi;
+                sol_cmd_enq(&cmd);
             }
         }
     }
@@ -727,6 +844,8 @@ float sol_step(struct s_file *fp, const float *g, float dt, int ui, int *m)
     float P[3], V[3], v[3], r[3], a[3], d, e, nt, b = 0.0f, tt = dt;
     int c = 16;
 
+    defer_cmds = 1;
+
     if (ui < fp->uc)
     {
         struct s_ball *up = fp->uv + ui;
@@ -776,6 +895,10 @@ float sol_step(struct s_file *fp, const float *g, float dt, int ui, int *m)
 
         while (c > 0 && tt > 0 && tt > (nt = sol_test_file(tt, P, V, up, fp)))
         {
+            cmd.type       = CMD_STEP_SIMULATION;
+            cmd.stepsim.dt = nt;
+            sol_cmd_enq(&cmd);
+
             sol_body_step(fp, nt);
             sol_swch_step(fp, nt);
             sol_ball_step(fp, nt);
@@ -788,6 +911,10 @@ float sol_step(struct s_file *fp, const float *g, float dt, int ui, int *m)
             c--;
         }
 
+        cmd.type       = CMD_STEP_SIMULATION;
+        cmd.stepsim.dt = tt;
+        sol_cmd_enq(&cmd);
+
         sol_body_step(fp, tt);
         sol_swch_step(fp, tt);
         sol_ball_step(fp, tt);
@@ -798,12 +925,16 @@ float sol_step(struct s_file *fp, const float *g, float dt, int ui, int *m)
 
         sol_pendulum(up, a, g, dt);
     }
+
+    sol_cmd_enq_deferred();
+    defer_cmds = 0;
+
     return b;
 }
 
 /*---------------------------------------------------------------------------*/
 
-struct s_item *sol_item_test(struct s_file *fp, float *p, float item_r)
+int sol_item_test(struct s_file *fp, float *p, float item_r)
 {
     const float *ball_p = fp->uv->p;
     const float  ball_r = fp->uv->r;
@@ -824,10 +955,10 @@ struct s_item *sol_item_test(struct s_file *fp, float *p, float item_r)
             p[1] = fp->hv[hi].p[1];
             p[2] = fp->hv[hi].p[2];
 
-            return &fp->hv[hi];
+            return hi;
         }
     }
-    return NULL;
+    return -1;
 }
 
 struct s_goal *sol_goal_test(struct s_file *fp, float *p, int ui)
@@ -940,17 +1071,32 @@ int sol_swch_test(struct s_file *fp, int ui)
                     /* The ball enters. */
 
                     if (xp->t0 == 0)
+                    {
                         xp->e = 1;
 
+                        cmd.type         = CMD_SWCH_ENTER;
+                        cmd.swchenter.xi = xi;
+                        sol_cmd_enq(&cmd);
+                    }
+
                     /* Toggle the state, update the path. */
 
                     xp->f = xp->f ? 0 : 1;
 
+                    cmd.type          = CMD_SWCH_TOGGLE;
+                    cmd.swchtoggle.xi = xi;
+                    sol_cmd_enq(&cmd);
+
                     do  /* Tortoise and hare cycle traverser. */
                     {
                         fp->pv[pi].f = xp->f;
                         fp->pv[pj].f = xp->f;
 
+                        cmd.type        = CMD_PATH_FLAG;
+                        cmd.pathflag.pi = pi;
+                        cmd.pathflag.f  = fp->pv[pi].f;
+                        sol_cmd_enq(&cmd);
+
                         pi = fp->pv[pi].pi;
                         pj = fp->pv[pj].pi;
                         pj = fp->pv[pj].pi;
@@ -972,7 +1118,13 @@ int sol_swch_test(struct s_file *fp, int ui)
             /* The ball exits. */
 
             else if (xp->e)
+            {
                 xp->e = 0;
+
+                cmd.type        = CMD_SWCH_EXIT;
+                cmd.swchexit.xi = xi;
+                sol_cmd_enq(&cmd);
+            }
         }
     }
     return res;
index aaab6c5..96cf91f 100644 (file)
@@ -16,6 +16,7 @@
 #define SOL_PHYS_H
 
 #include "solid.h"
+#include "cmd.h"
 
 /*---------------------------------------------------------------------------*/
 
@@ -29,7 +30,11 @@ int   sol_jump_test(struct s_file *, float *, int);
 int   sol_swch_test(struct s_file *, int);
 
 struct s_goal *sol_goal_test(struct s_file *, float *, int);
-struct s_item *sol_item_test(struct s_file *, float *, float);
+int            sol_item_test(struct s_file *, float *, float);
+
+/*---------------------------------------------------------------------------*/
+
+void sol_cmd_enq_func(void (*enq_fn) (const union cmd *));
 
 /*---------------------------------------------------------------------------*/