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 \
share/sync.o \
share/common.o \
share/syswm.o \
+ share/list.o \
putt/hud.o \
putt/game.o \
putt/hole.o \
#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
{
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)
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))
{
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;
}
}
}
/*---------------------------------------------------------------------------*/
+
+FILE *demo_file(void) { return demo_fp; }
+
+/*---------------------------------------------------------------------------*/
#define DEMO_H
#include <time.h>
+#include <stdio.h>
#include "level.h"
/*---------------------------------------------------------------------------*/
+FILE *demo_file(void);
+
+/*---------------------------------------------------------------------------*/
+
#endif
+++ /dev/null
-/*
- * 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");
- }
-}
-
-/*---------------------------------------------------------------------------*/
+++ /dev/null
-#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
--- /dev/null
+/*
+ * 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;
+}
+
+/*---------------------------------------------------------------------------*/
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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();
+}
+
+/*---------------------------------------------------------------------------*/
--- /dev/null
+#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
#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;
#include "audio.h"
#include "demo.h"
#include "progress.h"
-#include "game.h"
#include "gui.h"
#include "set.h"
#include "text.h"
#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>
/*---------------------------------------------------------------------------*/
int times;
};
+static int replay = 0;
+
static int mode = MODE_NORMAL;
static int level = 0;
mode = m;
bonus = 0;
+ replay = 0;
+
curr.balls = 2;
curr.score = 0;
curr.times = 0;
if (goal <= 0)
{
- game_set_goal();
+ if (!replay) game_set_goal();
goal = 0;
}
}
&curr.times))
{
goal_i = goal;
+ replay = 1;
return 1;
}
else
#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
/* 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);
#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"
{
int id, jd, kd;
- game_free();
+ game_client_free();
back_init("back/gui.png", config_get_d(CONFIG_GEOMETRY));
/* Initialize the configuration GUI. */
#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"
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;
}
#include "gui.h"
#include "set.h"
-#include "game.h"
#include "util.h"
#include "demo.h"
#include "progress.h"
#include "config.h"
#include "st_shared.h"
+#include "game_common.h"
+
#include "st_done.h"
#include "st_start.h"
#include "st_name.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_fall_out.h"
#include "st_save.h"
#include "st_over.h"
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);
#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"
{
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)
{
*/
#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"
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;
}
#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"
gui_layout(id, 0, 0);
}
- game_set_fly(1.f);
+ game_set_fly(1.f, NULL);
+ game_client_step(NULL);
return id;
}
#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"
if (draw_back)
{
- game_free();
+ game_client_free();
back_init("back/gui.png", config_get_d(CONFIG_GEOMETRY));
}
#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"
#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"
#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"
{
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);
{
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);
{
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;
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));
(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);
#include <ctype.h>
#include "gui.h"
-#include "game.h"
#include "util.h"
#include "audio.h"
#include "config.h"
#include "progress.h"
#include "text.h"
+#include "game_common.h"
+
#include "st_shared.h"
#include "st_save.h"
#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"
#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)
#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"
* General Public License for more details.
*/
-#include "game.h"
#include "util.h"
#include "progress.h"
#include "demo.h"
#include "gui.h"
#include "config.h"
+#include "game_common.h"
+
#include "st_over.h"
#include "st_start.h"
#include "st_save.h"
#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;
/* Initialize the title level for display. */
- game_init("map-medium/title.sol", 0, 1);
+ init_title_level();
real_time = 0.0f;
mode = 0;
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);
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;
}
if (real_time > 1.0f)
{
- game_init("map-medium/title.sol", 0, 1);
+ init_title_level();
real_time = 0.0f;
mode = 0;
--- /dev/null
+/*
+ * 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;
+}
+
+/*---------------------------------------------------------------------------*/
--- /dev/null
+/*
+ * 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
/*---------------------------------------------------------------------------*/
+#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;
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;
while (pi != pj);
xp->f = xp->f0;
+
+ cmd.type = CMD_SWCH_TOGGLE;
+ cmd.swchtoggle.xi = xi;
+ sol_cmd_enq(&cmd);
}
}
}
{
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);
}
}
}
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;
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);
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);
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;
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)
/* 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;
/* 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;
#define SOL_PHYS_H
#include "solid.h"
+#include "cmd.h"
/*---------------------------------------------------------------------------*/
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 *));
/*---------------------------------------------------------------------------*/