/* 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 #include #include "shared.h" #include "block.h" #include "bsp.h" #include "cmd.h" #include "dc_local.h" #include "dm2.h" #include "q2defines.h" #include "q2utils.h" #include "utils.h" void WriteFrameData(); extern block_t out; static vec3_t delta_vec = {0.125, 0.125, 0125}; static void CastMessage(void *buffer, size_t len, int id, int recipient, vec3_t origin, multicast_t type) { vec3_t alt_origin; int cluster1, cluster2; if (WriteOverflow(&out)) return; if (id != SVC_CONFIGSTRING) { if (startframe && dm2in.current_frame < startframe) return; if (endframe && dm2in.current_frame > endframe) return; if (!ISBITSET(dm2in.states[dm2in.current_frame & UPDATE_MASK].connected, playernum)) return; } else if (!wrote_preframe) return; if (recipient == -1) { if (type != MULTICAST_ALL) { if (player_cluster == -1) return; // the network protocol gives roundoff-errors, // so check the original point (xyz rounded down), and another // point (rounded up) for visibility VectorAdd(origin, delta_vec, alt_origin); cluster1 = PointInCluster(&map, origin); cluster2 = PointInCluster(&map, alt_origin); if (type == MULTICAST_PVS && !ClusterVisible(&map, player_cluster, cluster1, DVIS_PVS) && !ClusterVisible(&map, player_cluster, cluster2, DVIS_PVS)) return; if (type == MULTICAST_PHS && !ClusterVisible(&map, player_cluster, cluster1, DVIS_PHS) && !ClusterVisible(&map, player_cluster, cluster2, DVIS_PHS)) return; } } else { if (recipient != playernum) return; } WriteByte(&out, (byte)id); BlockWrite(&out, buffer, len); } int Frame_Parse(block_t *block) { int id, i, result, 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]; result = 0; 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) Sys_Error("Frame_Parse: Error reading SVC_MUZZLEFLASH message\n"); CastMessage(start, nbytes, id, recipient_index, current->entities[entity].origin, MULTICAST_PVS); } break; case SVC_TEMP_ENTITY: { temp_entity_t m; nbytes = DM2_ReadTempEntity(block, &dm2in, &m); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_TEMP_ENTITY message\n"); CastMessage(start, nbytes, SVC_TEMP_ENTITY, recipient_index, m.origin, MULTICAST_PVS); } break; case SVC_LAYOUT: { nbytes = DM2_ReadLayout(block, NULL, 0); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_LAYOUT message\n"); CastMessage(start, nbytes, SVC_LAYOUT, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_INVENTORY: { nbytes = DM2_ReadInventory(block, NULL); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_INVENTORY message\n"); CastMessage(start, nbytes, SVC_INVENTORY, recipient_index, NULL, MULTICAST_ALL); } 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) Sys_Error("Frame_Parse: Error reading SVC_SOUND message\n"); 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); } break; case SVC_PRINT: { nbytes = DM2_ReadPrint(block, NULL, NULL, 0); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_PRINT message\n"); CastMessage(start, nbytes, SVC_PRINT, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_STUFFTEXT: { char string[MAX_MSGLEN]; nbytes = DM2_ReadStufftext(block, string, sizeof(string)); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_STUFFTEXT message\n"); CastMessage(start, nbytes, SVC_STUFFTEXT, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_CONFIGSTRING: { int index; char string[MAX_MSGLEN]; nbytes = DM2_ReadConfigstring(block, &index, string); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_CONFIGSTRING message\n"); if (recipient_index == -1 || recipient_index == playernum) strcpy(dm2in.configstrings[index], string); CastMessage(start, nbytes, SVC_CONFIGSTRING, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_CENTERPRINT: { nbytes = DM2_ReadCenterprint(block, NULL, 0); if (nbytes < 0) Sys_Error("Frame_Parse: Error reading SVC_CENTERPRINT message\n"); CastMessage(start, nbytes, SVC_CENTERPRINT, recipient_index, NULL, MULTICAST_ALL); } break; case SVC_PLAYERINFO: { // old ps should have been copied over via SVC_FRAME message player_state_t *ps; vec3_t origin; 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) Sys_Error("Frame_Parse: Error reading SVC_PLAYERINFO message\n"); if (recipient_index == -1 || recipient_index == playernum) { for (i = 0; i < 3; i++) origin[i] = ps->pmove.origin[i] * 0.125; player_cluster = PointInCluster(&map, origin); } } 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) Sys_Error("Frame_Parse: Error reading SVC_PACKETENTITIES message\n"); if (!entity) break; } WriteFrameData(); } 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) Sys_Error("Frame_Parse: Error reading SVC_FRAME message\n"); 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); for (i = 1; i < MAX_EDICTS; i++) { current->entities[i].event = 0; VectorCopy(current->entities[i].origin, current->entities[i].old_origin); } } } break; default: Sys_Error("Frame_Parse: Invalid message id %02x\n", id); } } return result; } void WriteFrameData() { int i, current_index, delta_index, area_count; int ent_cluster1, ent_cluster2; byte areas[MAX_MAP_AREAS/8], *active, *old_active; vec3_t alt_origin; entity_state_t *es; static player_state_t null_ps; if (startframe && dm2in.current_frame < startframe) return; if (endframe && dm2in.current_frame > endframe) return; if (!ISBITSET(dm2in.states[dm2in.current_frame & UPDATE_MASK].connected, playernum)) return; current_index = dm2in.current_frame & UPDATE_MASK; delta_index = last_frame & UPDATE_MASK; memset(areas, 0xff, sizeof(areas)); area_count = MAX_MAP_AREAS/8; active = out_active[current_index]; memcpy(active, dm2in.states[current_index].active, sizeof(dm2in.states[current_index].active)); // write SVC_FRAME message WriteByte(&out, SVC_FRAME); DM2_WriteFrame(&out, &outsvd, dm2in.current_frame, last_frame, area_count, areas, 0, NULL); // write SVC_PLAYERINFO message if (last_frame == BASELINES_FRAME) { WriteByte(&out, SVC_PLAYERINFO); DM2_WritePS(&out, &dm2in.players[playernum].ps[current_index], &null_ps); old_active = NULL; } else { WriteByte(&out, SVC_PLAYERINFO); DM2_WritePS(&out, &dm2in.players[playernum].ps[current_index], &dm2in.players[playernum].ps[delta_index]); old_active = out_active[delta_index]; } // mark entities that are out of the PVS as not active for (i = 1, es = &dm2in.states[current_index].entities[1]; i < MAX_EDICTS; i++, es++) { if (!ISBITSET(active, i)) continue; if (player_cluster == -1) { // player is in a solid, mark everything as invisible SETBIT(active, i, false); continue; } if (es->solid == 31) { // mark all bsp models as visible until we find a way // to determine their origins continue; } else { // roundoff errors can put an entity in a wall (bad), // so test two points. This gets rid of most // entities incorrectly being marked as invisible. ent_cluster1 = PointInCluster(&map, es->origin); if (ClusterVisible(&map, player_cluster, ent_cluster1, DVIS_PVS)) continue; if (es->sound && ClusterVisible(&map, player_cluster, ent_cluster1, DVIS_PHS)) continue; VectorAdd(es->origin, delta_vec, alt_origin); ent_cluster2 = PointInCluster(&map, alt_origin); if (ent_cluster2 != ent_cluster1) { if (ClusterVisible(&map, player_cluster, ent_cluster2, DVIS_PVS)) continue; if (es->sound && ClusterVisible(&map, player_cluster, ent_cluster2, DVIS_PHS)) continue; } } SETBIT(active, i, false); } // write SVC_PACKETENTITIES WriteByte(&out, SVC_PACKETENTITIES); for (i = 1; i < MAX_EDICTS; i++) { if (old_active && ISBITSET(old_active, i)) { DM2_WriteEntity(&out, &dm2in.states[current_index].entities[i], &dm2in.states[delta_index].entities[i], ISBITSET(active, i), true); } else { DM2_WriteEntity(&out, &dm2in.states[current_index].entities[i], &dm2in.baselines.entities[i], ISBITSET(active, i), false); } } WriteShort(&out, 0); }