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 float game_rx; /* Floor rotation about X axis */
56 static float game_rz; /* Floor rotation about Z axis */
58 static float view_a; /* Ideal view rotation about Y axis */
59 static float view_fov; /* Field of view */
61 static float view_c[3]; /* Current view center */
62 static float view_p[3]; /* Current view position */
63 static float view_e[3][3]; /* Current view reference frame */
65 static int coins = 0; /* Collected coins */
66 static int goal_e = 0; /* Goal enabled flag */
67 static float goal_k = 0; /* Goal animation */
69 static int jump_e = 1; /* Jumping enabled flag */
70 static int jump_b = 0; /* Jump-in-progress flag */
71 static float jump_dt; /* Jump duration */
73 static float fade_k = 0.0; /* Fade in/out level */
74 static float fade_d = 0.0; /* Fade in/out direction */
76 static int ups; /* Updates per second */
77 static int first_update; /* First update flag */
78 static int curr_ball; /* Current ball index */
83 } version; /* Current map version */
85 /*---------------------------------------------------------------------------*/
87 static void game_run_cmd(const union cmd *cmd)
89 static const float gup[] = { 0.0f, +9.8f, 0.0f };
90 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
104 case CMD_END_OF_UPDATE:
111 /* Compute gravity for particle effects. */
113 if (status == GAME_GOAL)
114 game_comp_grav(f, gup, view_a, game_rx, game_rz);
116 game_comp_grav(f, gdn, view_a, game_rx, game_rz);
118 /* Step particle, goal and jump effects. */
122 dt = 1.0f / (float) ups;
124 if (goal_e && goal_k < 1.0f)
141 /* Allocate a new ball and mark it as the current ball. */
143 if ((up = realloc(file.uv, sizeof (*up) * (file.uc + 1))))
152 /* Allocate and initialise a new item. */
154 if ((hp = realloc(file.hv, sizeof (*hp) * (file.hc + 1))))
158 v_cpy(h.p, cmd->mkitem.p);
164 file.hv[file.hc] = h;
171 /* Set up particle effects and discard the item. */
173 assert(cmd->pkitem.hi < file.hc);
175 hp = &file.hv[cmd->pkitem.hi];
178 part_burst(hp->p, f);
185 game_rx = cmd->rotate.x;
186 game_rz = cmd->rotate.z;
190 /* Play the sound, then free its file name. */
194 audio_play(cmd->sound.n, cmd->sound.a);
197 * FIXME Command memory management should be done
198 * elsewhere and done properly.
206 timer = cmd->timer.t;
210 status = cmd->status.t;
214 coins = cmd->coins.n;
228 file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
232 file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
237 * Enable the goal and make sure it's fully visible if
238 * this is the first update.
244 goal_k = first_update ? 1.0f : 0.0f;
249 file.xv[cmd->swchenter.xi].e = 1;
252 case CMD_SWCH_TOGGLE:
253 file.xv[cmd->swchtoggle.xi].f = !file.xv[cmd->swchtoggle.xi].f;
257 file.xv[cmd->swchexit.xi].e = 0;
260 case CMD_UPDATES_PER_SECOND:
264 case CMD_BALL_RADIUS:
265 file.uv[curr_ball].r = cmd->ballradius.r;
268 case CMD_CLEAR_ITEMS:
277 case CMD_CLEAR_BALLS:
286 case CMD_BALL_POSITION:
287 v_cpy(file.uv[curr_ball].p, cmd->ballpos.p);
291 v_cpy(file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
292 v_cpy(file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
294 v_crs(file.uv[curr_ball].e[2],
295 file.uv[curr_ball].e[0],
296 file.uv[curr_ball].e[1]);
299 case CMD_BALL_PEND_BASIS:
300 v_cpy(file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
301 v_cpy(file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
303 v_crs(file.uv[curr_ball].E[2],
304 file.uv[curr_ball].E[0],
305 file.uv[curr_ball].E[1]);
308 case CMD_VIEW_POSITION:
309 v_cpy(view_p, cmd->viewpos.p);
312 case CMD_VIEW_CENTER:
313 v_cpy(view_c, cmd->viewcenter.c);
317 v_cpy(view_e[0], cmd->viewbasis.e[0]);
318 v_cpy(view_e[1], cmd->viewbasis.e[1]);
320 v_crs(view_e[2], view_e[0], view_e[1]);
322 view_a = V_DEG(fatan2f(view_e[2][0], view_e[2][2]));
326 case CMD_CURRENT_BALL:
327 curr_ball = cmd->currball.ui;
331 file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
334 case CMD_STEP_SIMULATION:
336 * Simulate body motion.
338 * This is done on the client side due to replay file size
339 * concerns and isn't done as part of CMD_END_OF_UPDATE to
340 * match the server state as closely as possible. Body
341 * time is still synchronised with the server on a
342 * semi-regular basis and path indices are handled through
343 * CMD_BODY_PATH, thus this code doesn't need to be as
344 * sophisticated as sol_body_step.
347 dt = cmd->stepsim.dt;
349 for (i = 0; i < file.bc; i++)
351 struct s_body *bp = file.bv + i;
352 struct s_path *pp = file.pv + bp->pi;
354 if (bp->pi >= 0 && pp->f)
362 * Note if the loaded map matches the server's
363 * expectations. (No, this doesn't actually load a map,
364 * yet. Something else somewhere else does.)
368 game_compat_map = version.x == cmd->map.version.x;
378 void game_client_step(FILE *demo_fp)
382 while ((cmdp = game_proxy_deq()))
385 * Note: cmd_put is called first here because game_run_cmd
386 * frees some command struct members.
390 cmd_put(demo_fp, cmdp);
398 /*---------------------------------------------------------------------------*/
400 int game_client_init(const char *file_name)
402 char *back_name = NULL, *grad_name = NULL;
411 if (!sol_load_gl(&file, config_data(file_name),
412 config_get_d(CONFIG_TEXTURES),
413 config_get_d(CONFIG_SHADOW)))
414 return (client_state = 0);
416 reflective = sol_reflective(&file);
423 /* Initialize jump and goal states. */
431 /* Initialise the level, background, particles, fade, and view. */
440 for (i = 0; i < file.dc; i++)
442 char *k = file.av + file.dv[i].ai;
443 char *v = file.av + file.dv[i].aj;
445 if (strcmp(k, "back") == 0) back_name = v;
446 if (strcmp(k, "grad") == 0) grad_name = v;
448 if (strcmp(k, "version") == 0)
449 sscanf(v, "%d.%d", &version.x, &version.y);
453 * Work around 1.5.0 replays that trigger bogus replay
454 * compatibility warnings: if the client map's version is 1,
455 * assume the map is compatible with the server. Post-1.5.0
456 * replays will have CMD_MAP override this.
459 game_compat_map = version.x == 1;
461 part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
463 view_fov = (float) config_get_d(CONFIG_VIEW_FOV);
468 back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
469 sol_load_gl(&back, config_data(back_name),
470 config_get_d(CONFIG_TEXTURES), 0);
475 void game_client_free(void)
487 /*---------------------------------------------------------------------------*/
491 return (int) (timer * 100.f);
499 int curr_status(void)
504 /*---------------------------------------------------------------------------*/
506 static void game_draw_balls(const struct s_file *fp,
507 const float *bill_M, float t)
509 float c[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
514 m_basis(ball_M, fp->uv[0].e[0], fp->uv[0].e[1], fp->uv[0].e[2]);
515 m_basis(pend_M, fp->uv[0].E[0], fp->uv[0].E[1], fp->uv[0].E[2]);
517 glPushAttrib(GL_LIGHTING_BIT);
520 glTranslatef(fp->uv[0].p[0],
521 fp->uv[0].p[1] + BALL_FUDGE,
523 glScalef(fp->uv[0].r,
528 ball_draw(ball_M, pend_M, bill_M, t);
534 static void game_draw_items(const struct s_file *fp, float t)
539 glPushAttrib(GL_LIGHTING_BIT);
541 item_push(ITEM_COIN);
543 for (hi = 0; hi < fp->hc; hi++)
545 if (fp->hv[hi].t == ITEM_COIN && fp->hv[hi].n > 0)
549 glTranslatef(fp->hv[hi].p[0],
552 glRotatef(r, 0.0f, 1.0f, 0.0f);
553 item_draw(&fp->hv[hi], r);
560 item_push(ITEM_SHRINK);
562 for (hi = 0; hi < fp->hc; hi++)
564 if (fp->hv[hi].t == ITEM_SHRINK)
568 glTranslatef(fp->hv[hi].p[0],
571 glRotatef(r, 0.0f, 1.0f, 0.0f);
572 item_draw(&fp->hv[hi], r);
579 item_push(ITEM_GROW);
581 for (hi = 0; hi < fp->hc; hi++)
583 if (fp->hv[hi].t == ITEM_GROW)
587 glTranslatef(fp->hv[hi].p[0],
590 glRotatef(r, 0.0f, 1.0f, 0.0f);
591 item_draw(&fp->hv[hi], r);
601 static void game_draw_goals(const struct s_file *fp, const float *M, float t)
607 /* Draw the goal particles. */
609 glEnable(GL_TEXTURE_2D);
611 for (zi = 0; zi < fp->zc; zi++)
615 glTranslatef(fp->zv[zi].p[0],
619 part_draw_goal(M, fp->zv[zi].r, goal_k, t);
624 glDisable(GL_TEXTURE_2D);
626 /* Draw the goal column. */
628 for (zi = 0; zi < fp->zc; zi++)
632 glTranslatef(fp->zv[zi].p[0],
636 glScalef(fp->zv[zi].r,
647 static void game_draw_jumps(const struct s_file *fp, const float *M, float t)
651 glEnable(GL_TEXTURE_2D);
653 for (ji = 0; ji < fp->jc; ji++)
657 glTranslatef(fp->jv[ji].p[0],
661 part_draw_jump(M, fp->jv[ji].r, 1.0f, t);
666 glDisable(GL_TEXTURE_2D);
668 for (ji = 0; ji < fp->jc; ji++)
672 glTranslatef(fp->jv[ji].p[0],
675 glScalef(fp->jv[ji].r,
685 static void game_draw_swchs(const struct s_file *fp)
689 for (xi = 0; xi < fp->xc; xi++)
696 glTranslatef(fp->xv[xi].p[0],
699 glScalef(fp->xv[xi].r,
703 swch_draw(fp->xv[xi].f, fp->xv[xi].e);
709 /*---------------------------------------------------------------------------*/
711 static void game_draw_tilt(int d)
713 const float *ball_p = file.uv->p;
715 /* Rotate the environment about the position of the ball. */
717 glTranslatef(+ball_p[0], +ball_p[1] * d, +ball_p[2]);
718 glRotatef(-game_rz * d, view_e[2][0], view_e[2][1], view_e[2][2]);
719 glRotatef(-game_rx * d, view_e[0][0], view_e[0][1], view_e[0][2]);
720 glTranslatef(-ball_p[0], -ball_p[1] * d, -ball_p[2]);
723 static void game_refl_all(void)
729 /* Draw the floor. */
736 /*---------------------------------------------------------------------------*/
738 static void game_draw_light(void)
740 const float light_p[2][4] = {
741 { -8.0f, +32.0f, -8.0f, 0.0f },
742 { +8.0f, +32.0f, +8.0f, 0.0f },
744 const float light_c[2][4] = {
745 { 1.0f, 0.8f, 0.8f, 1.0f },
746 { 0.8f, 1.0f, 0.8f, 1.0f },
749 /* Configure the lighting. */
752 glLightfv(GL_LIGHT0, GL_POSITION, light_p[0]);
753 glLightfv(GL_LIGHT0, GL_DIFFUSE, light_c[0]);
754 glLightfv(GL_LIGHT0, GL_SPECULAR, light_c[0]);
757 glLightfv(GL_LIGHT1, GL_POSITION, light_p[1]);
758 glLightfv(GL_LIGHT1, GL_DIFFUSE, light_c[1]);
759 glLightfv(GL_LIGHT1, GL_SPECULAR, light_c[1]);
762 static void game_draw_back(int pose, int d, float t)
768 glRotatef(game_rz * 2, view_e[2][0], view_e[2][1], view_e[2][2]);
769 glRotatef(game_rx * 2, view_e[0][0], view_e[0][1], view_e[0][2]);
772 glTranslatef(view_p[0], view_p[1] * d, view_p[2]);
774 if (config_get_d(CONFIG_BACKGROUND))
776 /* Draw all background layers back to front. */
778 sol_back(&back, BACK_DIST, FAR_DIST, t);
780 sol_back(&back, 0, BACK_DIST, t);
787 static void game_clip_refl(int d)
789 /* Fudge to eliminate the floor from reflection. */
791 GLdouble e[4], k = -0.00001;
798 glClipPlane(GL_CLIP_PLANE0, e);
801 static void game_clip_ball(int d, const float *p)
803 GLdouble r, c[3], pz[4], nz[4];
805 /* Compute the plane giving the front of the ball, as seen from view_p. */
811 pz[0] = view_p[0] - c[0];
812 pz[1] = view_p[1] - c[1];
813 pz[2] = view_p[2] - c[2];
815 r = sqrt(pz[0] * pz[0] + pz[1] * pz[1] + pz[2] * pz[2]);
820 pz[3] = -(pz[0] * c[0] +
824 /* Find the plane giving the back of the ball, as seen from view_p. */
831 /* Reflect these planes as necessary, and store them in the GL state. */
836 glClipPlane(GL_CLIP_PLANE1, nz);
837 glClipPlane(GL_CLIP_PLANE2, pz);
840 static void game_draw_fore(int pose, const float *M, int d, float t)
842 const float *ball_p = file.uv->p;
843 const float ball_r = file.uv->r;
847 /* Rotate the environment about the position of the ball. */
851 /* Compute clipping planes for reflection and ball facing. */
854 game_clip_ball(d, ball_p);
857 glEnable(GL_CLIP_PLANE0);
860 sol_draw(&file, 0, 1);
863 /* Draw the coins. */
865 game_draw_items(&file, t);
867 /* Draw the floor. */
869 sol_draw(&file, 0, 1);
871 /* Draw the ball shadow. */
873 if (d > 0 && config_get_d(CONFIG_SHADOW))
875 shad_draw_set(ball_p, ball_r);
882 game_draw_balls(&file, M, t);
885 /* Draw the particles and light columns. */
887 glEnable(GL_COLOR_MATERIAL);
888 glDisable(GL_LIGHTING);
889 glDepthMask(GL_FALSE);
891 glColor3f(1.0f, 1.0f, 1.0f);
893 sol_bill(&file, M, t);
894 part_draw_coin(M, t);
896 glDisable(GL_TEXTURE_2D);
898 game_draw_goals(&file, M, t);
899 game_draw_jumps(&file, M, t);
900 game_draw_swchs(&file);
902 glEnable(GL_TEXTURE_2D);
904 glColor3f(1.0f, 1.0f, 1.0f);
906 glDepthMask(GL_TRUE);
907 glEnable(GL_LIGHTING);
908 glDisable(GL_COLOR_MATERIAL);
911 glDisable(GL_CLIP_PLANE0);
916 void game_draw(int pose, float t)
918 float fov = view_fov;
920 if (jump_b) fov *= 2.f * fabsf(jump_dt - 0.5);
924 video_push_persp(fov, 0.1f, FAR_DIST);
927 float T[16], U[16], M[16], v[3];
929 /* Compute direct and reflected view bases. */
935 m_view(T, view_c, view_p, view_e[1]);
936 m_view(U, view_c, v, view_e[1]);
940 /* Apply current the view. */
942 v_sub(v, view_c, view_p);
944 glTranslatef(0.f, 0.f, -v_len(v));
946 glTranslatef(-view_c[0], -view_c[1], -view_c[2]);
948 if (reflective && config_get_d(CONFIG_REFLECTION))
950 glEnable(GL_STENCIL_TEST);
952 /* Draw the mirrors only into the stencil buffer. */
954 glStencilFunc(GL_ALWAYS, 1, 0xFFFFFFFF);
955 glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
956 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
957 glDepthMask(GL_FALSE);
961 glDepthMask(GL_TRUE);
962 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
963 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
964 glStencilFunc(GL_EQUAL, 1, 0xFFFFFFFF);
966 /* Draw the scene reflected into color and depth buffers. */
971 glScalef(+1.0f, -1.0f, +1.0f);
974 game_draw_back(pose, -1, t);
975 game_draw_fore(pose, U, -1, t);
980 glDisable(GL_STENCIL_TEST);
983 /* Draw the scene normally. */
987 game_draw_back(pose, +1, t);
988 game_draw_fore(pose, T, +1, t);
993 /* Draw the fade overlay. */
999 /*---------------------------------------------------------------------------*/
1001 void game_look(float phi, float theta)
1003 view_c[0] = view_p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
1004 view_c[1] = view_p[1] + fsinf(V_RAD(phi));
1005 view_c[2] = view_p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
1008 /*---------------------------------------------------------------------------*/
1010 void game_kill_fade(void)
1016 void game_step_fade(float dt)
1018 if ((fade_k < 1.0f && fade_d > 0.0f) ||
1019 (fade_k > 0.0f && fade_d < 0.0f))
1020 fade_k += fade_d * dt;
1034 void game_fade(float d)
1039 /*---------------------------------------------------------------------------*/
1041 const struct s_file *game_client_file(void)
1046 /*---------------------------------------------------------------------------*/