/* Relay -- a tool to record and play Quake2 demos Copyright (C) 2000 Conor Davis This program 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Conor Davis cedavis@planetquake.com */ #include "sv_local.h" void InitClient(client_t *client) { memset(client, 0, sizeof(client_t)); BlockInit(&client->unreliable, client->unreliable_buffer, sizeof(client->unreliable_buffer)); BlockInit(&client->reliable, client->reliable_buffer, sizeof(client->reliable_buffer)); BlockInit(&client->nextreliable, client->nextreliable_buffer, sizeof(client->nextreliable_buffer)); } int CL_ChangePlayer(client_t *client, int index) { int oldplayer; oldplayer = client->player; if (index == -1) { client->player = -1; WriteByte(&client->nextreliable, SVC_CONFIGSTRING); DM2_WriteConfigstring(&client->nextreliable, CS_STATUSBAR, ""); VectorAdd(client->origin, client->ps.viewoffset, client->origin); memset(client->ps.blend, 0, sizeof(client->ps.blend)); client->ps.viewangles[ROLL] = 0; client->ps.pmove.pm_flags = 0; client->ps.pmove.pm_time = 0; client->ps.pmove.gravity = 0; client->ps.gunindex = 0; client->ps.gunframe = 0; VectorClear(client->ps.gunangles); VectorClear(client->ps.gunoffset); VectorClear(client->ps.viewoffset); VectorClear(client->ps.kick_angles); client->ps.stats[STAT_LAYOUTS] = 0; return 0; } if (dm2in.svd.isdemo == RECORD_RELAY) { if (index < 0 || index >= dm2in.maxclients || !ISBITSET(server.current_connected, index)) return -1; client->player = index; } else { client->player = 0; } if (oldplayer == -1 && client->flags & RC_STATUSBAR) { WriteByte(&client->nextreliable, SVC_CONFIGSTRING); DM2_WriteConfigstring(&client->nextreliable, CS_STATUSBAR, dm2in.configstrings[CS_STATUSBAR]); } if (!client->curmenu && client->flags & RC_LAYOUT) { strcpy(client->layout, dm2in.players[client->player].layout); client->layout_modified = true; } return 0; } void ClientDisconnect(client_t *client) { printf("%s disconnected\n", client->netname); InitClient(client); server.numclients--; } void ClientTimeout(client_t *client) { printf("%s timed out\n", client->netname); InitClient(client); server.numclients--; } qboolean ClientConnect(client_t *client, const char *userinfo) { ClientUserinfoChanged(client, userinfo); printf("%s connected\n", client->netname); server.numclients++; client->player = -1; return true; } void ClientBegin(client_t *client) { if (dm2in.svd.isdemo == RECORD_NETWORK || dm2in.svd.isdemo == RECORD_CLIENT) client->player = 0; client->flags = RC_LOCKPOS|RC_LOCKVIEW|RC_STATUSBAR|RC_LAYOUT|RC_TINT; if (client->player == -1 || !(client->flags & RC_LAYOUT)) { // erase the statusbar WriteByte(&client->nextreliable, SVC_CONFIGSTRING); DM2_WriteConfigstring(&client->nextreliable, CS_STATUSBAR, ""); } ClientEndServerFrame(client); } void ClientUserinfoChanged(client_t *client, const char *userinfo) { if (!Info_Validate(userinfo)) userinfo = "\\name\\badinfo\\skin\\male/grunt"; strncpy(client->userinfo, userinfo, sizeof(client->userinfo)-1); strncpy(client->netname, Info_ValueForKey(userinfo, "name"), sizeof(client->netname)-1); client->ps.fov = atoi(Info_ValueForKey(userinfo, "fov")); if (client->ps.fov < 1) client->ps.fov = 90; else if (client->ps.fov > 160) client->ps.fov = 160; } void ClientThink(client_t *client, const usercmd_t *ucmd) { int i; pmove_t pm; for (i = 0; i < 3; i++) client->cmd_angles[i] = SHORT2ANGLE(ucmd->angles[i]); if (client->player != -1) { if (client->flags & RC_LOCKPOS) return; if (client->flags & RC_LOCKVIEW) { client->dist -= ucmd->forwardmove * ucmd->msec * 0.0005; if (client->dist < 32) client->dist = 32; return; } } memset(&pm, 0, sizeof(pmove_t)); client->ps.pmove.pm_type = PM_SPECTATOR; pm.s = client->ps.pmove; for (i = 0; i < 3; i++) { pm.s.origin[i] = client->origin[i] * 8; pm.s.velocity[i] = client->velocity[i] * 8; } pm.cmd = *ucmd; Pmove(&pm); client->ps.pmove = pm.s; for (i = 0; i < 3; i++) { client->origin[i] = pm.s.origin[i] * 0.125; client->velocity[i] = pm.s.velocity[i] * 0.125; } VectorCopy(pm.viewangles, client->ps.viewangles); client->buttons = ucmd->buttons; } void ClientBeginServerFrame(client_t *client) { } void UpdateClientOrigin(client_t *client) { int i; player_state_t *ps; vec3_t start, dir; if (client->player == -1) return; ps = &dm2in.players[client->player].ps[dm2in.current_frame & UPDATE_MASK]; if (client->flags & RC_LOCKPOS) { for (i = 0; i < 3; i++) client->origin[i] = ps->pmove.origin[i] * 0.125; } else if (client->flags & RC_LOCKVIEW) { for (i = 0; i < 3; i++) start[i] = ps->pmove.origin[i] * 0.125; VectorAdd(start, ps->viewoffset, start); if (client->flags & RC_CHASEVIEW) VectorAdd(ps->viewangles, client->cmd_angles, dir); else VectorCopy(client->cmd_angles, dir); AngleVectors(dir, dir, NULL, NULL); VectorMA(start, -client->dist, dir, client->origin); } } void ClientEndServerFrame(client_t *client) { int i; player_state_t *ps = NULL; vec3_t vec; if (client->player != -1) { UpdateClientOrigin(client); ps = &dm2in.players[client->player].ps[dm2in.current_frame & UPDATE_MASK]; if (client->flags & RC_LOCKPOS) { VectorCopy(ps->pmove.velocity, client->ps.pmove.velocity); VectorCopy(ps->viewoffset, client->ps.viewoffset); client->ps.pmove.pm_flags = ps->pmove.pm_flags; client->ps.pmove.pm_time = ps->pmove.pm_time; client->ps.pmove.gravity = ps->pmove.gravity; client->ps.rdflags = ps->rdflags; } else { VectorClear(client->ps.viewoffset); VectorClear(client->ps.kick_angles); VectorClear(client->ps.pmove.velocity); client->ps.pmove.gravity = 0; client->ps.pmove.pm_flags = 0; client->ps.pmove.pm_time = 0; client->ps.rdflags = 0; } if (client->flags & RC_LOCKVIEW) { if (client->flags & RC_LOCKPOS) { VectorCopy(ps->viewangles, client->ps.viewangles); VectorCopy(ps->kick_angles, client->ps.kick_angles); client->ps.fov = ps->fov; } else { for (i = 0; i < 3; i++) vec[i] = ps->pmove.origin[i] * 0.125; VectorSubtract(vec, client->origin, vec); VectorNormalize(vec); vectoangles(vec, client->ps.viewangles); } client->ps.pmove.pm_type = PM_FREEZE; } else if (client->flags & RC_LOCKPOS) client->ps.pmove.pm_type = PM_SPECTATOR; if (client->flags & RC_STATUSBAR) { client->ps.gunindex = ps->gunindex; client->ps.gunframe = ps->gunframe; VectorCopy(ps->gunoffset, client->ps.gunoffset); VectorCopy(ps->gunangles, client->ps.gunangles); for (i = 0; i < MAX_STATS; i++) { if (i != STAT_LAYOUTS) client->ps.stats[i] = ps->stats[i]; } } else { client->ps.gunindex = 0; client->ps.gunframe = 0; VectorClear(client->ps.gunoffset); VectorClear(client->ps.gunangles); for (i = 0; i < MAX_STATS; i++) { if (i != STAT_LAYOUTS) client->ps.stats[i] = 0; } } if (client->flags & RC_LAYOUT) { client->ps.stats[STAT_LAYOUTS] &= ~1; client->ps.stats[STAT_LAYOUTS] |= ps->stats[STAT_LAYOUTS] & 1; } } if (client->flags & RC_TINT && client->player != -1) { client->ps.stats[STAT_FLASHES] = ps->stats[STAT_FLASHES]; memcpy(client->ps.blend, ps->blend, sizeof(client->ps.blend)); } else { client->ps.stats[STAT_FLASHES] = 0; memset(client->ps.blend, 0, sizeof(client->ps.blend)); } if (client->ps.fov < 1) client->ps.fov = 1; else if (client->ps.fov > 160) client->ps.fov = 90; for (i = 0; i < 3; i++) client->ps.pmove.origin[i] = client->origin[i] * 8; if (client->ps.pmove.pm_type == PM_FREEZE) { for (i = 0; i < 3; i++) client->ps.pmove.delta_angles[i] = ANGLE2SHORT(client->ps.viewangles[i] - client->cmd_angles[i]); } if (client->layout_modified && client->nextreliable.writeoffset+strlen(client->layout)+2 < client->nextreliable.size) { client->layout_modified = false; WriteByte(&client->nextreliable, SVC_LAYOUT); DM2_WriteLayout(&client->nextreliable, client->layout); } if (client->inventory_modified && client->nextreliable.writeoffset+sizeof(client->inventory)+1 < client->nextreliable.size) { client->inventory_modified = false; WriteByte(&client->nextreliable, SVC_INVENTORY); DM2_WriteInventory(&client->nextreliable, client->inventory); } if (client->curmenu) client->ps.stats[STAT_LAYOUTS] = 1; }