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"
35 #include "game_draw.h"
39 /*---------------------------------------------------------------------------*/
41 int game_compat_map; /* Client/server map compat flag */
43 /*---------------------------------------------------------------------------*/
45 static struct game_draw dr;
47 static float timer = 0.0f; /* Clock time */
48 static int status = GAME_NONE; /* Outcome of the game */
49 static int coins = 0; /* Collected coins */
51 static int ups; /* Updates per second */
52 static int first_update; /* First update flag */
53 static int curr_ball; /* Current ball index */
58 } version; /* Current map version */
60 /*---------------------------------------------------------------------------*/
62 static void game_run_cmd(const union cmd *cmd)
64 static const float gup[] = { 0.0f, +9.8f, 0.0f };
65 static const float gdn[] = { 0.0f, -9.8f, 0.0f };
68 * Neverball <= 1.5.1 does not send explicit tilt axes, rotation
69 * happens directly around view vectors. So for compatibility if
70 * at the time of receiving tilt angles we have not yet received
71 * the tilt axes, we use the view vectors.
73 static int got_tilt_axes;
87 case CMD_END_OF_UPDATE:
97 /* Compute gravity for particle effects. */
99 if (status == GAME_GOAL)
100 game_tilt_grav(v, gup, &dr.tilt);
102 game_tilt_grav(v, gdn, &dr.tilt);
104 /* Step particle, goal and jump effects. */
108 dt = 1.0f / (float) ups;
110 if (dr.goal_e && dr.goal_k < 1.0f)
117 if (1.0f < dr.jump_dt)
127 /* Allocate a new ball and mark it as the current ball. */
129 if ((up = realloc(dr.file.uv, sizeof (*up) * (dr.file.uc + 1))))
132 curr_ball = dr.file.uc;
138 /* Allocate and initialise a new item. */
140 if ((hp = realloc(dr.file.hv, sizeof (*hp) * (dr.file.hc + 1))))
144 v_cpy(h.p, cmd->mkitem.p);
150 dr.file.hv[dr.file.hc] = h;
157 /* Set up particle effects and discard the item. */
159 assert(cmd->pkitem.hi < dr.file.hc);
161 hp = &dr.file.hv[cmd->pkitem.hi];
164 part_burst(hp->p, v);
170 case CMD_TILT_ANGLES:
172 game_tilt_axes(&dr.tilt, dr.view.e);
174 dr.tilt.rx = cmd->tiltangles.x;
175 dr.tilt.rz = cmd->tiltangles.z;
179 /* Play the sound, then free its dr.file name. */
183 audio_play(cmd->sound.n, cmd->sound.a);
186 * FIXME Command memory management should be done
187 * elsewhere and done properly.
195 timer = cmd->timer.t;
199 status = cmd->status.t;
203 coins = cmd->coins.n;
217 dr.file.bv[cmd->bodypath.bi].pi = cmd->bodypath.pi;
221 dr.file.bv[cmd->bodytime.bi].t = cmd->bodytime.t;
226 * Enable the goal and make sure it's fully visible if
227 * this is the first update.
233 dr.goal_k = first_update ? 1.0f : 0.0f;
238 dr.file.xv[cmd->swchenter.xi].e = 1;
241 case CMD_SWCH_TOGGLE:
242 dr.file.xv[cmd->swchtoggle.xi].f = !dr.file.xv[cmd->swchtoggle.xi].f;
246 dr.file.xv[cmd->swchexit.xi].e = 0;
249 case CMD_UPDATES_PER_SECOND:
253 case CMD_BALL_RADIUS:
254 dr.file.uv[curr_ball].r = cmd->ballradius.r;
257 case CMD_CLEAR_ITEMS:
266 case CMD_CLEAR_BALLS:
275 case CMD_BALL_POSITION:
276 v_cpy(dr.file.uv[curr_ball].p, cmd->ballpos.p);
280 v_cpy(dr.file.uv[curr_ball].e[0], cmd->ballbasis.e[0]);
281 v_cpy(dr.file.uv[curr_ball].e[1], cmd->ballbasis.e[1]);
283 v_crs(dr.file.uv[curr_ball].e[2],
284 dr.file.uv[curr_ball].e[0],
285 dr.file.uv[curr_ball].e[1]);
288 case CMD_BALL_PEND_BASIS:
289 v_cpy(dr.file.uv[curr_ball].E[0], cmd->ballpendbasis.E[0]);
290 v_cpy(dr.file.uv[curr_ball].E[1], cmd->ballpendbasis.E[1]);
292 v_crs(dr.file.uv[curr_ball].E[2],
293 dr.file.uv[curr_ball].E[0],
294 dr.file.uv[curr_ball].E[1]);
297 case CMD_VIEW_POSITION:
298 v_cpy(dr.view.p, cmd->viewpos.p);
301 case CMD_VIEW_CENTER:
302 v_cpy(dr.view.c, cmd->viewcenter.c);
306 v_cpy(dr.view.e[0], cmd->viewbasis.e[0]);
307 v_cpy(dr.view.e[1], cmd->viewbasis.e[1]);
309 v_crs(dr.view.e[2], dr.view.e[0], dr.view.e[1]);
313 case CMD_CURRENT_BALL:
314 curr_ball = cmd->currball.ui;
318 dr.file.pv[cmd->pathflag.pi].f = cmd->pathflag.f;
321 case CMD_STEP_SIMULATION:
323 * Simulate body motion.
325 * This is done on the client side due to replay file size
326 * concerns and isn't done as part of CMD_END_OF_UPDATE to
327 * match the server state as closely as possible. Body
328 * time is still synchronised with the server on a
329 * semi-regular basis and path indices are handled through
330 * CMD_BODY_PATH, thus this code doesn't need to be as
331 * sophisticated as sol_body_step.
334 dt = cmd->stepsim.dt;
336 for (i = 0; i < dr.file.bc; i++)
338 struct s_body *bp = dr.file.bv + i;
339 struct s_path *pp = dr.file.pv + bp->pi;
341 if (bp->pi >= 0 && pp->f)
348 * Note a version (mis-)match between the loaded map and
349 * what the server has. (This doesn't actually load a
354 game_compat_map = version.x == cmd->map.version.x;
359 v_cpy(dr.tilt.x, cmd->tiltaxes.x);
360 v_cpy(dr.tilt.z, cmd->tiltaxes.z);
370 void game_client_sync(fs_file demo_fp)
374 while ((cmdp = game_proxy_deq()))
377 * Note: cmd_put is called first here because game_run_cmd
378 * frees some command struct members.
382 cmd_put(demo_fp, cmdp);
390 /*---------------------------------------------------------------------------*/
392 int game_client_init(const char *file_name)
394 char *back_name = "", *grad_name = "";
403 if (!sol_load_gl(&dr.file, file_name, config_get_d(CONFIG_SHADOW)))
404 return (dr.state = 0);
406 dr.reflective = sol_reflective(&dr.file);
410 game_tilt_init(&dr.tilt);
412 /* Initialize jump and goal states. */
420 /* Initialise the level, background, particles, fade, and view. */
429 for (i = 0; i < dr.file.dc; i++)
431 char *k = dr.file.av + dr.file.dv[i].ai;
432 char *v = dr.file.av + dr.file.dv[i].aj;
434 if (strcmp(k, "back") == 0) back_name = v;
435 if (strcmp(k, "grad") == 0) grad_name = v;
437 if (strcmp(k, "version") == 0)
438 sscanf(v, "%d.%d", &version.x, &version.y);
442 * If the version of the loaded map is 1, assume we have a version
443 * match with the server. In this way 1.5.0 replays don't trigger
444 * bogus map compatibility warnings. Post-1.5.0 replays will have
445 * CMD_MAP override this.
448 game_compat_map = version.x == 1;
450 part_reset(GOAL_HEIGHT, JUMP_HEIGHT);
455 back_init(grad_name, config_get_d(CONFIG_GEOMETRY));
456 sol_load_gl(&dr.back, back_name, 0);
461 void game_client_free(void)
466 sol_free_gl(&dr.file);
467 sol_free_gl(&dr.back);
473 /*---------------------------------------------------------------------------*/
475 void game_client_draw(int pose, float t)
477 game_draw(&dr, pose, t);
480 /*---------------------------------------------------------------------------*/
484 return (int) (timer * 100.f);
492 int curr_status(void)
497 /*---------------------------------------------------------------------------*/
500 void game_look(float phi, float theta)
502 dr.view.c[0] = dr.view.p[0] + fsinf(V_RAD(theta)) * fcosf(V_RAD(phi));
503 dr.view.c[1] = dr.view.p[1] + fsinf(V_RAD(phi));
504 dr.view.c[2] = dr.view.p[2] - fcosf(V_RAD(theta)) * fcosf(V_RAD(phi));
507 /*---------------------------------------------------------------------------*/
509 void game_kill_fade(void)
515 void game_step_fade(float dt)
517 if ((dr.fade_k < 1.0f && dr.fade_d > 0.0f) ||
518 (dr.fade_k > 0.0f && dr.fade_d < 0.0f))
519 dr.fade_k += dr.fade_d * dt;
521 if (dr.fade_k < 0.0f)
526 if (dr.fade_k > 1.0f)
533 void game_fade(float d)
538 /*---------------------------------------------------------------------------*/
540 void game_client_fly(float k)
542 game_view_fly(&dr.view, &dr.file, k);
545 /*---------------------------------------------------------------------------*/