static void game_run_cmd(const union cmd *cmd) { if (gd.state) { struct game_view *view = &gl.view[CURR]; struct game_tilt *tilt = &gl.tilt[CURR]; struct s_vary *vary = &gd.vary; struct v_item *hp; float v[4]; float dt; int idx; if (cs.next_update) { game_lerp_copy(&gl); cs.next_update = 0; } switch (cmd->type) { case CMD_END_OF_UPDATE: cs.got_tilt_axes = 0; cs.next_update = 1; if (cs.first_update) { game_lerp_copy(&gl); /* Hack to sync state before the next update. */ game_lerp_apply(&gl, &gd); cs.first_update = 0; break; } /* Compute gravity for particle effects. */ if (status == GAME_GOAL) game_tilt_grav(v, GRAVITY_UP, tilt); else game_tilt_grav(v, GRAVITY_DN, tilt); /* Step particle, goal and jump effects. */ if (cs.ups > 0) { dt = 1.0f / cs.ups; if (gd.goal_e && gl.goal_k[CURR] < 1.0f) gl.goal_k[CURR] += dt; if (gd.jump_b) { gl.jump_dt[CURR] += dt; if (gl.jump_dt[PREV] >= 1.0f) gd.jump_b = 0; } part_step(v, dt); } break; case CMD_MAKE_BALL: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_MAKE_ITEM: /* Allocate and initialize a new item. */ if ((hp = realloc(vary->hv, sizeof (*hp) * (vary->hc + 1)))) { vary->hv = hp; hp = &vary->hv[vary->hc]; vary->hc++; memset(hp, 0, sizeof (*hp)); v_cpy(hp->p, cmd->mkitem.p); hp->t = cmd->mkitem.t; hp->n = cmd->mkitem.n; } break; case CMD_PICK_ITEM: /* Set up particle effects and discard the item. */ if ((idx = cmd->pkitem.hi) >= 0 && idx < vary->hc) { hp = &vary->hv[idx]; item_color(hp, v); part_burst(hp->p, v); hp->t = ITEM_NONE; } break; case CMD_TILT_ANGLES: if (!cs.got_tilt_axes) { /* * Neverball <= 1.5.1 does not send explicit tilt * axes, rotation happens directly around view * vectors. So for compatibility if at the time of * receiving tilt angles we have not yet received the * tilt axes, we use the view vectors. */ game_tilt_axes(tilt, view->e); } tilt->rx = cmd->tiltangles.x; tilt->rz = cmd->tiltangles.z; break; case CMD_SOUND: /* Play the sound. */ if (cmd->sound.n) audio_play(cmd->sound.n, cmd->sound.a); 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: gd.jump_b = 1; gd.jump_e = 0; gl.jump_dt[PREV] = 0.0f; gl.jump_dt[CURR] = 0.0f; break; case CMD_JUMP_EXIT: gd.jump_e = 1; break; case CMD_MOVE_PATH: case CMD_MOVE_TIME: case CMD_BODY_PATH: case CMD_BODY_TIME: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_GOAL_OPEN: /* * Enable the goal and make sure it's fully visible if * this is the first update. */ if (!gd.goal_e) { gd.goal_e = 1; gl.goal_k[CURR] = cs.first_update ? 1.0f : 0.0f; } break; case CMD_SWCH_ENTER: if ((idx = cmd->swchenter.xi) >= 0 && idx < vary->xc) vary->xv[idx].e = 1; break; case CMD_SWCH_TOGGLE: if ((idx = cmd->swchtoggle.xi) >= 0 && idx < vary->xc) vary->xv[idx].f = !vary->xv[idx].f; break; case CMD_SWCH_EXIT: if ((idx = cmd->swchexit.xi) >= 0 && idx < vary->xc) vary->xv[idx].e = 0; break; case CMD_UPDATES_PER_SECOND: cs.ups = cmd->ups.n; break; case CMD_BALL_RADIUS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_CLEAR_ITEMS: free(vary->hv); vary->hv = NULL; vary->hc = 0; break; case CMD_CLEAR_BALLS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_POSITION: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_BASIS: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_BALL_PEND_BASIS: sol_lerp_cmd(&gl.lerp, &cs, cmd); 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]); break; case CMD_CURRENT_BALL: if ((idx = cmd->currball.ui) >= 0 && idx < vary->uc) cs.curr_ball = idx; break; case CMD_PATH_FLAG: if ((idx = cmd->pathflag.pi) >= 0 && idx < vary->pc) vary->pv[idx].f = cmd->pathflag.f; break; case CMD_STEP_SIMULATION: sol_lerp_cmd(&gl.lerp, &cs, cmd); break; case CMD_MAP: /* * Note a version (mis-)match between the loaded map and what * the server has. (This doesn't actually load a map.) */ game_compat_map = (version.x == cmd->map.version.x); break; case CMD_TILT_AXES: cs.got_tilt_axes = 1; v_cpy(tilt->x, cmd->tiltaxes.x); v_cpy(tilt->z, cmd->tiltaxes.z); break; case CMD_NONE: case CMD_MAX: break; } } }
static int game_step(const float g[3], float dt, int bt) { if (server_state) { float h[3]; /* Smooth jittery or discontinuous input. */ tilt.rx += (input_get_x() - tilt.rx) * dt / input_get_s(); tilt.rz += (input_get_z() - tilt.rz) * dt / input_get_s(); game_tilt_axes(&tilt, view.e); game_cmd_tiltaxes(); game_cmd_tiltangles(); grow_step(&vary, dt); game_tilt_grav(h, g, &tilt); if (jump_b > 0) { jump_dt += dt; /* Handle a jump. */ if (jump_dt >= 0.5f) { /* Translate view at the exact instant of the jump. */ if (jump_b == 1) { float dp[3]; v_sub(dp, jump_p, vary.uv->p); v_add(view.p, view.p, dp); jump_b = 2; } /* Translate ball and hold it at the destination. */ v_cpy(vary.uv->p, jump_p); } if (jump_dt >= 1.0f) jump_b = 0; } else { /* Run the sim. */ float b = sol_step(&vary, 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 (vary.uv->r > grow_orig) audio_play(AUD_BUMPL, k); else if (vary.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; }