/* Copyright (C) 1996-1997 Id Software, Inc. 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. */ #include "qwsvdef.h" #ifndef CLIENTONLY void SV_CleanupEnts(void); extern cvar_t sv_nailhack; /* ============================================================================= The PVS must include a small area around the client to allow head bobbing or other small motion on the client side. Otherwise, a bob might cause an entity that should be visible to not show up, especially when the bob crosses a waterline. ============================================================================= */ int needcleanup; int fatbytes; qbyte fatpvs[(MAX_MAP_LEAFS+1)/4]; #ifdef Q2BSPS void SV_Q2BSP_FatPVS (model_t *mod, vec3_t org) { int leafs[64]; int i, j, count; int longs; qbyte *src; vec3_t mins, maxs; for (i=0 ; i<3 ; i++) { mins[i] = org[i] - 8; maxs[i] = org[i] + 8; } count = CM_BoxLeafnums (mod, mins, maxs, leafs, 64, NULL); if (count < 1) Sys_Error ("SV_Q2FatPVS: count < 1"); if (sv.worldmodel->fromgame == fg_quake3) longs = CM_ClusterSize(mod); else longs = (CM_NumClusters(mod)+31)>>5; // convert leafs to clusters for (i=0 ; iv->modelindex != sv_nailmodel && ent->v->modelindex != sv_supernailmodel) return false; if (sv_nailhack.value) return false; demonails = false; if (numnails == MAX_NAILS) return true; nails[numnails] = ent; numnails++; return true; } qboolean SV_DemoNailUpdate (int i) { demonails = true; if (numnails == MAX_NAILS) return true; nails[numnails] = (edict_t *)i; numnails++; return true; } #ifdef PEXT_LIGHTUPDATES qboolean SV_AddLightUpdate (edict_t *ent) { if (ent->v->modelindex != sv_lightningmodel) return false; if (numlight == MAX_NAILS) return true; light[numnails] = ent; numlight++; return true; } #endif void SV_EmitNailUpdate (sizebuf_t *msg, qboolean recorder) { qbyte bits[6]; // [48 bits] xyzpy 12 12 12 4 8 int n, i; edict_t *ent; int x, y, z, p, yaw; #ifdef PEXT_LIGHTUPDATES if (numlight) { MSG_WriteByte (msg, svc_lightnings); MSG_WriteByte (msg, numlight); for (n=0 ; nv->origin[0]+4096)>>1; y = (int)(ent->v->origin[1]+4096)>>1; z = (int)(ent->v->origin[2]+4096)>>1; p = (int)(16*ent->v->angles[0]/360)&15; yaw = (int)(256*ent->v->angles[1]/360)&255; bits[0] = x; bits[1] = (x>>8) | (y<<4); bits[2] = (y>>4); bits[3] = z; bits[4] = (z>>8) | (p<<4); bits[5] = yaw; for (i=0 ; i<6 ; i++) MSG_WriteByte (msg, bits[i]); } } #endif if (!numnails) return; if (recorder) MSG_WriteByte (msg, svc_nails2); else MSG_WriteByte (msg, svc_nails); MSG_WriteByte (msg, numnails); if (demonails) { for (n=0 ; n>1; y = (int)(sv.demospikes[i].org[1]+4096)>>1; z = (int)(sv.demospikes[i].org[2]+4096)>>1; p = (int)(sv.demospikes[i].pitch)&15; yaw = (int)(sv.demospikes[i].yaw)&255; bits[0] = x; bits[1] = (x>>8) | (y<<4); bits[2] = (y>>4); bits[3] = z; bits[4] = (z>>8) | (p<<4); bits[5] = yaw; for (i=0 ; i<6 ; i++) MSG_WriteByte (msg, bits[i]); } return; } for (n=0 ; nv->colormap) { if (!((++nailcount)&255)) nailcount++; ent->v->colormap = nailcount&255; } MSG_WriteByte (msg, (qbyte)ent->v->colormap); } x = (int)(ent->v->origin[0]+4096)>>1; y = (int)(ent->v->origin[1]+4096)>>1; z = (int)(ent->v->origin[2]+4096)>>1; p = (int)(16*ent->v->angles[0]/360)&15; yaw = (int)(256*ent->v->angles[1]/360)&255; bits[0] = x; bits[1] = (x>>8) | (y<<4); bits[2] = (y>>4); bits[3] = z; bits[4] = (z>>8) | (p<<4); bits[5] = yaw; for (i=0 ; i<6 ; i++) MSG_WriteByte (msg, bits[i]); } } //============================================================================= //this is the bit of the code that sends the csqc entity deltas out. //whenever the entity in question has a newer version than we sent to the client, we need to resend. //So, we track the outgoing sequence that an entity was sent in, and the version. //Upon detection of a dropped packet, we resend all entities who were last sent in that packet. //When an entities' last sent version doesn't match the current version, we send. static qboolean SV_AddCSQCUpdate (client_t *client, edict_t *ent) { #ifndef PEXT_CSQC return false; #else if (!(client->csqcactive)) return false; if (!ent->v->SendEntity) return false; csqcent[csqcnuments++] = ent; return true; #endif } sizebuf_t csqcmsgbuffer; void SV_EmitCSQCUpdate(client_t *client, sizebuf_t *msg) { #ifdef PEXT_CSQC qbyte messagebuffer[1024]; int en; int currentsequence = client->netchan.outgoing_sequence; unsigned short mask; globalvars_t *pr_globals = PR_globals(svprogfuncs, PR_CURRENT); edict_t *ent; qboolean writtenheader = false; // if (!csqcnuments) // return; if (!(client->csqcactive)) return; //FIXME: prioritise the list of csqc ents somehow csqcmsgbuffer.data = messagebuffer; csqcmsgbuffer.maxsize = sizeof(messagebuffer); csqcmsgbuffer.packing = msg->packing; for (en = 0; en < csqcnuments; en++) { ent = csqcent[en]; //prevent mishaps with entities being respawned and things. if ((int)ent->v->Version < sv.csqcentversion[ent->entnum]) ent->v->Version = sv.csqcentversion[ent->entnum]; else sv.csqcentversion[ent->entnum] = (int)ent->v->Version; //If it's not changed, don't send if (client->csqcentversions[ent->entnum] == sv.csqcentversion[ent->entnum]) continue; csqcmsgbuffer.cursize = 0; csqcmsgbuffer.currentbit = 0; //Ask CSQC to write a buffer for it. G_INT(OFS_PARM0) = EDICT_TO_PROG(svprogfuncs, client->edict); pr_global_struct->self = EDICT_TO_PROG(svprogfuncs, ent); PR_ExecuteProgram(svprogfuncs, ent->v->SendEntity); if (G_INT(OFS_RETURN)) //0 means not to tell the client about it. { if (msg->cursize + csqcmsgbuffer.cursize+5 >= msg->maxsize) { if (csqcmsgbuffer.cursize < 32) break; continue; } if (!writtenheader) { writtenheader=true; MSG_WriteByte(msg, svc_csqcentities); } MSG_WriteShort(msg, ent->entnum); if (sv.csqcdebug) //optional extra. { if (!csqcmsgbuffer.cursize) Con_Printf("Warning: empty csqc packet on %s\n", svprogfuncs->stringtable+ent->v->classname); MSG_WriteShort(msg, csqcmsgbuffer.cursize); } //FIXME: Add a developer mode to write the length of each entity. SZ_Write(msg, csqcmsgbuffer.data, csqcmsgbuffer.cursize); // Con_Printf("Sending update packet %i\n", ent->entnum); } else if (sv.csqcentversion[ent->entnum]) { //Don't want to send. if (!writtenheader) { writtenheader=true; MSG_WriteByte(msg, svc_csqcentities); } mask = (unsigned)ent->entnum | 0x8000; MSG_WriteShort(msg, mask); // Con_Printf("Sending remove 2 packet\n"); } client->csqcentversions[ent->entnum] = sv.csqcentversion[ent->entnum]; client->csqcentsequence[ent->entnum] = currentsequence; } for (en = 1; en < sv.num_edicts; en++) { if (client->csqcentversions[en] && (client->csqcentversions[en] != sv.csqcentversion[en])) { ent = EDICT_NUM(svprogfuncs, en); // if (!ent->isfree) // continue; if (msg->cursize + 5 >= msg->maxsize) //try removing next frame instead. { } else { if (!writtenheader) { writtenheader=true; MSG_WriteByte(msg, svc_csqcentities); } // Con_Printf("Sending remove packet %i\n", en); mask = (unsigned)en | 0x8000; MSG_WriteShort(msg, mask); client->csqcentversions[en] = 0; client->csqcentsequence[en] = currentsequence; } } } if (writtenheader) MSG_WriteShort(msg, 0); //a 0 means no more. csqcnuments = 0; //prevent the qc from trying to use it at inopertune times. csqcmsgbuffer.maxsize = 0; csqcmsgbuffer.data = NULL; #endif } #ifdef PEXT_CSQC void SV_CSQC_DroppedPacket(client_t *client, int sequence) { int i; if (!(client->csqcactive)) //we don't need this, but it might be a little faster. return; for (i = 0; i < sv.num_edicts; i++) if (client->csqcentsequence[i] == sequence) client->csqcentversions[i]--; //do that update thang (but later). } void SV_CSQC_DropAll(client_t *client) { int i; if (!(client->csqcactive)) //we don't need this, but it might be a little faster. return; for (i = 0; i < sv.num_edicts; i++) client->csqcentversions[i]--; //do that update thang (but later). } #endif //============================================================================= /* ================== SV_WriteDelta Writes part of a packetentities message. Can delta from either a baseline or a previous packet_entity ================== */ void SV_WriteDelta (entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, unsigned int protext) { #ifdef PROTOCOLEXTENSIONS int evenmorebits=0; #endif int bits; int i; float miss; static entity_state_t defaultbaseline; if (from == &((edict_t*)NULL)->baseline) from = &defaultbaseline; // send an update bits = 0; if (to->number >= 512) { if (to->number >= 1024) { if (to->number >= 1024+512) evenmorebits |= U_ENTITYDBL; evenmorebits |= U_ENTITYDBL2; if (to->number >= 2048) SV_Error ("Entity number >= 2048"); } else evenmorebits |= U_ENTITYDBL; } for (i=0 ; i<3 ; i++) { miss = to->origin[i] - from->origin[i]; if ( miss < -0.1 || miss > 0.1 ) { bits |= U_ORIGIN1<angles[0] != from->angles[0] ) bits |= U_ANGLE1; if ( to->angles[1] != from->angles[1] ) bits |= U_ANGLE2; if ( to->angles[2] != from->angles[2] ) bits |= U_ANGLE3; if ( to->colormap != from->colormap ) bits |= U_COLORMAP; if ( to->skinnum != from->skinnum ) bits |= U_SKIN; if ( to->frame != from->frame ) bits |= U_FRAME; if ( (to->effects&0x00ff) != (from->effects&0x00ff) ) bits |= U_EFFECTS; if ( (to->effects&0xff00) != (from->effects&0xff00) ) evenmorebits |= U_EFFECTS16; if ( to->modelindex != from->modelindex ) { bits |= U_MODEL; if (to->modelindex > 255) evenmorebits |= U_MODELDBL; } #ifdef PROTOCOLEXTENSIONS #ifdef U_SCALE if ( to->scale != from->scale && protext & PEXT_SCALE) evenmorebits |= U_SCALE; #endif #ifdef U_TRANS if ( to->trans != from->trans && protext & PEXT_TRANS) evenmorebits |= U_TRANS; #endif #ifdef U_FATNESS if ( to->fatness != from->fatness && protext & PEXT_FATNESS) evenmorebits |= U_FATNESS; #endif if ( to->hexen2flags != from->hexen2flags && protext & PEXT_HEXEN2) evenmorebits |= U_DRAWFLAGS; if ( to->abslight != from->abslight && protext & PEXT_HEXEN2) evenmorebits |= U_ABSLIGHT; if (to->glowsize != from->glowsize) to->dpflags |= 4; if (to->dpflags != from->dpflags && protext & PEXT_DPFLAGS) evenmorebits |= U_DPFLAGS; if ((to->tagentity != from->tagentity || to->tagindex != from->tagindex) && protext & PEXT_DPFLAGS) evenmorebits |= U_TAGINFO; if ((to->light[0] != from->light[0] || to->light[1] != from->light[1] || to->light[2] != from->light[2] || to->light[3] != from->light[3] || to->lightstyle != from->lightstyle || to->lightpflags != from->lightstyle) && protext & PEXT_DPFLAGS) evenmorebits |= U_LIGHT; if (evenmorebits&0xff00) evenmorebits |= U_YETMORE; if (evenmorebits&0x00ff) bits |= U_EVENMORE; if (bits & 511) bits |= U_MOREBITS; #endif if (to->flags & U_SOLID) bits |= U_SOLID; if (msg->cursize + 40 > msg->maxsize) { //not enough space in the buffer, don't send the entity this frame. (not sending means nothing changes, and it takes no bytes!!) *to = *from; return; } // // write the message // if (!to->number) SV_Error ("Unset entity number"); if (!bits && !force) return; // nothing to send! i = (to->number&511) | (bits&~511); if (i & U_REMOVE) Sys_Error ("U_REMOVE"); MSG_WriteShort (msg, i); if (bits & U_MOREBITS) MSG_WriteByte (msg, bits&255); #ifdef PROTOCOLEXTENSIONS if (bits & U_EVENMORE) MSG_WriteByte (msg, evenmorebits&255); if (evenmorebits & U_YETMORE) MSG_WriteByte (msg, (evenmorebits>>8)&255); #endif if (bits & U_MODEL) MSG_WriteByte (msg, to->modelindex&255); if (bits & U_FRAME) MSG_WriteByte (msg, to->frame); if (bits & U_COLORMAP) MSG_WriteByte (msg, to->colormap); if (bits & U_SKIN) MSG_WriteByte (msg, to->skinnum); if (bits & U_EFFECTS) MSG_WriteByte (msg, to->effects&0x00ff); if (bits & U_ORIGIN1) MSG_WriteCoord (msg, to->origin[0]); if (bits & U_ANGLE1) MSG_WriteAngle(msg, to->angles[0]); if (bits & U_ORIGIN2) MSG_WriteCoord (msg, to->origin[1]); if (bits & U_ANGLE2) MSG_WriteAngle(msg, to->angles[1]); if (bits & U_ORIGIN3) MSG_WriteCoord (msg, to->origin[2]); if (bits & U_ANGLE3) MSG_WriteAngle(msg, to->angles[2]); #ifdef U_SCALE if (evenmorebits & U_SCALE) MSG_WriteByte (msg, (qbyte)(to->scale)); #endif #ifdef U_TRANS if (evenmorebits & U_TRANS) MSG_WriteByte (msg, (qbyte)(to->trans)); #endif #ifdef U_FATNESS if (evenmorebits & U_FATNESS) MSG_WriteChar (msg, to->fatness); #endif if (evenmorebits & U_DRAWFLAGS) MSG_WriteByte (msg, to->hexen2flags); if (evenmorebits & U_ABSLIGHT) MSG_WriteByte (msg, to->abslight); if (evenmorebits & U_DPFLAGS) MSG_WriteByte (msg, to->dpflags); if (evenmorebits & U_TAGINFO) { MSG_WriteShort (msg, to->tagentity); MSG_WriteShort (msg, to->tagindex); } if (evenmorebits & U_LIGHT) { MSG_WriteShort (msg, to->light[0]); MSG_WriteShort (msg, to->light[1]); MSG_WriteShort (msg, to->light[2]); MSG_WriteShort (msg, to->light[3]); MSG_WriteByte (msg, to->lightstyle); MSG_WriteByte (msg, to->lightpflags); } if (evenmorebits & U_EFFECTS16) MSG_WriteByte (msg, (to->effects&0xff00)>>8); } /* ============= SV_EmitPacketEntities Writes a delta update of a packet_entities_t to the message. ============= */ void SV_EmitPacketEntities (client_t *client, packet_entities_t *to, sizebuf_t *msg) { edict_t *ent; client_frame_t *fromframe; packet_entities_t *from; int oldindex, newindex; int oldnum, newnum; int oldmax; // this is the frame that we are going to delta update from if (client->delta_sequence != -1) { fromframe = &client->frames[client->delta_sequence & UPDATE_MASK]; from = &fromframe->entities; oldmax = from->num_entities; MSG_WriteByte (msg, svc_deltapacketentities); MSG_WriteByte (msg, client->delta_sequence); } else { oldmax = 0; // no delta update from = NULL; MSG_WriteByte (msg, svc_packetentities); } newindex = 0; oldindex = 0; //Con_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK // , client->netchan.outgoing_sequence & UPDATE_MASK); while (newindex < to->num_entities || oldindex < oldmax) { newnum = newindex >= to->num_entities ? 9999 : to->entities[newindex].number; oldnum = oldindex >= oldmax ? 9999 : from->entities[oldindex].number; if (newnum == oldnum) { // delta update from old position //Con_Printf ("delta %i\n", newnum); #ifdef PROTOCOLEXTENSIONS SV_WriteDelta (&from->entities[oldindex], &to->entities[newindex], msg, false, client->fteprotocolextensions); #else SV_WriteDelta (&from->entities[oldindex], &to->entities[newindex], msg, false); #endif oldindex++; newindex++; continue; } if (newnum < oldnum) { // this is a new entity, send it from the baseline ent = EDICT_NUM(svprogfuncs, newnum); //Con_Printf ("baseline %i\n", newnum); #ifdef PROTOCOLEXTENSIONS SV_WriteDelta (&ent->baseline, &to->entities[newindex], msg, true, client->fteprotocolextensions); #else SV_WriteDelta (&ent->baseline, &to->entities[newindex], msg, true); #endif newindex++; continue; } if (newnum > oldnum) { // the old entity isn't present in the new message //Con_Printf ("remove %i\n", oldnum); if (oldnum > 512) { //yup, this is expensive. MSG_WriteShort (msg, oldnum | U_REMOVE|U_MOREBITS); MSG_WriteByte (msg, U_EVENMORE); if (oldnum >= 1024) { if (oldnum >= 1024+512) MSG_WriteByte (msg, U_ENTITYDBL2); else MSG_WriteByte (msg, U_ENTITYDBL|U_ENTITYDBL2); } else MSG_WriteByte (msg, U_ENTITYDBL); } else MSG_WriteShort (msg, oldnum | U_REMOVE); oldindex++; continue; } } if (newindex > to->max_entities) Con_Printf("Exceeded max entities\n"); MSG_WriteShort (msg, 0); // end of packetentities } #ifdef NQPROT // reset all entity fields (typically used if status changed) #define E5_FULLUPDATE (1<<0) // E5_ORIGIN32=0: short[3] = s->origin[0] * 8, s->origin[1] * 8, s->origin[2] * 8 // E5_ORIGIN32=1: float[3] = s->origin[0], s->origin[1], s->origin[2] #define E5_ORIGIN (1<<1) // E5_ANGLES16=0: byte[3] = s->angle[0] * 256 / 360, s->angle[1] * 256 / 360, s->angle[2] * 256 / 360 // E5_ANGLES16=1: short[3] = s->angle[0] * 65536 / 360, s->angle[1] * 65536 / 360, s->angle[2] * 65536 / 360 #define E5_ANGLES (1<<2) // E5_MODEL16=0: byte = s->modelindex // E5_MODEL16=1: short = s->modelindex #define E5_MODEL (1<<3) // E5_FRAME16=0: byte = s->frame // E5_FRAME16=1: short = s->frame #define E5_FRAME (1<<4) // byte = s->skin #define E5_SKIN (1<<5) // E5_EFFECTS16=0 && E5_EFFECTS32=0: byte = s->effects // E5_EFFECTS16=1 && E5_EFFECTS32=0: short = s->effects // E5_EFFECTS16=0 && E5_EFFECTS32=1: int = s->effects // E5_EFFECTS16=1 && E5_EFFECTS32=1: int = s->effects #define E5_EFFECTS (1<<6) // bits >= (1<<8) #define E5_EXTEND1 (1<<7) // byte = s->renderflags #define E5_FLAGS (1<<8) // byte = bound(0, s->alpha * 255, 255) #define E5_ALPHA (1<<9) // byte = bound(0, s->scale * 16, 255) #define E5_SCALE (1<<10) // flag #define E5_ORIGIN32 (1<<11) // flag #define E5_ANGLES16 (1<<12) // flag #define E5_MODEL16 (1<<13) // byte = s->colormap #define E5_COLORMAP (1<<14) // bits >= (1<<16) #define E5_EXTEND2 (1<<15) // short = s->tagentity // byte = s->tagindex #define E5_ATTACHMENT (1<<16) // short[4] = s->light[0], s->light[1], s->light[2], s->light[3] // byte = s->lightstyle // byte = s->lightpflags #define E5_LIGHT (1<<17) // byte = s->glowsize // byte = s->glowcolor #define E5_GLOW (1<<18) // short = s->effects #define E5_EFFECTS16 (1<<19) // int = s->effects #define E5_EFFECTS32 (1<<20) // flag #define E5_FRAME16 (1<<21) // unused #define E5_COLORMOD (1<<22) // bits >= (1<<24) #define E5_EXTEND3 (1<<23) // unused #define E5_UNUSED24 (1<<24) // unused #define E5_UNUSED25 (1<<25) // unused #define E5_UNUSED26 (1<<26) // unused #define E5_UNUSED27 (1<<27) // unused #define E5_UNUSED28 (1<<28) // unused #define E5_UNUSED29 (1<<29) // unused #define E5_UNUSED30 (1<<30) // bits2 > 0 #define E5_EXTEND4 (1<<31) void SVDP_EmitEntity(entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean isnew) { int bits; if (!isnew && !memcmp(from, to, sizeof(entity_state_t))) { to->bitmask = 0; return; //didn't change } bits = 0; if (isnew) { bits |= E5_FULLUPDATE; to->bitmask = 0; //no point... } if (!VectorCompare(from->origin, to->origin)) bits |= E5_ORIGIN; if (!VectorCompare(from->angles, to->angles)) bits |= E5_ANGLES; if (from->modelindex != to->modelindex) bits |= E5_MODEL; if (from->frame != to->frame) bits |= E5_FRAME; if (from->skinnum != to->skinnum) bits |= E5_SKIN; if (from->effects != to->effects) bits |= E5_EFFECTS; if (from->dpflags != to->dpflags) bits |= E5_FLAGS; if (from->trans != to->trans) bits |= E5_ALPHA; if (from->scale != to->scale) bits |= E5_SCALE; if (from->colormap != to->colormap) bits |= E5_COLORMAP; if (from->tagentity != to->tagentity || from->tagindex != to->tagindex) bits |= E5_ATTACHMENT; if (from->light[0] != to->light[0] || from->light[1] != to->light[1] || from->light[2] != to->light[2] || from->light[3] != to->light[3] || from->lightstyle != to->lightstyle || from->lightpflags != to->lightpflags) bits |= E5_LIGHT; if (from->glowsize != to->glowsize || from->glowcolour != to->glowcolour) bits |= E5_GLOW; if (from->colormod[0] != to->colormod[0] || from->colormod[1] != to->colormod[1] || from->colormod[2] != to->colormod[2]) bits |= E5_COLORMOD; if ((bits & E5_ORIGIN) && (/*!(to->flags & RENDER_LOWPRECISION) ||*/ to->origin[0] < -4096 || to->origin[0] >= 4096 || to->origin[1] < -4096 || to->origin[1] >= 4096 || to->origin[2] < -4096 || to->origin[2] >= 4096)) bits |= E5_ORIGIN32; if ((bits & E5_ANGLES)/* && !(to->flags & RENDER_LOWPRECISION)*/) bits |= E5_ANGLES16; if ((bits & E5_MODEL) && to->modelindex >= 256) bits |= E5_MODEL16; if ((bits & E5_FRAME) && to->frame >= 256) bits |= E5_FRAME16; if (bits & E5_EFFECTS) { if (to->effects >= 65536) bits |= E5_EFFECTS32; else if (to->effects >= 256) bits |= E5_EFFECTS16; } if (bits >= 256) bits |= E5_EXTEND1; if (bits >= 65536) bits |= E5_EXTEND2; if (bits >= 16777216) bits |= E5_EXTEND3; bits |= to->bitmask; to->bitmask = bits; if (!bits) return; MSG_WriteShort(msg, to->number); MSG_WriteByte(msg, bits & 0xFF); if (bits & E5_EXTEND1) MSG_WriteByte(msg, (bits >> 8) & 0xFF); if (bits & E5_EXTEND2) MSG_WriteByte(msg, (bits >> 16) & 0xFF); if (bits & E5_EXTEND3) MSG_WriteByte(msg, (bits >> 24) & 0xFF); if (bits & E5_FLAGS) MSG_WriteByte(msg, to->dpflags); if (bits & E5_ORIGIN) { if (bits & E5_ORIGIN32) { MSG_WriteFloat(msg, to->origin[0]); MSG_WriteFloat(msg, to->origin[1]); MSG_WriteFloat(msg, to->origin[2]); } else { MSG_WriteShort(msg, to->origin[0]*8); MSG_WriteShort(msg, to->origin[1]*8); MSG_WriteShort(msg, to->origin[2]*8); } } if (bits & E5_ANGLES) { if (bits & E5_ANGLES16) { MSG_WriteAngle16(msg, to->angles[0]); MSG_WriteAngle16(msg, to->angles[1]); MSG_WriteAngle16(msg, to->angles[2]); } else { MSG_WriteAngle8(msg, to->angles[0]); MSG_WriteAngle8(msg, to->angles[1]); MSG_WriteAngle8(msg, to->angles[2]); } } if (bits & E5_MODEL) { if (bits & E5_MODEL16) MSG_WriteShort(msg, to->modelindex); else MSG_WriteByte(msg, to->modelindex); } if (bits & E5_FRAME) { if (bits & E5_FRAME16) MSG_WriteShort(msg, to->frame); else MSG_WriteByte(msg, to->frame); } if (bits & E5_SKIN) MSG_WriteByte(msg, to->skinnum); if (bits & E5_EFFECTS) { if (bits & E5_EFFECTS32) MSG_WriteLong(msg, to->effects); else if (bits & E5_EFFECTS16) MSG_WriteShort(msg, to->effects); else MSG_WriteByte(msg, to->effects); } if (bits & E5_ALPHA) MSG_WriteByte(msg, to->trans); if (bits & E5_SCALE) MSG_WriteByte(msg, to->scale); if (bits & E5_COLORMAP) MSG_WriteByte(msg, to->colormap); if (bits & E5_ATTACHMENT) { MSG_WriteShort(msg, to->tagentity); MSG_WriteByte(msg, to->tagindex); } if (bits & E5_LIGHT) { MSG_WriteShort(msg, to->light[0]); MSG_WriteShort(msg, to->light[1]); MSG_WriteShort(msg, to->light[2]); MSG_WriteShort(msg, to->light[3]); MSG_WriteByte(msg, to->lightstyle); MSG_WriteByte(msg, to->lightpflags); } if (bits & E5_GLOW) { MSG_WriteByte(msg, to->glowsize); MSG_WriteByte(msg, to->glowcolour); } if (bits & E5_COLORMOD) { MSG_WriteByte(msg, to->colormod[0]); MSG_WriteByte(msg, to->colormod[1]); MSG_WriteByte(msg, to->colormod[2]); } } entity_state_t defaultstate; void SVDP_EmitEntitiesUpdate (client_t *client, packet_entities_t *to, sizebuf_t *msg) { edict_t *ent; client_frame_t *fromframe; packet_entities_t *from; int oldindex, newindex; int oldnum, newnum; int oldmax; client->netchan.incoming_sequence++; // this is the frame that we are going to delta update from fromframe = &client->frames[(client->netchan.incoming_sequence-2) & UPDATE_MASK]; from = &fromframe->entities; oldmax = from->num_entities; // Con_Printf ("frame %i\n", client->netchan.incoming_sequence); MSG_WriteByte(msg, svcdp_entities); MSG_WriteLong(msg, 0); if (client->protocol == SCP_DARKPLACES7) MSG_WriteLong(msg, client->last_sequence); for (newindex = 0; newindex < to->num_entities; newindex++) to->entities[newindex].bitmask = 0; //add in the bitmasks of dropped packets. newindex = 0; oldindex = 0; //Con_Printf ("---%i to %i ----\n", client->delta_sequence & UPDATE_MASK // , client->netchan.outgoing_sequence & UPDATE_MASK); while (newindex < to->num_entities || oldindex < oldmax) { newnum = newindex >= to->num_entities ? 0x7fff : to->entities[newindex].number; oldnum = oldindex >= oldmax ? 0x7fff : from->entities[oldindex].number; if (newnum == oldnum) { // delta update from old position //Con_Printf ("delta %i\n", newnum); SVDP_EmitEntity (&from->entities[oldindex], &to->entities[newindex], msg, false); oldindex++; newindex++; continue; } if (newnum < oldnum) { // this is a new entity, send it from the baseline... as far as dp understands it... ent = EDICT_NUM(svprogfuncs, newnum); //Con_Printf ("baseline %i\n", newnum); SVDP_EmitEntity (&defaultstate, &to->entities[newindex], msg, true); newindex++; continue; } if (newnum > oldnum) { // the old entity isn't present in the new message //Con_Printf ("remove %i\n", oldnum); MSG_WriteShort(msg, oldnum | 0x8000); oldindex++; continue; } } MSG_WriteShort(msg, 0x8000); } #endif int SV_HullNumForPlayer(int h2hull, float *mins, float *maxs) { vec3_t size; int diff; int best; int hullnum, i; if (sv.worldmodel->fromgame == fg_quake2 || sv.worldmodel->fromgame == fg_quake3) { VectorSubtract (maxs, mins, size); return size[2]; //clients are expected to decide themselves. } if (h2hull) return h2hull-1; hullnum = 0; best = 8192; //x/y pos/neg are assumed to be the same magnitute. //y pos/height are assumed to be different from all the others. for (i = 0; i < MAX_MAP_HULLSM; i++) { #define sq(x) ((x)*(x)) diff = sq(sv.worldmodel->hulls[i].clip_maxs[2] - maxs[2]) + sq(sv.worldmodel->hulls[i].clip_mins[2] - mins[2]) + sq(sv.worldmodel->hulls[i].clip_maxs[0] - maxs[0]) + sq(sv.worldmodel->hulls[i].clip_mins[0] - mins[0]); if (diff < best) { best = diff; hullnum=i; } } return hullnum; } #if 1 typedef struct { int playernum; qboolean onladder; usercmd_t *lastcmd; int modelindex; int modelindex2; int frame; int weaponframe; float *angles; float *origin; float *velocity; int effects; int skin; float *mins; float *maxs; float scale; float transparency; float fatness; float localtime; int health; int spectator; //0=send to a player. 1=non-tracked player, to a spec. 2=tracked player, to a spec(or self) qboolean isself; int fteext; int zext; int hull; client_t *cl; } clstate_t; void SV_WritePlayerToClient(sizebuf_t *msg, clstate_t *ent) { usercmd_t cmd; int msec; int hullnumber; int i; int pflags; int pm_type, pm_code; int zext = ent->zext; pflags = PF_MSEC | PF_COMMAND; if (ent->modelindex != sv_playermodel) pflags |= PF_MODEL; if (ent->velocity) for (i=0 ; i<3 ; i++) if (ent->velocity[i]) pflags |= PF_VELOCITY1<effects) pflags |= PF_EFFECTS; if (ent->skin || ent->modelindex>=256) pflags |= PF_SKINNUM; if (ent->health <= 0) pflags |= PF_DEAD; if (progstype == PROG_QW) { if (ent->mins[2] != -24) pflags |= PF_GIB; } else if (progstype == PROG_H2) { // if (ent->maxs[2] != 56) // pflags |= PF_GIB; } else { if (ent->mins[2] != -24) pflags |= PF_GIB; } if (ent->isself) { if (ent->spectator) pflags &= PF_VELOCITY1 | PF_VELOCITY2 | PF_VELOCITY3 | PF_DEAD | PF_GIB; else { // don't send a lot of data on personal entity pflags &= ~(PF_MSEC|PF_COMMAND); if (ent->weaponframe) pflags |= PF_WEAPONFRAME; } } if (ent->spectator == 2 && ent->weaponframe) //it's not us, but we are spectating, so we need the correct weaponframe pflags |= PF_WEAPONFRAME; if (!ent->isself || ent->fteext & PEXT_SPLITSCREEN) { #ifdef PEXT_SCALE //this is graphics, not physics if (ent->fteext & PEXT_SCALE) { if (ent->scale) pflags |= PF_SCALE_Z; } #endif #ifdef PEXT_TRANS if (ent->fteext & PEXT_TRANS) { if (ent->transparency) pflags |= PF_TRANS_Z; } #endif #ifdef PEXT_FATNESS if (ent->fteext & PEXT_FATNESS) { if (ent->fatness) pflags |= PF_FATNESS_Z; } #endif } #ifdef PEXT_HULLSIZE if (ent->fteext & PEXT_HULLSIZE) { hullnumber = SV_HullNumForPlayer(ent->hull, ent->mins, ent->maxs); if (hullnumber != 1) pflags |= PF_HULLSIZE_Z; } else hullnumber=1; #endif if (zext&Z_EXT_PM_TYPE) { if (ent->cl) { if (ent->cl->viewent) pm_type = PMC_NONE; else pm_type = SV_PMTypeForClient (ent->cl); switch (pm_type) { case PM_NORMAL: // Z_EXT_PM_TYPE protocol extension if (ent->cl->jump_held) pm_code = PMC_NORMAL_JUMP_HELD; // encode pm_type and jump_held into pm_code else pm_code = PMC_NORMAL; break; case PM_OLD_SPECTATOR: pm_code = PMC_OLD_SPECTATOR; break; case PM_SPECTATOR: // Z_EXT_PM_TYPE_NEW protocol extension pm_code = PMC_SPECTATOR; break; case PM_FLY: pm_code = PMC_FLY; break; case PM_DEAD: pm_code = PMC_NORMAL; break; case PM_NONE: pm_code = PMC_NONE; break; default: Sys_Error("SV_WritePlayersToClient: unexpected pm_type"); pm_code=0; } } else pm_code = (ent->zext & Z_EXT_PM_TYPE_NEW)?PMC_SPECTATOR:PMC_OLD_SPECTATOR;//(ent->spectator && ent->isself) ? PMC_OLD_SPECTATOR : PMC_NORMAL; pflags |= pm_code << PF_PMC_SHIFT; } if (pflags & 0xff0000) pflags |= PF_EXTRA_PFS; MSG_WriteByte (msg, svc_playerinfo); MSG_WriteByte (msg, ent->playernum); MSG_WriteShort (msg, pflags&0xffff); if (pflags & PF_EXTRA_PFS) { MSG_WriteByte(msg, (pflags&0xff0000)>>16); } //we need to tell the client that it's moved, as it's own origin might not be natural for (i=0 ; i<3 ; i++) MSG_WriteCoord (msg, ent->origin[i]+(sv.demostatevalid?1:0)); MSG_WriteByte (msg, ent->frame); if (pflags & PF_MSEC) { msec = 1000*(sv.time - ent->localtime); if (msec < 0) msec = 0; if (msec > 255) msec = 255; MSG_WriteByte (msg, msec); } if (pflags & PF_COMMAND) { if (ent->lastcmd) cmd = *ent->lastcmd; else { memset(&cmd, 0, sizeof(cmd)); cmd.angles[0] = ent->angles[0] * 65535/360.0f; cmd.angles[1] = ent->angles[1] * 65535/360.0f; cmd.angles[2] = ent->angles[2] * 65535/360.0f; } if (ent->health <= 0) { // don't show the corpse looking around... cmd.angles[0] = 0; cmd.angles[1] = ent->angles[1]*65535/360; cmd.angles[0] = 0; } cmd.buttons = 0; // never send buttons cmd.impulse = 0; // never send impulses MSG_WriteDeltaUsercmd (msg, &nullcmd, &cmd); } if (ent->velocity) { for (i=0 ; i<3 ; i++) if (pflags & (PF_VELOCITY1<velocity[i]); } else { for (i=0 ; i<3 ; i++) if (pflags & (PF_VELOCITY1<modelindex); } if (pflags & PF_SKINNUM) MSG_WriteByte (msg, ent->skin | (((pflags & PF_MODEL)&&(ent->modelindex>=256))<<7)); if (pflags & PF_EFFECTS) MSG_WriteByte (msg, ent->effects); if (pflags & PF_WEAPONFRAME) MSG_WriteByte (msg, ent->weaponframe); #ifdef PEXT_SCALE if (pflags & PF_SCALE_Z) MSG_WriteByte (msg, ent->scale*50); #endif #ifdef PEXT_TRANS if (pflags & PF_TRANS_Z) MSG_WriteByte (msg, (qbyte)(ent->transparency*255)); #endif #ifdef PEXT_FATNESS if (pflags & PF_FATNESS_Z) MSG_WriteChar (msg, ent->fatness*2); #endif #ifdef PEXT_HULLSIZE //shrunken or crouching in halflife levels. (possibly enlarged) if (pflags & PF_HULLSIZE_Z) MSG_WriteChar (msg, hullnumber + (ent->onladder?128:0)); //physics. #endif } #endif /* ============= SV_WritePlayersToClient ============= */ void SV_WritePlayersToClient (client_t *client, edict_t *clent, qbyte *pvs, sizebuf_t *msg) { qboolean isbot; int i, j; client_t *cl; edict_t *ent, *vent; int pflags; demo_frame_t *demo_frame; demo_client_t *dcl; #define DF_DEAD (1<<8) #define DF_GIB (1<<9) if (clent == NULL) //write to demo file. (no PVS) { demo_frame = &demo.frames[demo.parsecount&DEMO_FRAMES_MASK]; for (j=0,cl=svs.clients, dcl = demo_frame->clients; jstate != cs_spawned) continue; if (sv.demostatevalid) { if (client != cl) continue; } ent = cl->edict; if (cl->viewent && ent == clent) vent = EDICT_NUM(svprogfuncs, cl->viewent); else vent = ent; if (progstype != PROG_QW) { if ((int)ent->v->effects & EF_MUZZLEFLASH) { if (needcleanup < (j+1)) { needcleanup = (j+1); MSG_WriteByte(&sv.multicast, svc_muzzleflash); MSG_WriteShort(&sv.multicast, (j+1)); SV_Multicast(ent->v->origin, MULTICAST_PVS); } } } if (cl->spectator) continue; dcl->parsecount = demo.parsecount; VectorCopy(vent->v->origin, dcl->info.origin); VectorCopy(vent->v->angles, dcl->info.angles); dcl->info.angles[0] *= -3; dcl->info.angles[2] = 0; // no roll angle if (ent->v->health <= 0) { // don't show the corpse looking around... dcl->info.angles[0] = 0; dcl->info.angles[1] = vent->v->angles[1]; dcl->info.angles[2] = 0; } if (ent != vent) { dcl->info.model = 0; //invisible. dcl->info.effects = 0; } else { dcl->info.skinnum = ent->v->skin; dcl->info.effects = ent->v->effects; dcl->info.weaponframe = ent->v->weaponframe; dcl->info.model = ent->v->modelindex; } dcl->sec = sv.time - cl->localtime; dcl->frame = ent->v->frame; dcl->flags = 0; dcl->cmdtime = cl->localtime; dcl->fixangle = demo.fixangle[j]; demo.fixangle[j] = 0; if (ent->v->health <= 0) dcl->flags |= DF_DEAD; if (ent->v->mins[2] != -24) dcl->flags |= DF_GIB; continue; } return; } #ifdef NQPROT if (!ISQWCLIENT(client)) return; #endif if (sv.demostatevalid) //this is a demo { usercmd_t cmd; vec3_t ang; vec3_t org; vec3_t vel; float lerp; float a1, a2; extern vec3_t player_mins, player_maxs; clstate_t clst; extern float olddemotime, nextdemotime; for (i=0 ; ifteprotocolextensions; clst.zext = 0;//client->zquake_extensions; clst.cl = NULL; lerp = (realtime - olddemotime) / (nextdemotime - olddemotime); if (lerp < 0) lerp = 0; if (lerp > 1) lerp = 1; for (j = 0; j < 3; j++) { a1 = (360.0f/256)*sv.recordedplayer[i].oldang[j]; a2 = (360.0f/256)*sv.demostate[i+1].angles[j]; a2 = a2 - a1; if (a2 > 180) a2-=360; if (a2 < -180) a2+=360; ang[j] = (a1 + (a2)*lerp); org[j] = sv.recordedplayer[i].oldorg[j] + (sv.demostate[i+1].origin[j] - sv.recordedplayer[i].oldorg[j])*lerp; vel[j] = (-sv.recordedplayer[i].oldorg[j] + sv.demostate[i+1].origin[j])*(nextdemotime - olddemotime); } ang[0] *= -3; // ang[0] = ang[1] = ang[2] = 0; memset(&cmd, 0, sizeof(cmd)); cmd.angles[0] = ang[0]*65535/360.0f; cmd.angles[1] = ang[1]*65535/360.0f; cmd.angles[2] = ang[2]*65535/360.0f; cmd.msec = 50; {vec3_t f, r, u, v; AngleVectors(ang, f, r, u); VectorCopy(vel, v); cmd.forwardmove = DotProduct(f, v); cmd.sidemove = DotProduct(r, v); cmd.upmove = DotProduct(u, v); } clst.lastcmd=NULL; SV_WritePlayerToClient(msg, &clst); } } //now build the spectator's thingie memset(&clst, 0, sizeof(clst)); clst.fteext = 0;//client->fteprotocolextensions; clst.zext = 0;//client->zquake_extensions; clst.playernum = MAX_CLIENTS-1; clst.isself = true; clst.modelindex = 0; clst.hull = 1; clst.frame = 0; clst.localtime = sv.time; clst.mins = player_mins; clst.maxs = player_maxs; clst.angles = vec3_origin; //not needed, as the client knows better than us anyway. clst.origin = client->specorigin; clst.velocity = client->specvelocity; for (client = client; client; client = client->controller) { clst.health = 100; if (client->spec_track) { clst.weaponframe = sv.recordedplayer[client->spec_track-1].weaponframe; clst.spectator = 2; } else { clst.weaponframe = 0; clst.spectator = 1; } SV_WritePlayerToClient(msg, &clst); clst.playernum--; } return; } for (j=0,cl=svs.clients ; jstate && cl->name[0] || cl->protocol == SCP_BAD; if (cl->state != cs_spawned) //this includes bots if (!isbot || progstype == PROG_QW) //unless they're NQ bots... continue; ent = cl->edict; if (cl->viewent && ent == clent) { vent = EDICT_NUM(svprogfuncs, cl->viewent); if (!vent) vent = ent; } else vent = ent; if (progstype != PROG_QW) { if (progstype == PROG_H2 && (int)ent->v->effects & H2EF_NODRAW && ent != clent) continue; if ((int)ent->v->effects & EF_MUZZLEFLASH) { if (needcleanup < (j+1)) { needcleanup = (j+1); MSG_WriteByte(&sv.multicast, svc_muzzleflash); MSG_WriteShort(&sv.multicast, (j+1)); SV_Multicast(ent->v->origin, MULTICAST_PVS); } } } // ZOID visibility tracking if (ent != clent && !(client->spec_track && client->spec_track - 1 == j)) { if (cl->spectator) continue; // ignore if not touching a PV leaf if (!sv.worldmodel->funcs.EdictInFatPVS(sv.worldmodel, ent)) continue; if (!((int)clent->v->dimension_see & ((int)ent->v->dimension_seen | (int)ent->v->dimension_ghost))) continue; //not in this dimension - sorry... } if (SV_AddCSQCUpdate(client, ent)) continue; { clstate_t clst; clst.playernum = j; clst.onladder = (int)ent->v->fteflags&FF_LADDER; clst.lastcmd = &cl->lastcmd; clst.modelindex = vent->v->modelindex; clst.modelindex2 = vent->v->vweapmodelindex; clst.frame = vent->v->frame; clst.weaponframe = ent->v->weaponframe; clst.angles = ent->v->angles; clst.origin = vent->v->origin; clst.velocity = vent->v->velocity; clst.effects = ent->v->effects; if (progstype == PROG_H2 && ((int)vent->v->effects & H2EF_NODRAW)) { clst.effects = 0; clst.modelindex = 0; } clst.skin = vent->v->skin; clst.mins = vent->v->mins; clst.hull = vent->v->hull; clst.maxs = vent->v->maxs; clst.scale = vent->v->scale; clst.transparency = vent->v->alpha; //QSG_DIMENSION_PLANES - if the only shared dimensions are ghost dimensions, Set half alpha. if (((int)clent->v->dimension_see & (int)ent->v->dimension_ghost)) if (!((int)clent->v->dimension_see & ((int)ent->v->dimension_seen & ~(int)ent->v->dimension_ghost)) ) { if (ent->v->dimension_ghost_alpha) clst.transparency *= ent->v->dimension_ghost_alpha; else clst.transparency *= 0.5; } clst.fatness = vent->v->fatness; clst.localtime = cl->localtime; clst.health = ent->v->health; clst.spectator = 0; clst.fteext = client->fteprotocolextensions; clst.zext = client->zquake_extensions; clst.cl = cl; if (ent != vent) clst.modelindex = 0; if (sv.demostatevalid) clst.health = 100; clst.isself = false; if ((cl == client || cl->controller == client)) { clst.isself = true; clst.spectator = 0; if (client->spectator) { if (client->spec_track) { clst.spectator = 2; clst.mins = svs.clients[client->spec_track-1].edict->v->mins; clst.maxs = svs.clients[client->spec_track-1].edict->v->maxs; clst.health = svs.clients[client->spec_track-1].edict->v->health; clst.weaponframe = svs.clients[client->spec_track-1].edict->v->weaponframe; } else { clst.spectator = 1; clst.health = 1; } } } else if (client->spectator) { clst.health=100; if (client->spec_track && ent == clent) clst.spectator = 2; else clst.spectator = 1; } if (isbot) { clst.lastcmd = NULL; clst.velocity = NULL; } SV_WritePlayerToClient(msg, &clst); } //FIXME: Name flags //player is visible, now would be a good time to update what the player is like. pflags = 0; if (client->fteprotocolextensions & PEXT_VWEAP && client->otherclientsknown[j].vweap != ent->v->vweapmodelindex) { pflags |= 1; client->otherclientsknown[j].vweap = ent->v->vweapmodelindex; } if (pflags) { ClientReliableWrite_Begin(client, svc_ftesetclientpersist, 10); ClientReliableWrite_Short(client, pflags); if (pflags & 1) ClientReliableWrite_Short(client, client->otherclientsknown[j].vweap); ClientReliable_FinishWrite(client); } } } void SVNQ_EmitEntity(sizebuf_t *msg, edict_t *ent, int entnum) { #define NQU_MOREBITS (1<<0) #define NQU_ORIGIN1 (1<<1) #define NQU_ORIGIN2 (1<<2) #define NQU_ORIGIN3 (1<<3) #define NQU_ANGLE2 (1<<4) #define NQU_NOLERP (1<<5) // don't interpolate movement #define NQU_FRAME (1<<6) #define NQU_SIGNAL (1<<7) // just differentiates from other updates // svc_update can pass all of the fast update bits, plus more #define NQU_ANGLE1 (1<<8) #define NQU_ANGLE3 (1<<9) #define NQU_MODEL (1<<10) #define NQU_COLORMAP (1<<11) #define NQU_SKIN (1<<12) #define NQU_EFFECTS (1<<13) #define NQU_LONGENTITY (1<<14) // LordHavoc's: protocol extension #define DPU_EXTEND1 (1<<15) // LordHavoc: first extend byte #define DPU_DELTA (1<<16) // no data, while this is set the entity is delta compressed (uses previous frame as a baseline, meaning only things that have changed from the previous frame are sent, except for the forced full update every half second) #define DPU_ALPHA (1<<17) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 1, and the entity is not sent if <=0 unless it has effects (model effects are checked as well) #define DPU_SCALE (1<<18) // 1 byte, scale / 16 positive, not sent if 1.0 #define DPU_EFFECTS2 (1<<19) // 1 byte, this is .effects & 0xFF00 (second byte) #define DPU_GLOWSIZE (1<<20) // 1 byte, encoding is float/4.0, unsigned, not sent if 0 #define DPU_GLOWCOLOR (1<<21) // 1 byte, palette index, default is 254 (white), this IS used for darklight (allowing colored darklight), however the particles from a darklight are always black, not sent if default value (even if glowsize or glowtrail is set) // LordHavoc: colormod feature has been removed, because no one used it #define DPU_COLORMOD (1<<22) // 1 byte, 3 bit red, 3 bit green, 2 bit blue, this lets you tint an object artifically, so you could make a red rocket, or a blue fiend... #define DPU_EXTEND2 (1<<23) // another byte to follow // LordHavoc: second extend byte #define DPU_GLOWTRAIL (1<<24) // leaves a trail of particles (of color .glowcolor, or black if it is a negative glowsize) #define DPU_VIEWMODEL (1<<25) // attachs the model to the view (origin and angles become relative to it), only shown to owner, a more powerful alternative to .weaponmodel and such #define DPU_FRAME2 (1<<26) // 1 byte, this is .frame & 0xFF00 (second byte) #define DPU_MODEL2 (1<<27) // 1 byte, this is .modelindex & 0xFF00 (second byte) #define DPU_EXTERIORMODEL (1<<28) // causes this model to not be drawn when using a first person view (third person will draw it, first person will not) #define DPU_UNUSED29 (1<<29) // future expansion #define DPU_UNUSED30 (1<<30) // future expansion #define DPU_EXTEND3 (1<<31) // another byte to follow, future expansion int i, eff; float miss; unsigned int bits=0; int glowsize, glowcolor; for (i=0 ; i<3 ; i++) { miss = ent->v->origin[i] - ent->baseline.origin[i]; if ( miss < -0.1 || miss > 0.1 ) bits |= NQU_ORIGIN1<v->angles[0] != ent->baseline.angles[0] ) bits |= NQU_ANGLE1; if (ent->v->angles[1] != ent->baseline.angles[1] ) bits |= NQU_ANGLE2; if (ent->v->angles[2] != ent->baseline.angles[2] ) bits |= NQU_ANGLE3; if ((ent->v->movetype == MOVETYPE_STEP || (ent->v->movetype == MOVETYPE_PUSH)) && (bits & (U_ANGLE1|U_ANGLE2|U_ANGLE3))) bits |= NQU_NOLERP; // don't mess up the step animation if (ent->baseline.colormap != ent->v->colormap && ent->v->colormap>=0) bits |= NQU_COLORMAP; if (ent->baseline.skinnum != ent->v->skin) bits |= NQU_SKIN; if (ent->baseline.frame != ent->v->frame) bits |= NQU_FRAME; eff = ent->v->effects; if ((ent->baseline.effects & 0x00ff) != ((int)eff & 0x00ff)) bits |= NQU_EFFECTS; if (ent->baseline.modelindex != ent->v->modelindex) bits |= NQU_MODEL; if (entnum >= 256) bits |= NQU_LONGENTITY; if (1) { if (ent->baseline.trans != ent->v->alpha) if (!(ent->baseline.trans == 1 && !ent->v->alpha)) bits |= DPU_ALPHA; if (ent->baseline.scale != ent->v->scale) { if (ent->v->scale != 0 || ent->baseline.scale != 1) bits |= DPU_SCALE; } if (ent->v->modelindex >= 256) //as much as protocols can handle bits |= DPU_MODEL2; if ((ent->baseline.effects&0xff00) != ((int)eff & 0xff00)) bits |= DPU_EFFECTS2; if (ent->v->exteriormodeltoclient == EDICT_TO_PROG(svprogfuncs, host_client->edict)) bits |= DPU_EXTERIORMODEL; if (ent->v->viewmodelforclient == EDICT_TO_PROG(svprogfuncs, host_client->edict)) bits |= DPU_VIEWMODEL; glowsize = ent->v->glow_size*0.25f; glowcolor = ent->v->glow_color; if (0 != glowsize) bits |= DPU_GLOWSIZE; if (0 != glowcolor) bits |= DPU_GLOWCOLOR; } else { if (ent->v->modelindex >= 256) //as much as protocols can handle return; if (entnum >= 768) //too many for a conventional nq client. return; } if (bits & 0xFF000000) bits |= DPU_EXTEND2; if (bits & 0xFF0000) bits |= DPU_EXTEND1; if (bits & 0xFF00) bits |= NQU_MOREBITS; // // write the message // #ifdef PARANOID MSG_WriteByte (msg,(bits | NQU_SIGNAL) & 0xFF); //gets caught on 'range error' #else MSG_WriteByte (msg,bits | NQU_SIGNAL); #endif if (bits & NQU_MOREBITS) MSG_WriteByte (msg, bits>>8); if (bits & DPU_EXTEND1) MSG_WriteByte (msg, bits>>16); if (bits & DPU_EXTEND2) MSG_WriteByte (msg, bits>>24); if (bits & NQU_LONGENTITY) MSG_WriteShort (msg,entnum); else MSG_WriteByte (msg,entnum); if (bits & NQU_MODEL) MSG_WriteByte (msg, ent->v->modelindex); if (bits & NQU_FRAME) MSG_WriteByte (msg, ent->v->frame); if (bits & NQU_COLORMAP) MSG_WriteByte (msg, ent->v->colormap); if (bits & NQU_SKIN) MSG_WriteByte (msg, ent->v->skin); if (bits & NQU_EFFECTS) MSG_WriteByte (msg, eff & 0x00ff); if (bits & NQU_ORIGIN1) MSG_WriteCoord (msg, ent->v->origin[0]); if (bits & NQU_ANGLE1) MSG_WriteAngle(msg, ent->v->angles[0]); if (bits & NQU_ORIGIN2) MSG_WriteCoord (msg, ent->v->origin[1]); if (bits & NQU_ANGLE2) MSG_WriteAngle(msg, ent->v->angles[1]); if (bits & NQU_ORIGIN3) MSG_WriteCoord (msg, ent->v->origin[2]); if (bits & NQU_ANGLE3) MSG_WriteAngle(msg, ent->v->angles[2]); if (bits & DPU_ALPHA) MSG_WriteByte(msg, ent->v->alpha*255); if (bits & DPU_SCALE) MSG_WriteByte(msg, ent->v->scale*16); if (bits & DPU_EFFECTS2) MSG_WriteByte(msg, eff >> 8); if (bits & DPU_GLOWSIZE) MSG_WriteByte(msg, glowsize); if (bits & DPU_GLOWCOLOR) MSG_WriteByte(msg, glowcolor); // if (bits & DPU_COLORMOD) MSG_WriteByte(msg, colormod); if (bits & DPU_FRAME2) MSG_WriteByte(msg, (int)ent->v->frame >> 8); if (bits & DPU_MODEL2) MSG_WriteByte(msg, (int)ent->v->modelindex >> 8); } typedef struct gibfilter_s { struct gibfilter_s *next; int modelindex; int minframe; int maxframe; } gibfilter_t; gibfilter_t *gibfilter; void SV_GibFilterAdd(char *modelname, int min, int max) { int i; gibfilter_t *gf; for (i=1; sv.model_precache[i] ; i++) if (!strcmp(sv.model_precache[i], modelname)) break; if (!sv.model_precache[i]) { Con_Printf("Filtered model \"%s\" was not precached\n", modelname); return; //model not in use. } gf = Z_Malloc(sizeof(gibfilter_t)); gf->modelindex = i; gf->minframe = ((min==-1)?0:min); gf->maxframe = ((max==-1)?0x80000000:max); gf->next = gibfilter; gibfilter = gf; } void SV_GibFilterInit(void) { char buffer[2048]; char *file; int min, max; gibfilter_t *gf; while(gibfilter) { gf = gibfilter; gibfilter = gibfilter->next; Z_Free(gf); } file = COM_LoadStackFile("gibfiltr.cfg", buffer, sizeof(buffer)); if (!file) { Con_Printf("gibfiltr.cfg file was not found. Using defaults\n"); SV_GibFilterAdd("progs/gib1.mdl", -1, -1); SV_GibFilterAdd("progs/gib2.mdl", -1, -1); SV_GibFilterAdd("progs/gib3.mdl", -1, -1); SV_GibFilterAdd("progs/h_player.mdl", -1, -1); return; } while(file) { file = COM_Parse(file); if (!file) { return; } min = atoi(com_token); file = COM_Parse(file); //handles nulls nicly max = atoi(com_token); file = COM_Parse(file); if (!file) { Con_Printf("Sudden ending to gibfiltr.cfg\n"); return; } SV_GibFilterAdd(com_token, min, max); } } qboolean SV_GibFilter(edict_t *ent) { int indx = ent->v->modelindex; int frame = ent->v->frame; gibfilter_t *gf; for (gf = gibfilter; gf; gf=gf->next) { if (gf->modelindex == indx) if (frame >= gf->minframe && frame <= gf->maxframe) return true; } return false; } #ifdef Q2BSPS static int clientarea; void Q2BSP_FatPVS(model_t *mod, vec3_t org, qboolean add) {//fixme: this doesn't add int leafnum; leafnum = CM_PointLeafnum (mod, org); clientarea = CM_LeafArea (mod, leafnum); SV_Q2BSP_FatPVS (mod, org); } qboolean Q2BSP_EdictInFatPVS(model_t *mod, edict_t *ent) { int i,l; if (!CM_AreasConnected (mod, clientarea, ent->areanum)) { // doors can legally straddle two areas, so // we may need to check another one if (!ent->areanum2 || !CM_AreasConnected (mod, clientarea, ent->areanum2)) return false; // blocked by a door } if (ent->num_leafs == -1) { // too many leafs for individual check, go by headnode if (!CM_HeadnodeVisible (mod, ent->headnode, fatpvs)) return false; } else { // check individual leafs for (i=0 ; i < ent->num_leafs ; i++) { l = ent->leafnums[i]; if (fatpvs[l >> 3] & (1 << (l&7) )) break; } if (i == ent->num_leafs) return false; // not visible } return true; } #endif /* ============= SV_WriteEntitiesToClient Encodes the current state of the world as a svc_packetentities messages and possibly a svc_nails message and svc_playerinfo messages ============= */ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg, qboolean ignorepvs) { #define DEPTHOPTIMISE #ifdef DEPTHOPTIMISE float distances[MAX_EXTENDED_PACKET_ENTITIES]; float dist; #endif int e, i; qbyte *pvs; vec3_t org; edict_t *ent; packet_entities_t *pack; mvdentity_state_t *dement; edict_t *clent; client_frame_t *frame; entity_state_t *state; client_t *split; // this is the frame we are creating frame = &client->frames[client->netchan.incoming_sequence & UPDATE_MASK]; // find the client's PVS if (!ignorepvs) { clent = client->edict; VectorAdd (clent->v->origin, clent->v->view_ofs, org); sv.worldmodel->funcs.FatPVS(sv.worldmodel, org, false); #ifdef PEXT_VIEW2 if (clent->v->view2) sv.worldmodel->funcs.FatPVS(sv.worldmodel, PROG_TO_EDICT(svprogfuncs, clent->v->view2)->v->origin, true); #endif for (split = client->controlled; split; split = split->controlled) sv.worldmodel->funcs.FatPVS(sv.worldmodel, split->edict->v->origin, true); /* if (sv.worldmodel->fromgame == fg_doom) { } else #ifdef Q2BSPS if (sv.worldmodel->fromgame == fg_quake2 || sv.worldmodel->fromgame == fg_quake3) { leafnum = CM_PointLeafnum (org); clientarea = CM_LeafArea (leafnum); clientcluster = CM_LeafCluster (leafnum); SV_Q2BSP_FatPVS (org); } else #endif { SV_Q1BSP_FatPVS (org); #ifdef PEXT_VIEW2 if (clent->v->view2) SV_Q1BSP_AddToFatPVS (PROG_TO_EDICT(svprogfuncs, clent->v->view2)->v->origin, sv.worldmodel->nodes); //add a little more... #endif for (split = client->controlled; split; split = split->controlled) SV_Q1BSP_AddToFatPVS (split->edict->v->origin, sv.worldmodel->nodes); //add a little more... } */ } else clent = NULL; pvs = fatpvs; host_client = client; // send over the players in the PVS SV_WritePlayersToClient (client, clent, pvs, msg); // put other visible entities into either a packet_entities or a nails message pack = &frame->entities; pack->num_entities = 0; numnails = 0; #ifdef PEXT_LIGHTUPDATES numlight = 0; #endif if (sv.demostatevalid) //generate info from demo stats { for (e=1, dement=&sv.demostate[e] ; e<=sv.demomaxents ; e++, dement++) { if (!dement->modelindex) continue; if (e >= 1 && e <= MAX_CLIENTS) continue; if (pack->num_entities == pack->max_entities) continue; // all full //the entity would mess up the client and possibly disconnect them. //FIXME: add an option to drop clients... entity fog could be killed in this way. if (e >= 512 && !(client->fteprotocolextensions & PEXT_ENTITYDBL)) continue; if (e >= 1024 && !(client->fteprotocolextensions & PEXT_ENTITYDBL2)) continue; // if (dement->modelindex >= 256 && !(client->fteprotocolextensions & PEXT_MODELDBL)) // continue; state = &pack->entities[pack->num_entities]; pack->num_entities++; state->number = e; state->flags = EF_DIMLIGHT; VectorCopy (dement->origin, state->origin); state->angles[0] = dement->angles[0]*360.0f/256; state->angles[1] = dement->angles[1]*360.0f/256; state->angles[2] = dement->angles[2]*360.0f/256; state->modelindex = dement->modelindex; state->frame = dement->frame; state->colormap = dement->colormap; state->skinnum = dement->skinnum; state->effects = dement->effects; #ifdef PEXT_SCALE state->scale = dement->scale; #endif #ifdef PEXT_TRANS state->trans = dement->trans; #endif #ifdef PEXT_FATNESS state->fatness = dement->fatness; #endif } for (e = 0; e < sv.numdemospikes; e++) { if (SV_DemoNailUpdate (e)) continue; } // encode the packet entities as a delta from the // last packetentities acknowledged by the client SV_EmitPacketEntities (client, pack, msg); // now add the specialized nail update SV_EmitNailUpdate (msg, ignorepvs); return; } if (client->viewent #ifdef NQPROT && ISQWCLIENT(client) #endif ) //this entity is watching from outside themselves. The client is tricked into thinking that they themselves are in the view ent, and a new dummy ent (the old them) must be spawned. { distances[0] = 0; state = &pack->entities[pack->num_entities]; pack->num_entities++; state->number = client - svs.clients + 1; state->flags = 0; VectorCopy (clent->v->origin, state->origin); VectorCopy (clent->v->angles, state->angles); state->modelindex = clent->v->modelindex; state->frame = clent->v->frame; state->colormap = clent->v->colormap; state->skinnum = clent->v->skin; state->effects = clent->v->effects; state->hexen2flags = clent->v->drawflags; state->abslight = clent->v->abslight; #ifdef PEXT_SCALE state->scale = clent->v->scale*16; if (!state->scale) state->scale = 1*16; #endif #ifdef PEXT_TRANS state->trans = clent->v->alpha*255; if (!state->trans) state->trans = 255; #endif #ifdef PEXT_FATNESS state->fatness = clent->v->fatness*2; #endif if (state->effects & QWEF_FLAG1) { memcpy(&pack->entities[pack->num_entities], state, sizeof(*state)); state = &pack->entities[pack->num_entities]; pack->num_entities++; state->modelindex = SV_ModelIndex("progs/flag.mdl"); state->frame = 0; state->number++; state->skinnum = 0; } else if (state->effects & QWEF_FLAG2) { memcpy(&pack->entities[pack->num_entities], state, sizeof(*state)); state = &pack->entities[pack->num_entities]; pack->num_entities++; state->modelindex = SV_ModelIndex("progs/flag.mdl"); state->frame = 0; state->number++; state->skinnum = 1; } } #ifdef NQPROT for (e=(ISQWCLIENT(client)?sv.allocated_client_slots+1:1) ; ev->SendEntity && (!ent->v->modelindex || !*PR_GetString(svprogfuncs, ent->v->model)) && !((int)ent->v->pflags & PFLAGS_FULLDYNAMIC)) continue; if (progstype != PROG_QW) { if ((int)ent->v->effects & EF_MUZZLEFLASH) { if (needcleanup < e) { needcleanup = e; MSG_WriteByte(&sv.multicast, svc_muzzleflash); MSG_WriteShort(&sv.multicast, e); SV_Multicast(ent->v->origin, MULTICAST_PVS); } } } if (!ignorepvs && ent != clent) { //branch out to the pvs testing. if (ent->v->viewmodelforclient == EDICT_TO_PROG(svprogfuncs, client->edict)) { //unconditional } else if (ent->v->tag_entity) { edict_t *p = ent; int c = 10; while(p->v->tag_entity&&c-->0) { p = EDICT_NUM(svprogfuncs, p->v->tag_entity); } if (!sv.worldmodel->funcs.EdictInFatPVS(sv.worldmodel, p)) continue; } else { if (!sv.worldmodel->funcs.EdictInFatPVS(sv.worldmodel, ent)) continue; } } if (client->gibfilter && SV_GibFilter(ent)) continue; // if (strstr(sv.model_precache[(int)ent->v->modelindex], "gib")) // continue; if (ent->v->nodrawtoclient) //DP extension. if (ent->v->nodrawtoclient == EDICT_TO_PROG(svprogfuncs, client->edict)) continue; if (ent->v->drawonlytoclient) if (ent->v->drawonlytoclient != EDICT_TO_PROG(svprogfuncs, client->edict)) { client_t *split; for (split = client->controlled; split; split=split->controlled) { if (split->edict->v->view2 == EDICT_TO_PROG(svprogfuncs, ent)) break; } if (!split) continue; } //QSG_DIMENSION_PLANES if (client->edict) if (!((int)client->edict->v->dimension_see & ((int)ent->v->dimension_seen | (int)ent->v->dimension_ghost))) continue; //not in this dimension - sorry... if (SV_AddCSQCUpdate(client, ent)) //csqc took it. continue; #ifdef NQPROT if (client->protocol == SCP_NETQUAKE) { if (msg->cursize + 32 > msg->maxsize) break; SVNQ_EmitEntity(msg, ent, e); continue; } #endif if (ISQWCLIENT(client)) if (SV_AddNailUpdate (ent)) continue; // added to the special update list #ifdef PEXT_LIGHTUPDATES if (client->fteprotocolextensions & PEXT_LIGHTUPDATES) if (SV_AddLightUpdate (ent)) continue; #endif //the entity would mess up the client and possibly disconnect them. //FIXME: add an option to drop clients... entity fog could be killed in this way. if (e >= 512) { if (!(client->fteprotocolextensions & PEXT_ENTITYDBL)) { continue; } else if (e >= 1024) { if (!(client->fteprotocolextensions & PEXT_ENTITYDBL2)) continue; else if (e >= 2048) continue; } } if (ent->v->modelindex >= 256 && !(client->fteprotocolextensions & PEXT_MODELDBL)) continue; #ifdef DEPTHOPTIMISE if (clent) { //find distance based upon absolute mins/maxs so bsps are treated fairly. VectorAdd(ent->v->absmin, ent->v->absmax, org); VectorMA(clent->v->origin, -0.5, org, org); dist = Length(org); // add to the packetentities if (pack->num_entities == pack->max_entities) { float furthestdist = -1; int best=-1; for (i = 0; i < pack->max_entities; i++) if (furthestdist < distances[i]) { furthestdist = distances[i]; best = i; } if (furthestdist > dist && best != -1) { state = &pack->entities[best]; // Con_Printf("Dropping ent %s\n", sv.model_precache[state->modelindex]); memmove(&distances[best], &distances[best+1], sizeof(*distances)*(pack->num_entities-best-1)); memmove(state, state+1, sizeof(*state)*(pack->num_entities-best-1)); best = pack->num_entities-1; distances[best] = dist; state = &pack->entities[best]; } else continue; // all full } else { state = &pack->entities[pack->num_entities]; distances[pack->num_entities] = dist; pack->num_entities++; } } else #endif { // add to the packetentities if (pack->num_entities == pack->max_entities) continue; // all full else { state = &pack->entities[pack->num_entities]; pack->num_entities++; } } // these are bits for the 'flags' field of the entity_state_t #define RENDER_STEP 1 #define RENDER_GLOWTRAIL 2 #define RENDER_VIEWMODEL 4 #define RENDER_EXTERIORMODEL 8 #define RENDER_LOWPRECISION 16 // send as low precision coordinates to save bandwidth #define RENDER_COLORMAPPED 32 state->dpflags = 0; if (ent->v->viewmodelforclient) { if (ent->v->viewmodelforclient == EDICT_TO_PROG(svprogfuncs, client->edict)) state->dpflags |= RENDER_VIEWMODEL; else { //noone else sees it. pack->num_entities--; continue; } } if (ent->v->exteriormodeltoclient) { if (ent->v->exteriormodeltoclient == EDICT_TO_PROG(svprogfuncs, client->edict)) state->dpflags |= RENDER_EXTERIORMODEL; //everyone else sees it normally. } state->number = e; state->flags = 0; VectorCopy (ent->v->origin, state->origin); VectorCopy (ent->v->angles, state->angles); state->modelindex = ent->v->modelindex; state->frame = ent->v->frame; state->colormap = ent->v->colormap; state->skinnum = ent->v->skin; state->effects = ent->v->effects; state->hexen2flags = ent->v->drawflags; state->abslight = (int)(ent->v->abslight*255) & 255; state->tagentity = ent->v->tag_entity; state->tagindex = ent->v->tag_index; state->light[0] = ent->v->color[0]*255; state->light[1] = ent->v->color[1]*255; state->light[2] = ent->v->color[2]*255; state->light[3] = ent->v->light_lev; state->lightstyle = ent->v->style; state->lightpflags = ent->v->pflags; if ((int)ent->v->flags & FL_CLASS_DEPENDENT && client->playerclass) //hexen2 wierdness. { char modname[MAX_QPATH]; Q_strncpyz(modname, sv.model_precache[state->modelindex], sizeof(modname)); if (strlen(modname)>5) { modname[strlen(modname)-5] = client->playerclass+'0'; state->modelindex = SV_ModelIndex(modname); } } if (/*progstype == PROG_H2 &&*/ ent->v->solid == SOLID_BSP) state->angles[0]*=-1; if (state->effects & EF_FULLBRIGHT) { state->hexen2flags |= MLS_FULLBRIGHT; } if (progstype != PROG_QW && state->effects && ISQWCLIENT(client)) //don't send extra nq effects to a qw client. { //EF_NODRAW doesn't draw the model. //The client still needs to know about it though, as it might have other effects on it. if (progstype == PROG_H2) { if (state->effects & H2EF_NODRAW) state->modelindex = 0; } else { if (state->effects & NQEF_NODRAW) state->modelindex = 0; } state->effects &= ~ (QWEF_FLAG1|QWEF_FLAG2); } state->glowsize = ent->v->glow_size*0.25; state->glowcolour = ent->v->glow_color; if (ent->v->glow_trail) state->dpflags |= RENDER_GLOWTRAIL; #ifdef PEXT_SCALE state->scale = ent->v->scale*16; if (!ent->v->scale) state->scale = 1*16; #endif #ifdef PEXT_TRANS state->trans = ent->v->alpha*255; if (!ent->v->alpha) state->trans = 255; //QSG_DIMENSION_PLANES - if the only shared dimensions are ghost dimensions, Set half alpha. if (client->edict) if (((int)client->edict->v->dimension_see & (int)ent->v->dimension_ghost)) if (!((int)client->edict->v->dimension_see & ((int)ent->v->dimension_seen & ~(int)ent->v->dimension_ghost)) ) { if (ent->v->dimension_ghost_alpha) state->trans *= ent->v->dimension_ghost_alpha; else state->trans *= 0.5; } #endif #ifdef PEXT_FATNESS state->fatness = ent->v->fatness*2; #endif } #ifdef NQPROT if (ISNQCLIENT(client)) { if (client->protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) { SVDP_EmitEntitiesUpdate(client, pack, msg); SV_EmitCSQCUpdate(client, msg); return; } else { client->netchan.incoming_sequence++; return; } } #endif // encode the packet entities as a delta from the // last packetentities acknowledged by the client SV_EmitPacketEntities (client, pack, msg); SV_EmitCSQCUpdate(client, msg); // now add the specialized nail update SV_EmitNailUpdate (msg, ignorepvs); } void SV_CleanupEnts(void) { int e; edict_t *ent; if (!needcleanup) return; for (e=1 ; e<=needcleanup ; e++) { ent = EDICT_NUM(svprogfuncs, e); if ((int)ent->v->effects & EF_MUZZLEFLASH) ent->v->effects = (int)ent->v->effects & ~EF_MUZZLEFLASH; } needcleanup=0; } #endif