2 * Copyright (C) 2003 Robert Kooima
4 * NEVERBALL is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published
6 * by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
32 #include "game_client.h"
33 #include "game_common.h"
34 #include "game_proxy.h"
38 /*---------------------------------------------------------------------------*/
40 int game_compat_map; /* Client/server map compat flag */
42 /*---------------------------------------------------------------------------*/
44 static int client_state = 0;
46 static struct s_file file;
47 static struct s_file back;
49 static int reflective; /* Reflective geometry used? */
51 static float timer = 0.f; /* Clock time */
53 static int status = GAME_NONE; /* Outcome of the game */
55 static struct game_tilt tilt; /* Floor rotation */
56 static struct game_view view; /* Current view */
58 static int coins = 0; /* Collected coins */
59 static int goal_e = 0; /* Goal enabled flag */
60 static float goal_k = 0; /* Goal animation */
62 static int jump_e = 1; /* Jumping enabled flag */
63 static int jump_b = 0; /* Jump-in-progress flag */
64 static float jump_dt; /* Jump duration */
66 static float fade_k = 0.0; /* Fade in/out level */
67 static float fade_d = 0.0; /* Fade in/out direction */
69 static int ups; /* Updates per second */
70 static int first_update; /* First update flag */
71 static int curr_ball; /* Current ball index */
76 } version; /* Current map version */
78 /*---------------------------------------------------------------------------*/
80 static void game_run_cmd(const union cmd *cmd)
82 static const float gup[] = { 0.0f, +9.8f, 0.0f };
83 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
86 * Neverball <= 1.5.1 does not send explicit tilt axes, rotation
87 * happens directly around view vectors. So for compatibility if
88 * at the time of receiving tilt angles we have not yet received
89 * the tilt axes, we use the view vectors.
91 static int got_tilt_axes;
105 case CMD_END_OF_UPDATE:
115 /* Compute gravity for particle effects. */
117 if (status == GAME_GOAL)
118 game_tilt_grav(f, gup, &tilt);
120 game_tilt_grav(f, gdn, &tilt);
122 /* Step particle, goal and jump effects. */
126 dt = 1.0f / (float) ups;
128 if (goal_e && goal_k < 1.0f)
145 /* Allocate a new ball and mark it as the current ball. */
147 if ((up = realloc(file.uv, sizeof (*up) * (file.uc + 1))))
156 /* Allocate and initialise a new item. */
158 if ((hp = realloc(file.hv, sizeof (*hp) * (file.hc + 1))))
162 v_cpy(h.p, cmd->mkitem.p);
168 file.hv[file.hc] = h;
175 /* Set up particle effects and discard the item. */
177 assert(cmd->pkitem.hi < file.hc);
179 hp = &file.hv[cmd->pkitem.hi];
182 part_burst(hp->p, f);
188 case CMD_TILT_ANGLES:
190 game_tilt_axes(&tilt, view.e);
192 tilt.rx = cmd->tiltangles.x;
193 tilt.rz = cmd->tiltangles.z;
197 /* Play the sound, then free its file name. */
201 audio_play(cmd->sound.n, cmd->sound.a);
204 * FIXME Command memory management should be done
205 * elsewhere and done properly.
213 timer = cmd->timer.t;
217 status = cmd->status.t;
221 coins = cmd->coins.n;
235 file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
239 file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
244 * Enable the goal and make sure it's fully visible if
245 * this is the first update.
251 goal_k = first_update ? 1.0f : 0.0f;
256 file.xv[cmd->swchenter.xi].e = 1;
259 case CMD_SWCH_TOGGLE:
260 file.xv[cmd->swchtoggle.xi].f = !file.xv[cmd->swchtoggle.xi].f;
264 file.xv[cmd->swchexit.xi].e = 0;
267 case CMD_UPDATES_PER_SECOND:
271 case CMD_BALL_RADIUS:
272 file.uv[curr_ball].r = cmd->ballradius.r;
275 case CMD_CLEAR_ITEMS:
284 case CMD_CLEAR_BALLS:
293 case CMD_BALL_POSITION:
294 v_cpy(file.uv[curr_ball].p, cmd->ballpos.p);
298 v_cpy(file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
299 v_cpy(file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
301 v_crs(file.uv[curr_ball].e[2],
302 file.uv[curr_ball].e[0],
303 file.uv[curr_ball].e[1]);
306 case CMD_BALL_PEND_BASIS:
307 v_cpy(file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
308 v_cpy(file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
310 v_crs(file.uv[curr_ball].E[2],
311 file.uv[curr_ball].E[0],
312 file.uv[curr_ball].E[1]);
315 case CMD_VIEW_POSITION:
316 v_cpy(view.p, cmd->viewpos.p);
319 case CMD_VIEW_CENTER:
320 v_cpy(view.c, cmd->viewcenter.c);
324 v_cpy(view.e[0], cmd->viewbasis.e[0]);
325 v_cpy(view.e[1], cmd->viewbasis.e[1]);
327 v_crs(view.e[2], view.e[0], view.e[1]);
331 case CMD_CURRENT_BALL:
332 curr_ball = cmd->currball.ui;
336 file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
339 case CMD_STEP_SIMULATION:
341 * Simulate body motion.
343 * This is done on the client side due to replay file size
344 * concerns and isn't done as part of CMD_END_OF_UPDATE to
345 * match the server state as closely as possible. Body
346 * time is still synchronised with the server on a
347 * semi-regular basis and path indices are handled through
348 * CMD_BODY_PATH, thus this code doesn't need to be as
349 * sophisticated as sol_body_step.
352 dt = cmd->stepsim.dt;
354 for (i = 0; i < file.bc; i++)
356 struct s_body *bp = file.bv + i;
357 struct s_path *pp = file.pv + bp->pi;
359 if (bp->pi >= 0 && pp->f)
367 * Note if the loaded map matches the server's
368 * expectations. (No, this doesn't actually load a map,
369 * yet. Something else somewhere else does.)
373 game_compat_map = version.x == cmd->map.version.x;
378 v_cpy(tilt.x, cmd->tiltaxes.x);
379 v_cpy(tilt.z, cmd->tiltaxes.z);
389 void game_client_step(fs_file demo_fp)
393 while ((cmdp = game_proxy_deq()))
396 * Note: cmd_put is called first here because game_run_cmd
397 * frees some command struct members.
401 cmd_put(demo_fp, cmdp);
409 /*---------------------------------------------------------------------------*/
411 int game_client_init(const char *file_name)
413 char *back_name = "", *grad_name = "";
422 if (!sol_load_gl(&file, file_name, config_get_d(CONFIG_SHADOW)))
423 return (client_state = 0);
425 reflective = sol_reflective(&file);
429 game_tilt_init(&tilt);
431 /* Initialize jump and goal states. */
439 /* Initialise the level, background, particles, fade, and view. */
448 for (i = 0; i < file.dc; i++)
450 char *k = file.av + file.dv[i].ai;
451 char *v = file.av + file.dv[i].aj;
453 if (strcmp(k, "back") == 0) back_name = v;
454 if (strcmp(k, "grad") == 0) grad_name = v;
456 if (strcmp(k, "version") == 0)
457 sscanf(v, "%d.%d", &version.x, &version.y);
461 * If the client map's version is 1, assume the map is compatible
462 * with the server. This ensures that 1.5.0 replays don't trigger
463 * bogus map compatibility warnings. (Post-1.5.0 replays will
464 * have CMD_MAP override this.)
467 game_compat_map = version.x == 1;
469 part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
474 back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
475 sol_load_gl(&back, back_name, 0);
480 void game_client_free(void)
492 /*---------------------------------------------------------------------------*/
496 return (int) (timer * 100.f);
504 int curr_status(void)
509 /*---------------------------------------------------------------------------*/
511 static void game_draw_balls(const struct s_file *fp,
512 const float *bill_M, float t)
514 float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
519 m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
520 m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
522 glPushAttrib(GL_LIGHTING_BIT);
525 glTranslatef(fp->uv[0].p[0],
526 fp->uv[0].p[1] + BALL_FUDGE,
528 glScalef(fp->uv[0].r,
533 ball_draw(ball_M, pend_M, bill_M, t);
539 static void game_draw_items(const struct s_file *fp, float t)
544 glPushAttrib(GL_LIGHTING_BIT);
546 item_push(ITEM_COIN);
548 for (hi = 0; hi < fp->hc; hi++)
550 if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
554 glTranslatef(fp->hv[hi].p[0],
557 glRotatef(r, 0.0f, 1.0f, 0.0f);
558 item_draw(&fp->hv[hi], r);
565 item_push(ITEM_SHRINK);
567 for (hi = 0; hi < fp->hc; hi++)
569 if (fp->hv[hi].t == ITEM_SHRINK)
573 glTranslatef(fp->hv[hi].p[0],
576 glRotatef(r, 0.0f, 1.0f, 0.0f);
577 item_draw(&fp->hv[hi], r);
584 item_push(ITEM_GROW);
586 for (hi = 0; hi < fp->hc; hi++)
588 if (fp->hv[hi].t == ITEM_GROW)
592 glTranslatef(fp->hv[hi].p[0],
595 glRotatef(r, 0.0f, 1.0f, 0.0f);
596 item_draw(&fp->hv[hi], r);
606 static void game_draw_goals(const struct s_file *fp, const float *M, float t)
612 /* Draw the goal particles. */
614 glEnable(GL_TEXTURE_2D);
616 for (zi = 0; zi < fp->zc; zi++)
620 glTranslatef(fp->zv[zi].p[0],
624 part_draw_goal(M, fp->zv[zi].r, goal_k, t);
629 glDisable(GL_TEXTURE_2D);
631 /* Draw the goal column. */
633 for (zi = 0; zi < fp->zc; zi++)
637 glTranslatef(fp->zv[zi].p[0],
641 glScalef(fp->zv[zi].r,
652 static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
656 glEnable(GL_TEXTURE_2D);
658 for (ji = 0; ji < fp->jc; ji++)
662 glTranslatef(fp->jv[ji].p[0],
666 part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
671 glDisable(GL_TEXTURE_2D);
673 for (ji = 0; ji < fp->jc; ji++)
677 glTranslatef(fp->jv[ji].p[0],
680 glScalef(fp->jv[ji].r,
690 static void game_draw_swchs(const struct s_file *fp)
694 for (xi = 0; xi < fp->xc; xi++)
701 glTranslatef(fp->xv[xi].p[0],
704 glScalef(fp->xv[xi].r,
708 swch_draw(fp->xv[xi].f, fp->xv[xi].e);
714 /*---------------------------------------------------------------------------*/
716 static void game_draw_tilt(int d)
718 const float *ball_p = file.uv->p;
720 /* Rotate the environment about the position of the ball. */
722 glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
723 glRotatef(-tilt.rz * d, tilt.z[0], tilt.z[1], tilt.z[2]);
724 glRotatef(-tilt.rx * d, tilt.x[0], tilt.x[1], tilt.x[2]);
725 glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
728 static void game_refl_all(void)
734 /* Draw the floor. */
741 /*---------------------------------------------------------------------------*/
743 static void game_draw_light(void)
745 const float light_p[2][4] = {
746 { -8.0f, +32.0f, -8.0f, 0.0f },
747 { +8.0f, +32.0f, +8.0f, 0.0f },
749 const float light_c[2][4] = {
750 { 1.0f, 0.8f, 0.8f, 1.0f },
751 { 0.8f, 1.0f, 0.8f, 1.0f },
754 /* Configure the lighting. */
757 glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
758 glLightfv(GL_LIGHT0, GL_DIFFUSE, light_c[0]);
759 glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
762 glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
763 glLightfv(GL_LIGHT1, GL_DIFFUSE, light_c[1]);
764 glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
767 static void game_draw_back(int pose, int d, float t)
769 if (pose == POSE_BALL)
776 glRotatef(tilt.rz * 2, tilt.z[0], tilt.z[1], tilt.z[2]);
777 glRotatef(tilt.rx * 2, tilt.x[0], tilt.x[1], tilt.x[2]);
780 glTranslatef(view.p[0], view.p[1] * d, view.p[2]);
782 if (config_get_d(CONFIG_BACKGROUND))
784 /* Draw all background layers back to front. */
786 sol_back(&back, BACK_DIST, FAR_DIST, t);
788 sol_back(&back, 0, BACK_DIST, t);
795 static void game_clip_refl(int d)
797 /* Fudge to eliminate the floor from reflection. */
799 GLdouble e[4], k = -0.00001;
806 glClipPlane(GL_CLIP_PLANE0, e);
809 static void game_clip_ball(int d, const float *p)
811 GLdouble r, c[3], pz[4], nz[4];
813 /* Compute the plane giving the front of the ball, as seen from view.p. */
819 pz[0] = view.p[0] - c[0];
820 pz[1] = view.p[1] - c[1];
821 pz[2] = view.p[2] - c[2];
823 r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
828 pz[3] = -(pz[0] * c[0] +
832 /* Find the plane giving the back of the ball, as seen from view.p. */
839 /* Reflect these planes as necessary, and store them in the GL state. */
844 glClipPlane(GL_CLIP_PLANE1, nz);
845 glClipPlane(GL_CLIP_PLANE2, pz);
848 static void game_draw_fore(int pose, const float *M, int d, float t)
850 const float *ball_p = file.uv->p;
851 const float ball_r = file.uv->r;
855 /* Rotate the environment about the position of the ball. */
859 /* Compute clipping planes for reflection and ball facing. */
862 game_clip_ball(d, ball_p);
865 glEnable(GL_CLIP_PLANE0);
870 sol_draw(&file, 0, 1);
874 /* Draw the coins. */
876 game_draw_items(&file, t);
878 /* Draw the floor. */
880 sol_draw(&file, 0, 1);
886 /* Draw the ball shadow. */
888 if (d > 0 && config_get_d(CONFIG_SHADOW))
890 shad_draw_set(ball_p, ball_r);
897 game_draw_balls(&file, M, t);
902 /* Draw the particles and light columns. */
904 glEnable(GL_COLOR_MATERIAL);
905 glDisable(GL_LIGHTING);
906 glDepthMask(GL_FALSE);
908 glColor3f(1.0f, 1.0f, 1.0f);
910 sol_bill(&file, M, t);
911 part_draw_coin(M, t);
913 glDisable(GL_TEXTURE_2D);
915 game_draw_goals(&file, M, t);
916 game_draw_jumps(&file, M, t);
917 game_draw_swchs(&file);
919 glEnable(GL_TEXTURE_2D);
921 glColor3f(1.0f, 1.0f, 1.0f);
923 glDepthMask(GL_TRUE);
924 glEnable(GL_LIGHTING);
925 glDisable(GL_COLOR_MATERIAL);
928 glDisable(GL_CLIP_PLANE0);
933 void game_draw(int pose, float t)
935 float fov = (float) config_get_d(CONFIG_VIEW_FOV);
937 if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
941 video_push_persp(fov, 0.1f, FAR_DIST);
944 float T[16], U[16], M[16], v[3];
946 /* Compute direct and reflected view bases. */
952 m_view(T, view.c, view.p, view.e[1]);
953 m_view(U, view.c, v, view.e[1]);
957 /* Apply the current view. */
959 v_sub(v, view.c, view.p);
961 glTranslatef(0.f, 0.f, -v_len(v));
963 glTranslatef(-view.c[0], -view.c[1], -view.c[2]);
965 if (reflective && config_get_d(CONFIG_REFLECTION))
967 glEnable(GL_STENCIL_TEST);
969 /* Draw the mirrors only into the stencil buffer. */
971 glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
972 glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
973 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
974 glDepthMask(GL_FALSE);
978 glDepthMask(GL_TRUE);
979 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
980 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
981 glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
983 /* Draw the scene reflected into color and depth buffers. */
988 glScalef(+1.0f, -1.0f, +1.0f);
991 game_draw_back(pose, -1, t);
992 game_draw_fore(pose, U, -1, t);
997 glDisable(GL_STENCIL_TEST);
1000 /* Draw the scene normally. */
1006 if (config_get_d(CONFIG_REFLECTION))
1008 /* Draw background while preserving reflections. */
1010 glEnable(GL_STENCIL_TEST);
1012 glStencilFunc(GL_NOTEQUAL, 1, 0xFFFFFFFF);
1013 game_draw_back(pose, +1, t);
1015 glDisable(GL_STENCIL_TEST);
1023 /* Draw background. */
1025 game_draw_back(pose, +1, t);
1028 * Draw mirrors, first fully opaque with a custom
1029 * material color, then blending normally with the
1030 * opaque surfaces using their original material
1031 * properties. (Keeps background from showing
1035 glEnable(GL_COLOR_MATERIAL);
1037 glColor4f(0.0, 0.0, 0.05, 1.0);
1039 glColor4f(1.0, 1.0, 1.0, 1.0);
1041 glDisable(GL_COLOR_MATERIAL);
1048 game_draw_back(pose, +1, t);
1052 game_draw_fore(pose, T, +1, t);
1057 /* Draw the fade overlay. */
1063 /*---------------------------------------------------------------------------*/
1065 void game_look(float phi, float theta)
1067 view.c[0] = view.p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
1068 view.c[1] = view.p[1] + fsinf(V_RAD(phi));
1069 view.c[2] = view.p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
1072 /*---------------------------------------------------------------------------*/
1074 void game_kill_fade(void)
1080 void game_step_fade(float dt)
1082 if ((fade_k < 1.0f && fade_d > 0.0f) ||
1083 (fade_k > 0.0f && fade_d < 0.0f))
1084 fade_k += fade_d * dt;
1098 void game_fade(float d)
1103 void game_client_fly(float k)
1105 game_view_fly(&view, &file, k);
1108 /*---------------------------------------------------------------------------*/