/* 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 #include "block.h" #include "cmd.h" #include "dm2.h" #include "endian.h" #include "pak.h" #include "q2defines.h" #include "shared.h" #include "utils.h" // // DM2_Init // Initializes a dm2_t struct so that it can // be properly read or written to. // void DM2_Init(dm2_t *dm2) { int i; memset(dm2, 0, sizeof(dm2_t)); for (i = 1; i < MAX_EDICTS; i++) dm2->baselines.entities[i].number = i; for (i = 0; i < UPDATE_BACKUP; i++) dm2->states[i].frame = BASELINES_FRAME; dm2->baselines.frame = BASELINES_FRAME; } // pre-0.4 relay demos could leave gaps in the model table (bad); // this attempts to fix it void DM2_FillConfigstrings(char (*configstrings)[64]) { int i; for (i = 2; i < MAX_MODELS; i++) { if (configstrings[CS_MODELS+i][0] && configstrings[CS_MODELS+i][0] != '*') break; if (!configstrings[CS_MODELS+i][0]) sprintf(configstrings[CS_MODELS+i], "*%d", i - 1); } } // // DM2_ReadBlock // Reads a dm2 block from a file. // int DM2_ReadBlock(block_t *block, PFILE *fd) { if (!pfread(&block->writeoffset, 4, 1, fd)) block->writeoffset = 0xffffffff; else block->writeoffset = LittleLong(block->writeoffset); if (block->writeoffset == 0xffffffff) return 0; if (WriteOverflow(block)) return -1; if (!pfread(block->buffer, block->writeoffset, 1, fd)) { block->writeoffset = 0; return -1; } block->readoffset = 0; return 0; } // // DM2_WriteBlock // Writes a demo block to a file. // int DM2_WriteBlock(block_t *block, PFILE *fd) { int len; len = LittleLong(block->writeoffset); if (!pfwrite(&len, 4, 1, fd)) return -1; if (!pfwrite(block->buffer, block->writeoffset, 1, fd)) return -1; return 0; } // // functions to read/write dm2 messages // they should return the number of bytes read, or -1 // on error // #define SET(a,b) ((a) ? *(a) = (b) : (b)) #define SETSTR(a,b) ((a) ? strcpy((a), (b)) : (b)) #define SETSTRN(a,b,c) ((a) ? ( (a)[(c)-1] = 0, strncpy((a), (b), (c)) ) : (b)) // for SVC_LAYOUT, SVC_CENTERPRINT, and SVC_STUFFTEXT int DM2_ReadGenericString(block_t *block, char *p, size_t len) { const char *s; size_t start; start = block->readoffset; s = ReadString(block); if (ReadOverflow(block)) return -1; if (p) { strncpy(p, s, len-1); p[len-1] = 0; } return block->readoffset - start; } int DM2_WriteGenericString(block_t *block, const char *p) { size_t start; start = block->writeoffset; WriteString(block, p); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadMuzzleflash(block_t *block, int *entity, int *value) { SET(entity, (unsigned short)ReadShort(block)); SET(value, ReadByte(block)); if (ReadOverflow(block)) return -1; // 3 bytes were read return 3; } int DM2_WriteMuzzleflash(block_t *block, int entity, int value) { WriteShort(block, (unsigned short)entity); WriteByte(block, (byte)value); if (WriteOverflow(block)) return -1; // wrote 3 bytes return 3; } int DM2_ReadTempEntity(block_t *block, const dm2_t *dm2, temp_entity_t *p) { size_t start; temp_entity_t m; start = block->readoffset; m.entity = 0; m.dest_entity = 0; m.type = ReadByte(block); switch(m.type) { // case TE_PLASMATRAIL: // old meaning case TE_GREENBLOOD: // new meaning (3.15+) if (dm2->svd.version >= 32) goto impact_entity; else goto line_entity; // case TE_GREENBLOOD_old: // old meaning case TE_BLUEHYPERBLASTER: // new meaning (3.15+) if (dm2->svd.version >= 32) goto line_entity; else goto impact_entity; // point entity case TE_EXPLOSION1: case TE_EXPLOSION2: case TE_ROCKET_EXPLOSION: case TE_GRENADE_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: case TE_GRENADE_EXPLOSION_WATER: case TE_BFG_EXPLOSION: case TE_BFG_BIGEXPLOSION: case TE_BOSSTPORT: case TE_PLASMA_EXPLOSION: case TE_PLAIN_EXPLOSION: case TE_CHAINFIST_SMOKE: case TE_TRACKER_EXPLOSION: case TE_TELEPORT_EFFECT: case TE_DBALL_GOAL: case TE_NUKEBLAST: case TE_WIDOWSPLASH: case TE_EXPLOSION1_BIG: case TE_EXPLOSION1_NP: ReadPosition(block, m.origin); break; // impact entity case TE_GUNSHOT: case TE_BLOOD: case TE_BLASTER: case TE_SHOTGUN: case TE_SPARKS: case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: case TE_BULLET_SPARKS: // case TE_GREENBLOOD_new: // case TE_GREENBLOOD_old: case TE_BLASTER2: case TE_MOREBLOOD: case TE_HEATBEAM_SPARKS: case TE_HEATBEAM_STEAM: case TE_ELECTRIC_SPARKS: case TE_FLECHETTE: impact_entity: ReadPosition(block, m.origin); ReadDir(block, m.movedir); break; // line entity case TE_RAILTRAIL: case TE_BUBBLETRAIL: case TE_BFG_LASER: // case TE_PLASMATRAIL: // case TE_BLUEHYPERBLASTER: case TE_DEBUGTRAIL: case TE_BUBBLETRAIL2: line_entity: ReadPosition(block, m.origin); ReadPosition(block, m.endpos); break; // special entity case TE_SPLASH: case TE_LASER_SPARKS: case TE_WELDING_SPARKS: case TE_TUNNEL_SPARKS: m.count = ReadByte(block); ReadPosition(block, m.origin); ReadDir(block, m.movedir); m.style = ReadByte(block); break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: case TE_HEATBEAM: case TE_MONSTER_HEATBEAM: m.entity = ReadShort(block); ReadPosition(block, m.origin); ReadPosition(block, m.endpos); break; case TE_GRAPPLE_CABLE: m.entity = ReadShort(block); ReadPosition(block, m.origin); ReadPosition(block, m.endpos); ReadPosition(block, m.pos1); break; case TE_FLAME: m.entity = ReadShort(block); m.count = ReadShort(block); ReadPosition(block, m.endpos); ReadPosition(block, m.origin); ReadPosition(block, m.pos1); ReadPosition(block, m.pos2); ReadPosition(block, m.pos3); ReadPosition(block, m.pos4); break; case TE_LIGHTNING: m.dest_entity = ReadShort(block); m.entity = ReadShort(block); ReadPosition(block, m.endpos); ReadPosition(block, m.origin); break; case TE_FLASHLIGHT: ReadPosition(block, m.origin); m.entity = ReadShort(block); break; case TE_FORCEWALL: ReadPosition(block, m.origin); ReadPosition(block, m.endpos); m.style = ReadShort(block); break; case TE_STEAM: m.nextid = ReadShort(block); m.count = ReadByte(block); ReadPosition(block, m.origin); ReadDir(block, m.movedir); m.style = ReadByte(block); m.plat2flags = ReadShort(block); if (m.nextid != -1) m.wait = ReadLong(block); break; default: return -1; break; } if (ReadOverflow(block)) return -1; if (p) *p = m; return block->readoffset - start; } int DM2_WriteTempEntity(block_t *block, const dm2_t *dm2, const temp_entity_t *p) { size_t start; start = block->writeoffset; WriteByte(block, p->type); switch(p->type) { // case TE_PLASMATRAIL: // old meaning case TE_GREENBLOOD: // new meaning (3.15+) if (dm2->svd.version >= 32) goto impact_entity; else goto line_entity; // case TE_GREENBLOOD_old: // old meaning case TE_BLUEHYPERBLASTER: // new meaning (3.15+) if (dm2->svd.version >= 32) goto line_entity; else goto impact_entity; // point entity case TE_EXPLOSION1: case TE_EXPLOSION2: case TE_ROCKET_EXPLOSION: case TE_GRENADE_EXPLOSION: case TE_ROCKET_EXPLOSION_WATER: case TE_GRENADE_EXPLOSION_WATER: case TE_BFG_EXPLOSION: case TE_BFG_BIGEXPLOSION: case TE_BOSSTPORT: case TE_PLASMA_EXPLOSION: case TE_PLAIN_EXPLOSION: case TE_CHAINFIST_SMOKE: case TE_TRACKER_EXPLOSION: case TE_TELEPORT_EFFECT: case TE_DBALL_GOAL: case TE_NUKEBLAST: case TE_WIDOWSPLASH: case TE_EXPLOSION1_BIG: case TE_EXPLOSION1_NP: WritePosition(block, p->origin); break; // impact entity case TE_GUNSHOT: case TE_BLOOD: case TE_BLASTER: case TE_SHOTGUN: case TE_SPARKS: case TE_SCREEN_SPARKS: case TE_SHIELD_SPARKS: case TE_BULLET_SPARKS: // case TE_GREENBLOOD_new: // case TE_GREENBLOOD_old: case TE_BLASTER2: case TE_MOREBLOOD: case TE_HEATBEAM_SPARKS: case TE_HEATBEAM_STEAM: case TE_ELECTRIC_SPARKS: case TE_FLECHETTE: impact_entity: WritePosition(block, p->origin); WriteDir(block, p->movedir); break; // line entity case TE_RAILTRAIL: case TE_BUBBLETRAIL: case TE_BFG_LASER: // case TE_PLASMATRAIL: // case TE_BLUEHYPERBLASTER: case TE_DEBUGTRAIL: case TE_BUBBLETRAIL2: line_entity: WritePosition(block, p->origin); WritePosition(block, p->endpos); break; // special entity case TE_SPLASH: case TE_LASER_SPARKS: case TE_WELDING_SPARKS: case TE_TUNNEL_SPARKS: WriteByte(block, (byte)p->count); WritePosition(block, p->origin); WriteDir(block, p->movedir); WriteByte(block, (byte)p->style); break; case TE_PARASITE_ATTACK: case TE_MEDIC_CABLE_ATTACK: case TE_HEATBEAM: case TE_MONSTER_HEATBEAM: WriteShort(block, p->entity); WritePosition(block, p->origin); WritePosition(block, p->endpos); break; case TE_GRAPPLE_CABLE: WriteShort(block, p->entity); WritePosition(block, p->origin); WritePosition(block, p->endpos); WritePosition(block, p->pos1); break; case TE_FLAME: WriteShort(block, p->entity); WriteShort(block, p->count); WritePosition(block, p->endpos); WritePosition(block, p->origin); WritePosition(block, p->pos1); WritePosition(block, p->pos2); WritePosition(block, p->pos3); WritePosition(block, p->pos4); break; case TE_LIGHTNING: WriteShort(block, p->dest_entity); WriteShort(block, p->entity); WritePosition(block, p->endpos); WritePosition(block, p->origin); break; case TE_FLASHLIGHT: WritePosition(block, p->origin); WriteShort(block, p->entity); break; case TE_FORCEWALL: WritePosition(block, p->origin); WritePosition(block, p->endpos); WriteShort(block, p->style); break; case TE_STEAM: WriteShort(block, p->nextid); WriteByte(block, (byte)p->count); WritePosition(block, p->origin); WriteDir(block, p->movedir); WriteByte(block, (byte)p->style); WriteShort(block, p->plat2flags); if (p->nextid != -1) WriteLong(block, p->wait); break; default: return -1; break; } if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadInventory(block_t *block, short p[MAX_ITEMS]) { int i; if (p) { for (i = 0; i < MAX_ITEMS; i++) p[i] = ReadShort(block); } else BlockRead(block, NULL, MAX_ITEMS*2); if (ReadOverflow(block)) return -1; return MAX_ITEMS*2; } int DM2_WriteInventory(block_t *block, const short p[MAX_ITEMS]) { int i; for (i = 0; i < MAX_ITEMS; i++) WriteShort(block, p[i]); if (WriteOverflow(block)) return -1; return MAX_ITEMS*2; } int DM2_ReadSound(block_t *block, int *soundindex, float *volume, float *attenuation, float *timeofs, int *entity, int *channel, vec3_t origin, qboolean *positioned) { size_t start; int mask, mix; start = block->readoffset; mask = ReadByte(block); SET(soundindex, ReadByte(block)); if (mask & SND_VOLUME) SET(volume, (float)ReadByte(block) / 255); else SET(volume, 1.0F); if (mask & SND_ATTENUATION) SET(attenuation, (float)ReadByte(block) / 64); else SET(attenuation, 1.0F); if (mask & SND_OFFSET) SET(timeofs, (float)ReadByte(block) * 0.001); else SET(timeofs, 0.0F); if (mask & SND_ENT) { mix = ReadShort(block); SET(entity, mix >> 3); SET(channel, mix & 0x07); } else { SET(entity, 0); SET(channel, 0); } if (mask & SND_POS) { if (origin) ReadPosition(block, origin); else BlockRead(block, NULL, 6); SET(positioned, true); } else SET(positioned, false); if (ReadOverflow(block)) return -1; return block->readoffset - start; } int DM2_WriteSound(block_t *block, int soundindex, float volume, float attenuation, float timeofs, int entity, int channel, vec3_t origin, qboolean positioned) { int mask; size_t start; start = block->writeoffset; mask = 0; if (volume != 1.0F) mask |= SND_VOLUME; if (attenuation != 1.0F) mask |= SND_ATTENUATION; if (timeofs != 0.0F) mask |= SND_OFFSET; if (entity) mask |= SND_ENT; if (positioned) mask |= SND_POS; WriteByte(block, (byte)mask); WriteByte(block, (byte)soundindex); if (mask & SND_VOLUME) WriteByte(block, (byte)(volume * 255)); if (mask & SND_ATTENUATION) WriteByte(block, (byte)(attenuation * 64)); if (mask & SND_OFFSET) WriteByte(block, (byte)(timeofs * 1000)); if (mask & SND_ENT) WriteShort(block, (unsigned short)((entity << 3) | (channel & 0x07))); if (mask & SND_POS) WritePosition(block, origin); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadPrint(block_t *block, int *level, char *string, size_t len) { size_t start; start = block->readoffset; SET(level, ReadByte(block)); SETSTRN(string, ReadString(block), len); if (ReadOverflow(block)) return -1; return block->readoffset - start; } int DM2_WritePrint(block_t *block, int level, const char *string) { size_t start; start = block->writeoffset; WriteByte(block, (byte)level); WriteString(block, string); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadServerdata(block_t *block, serverdata_t *p) { serverdata_t m; size_t start; start = block->readoffset; m.version = ReadLong(block); m.key = ReadLong(block); m.isdemo = ReadByte(block); strncpy(m.game, ReadString(block), sizeof(m.game)-1); m.game[sizeof(m.game)-1] = 0; m.player = ReadShort(block); strncpy(m.mapname, ReadString(block), sizeof(m.mapname)-1); m.mapname[sizeof(m.mapname)-1] = 0; if (m.isdemo == RECORD_RELAY) { m.relayversion = m.version >> 16; m.version &= 0xffff; } else m.relayversion = 0; if (ReadOverflow(block)) return -1; if (p) *p = m; return block->readoffset - start; } int DM2_WriteServerdata(block_t *block, const serverdata_t *p) { size_t start; start = block->writeoffset; if (p->isdemo == RECORD_RELAY) WriteLong(block, (p->relayversion << 16) | p->version); else WriteLong(block, p->version); WriteLong(block, p->key); WriteByte(block, p->isdemo); WriteString(block, p->game); WriteShort(block, p->player); WriteString(block, p->mapname); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadConfigstring(block_t *block, int *index, char *string) { size_t start; start = block->readoffset; SET(index, ReadShort(block)); SETSTR(string, ReadString(block)); if (ReadOverflow(block)) return -1; return block->readoffset - start; } int DM2_WriteConfigstring(block_t *block, int index, const char *string) { size_t start; start = block->writeoffset; WriteShort(block, (unsigned short)index); WriteString(block, string); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } int DM2_ReadFrame(block_t *block, const serverdata_t *svd, int *seq1, int *seq2, int *area_count, byte *areas, int *connected_count, byte *connected) { int len; size_t start; start = block->readoffset; if (svd->isdemo != RECORD_SERVER) { SET(seq1, ReadLong(block)); SET(seq2, ReadLong(block)); if (svd->version != 26) ReadByte(block); // ??? len = ReadByte(block); SET(area_count, len); if (areas) BlockRead(block, areas, len); else BlockRead(block, NULL, len); if (svd->isdemo == RECORD_RELAY) { len = ReadByte(block); SET(connected_count, len); if (connected) BlockRead(block, connected, len); else BlockRead(block, NULL, len); } } else { SET(seq1, ReadLong(block)); } if (ReadOverflow(block)) return -1; return block->readoffset - start; } int DM2_WriteFrame(block_t *block, const serverdata_t *svd, int seq1, int seq2, int area_count, const byte *areas, int connected_count, const byte *connected) { size_t start; start = block->writeoffset; if (svd->isdemo != RECORD_SERVER) { WriteLong(block, seq1); WriteLong(block, seq2); if (svd->version != 26) WriteByte(block, 0); // ??? WriteByte(block, (byte)area_count); BlockWrite(block, areas, area_count); if (svd->isdemo == RECORD_RELAY) { WriteByte(block, (byte)connected_count); BlockWrite(block, connected, connected_count); } } else { WriteLong(block, seq1); } if (WriteOverflow(block)) return -1; return block->writeoffset - start; } // // ReadPS // Reads a SVC_PLAYERINFO message from a block. // int DM2_ReadPS(block_t *block, player_state_t *ps) { int mask, i; size_t start; start = block->readoffset; mask = (unsigned short)ReadShort(block); if (mask & PS_M_TYPE) ps->pmove.pm_type = ReadByte(block); if (mask & PS_M_ORIGIN) ReadShortPosition(block, ps->pmove.origin); if (mask & PS_M_VELOCITY) ReadShortPosition(block, ps->pmove.velocity); if (mask & PS_M_TIME) ps->pmove.pm_type = ReadByte(block); if (mask & PS_M_FLAGS) ps->pmove.pm_flags = ReadByte(block); if (mask & PS_M_GRAVITY) ps->pmove.gravity = ReadShort(block); if (mask & PS_M_DELTA_ANGLES) ReadShortPosition(block, ps->pmove.delta_angles); if (mask & PS_VIEWOFFSET) ReadOffsetVec(block, ps->viewoffset); if (mask & PS_VIEWANGLES) { ps->viewangles[0] = ReadAngle16(block); ps->viewangles[1] = ReadAngle16(block); ps->viewangles[2] = ReadAngle16(block); } if (mask & PS_KICKANGLES) ReadOffsetVec(block, ps->kick_angles); if (mask & PS_WEAPONINDEX) ps->gunindex = ReadByte(block); if (mask & PS_WEAPONFRAME) { ps->gunframe = ReadByte(block); ReadOffsetVec(block, ps->gunoffset); ReadOffsetVec(block, ps->gunangles); } if (mask & PS_BLEND) ReadBlendVec(block, ps->blend); if (mask & PS_FOV) ps->fov = ReadByte(block); if (mask & PS_RDFLAGS) ps->rdflags = ReadByte(block); mask = ReadLong(block); for (i = 0; i < MAX_STATS; i++) { if (mask & (1 << i)) ps->stats[i] = ReadShort(block); } if (ReadOverflow(block)) return -1; return block->readoffset - start; } // // WritePS // Generates a SVC_PLAYERINFO message from two player states and // writes it to a block. // int DM2_WritePS(block_t *block, const player_state_t *to, const player_state_t *from) { unsigned short mask; unsigned long mask2; int i; size_t start; start = block->writeoffset; // generate masks to save bandwidth mask = mask2 = 0; if (to->pmove.pm_type != from->pmove.pm_type) mask |= PS_M_TYPE; if (!VectorCompare(to->pmove.origin, from->pmove.origin)) mask |= PS_M_ORIGIN; if (!VectorCompare(to->pmove.velocity, from->pmove.velocity)) mask |= PS_M_VELOCITY; if (to->pmove.pm_time != from->pmove.pm_time) mask |= PS_M_TIME; if (to->pmove.pm_flags != from->pmove.pm_flags) mask |= PS_M_FLAGS; if (to->pmove.gravity != from->pmove.gravity) mask |= PS_M_GRAVITY; if (!VectorCompare(to->pmove.delta_angles, from->pmove.delta_angles)) mask |= PS_M_DELTA_ANGLES; if (!VectorCompare(to->viewoffset, from->viewoffset)) mask |= PS_VIEWOFFSET; if (!VectorCompare(to->viewangles, from->viewangles)) mask |= PS_VIEWANGLES; if (!VectorCompare(to->kick_angles, from->kick_angles)) mask |= PS_KICKANGLES; if (to->gunindex != from->gunindex) mask |= PS_WEAPONINDEX; if (to->gunframe != from->gunframe || !VectorCompare(to->gunoffset, from->gunoffset) || !VectorCompare(to->gunangles, from->gunangles)) mask |= PS_WEAPONFRAME; if (to->blend[0] != from->blend[0] || to->blend[1] != from->blend[1] || to->blend[2] != from->blend[2] || to->blend[3] != from->blend[3]) mask |= PS_BLEND; if (to->fov != from->fov) mask |= PS_FOV; if (to->rdflags != from->rdflags) mask |= PS_RDFLAGS; for (i = 0; i < MAX_STATS; i++) { if (i == STAT_FLASHES) { // stats are cleared every frame, so delta // from 0 if (to->stats[i]) mask2 |= (1 << i); } else { if (to->stats[i] != from->stats[i]) mask2 |= (1 << i); } } WriteShort(block, mask); if (mask & PS_M_TYPE) WriteByte(block, (byte)to->pmove.pm_type); if (mask & PS_M_ORIGIN) WriteShortPosition(block, to->pmove.origin); if (mask & PS_M_VELOCITY) WriteShortPosition(block, to->pmove.velocity); if (mask & PS_M_TIME) WriteByte(block, to->pmove.pm_time); if (mask & PS_M_FLAGS) WriteByte(block, to->pmove.pm_flags); if (mask & PS_M_GRAVITY) WriteShort(block, to->pmove.gravity); if (mask & PS_M_DELTA_ANGLES) WriteShortPosition(block, to->pmove.delta_angles); if (mask & PS_VIEWOFFSET) WriteOffsetVec(block, to->viewoffset); if (mask & PS_VIEWANGLES) { WriteAngle16(block, to->viewangles[0]); WriteAngle16(block, to->viewangles[1]); WriteAngle16(block, to->viewangles[2]); } if (mask & PS_KICKANGLES) WriteOffsetVec(block, to->kick_angles); if (mask & PS_WEAPONINDEX) WriteByte(block, (byte)to->gunindex); if (mask & PS_WEAPONFRAME) { WriteByte(block, (byte)to->gunframe); WriteOffsetVec(block, to->gunoffset); WriteOffsetVec(block, to->gunangles); } if (mask & PS_BLEND) WriteBlendVec(block, to->blend); if (mask & PS_FOV) WriteByte(block, (byte)to->fov); if (mask & PS_RDFLAGS) WriteByte(block, (byte)to->rdflags); WriteULong(block, mask2); for (i = 0; i < MAX_STATS; i++) { if (mask2 & (1 << i)) WriteShort(block, to->stats[i]); } if (WriteOverflow(block)) return -1; return block->writeoffset - start; } // // ReadEntityMask // Reads in the mask and entity number of a SVC_SPAWNBASELINE or // SVC_PACKETENTITIES message. This is separate from ReadEntity so // the caller can choose which entity state to read to. // int DM2_ReadEntityMask(block_t *block, int *mask) { int entity; *mask = ReadByte(block); if (*mask & U_MOREBITS1) *mask |= ReadByte(block) << 8; if (*mask & U_MOREBITS2) *mask |= ReadByte(block) << 16; if (*mask & U_MOREBITS3) *mask |= ReadByte(block) << 24; if (*mask & U_NUMBER16) entity = (unsigned short)ReadShort(block); else entity = ReadByte(block); return entity; } // // ReadEntity // Used by ReadPacketEntity or ReadBaselineEntity to read the changes to // one entity. // qboolean DM2_ReadEntity(block_t *block, entity_state_t *es, int mask) { if (mask & U_MODEL) es->modelindex = ReadByte(block); if (mask & U_MODEL2) es->modelindex2 = ReadByte(block); if (mask & U_MODEL3) es->modelindex3 = ReadByte(block); if (mask & U_MODEL4) es->modelindex4 = ReadByte(block); if (mask & U_FRAME8) es->frame = ReadByte(block); if (mask & U_FRAME16) es->frame = ReadShort(block); if (mask & U_SKIN8) { if (mask & U_SKIN16) es->skinnum = ReadLong(block); else es->skinnum = ReadByte(block); } else if (mask & U_SKIN16) es->skinnum = (unsigned short)ReadShort(block); if (mask & U_EFFECTS8) { if (mask & U_EFFECTS16) es->effects = ReadLong(block); else es->effects = ReadByte(block); } else if (mask & U_EFFECTS16) es->effects = (unsigned short)ReadShort(block); if (mask & U_RENDERFX8) { if (mask & U_RENDERFX16) es->renderfx = ReadLong(block); else es->renderfx = ReadByte(block); } else if (mask & U_RENDERFX16) es->renderfx = (unsigned short)ReadShort(block); if (mask & U_ORIGIN1) es->origin[0] = ReadCoord(block); if (mask & U_ORIGIN2) es->origin[1] = ReadCoord(block); if (mask & U_ORIGIN3) es->origin[2] = ReadCoord(block); if (mask & U_ANGLE1) es->angles[0] = ReadAngle(block); if (mask & U_ANGLE2) es->angles[1] = ReadAngle(block); if (mask & U_ANGLE3) es->angles[2] = ReadAngle(block); if (mask & U_OLDORIGIN) ReadPosition(block, es->old_origin); if (mask & U_SOUND) es->sound = ReadByte(block); if (mask & U_EVENT) es->event = ReadByte(block); if (mask & U_SOLID) es->solid = ReadShort(block); if (mask & U_REMOVE) return false; return true; } // // ReadPacketEntity // Reads one entity out of a SVC_PACKETENTITES message. // int DM2_ReadPacketEntity(block_t *block, state_t *state, const state_t *baselines) { int entity, mask; entity = DM2_ReadEntityMask(block, &mask); if (ReadOverflow(block)) return -1; if (!entity && !mask) return 0; if (!ISBITSET(state->active, entity)) state->entities[entity] = baselines->entities[entity]; if (DM2_ReadEntity(block, &state->entities[entity], mask)) SETBIT(state->active, entity, true); else SETBIT(state->active, entity, false); if (ReadOverflow(block)) return -1; return entity; } // // ReadBaselineEntity // Reads a SVC_SPAWNBASELINE message. // int DM2_ReadBaselineEntity(block_t *block, state_t *baselines) { int entity, mask; entity = DM2_ReadEntityMask(block, &mask); DM2_ReadEntity(block, &baselines->entities[entity], mask); if (ReadOverflow(block)) return -1; return entity; } // // WriteEntity // Given a current entity and a delta entity, generates a bit mask // of the changes and writes them to a block. // With SVC_SPAWNBASELINE, use a zero-filled delta entity // With SVC_PACKETENTITIES, determine which delta entity to use (previous // frame or baseline) // int DM2_WriteEntity(block_t *block, const entity_state_t *to, const entity_state_t *from, qboolean is_active, qboolean was_active) { int mask; size_t start; start = block->writeoffset; mask = 0; if (!is_active && was_active) { // entity is no longer active mask |= U_REMOVE; } else { // generate delta bit mask if (to->modelindex != from->modelindex) mask |= U_MODEL; if (to->modelindex2 != from->modelindex2) mask |= U_MODEL2; if (to->modelindex3 != from->modelindex3) mask |= U_MODEL3; if (to->modelindex4 != from->modelindex4) mask |= U_MODEL4; if (to->origin[0] != from->origin[0]) mask |= U_ORIGIN1; if (to->origin[1] != from->origin[1]) mask |= U_ORIGIN2; if (to->origin[2] != from->origin[2]) mask |= U_ORIGIN3; if (to->angles[0] != from->angles[0]) mask |= U_ANGLE1; if (to->angles[1] != from->angles[1]) mask |= U_ANGLE2; if (to->angles[2] != from->angles[2]) mask |= U_ANGLE3; if (to->frame != from->frame) { if ((unsigned)to->frame < 0x100) mask |= U_FRAME8; else mask |= U_FRAME16; } // Use '< 0x8000' instead of '< 0x10000' because shorts are // read as signed shorts. If bit 0x8000 is set and read // as a short, it will be treated as negative when expanded // to an int, which messes things up. "Fixing" this // (writing it as a short and expecting the reader to typecast it // to unsigned short before expanding to int) // would break compatibility with quake2.exe if (to->skinnum != from->skinnum) { if ((unsigned)to->skinnum < 0x100) mask |= U_SKIN8; else if ((unsigned)to->skinnum < 0x8000) mask |= U_SKIN16; else mask |= U_SKIN16|U_SKIN8; } if (to->effects != from->effects) { if ((unsigned)to->effects < 0x100) mask |= U_EFFECTS8; else if ((unsigned)to->effects < 0x8000) mask |= U_EFFECTS16; else mask |= U_EFFECTS16|U_EFFECTS8; } if (to->renderfx != from->renderfx) { if ((unsigned)to->renderfx < 0x100) mask |= U_RENDERFX8; else if ((unsigned)to->renderfx < 0x8000) mask |= U_RENDERFX16; else mask |= U_RENDERFX16|U_RENDERFX8; } if (!VectorCompare(to->old_origin, from->origin)) mask |= U_OLDORIGIN; if (to->sound != from->sound) mask |= U_SOUND; if (to->event) mask |= U_EVENT; if (to->solid != from->solid) mask |= U_SOLID; } // check whether the entity needs to be sent if (!mask && is_active && was_active) return 0; if (!is_active && !was_active) return 0; if (to->number > 0xFF) mask |= U_NUMBER16; if (mask & 0xFF000000) mask |= U_MOREBITS1|U_MOREBITS2|U_MOREBITS3; else if (mask & 0x00FF0000) mask |= U_MOREBITS1|U_MOREBITS2; else if (mask & 0x0000FF00) mask |= U_MOREBITS1; WriteByte(block, (byte)(mask & 0xFF)); if (mask & U_MOREBITS1) WriteByte(block, (byte)((mask >> 8) & 0xFF)); if (mask & U_MOREBITS2) WriteByte(block, (byte)((mask >> 16) & 0xFF)); if (mask & U_MOREBITS3) WriteByte(block, (byte)((mask >> 24) & 0xFF)); if (mask & U_NUMBER16) WriteShort(block, (unsigned short)to->number); else WriteByte(block, (byte)to->number); if (mask & U_MODEL) WriteByte(block, (byte)to->modelindex); if (mask & U_MODEL2) WriteByte(block, (byte)to->modelindex2); if (mask & U_MODEL3) WriteByte(block, (byte)to->modelindex3); if (mask & U_MODEL4) WriteByte(block, (byte)to->modelindex4); if (mask & U_FRAME8) WriteByte(block, (byte)to->frame); if (mask & U_FRAME16) WriteShort(block, (unsigned short)to->frame); if (mask & U_SKIN8) { if (mask & U_SKIN16) WriteLong(block, to->skinnum); else WriteByte(block, (byte)to->skinnum); } else if (mask & U_SKIN16) WriteShort(block, (unsigned short)to->skinnum); if (mask & U_EFFECTS8) { if (mask & U_EFFECTS16) WriteLong(block, to->effects); else WriteByte(block, (byte)to->effects); } else if (mask & U_EFFECTS16) WriteShort(block, (unsigned short)to->effects); if (mask & U_RENDERFX8) { if (mask & U_RENDERFX16) WriteLong(block, to->renderfx); else WriteByte(block, (byte)to->renderfx); } else if (mask & U_RENDERFX16) WriteShort(block, (unsigned short)to->renderfx); if (mask & U_ORIGIN1) WriteCoord(block, to->origin[0]); if (mask & U_ORIGIN2) WriteCoord(block, to->origin[1]); if (mask & U_ORIGIN3) WriteCoord(block, to->origin[2]); if (mask & U_ANGLE1) WriteAngle(block, to->angles[0]); if (mask & U_ANGLE2) WriteAngle(block, to->angles[1]); if (mask & U_ANGLE3) WriteAngle(block, to->angles[2]); if (mask & U_OLDORIGIN) WritePosition(block, to->old_origin); if (mask & U_SOUND) WriteByte(block, (byte)to->sound); if (mask & U_EVENT) WriteByte(block, (byte)to->event); if (mask & U_SOLID) WriteShort(block, (unsigned short)to->solid); if (WriteOverflow(block)) return -1; return block->writeoffset - start; } // // WritePacketEntities // Generates a full SVC_PACKETENTITIES message via WriteEntity // int DM2_WritePacketEntities(block_t *block, const state_t *current, const state_t *delta, const state_t *baselines) { int i; size_t start; const entity_state_t *to, *from; start = block->writeoffset; for (i = 1; i < MAX_EDICTS; i++) { to = ¤t->entities[i]; from = &delta->entities[i]; if (ISBITSET(delta->active, i)) DM2_WriteEntity(block, to, from, ISBITSET(current->active, i), true); else DM2_WriteEntity(block, to, &baselines->entities[i], ISBITSET(current->active, i), false); } WriteShort(block, 0); // terminating packetentities message (mask=0, entity=0) if (WriteOverflow(block)) return -1; return block->writeoffset - start; } // // PRE-FRAME FUNCTIONS // int DM2_WriteConfigstrings(block_t *block, const char (*configstrings)[64], int start, size_t stopsize) { int i; for (i = start; i < MAX_CONFIGSTRINGS; i++) { if (!configstrings[i][0]) continue; if (i != 0 && strlen(configstrings[i-1]) >= 64) continue; if (block->writelen > stopsize) break; WriteByte(block, SVC_CONFIGSTRING); DM2_WriteConfigstring(block, i, configstrings[i]); } return i; } int DM2_WriteBaselines(block_t *block, const state_t *baselines, int start, size_t stopsize) { int i; const entity_state_t *es; static entity_state_t null_es = {0}; for (i = start, es = &baselines->entities[start]; i < MAX_EDICTS; i++, es++) { if (!es->origin[0] && !es->origin[1] && !es->origin[2] && !es->angles[0] && !es->angles[1] && !es->angles[2] && !es->old_origin[0] && !es->old_origin[1] && !es->old_origin[2] && !es->modelindex && !es->modelindex2 && !es->modelindex3 && !es->modelindex4 && !es->frame && !es->skinnum && !es->effects && !es->renderfx && !es->solid && !es->sound && !es->event) { continue; } if (block->writeoffset > stopsize) break; WriteByte(block, SVC_SPAWNBASELINE); DM2_WriteEntity(block, es, &null_es, true, false); } return i; } int DM2_ReadPreFrame(serverdata_t *svd, relayinfo_t *relayinfo, char (*configstrings)[64], state_t *baselines, PFILE *fd) { block_t in; char in_buffer[MAX_SVSLEN]; int numblocks, id; qboolean end; BlockInit(&in, in_buffer, sizeof(in_buffer)); end = false; numblocks = 0; while (!end) { numblocks++; if (DM2_ReadBlock(&in, fd) < 0) return -1; if (in.writeoffset == 0xffffffff) return -1; while (in.readoffset < in.writeoffset) { id = ReadByte(&in); if (ReadOverflow(&in)) return -1; switch(id) { case SVC_STUFFTEXT: { char string[MAX_MSGLEN]; const char *cur; if (DM2_ReadStufftext(&in, string, sizeof(string)) < 0) return -1; cur = string; while (cur) { cur = Cmd_TokenizeString(cur); if (Cmd_Argc() == 0) continue; if (!strcmp(Cmd_Argv(0), "precache")) end = true; } } break; case SVC_SERVERDATA: { if (DM2_ReadServerdata(&in, svd) < 0) return -1; } break; case SVC_CONFIGSTRING: { char string[MAX_MSGLEN]; int index; if (DM2_ReadConfigstring(&in, &index, string) < 0) return -1; strcpy(configstrings[index], string); } break; case SVC_SPAWNBASELINE: { int entity; entity = DM2_ReadBaselineEntity(&in, baselines); if (entity < 0) return -1; } break; default: return -1; } } } return numblocks; } // // DM2_WritePreFrame // Writes the SVC_SERVERDATA, SVC_CONFIGSTRING, SVC_SPAWNBASELINE, and SVC_STUFFTEXT // that precedes all frame information // int DM2_WritePreFrame(const serverdata_t *svd, const relayinfo_t *relayinfo, const char (*configstrings)[64], const state_t *baselines, PFILE *fd) { block_t out; char out_buffer[MAX_SVSLEN]; int i, numblocks; BlockInit(&out, out_buffer, sizeof(out_buffer)); numblocks = 0; WriteByte(&out, SVC_SERVERDATA); DM2_WriteServerdata(&out, svd); i = 0; for(;;) { i = DM2_WriteConfigstrings(&out, configstrings, i, 1024); if (i == MAX_CONFIGSTRINGS) break; if (WriteOverflow(&out)) return -1; DM2_WriteBlock(&out, fd); BlockRewind(&out); numblocks++; } i = 1; for (;;) { i = DM2_WriteBaselines(&out, baselines, i, 1024); if (i == MAX_EDICTS) break; if (WriteOverflow(&out)) return -1; DM2_WriteBlock(&out, fd); BlockRewind(&out); numblocks++; } WriteByte(&out, SVC_STUFFTEXT); DM2_WriteStufftext(&out, "precache\n"); if (WriteOverflow(&out)) return -1; DM2_WriteBlock(&out, fd); numblocks++; return numblocks; }