/* 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" // // DEMO COMMANDS // qboolean RunDemoCommand() { return true; } // // FRAME READING FUNCTIONS // static vec3_t delta_vec = {0.125, 0.125, 0.125}; static void CastMessage(void *buffer, size_t len, int id, int recipient_index, vec3_t origin, multicast_t type, qboolean reliable) { int i; client_t *client; int client_cluster, cluster1, cluster2; vec3_t alt_origin; for (i = 0, client = server.clients; i < server.maxclients; i++, client++) { if (id == SVC_PRINT || id == SVC_CONFIGSTRING) { // partially connected clients need this info too if (client->status < CL_CONFIGSTRINGS) continue; } else { if (client->status != CL_CONNECTED) continue; } if (recipient_index != -1 && recipient_index != client->player) continue; if (type != MULTICAST_ALL) { client_cluster = PointInCluster(&map, client->origin); if (client_cluster == -1) continue; cluster1 = PointInCluster(&map, origin); VectorAdd(origin, delta_vec, alt_origin); cluster2 = PointInCluster(&map, alt_origin); if (type == MULTICAST_PVS && !ClusterVisible(&map, client_cluster, cluster1, DVIS_PVS) && !ClusterVisible(&map, client_cluster, cluster2, DVIS_PVS)) { continue; } // type == MULTICAST_PHS if (!ClusterVisible(&map, client_cluster, cluster1, DVIS_PHS) && !ClusterVisible(&map, client_cluster, cluster2, DVIS_PHS)) { continue; } } if (type == SVC_LAYOUT) { if (client->flags & RC_LAYOUT) { strcpy(client->layout, buffer); client->layout_modified = true; } continue; } if (type == SVC_INVENTORY) { if (client->flags & RC_INVENTORY) { memcpy(client->inventory, buffer, sizeof(client->inventory)); client->inventory_modified = true; } continue; } if (reliable) { WriteByte(&client->nextreliable, (byte)id); BlockWrite(&client->nextreliable, buffer, len); } else { WriteByte(&client->unreliable, (byte)id); BlockWrite(&client->unreliable, buffer, len); } } } static int Frame_Parse(block_t *block) { int id, i, recipient_index, nbytes; int current_index, delta_index; char *start; state_t *current; // find the most recent state before parsing messages, // so we can get an idea of entity positions before a SVC_FRAME // is received (just in case) current_index = dm2in.current_frame & UPDATE_MASK; if (dm2in.current_frame == -1) current = &dm2in.baselines; else current = &dm2in.states[current_index]; while (block->readoffset < block->writeoffset) { id = ReadByte(block); if (id & MSG_UNICAST) { recipient_index = ReadByte(block); id &= ~MSG_UNICAST; } else recipient_index = -1; if (ReadOverflow(block)) return -1; start = block->buffer + block->readoffset; switch (id) { case SVC_MUZZLEFLASH: case SVC_MUZZLEFLASH2: { int entity; nbytes = DM2_ReadMuzzleflash(block, &entity, NULL); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_MUZZLEFLASH message\n"); return -1; } if ((unsigned)entity >= MAX_EDICTS) { Error("Frame_Parse: SVC_MUZZLEFLASH ent >= MAX_EDICTS\n"); return -1; } CastMessage(start, nbytes, id, recipient_index, current->entities[entity].origin, MULTICAST_PVS, false); } break; case SVC_TEMP_ENTITY: { temp_entity_t m; nbytes = DM2_ReadTempEntity(block, &dm2in, &m); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_TEMP_ENTITY message\n"); return -1; } CastMessage(start, nbytes, SVC_TEMP_ENTITY, recipient_index, m.origin, MULTICAST_PVS, false); } break; case SVC_LAYOUT: { nbytes = DM2_ReadLayout(block, NULL, 0); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_LAYOUT message\n"); return -1; } CastMessage(start, nbytes, SVC_LAYOUT, recipient_index, NULL, MULTICAST_ALL, true); } break; case SVC_INVENTORY: { nbytes = DM2_ReadInventory(block, NULL); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_INVENTORY message\n"); return -1; } CastMessage(start, nbytes, SVC_INVENTORY, recipient_index, NULL, MULTICAST_ALL, true); } break; case SVC_NOP: case SVC_DISCONNECT: case SVC_RECONNECT: { // CastMessage(start, 0, id, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_SOUND: { int entity; vec3_t origin; qboolean positioned; nbytes = DM2_ReadSound(block, NULL, NULL, NULL, NULL, &entity, NULL, origin, &positioned); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_SOUND message\n"); return -1; } if (!positioned) { if (entity) VectorCopy(current->entities[entity].origin, origin); else VectorCopy(current->entities[dm2in.svd.player+1].origin, origin); } CastMessage(start, nbytes, SVC_SOUND, recipient_index, origin, MULTICAST_PHS, false); } break; case SVC_PRINT: { nbytes = DM2_ReadPrint(block, NULL, NULL, 0); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_PRINT message\n"); return -1; } CastMessage(start, nbytes, SVC_PRINT, recipient_index, NULL, MULTICAST_ALL, true); } break; case SVC_STUFFTEXT: { char string[MAX_MSGLEN]; nbytes = DM2_ReadStufftext(block, string, sizeof(string)); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_STUFFTEXT message\n"); return -1; } if (recipient_index != RECORD_RELAY) Cmd_AddText(&server.demo_commandstring, string); else CastMessage(start, nbytes, SVC_STUFFTEXT, recipient_index, NULL, MULTICAST_ALL, true); } break; case SVC_CONFIGSTRING: { int index; char string[MAX_MSGLEN]; nbytes = DM2_ReadConfigstring(block, &index, string); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_CONFIGSTRING message\n"); return -1; } if (recipient_index == -1) strcpy(dm2in.configstrings[index], string); CastMessage(start, nbytes, SVC_CONFIGSTRING, recipient_index, NULL, MULTICAST_ALL, true); } break; case SVC_CENTERPRINT: { nbytes = DM2_ReadCenterprint(block, NULL, 0); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_CENTERPRINT message\n"); return -1; } CastMessage(start, nbytes, SVC_CENTERPRINT, recipient_index, NULL, MULTICAST_ALL, false); } break; case SVC_PLAYERINFO: { // old ps should have been copied over via SVC_FRAME message player_state_t *ps; if (recipient_index == -1) ps = &dm2in.players[0].ps[current_index]; else ps = &dm2in.players[recipient_index].ps[current_index]; nbytes = DM2_ReadPS(block, ps); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_PLAYERINFO message\n"); return -1; } } break; case SVC_PACKETENTITIES: { // old entities should have been copied over via SVC_FRAME message int entity; for (;;) { entity = DM2_ReadPacketEntity(block, current, &dm2in.baselines); if (entity < 0) { Error("Frame_Parse: Error reading SVC_PACKETENTITIES message\n"); return -1; } if (!entity) break; } } break; case SVC_FRAME: { int seq1, seq2, area_count, connected_count; byte areas[MAX_MAP_AREAPORTALS/8], connected[MAX_CLIENTS/8]; nbytes = DM2_ReadFrame(block, &dm2in.svd, &seq1, &seq2, &area_count, areas, &connected_count, connected); if (nbytes < 0) { Error("Frame_Parse: Error reading SVC_FRAME message\n"); return -1; } dm2in.current_frame = seq1; if (dm2in.svd.isdemo != RECORD_SERVER) { dm2in.delta_frame = seq2; current_index = dm2in.current_frame & UPDATE_MASK; delta_index = dm2in.delta_frame & UPDATE_MASK; current = &dm2in.states[current_index]; // copy data from delta frame to current frame if (dm2in.delta_frame == BASELINES_FRAME) { *current = dm2in.baselines; for (i = 0; i < dm2in.maxclients; i++) memset(&dm2in.players[i].ps[current_index], 0, sizeof(player_state_t)); } else { *current = dm2in.states[delta_index]; for (i = 0; i < dm2in.maxclients; i++) { dm2in.players[i].ps[current_index] = dm2in.players[i].ps[delta_index]; dm2in.players[i].ps[current_index].stats[STAT_FLASHES] = 0; } } current->frame = dm2in.current_frame; memcpy(current->areas, areas, area_count); if (dm2in.svd.isdemo == RECORD_RELAY) { memcpy(current->connected, connected, connected_count); memcpy(server.current_connected, current->connected, sizeof(server.current_connected)); } for (i = 1; i < MAX_EDICTS; i++) { current->entities[i].event = 0; VectorCopy(current->entities[i].origin, current->entities[i].old_origin); } } } break; case SVC_SERVERDATA: case SVC_SPAWNBASELINE: Error("Frame_Parse: Pre-frame server command %d\n", id); return -1; default: Error("Frame_Parse: Unknown server command id %d\n", id); return -1; } } return 0; } int Frame_Read() { block_t in; char in_buffer[MAX_SVSLEN]; BlockInit(&in, in_buffer, sizeof(in_buffer)); if (DM2_ReadBlock(&in, server.infile) < 0) { printf("ReadFrame: Error reading from demo file\n"); return -1; } if (in.writeoffset == 0xffffffff) { CL_bprintf(true, PRINT_HIGH, "End of demo reached\n"); pfclose(server.infile); server.infile = NULL; return 0; } if (Frame_Parse(&in) < 0) return -1; Cmd_RunCommands(&server.demo_commandstring, RunDemoCommand); return in.writeoffset; } static void CL_GetActiveEnts(client_t *client, state_t *state) { static vec3_t delta_vec = {0.125, 0.125, 0.125}; vec3_t alt_origin; int client_cluster, ent_cluster1, ent_cluster2, i; entity_state_t *es; memcpy(client->active, state->active, sizeof(client->active)); client_cluster = PointInCluster(&map, client->origin); for (i = 1, es = &state->entities[1]; i < MAX_EDICTS; i++, es++) { if (!ISBITSET(client->active, i)) continue; if (client->flags & RC_LOCKPOS && es->number == client->player + 1) { SETBIT(client->active, i, false); continue; } // don't know how do do visibility for BSP models if (es->solid == 31) { SETBIT(client->active, i, true); continue; } ent_cluster1 = PointInCluster(&map, es->origin); if (ClusterVisible(&map, client_cluster, ent_cluster1, DVIS_PVS)) { SETBIT(client->active, i, true); continue; } if (es->sound && ClusterVisible(&map, client_cluster, ent_cluster1, DVIS_PHS)) { SETBIT(client->active, i, true); continue; } VectorAdd(es->origin, delta_vec, alt_origin); ent_cluster2 = PointInCluster(&map, alt_origin); if (ent_cluster2 != ent_cluster1) { if (ClusterVisible(&map, client_cluster, ent_cluster2, DVIS_PVS)) { SETBIT(client->active, i, true); continue; } if (es->sound && ClusterVisible(&map, client_cluster, ent_cluster2, DVIS_PHS)) { SETBIT(client->active, i, true); continue; } } // not in PVS or PHS SETBIT(client->active, i, false); } } void CL_WriteEntities(block_t *block, client_t *client, size_t delta_frame) { int i; byte *oldactive; state_t *current, *delta; current = &dm2in.states[dm2in.current_frame & UPDATE_MASK]; if (delta_frame == BASELINES_FRAME) { delta = &dm2in.baselines; oldactive = NULL; } else { delta = &dm2in.states[delta_frame & UPDATE_MASK]; oldactive = client->old[delta_frame & UPDATE_MASK].active; } // determine which entities are active for this client CL_GetActiveEnts(client, current); WriteByte(block, SVC_PACKETENTITIES); for (i = 1; i < MAX_EDICTS; i++) { if (oldactive && ISBITSET(oldactive, i)) { // delta from previous frame DM2_WriteEntity(block, ¤t->entities[i], // current state &delta->entities[i], // delta state ISBITSET(client->active, i), // is_active true); // was_active } else { // delta from baseline DM2_WriteEntity(block, ¤t->entities[i], &dm2in.baselines.entities[i], ISBITSET(client->active, i), false); } } WriteShort(block, 0); // mask=0, entity=0 }