/* 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. */ // sv_main.c -- server main program #include "quakedef.h" #ifndef CLIENTONLY #define CHAN_AUTO 0 #define CHAN_WEAPON 1 #define CHAN_VOICE 2 #define CHAN_ITEM 3 #define CHAN_BODY 4 extern cvar_t sv_gravity, sv_friction, sv_waterfriction, sv_gamespeed, sv_stopspeed, sv_spectatormaxspeed, sv_accelerate, sv_airaccelerate, sv_wateraccelerate, sv_edgefriction; extern cvar_t dpcompat_stats; /* ============================================================================= Con_Printf redirection ============================================================================= */ char outputbuf[8000]; redirect_t sv_redirected; int sv_redirectedlang; extern cvar_t sv_phs; /* ================== SV_FlushRedirect ================== */ void SV_FlushRedirect (void) { int totallen; char send[8000+6]; if (!*outputbuf) return; if (sv_redirected == RD_PACKET) { send[0] = 0xff; send[1] = 0xff; send[2] = 0xff; send[3] = 0xff; send[4] = A2C_PRINT; memcpy (send+5, outputbuf, strlen(outputbuf)+1); NET_SendPacket (NS_SERVER, strlen(send)+1, send, &net_from); } else if (sv_redirected == RD_CLIENT) { int chop; char spare; char *s = outputbuf; totallen = strlen(s)+3; while (sizeof(host_client->backbuf_data[0])/2 < totallen) { chop = sizeof(host_client->backbuf_data[0]) / 2; spare = s[chop]; s[chop] = '\0'; ClientReliableWrite_Begin (host_client, host_client->protocol==SCP_QUAKE2?svcq2_print:svc_print, chop+3); ClientReliableWrite_Byte (host_client, PRINT_HIGH); ClientReliableWrite_String (host_client, s); s += chop; totallen -= chop; s[0] = spare; } ClientReliableWrite_Begin (host_client, host_client->protocol==SCP_QUAKE2?svcq2_print:svc_print, strlen(s)+3); ClientReliableWrite_Byte (host_client, PRINT_HIGH); ClientReliableWrite_String (host_client, s); } // clear it outputbuf[0] = 0; } /* ================== SV_BeginRedirect Send Con_Printf data to the remote client instead of the console ================== */ void SV_BeginRedirect (redirect_t rd, int lang) { sv_redirected = rd; sv_redirectedlang = lang; outputbuf[0] = 0; } void SV_EndRedirect (void) { SV_FlushRedirect (); sv_redirectedlang = 0; //clenliness rather than functionality. Shouldn't be needed. sv_redirected = RD_NONE; } /* ================ Con_Printf Handles cursor positioning, line wrapping, etc ================ */ #define MAXPRINTMSG 4096 // FIXME: make a buffer size safe vsprintf? #ifdef SERVERONLY void VARGS Con_Printf (const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; va_start (argptr,fmt); vsnprintf (msg,sizeof(msg)-1, fmt,argptr); va_end (argptr); // add to redirected message if (sv_redirected) { if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1) SV_FlushRedirect (); strcat (outputbuf, msg); if (sv_redirected != -1) return; } Sys_Printf ("%s", msg); // also echo to debugging console Con_Log(msg); // log to console } void Con_TPrintf (translation_t stringnum, ...) { va_list argptr; char msg[MAXPRINTMSG]; char *fmt; // add to redirected message if (sv_redirected) { fmt = languagetext[stringnum][sv_redirectedlang]; va_start (argptr,stringnum); vsnprintf (msg,sizeof(msg)-1, fmt,argptr); va_end (argptr); if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1) SV_FlushRedirect (); strcat (outputbuf, msg); return; } fmt = languagetext[stringnum][svs.language]; va_start (argptr,stringnum); vsnprintf (msg,sizeof(msg)-1, fmt,argptr); va_end (argptr); Sys_Printf ("%s", msg); // also echo to debugging console Con_Log(msg); // log to console } /* ================ Con_DPrintf A Con_Printf that only shows up if the "developer" cvar is set ================ */ void Con_DPrintf (const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; extern cvar_t log_developer; if (!developer.value && !log_developer.value) return; va_start (argptr,fmt); vsnprintf (msg,sizeof(msg)-1, fmt,argptr); va_end (argptr); // add to redirected message if (sv_redirected) { if (strlen (msg) + strlen(outputbuf) > sizeof(outputbuf) - 1) SV_FlushRedirect (); strcat (outputbuf, msg); if (sv_redirected != -1) return; } if (developer.value) Sys_Printf ("%s", msg); // also echo to debugging console if (log_developer.value) Con_Log(msg); // log to console } #endif /* ============================================================================= EVENT MESSAGES ============================================================================= */ void SV_PrintToClient(client_t *cl, int level, char *string) { switch (cl->protocol) { case SCP_BAD: //bot break; case SCP_QUAKE2: #ifdef Q2SERVER ClientReliableWrite_Begin (cl, svcq2_print, strlen(string)+3); ClientReliableWrite_Byte (cl, level); ClientReliableWrite_String (cl, string); #endif break; case SCP_QUAKE3: break; case SCP_QUAKEWORLD: ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3); ClientReliableWrite_Byte (cl, level); ClientReliableWrite_String (cl, string); break; case SCP_DARKPLACES6: case SCP_DARKPLACES7: case SCP_NETQUAKE: case SCP_PROQUAKE: case SCP_FITZ666: #ifdef NQPROT ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3); if (level == PRINT_CHAT) ClientReliableWrite_Byte (cl, 1); ClientReliableWrite_String (cl, string); #endif break; } } void SV_StuffcmdToClient(client_t *cl, char *string) { switch (cl->protocol) { case SCP_BAD: //bot break; case SCP_QUAKE2: #ifdef Q2SERVER ClientReliableWrite_Begin (cl, svcq2_stufftext, strlen(string)+3); ClientReliableWrite_String (cl, string); #endif break; case SCP_QUAKE3: break; case SCP_QUAKEWORLD: case SCP_DARKPLACES6: case SCP_DARKPLACES7: case SCP_NETQUAKE: case SCP_PROQUAKE: case SCP_FITZ666: ClientReliableWrite_Begin (cl, svc_stufftext, strlen(string)+3); ClientReliableWrite_String (cl, string); break; } } /* ================= SV_ClientPrintf Sends text across to be displayed if the level passes ================= */ void VARGS SV_ClientPrintf (client_t *cl, int level, char *fmt, ...) { va_list argptr; char string[1024]; if (level < cl->messagelevel) return; va_start (argptr,fmt); vsnprintf (string,sizeof(string)-1, fmt,argptr); va_end (argptr); if(strlen(string) >= sizeof(string)) Sys_Error("SV_ClientPrintf: Buffer stomped\n"); if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3); MSG_WriteByte (msg, svc_print); MSG_WriteByte (msg, level); MSG_WriteString (msg, string); } if (cl->controller) SV_PrintToClient(cl->controller, level, string); else SV_PrintToClient(cl, level, string); } void VARGS SV_ClientTPrintf (client_t *cl, int level, translation_t stringnum, ...) { va_list argptr; char string[1024]; char *fmt = languagetext[stringnum][cl->language]; if (level < cl->messagelevel) return; va_start (argptr,stringnum); vsnprintf (string,sizeof(string)-1, fmt,argptr); va_end (argptr); if(strlen(string) >= sizeof(string)) Sys_Error("SV_ClientTPrintf: Buffer stomped\n"); if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, strlen(string)+3); MSG_WriteByte (msg, svc_print); MSG_WriteByte (msg, level); MSG_WriteString (msg, string); } SV_PrintToClient(cl, level, string); } /* ================= SV_BroadcastPrintf Sends text to all active clients ================= */ void VARGS SV_BroadcastPrintf (int level, char *fmt, ...) { va_list argptr; char string[1024]; client_t *cl; int i; va_start (argptr,fmt); vsnprintf (string,sizeof(string)-1, fmt,argptr); va_end (argptr); if(strlen(string) >= sizeof(string)) Sys_Error("SV_BroadcastPrintf: Buffer stomped\n"); Sys_Printf ("%s", string); // print to the console for (i=0, cl = svs.clients ; imessagelevel) continue; if (!cl->state) continue; if (cl->protocol == SCP_BAD) continue; if (cl->controller) continue; SV_PrintToClient(cl, level, string); } if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin (dem_all, 0, strlen(string)+3); MSG_WriteByte (msg, svc_print); MSG_WriteByte (msg, level); MSG_WriteString (msg, string); } } void VARGS SV_BroadcastTPrintf (int level, translation_t stringnum, ...) { va_list argptr; char string[1024]; client_t *cl; int i; int oldlang=-1; char *fmt = languagetext[stringnum][oldlang=svs.language]; va_start (argptr,stringnum); vsnprintf (string,sizeof(string)-1, fmt,argptr); va_end (argptr); if(strlen(string) >= sizeof(string)) Sys_Error("SV_BroadcastPrintf: Buffer stomped\n"); Sys_Printf ("%s", string); // print to the console for (i=0, cl = svs.clients ; imessagelevel) continue; if (!cl->state) continue; if (cl->controller) continue; if (oldlang!=cl->language) { fmt = languagetext[stringnum][oldlang=cl->language]; va_start (argptr,stringnum); vsnprintf (string,sizeof(string)-1, fmt,argptr); va_end (argptr); if(strlen(string) >= sizeof(string)) Sys_Error("SV_BroadcastPrintf: Buffer stomped\n"); } SV_PrintToClient(cl, level, string); } } /* ================= SV_BroadcastCommand Sends text to all active clients ================= */ void VARGS SV_BroadcastCommand (char *fmt, ...) { va_list argptr; char string[1024]; int i; client_t *cl; if (!sv.state) return; va_start (argptr,fmt); vsnprintf (string,sizeof(string), fmt,argptr); va_end (argptr); for (i=0, cl = svs.clients ; icontroller) continue; if (cl->state>=cs_connected) { if (ISQWCLIENT(cl) || ISNQCLIENT(cl)) { ClientReliableWrite_Begin(cl, svc_stufftext, strlen(string)+2); ClientReliableWrite_String (cl, string); } else if (ISQ2CLIENT(cl)) { ClientReliableWrite_Begin(cl, svcq2_stufftext, strlen(string)+2); ClientReliableWrite_String (cl, string); } } } } /* ================= SV_Multicast Sends the contents of sv.multicast to a subset of the clients, then clears sv.multicast. MULTICAST_ALL same as broadcast MULTICAST_PVS send to clients potentially visible from org MULTICAST_PHS send to clients potentially hearable from org MULTICAST_ONE sent to a single client. MULTICAST_INIT sent to clients when they first connect. for completeness. ================= */ void SV_MulticastProtExt(vec3_t origin, multicast_t to, int dimension_mask, int with, int without) { client_t *client; qbyte *mask; int leafnum; int j; qboolean reliable; int pnum = 0; if (to == MULTICAST_INIT) { //we only have one signon buffer. make sure you don't put non-identical protocols in the buffer SV_FlushSignon(); SZ_Write (&sv.signon, sv.multicast.data, sv.multicast.cursize); //and send to players that are already on to = MULTICAST_ALL_R; } // to = MULTICAST_ALL; #ifdef Q2BSPS if (sv.world.worldmodel->fromgame == fg_quake2 || sv.world.worldmodel->fromgame == fg_quake3) { int area1, area2, cluster; reliable = false; if (to != MULTICAST_ALL_R && to != MULTICAST_ALL) { leafnum = CM_PointLeafnum (sv.world.worldmodel, origin); area1 = CM_LeafArea (sv.world.worldmodel, leafnum); } else { leafnum = 0; // just to avoid compiler warnings area1 = 0; } switch (to) { case MULTICAST_ALL_R: reliable = true; // intentional fallthrough case MULTICAST_ALL: leafnum = 0; mask = NULL; break; case MULTICAST_PHS_R: reliable = true; // intentional fallthrough case MULTICAST_PHS: leafnum = CM_PointLeafnum (sv.world.worldmodel, origin); cluster = CM_LeafCluster (sv.world.worldmodel, leafnum); mask = CM_ClusterPHS (sv.world.worldmodel, cluster); break; case MULTICAST_PVS_R: reliable = true; // intentional fallthrough case MULTICAST_PVS: leafnum = CM_PointLeafnum (sv.world.worldmodel, origin); cluster = CM_LeafCluster (sv.world.worldmodel, leafnum); mask = CM_ClusterPVS (sv.world.worldmodel, cluster, NULL, 0); break; default: mask = NULL; SV_Error ("SV_Multicast: bad to:%i", to); } // send the data to all relevent clients for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state != cs_spawned) continue; if (client->protocol == SCP_QUAKEWORLD) { if (client->fteprotocolextensions & without) { // Con_Printf ("Version supressed multicast - without pext\n"); continue; } if (!(~client->fteprotocolextensions & ~with)) { // Con_Printf ("Version supressed multicast - with pext\n"); continue; } } if (mask) { #ifdef Q2SERVER if (ge) leafnum = CM_PointLeafnum (sv.world.worldmodel, client->q2edict->s.origin); else #endif { if (svprogfuncs) { if (!((int)client->edict->xv->dimension_see & dimension_mask)) continue; } leafnum = CM_PointLeafnum (sv.world.worldmodel, client->edict->v->origin); } cluster = CM_LeafCluster (sv.world.worldmodel, leafnum); area2 = CM_LeafArea (sv.world.worldmodel, leafnum); if (!CM_AreasConnected (sv.world.worldmodel, area1, area2)) continue; if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) ) continue; } switch (client->protocol) { case SCP_BAD: continue; //a bot. default: SV_Error("Multicast: Client is using a bad protocl"); case SCP_QUAKE3: Con_Printf("Skipping multicast for q3 client\n"); break; #ifdef NQPROT case SCP_NETQUAKE: case SCP_PROQUAKE: case SCP_FITZ666: case SCP_DARKPLACES6: case SCP_DARKPLACES7: if (reliable) { ClientReliableCheckBlock(client, sv.nqmulticast.cursize); ClientReliableWrite_SZ(client, sv.nqmulticast.data, sv.nqmulticast.cursize); } else SZ_Write (&client->datagram, sv.nqmulticast.data, sv.nqmulticast.cursize); break; #endif #ifdef Q2SERVER case SCP_QUAKE2: if (reliable) { ClientReliableCheckBlock(client, sv.q2multicast.cursize); ClientReliableWrite_SZ(client, sv.q2multicast.data, sv.q2multicast.cursize); } else SZ_Write (&client->datagram, sv.q2multicast.data, sv.q2multicast.cursize); break; #endif case SCP_QUAKEWORLD: if (reliable) { ClientReliableCheckBlock(client, sv.multicast.cursize); ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize); } else SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize); break; } } } else #endif { reliable = false; switch (to) { case MULTICAST_ALL_R: reliable = true; // intentional fallthrough case MULTICAST_ALL: mask = sv.pvs; // leaf 0 is everything; break; case MULTICAST_PHS_R: reliable = true; // intentional fallthrough case MULTICAST_PHS: if (!sv.phs) /*broadcast if no pvs*/ mask = sv.pvs; else { leafnum = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, origin); mask = sv.phs + leafnum * 4*((sv.world.worldmodel->numleafs+31)>>5); } break; case MULTICAST_PVS_R: reliable = true; // intentional fallthrough case MULTICAST_PVS: leafnum = sv.world.worldmodel->funcs.LeafnumForPoint(sv.world.worldmodel, origin); mask = sv.pvs + leafnum * 4*((sv.world.worldmodel->numleafs+31)>>5); break; case MULTICAST_ONE_R: reliable = true; case MULTICAST_ONE: if (svprogfuncs) { edict_t *ent = PROG_TO_EDICT(svprogfuncs, pr_global_struct->msg_entity); pnum = NUM_FOR_EDICT(svprogfuncs, ent) - 1; } mask = NULL; break; default: mask = NULL; SV_Error ("SV_Multicast: bad to:%i", to); } // send the data to all relevent clients for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) { if (client->state != cs_spawned) continue; if (client->controller) continue; //FIXME: send if at least one of the players is near enough. if (client->protocol == SCP_QUAKEWORLD) { if (client->fteprotocolextensions & without) { // Con_Printf ("Version supressed multicast - without pext\n"); continue; } if (!(client->fteprotocolextensions & with) && with) { // Con_Printf ("Version supressed multicast - with pext\n"); continue; } } if (!mask) { if (pnum != j) continue; } else if (svprogfuncs) { if (!((int)client->edict->xv->dimension_see & dimension_mask)) continue; if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) { vec3_t delta; VectorSubtract(origin, client->edict->v->origin, delta); if (Length(delta) <= 1024) goto inrange; } // -1 is because pvs rows are 1 based, not 0 based like leafs if (mask != sv.pvs) { vec3_t pos; VectorAdd(client->edict->v->origin, client->edict->v->view_ofs, pos); leafnum = sv.world.worldmodel->funcs.LeafnumForPoint (sv.world.worldmodel, pos)-1; if ( !(mask[leafnum>>3] & (1<<(leafnum&7)) ) ) { // Con_Printf ("PVS supressed multicast\n"); continue; } } } inrange: switch (client->protocol) { case SCP_BAD: continue; //a bot. default: SV_Error("multicast: Client is using a bad protocol"); case SCP_QUAKE3: Con_Printf("Skipping multicast for q3 client\n"); break; #ifdef NQPROT case SCP_NETQUAKE: case SCP_PROQUAKE: case SCP_FITZ666: case SCP_DARKPLACES6: case SCP_DARKPLACES7: //extra prediction stuff if (reliable) { ClientReliableCheckBlock(client, sv.nqmulticast.cursize); ClientReliableWrite_SZ(client, sv.nqmulticast.data, sv.nqmulticast.cursize); } else SZ_Write (&client->datagram, sv.nqmulticast.data, sv.nqmulticast.cursize); break; #endif #ifdef Q2SERVER case SCP_QUAKE2: if (reliable) { ClientReliableCheckBlock(client, sv.q2multicast.cursize); ClientReliableWrite_SZ(client, sv.q2multicast.data, sv.q2multicast.cursize); } else SZ_Write (&client->datagram, sv.q2multicast.data, sv.q2multicast.cursize); break; #endif case SCP_QUAKEWORLD: if (reliable) { ClientReliableCheckBlock(client, sv.multicast.cursize); ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize); } else SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize); break; } } } if (sv.mvdrecording && ((demo.recorder.fteprotocolextensions & with) == with) && !(demo.recorder.fteprotocolextensions & without)) { sizebuf_t *msg; if (!mask) { /*no distinction between reliable or not*/ msg = MVDWrite_Begin(dem_single, pnum, sv.multicast.cursize); } else { if (reliable) { msg = MVDWrite_Begin(dem_all, 0, sv.multicast.cursize); } else msg = &demo.datagram; } SZ_Write(msg, sv.multicast.data, sv.multicast.cursize); } #ifdef NQPROT SZ_Clear (&sv.nqmulticast); #endif #ifdef Q2SERVER SZ_Clear (&sv.q2multicast); #endif SZ_Clear (&sv.multicast); } //version does all the work now void VARGS SV_Multicast (vec3_t origin, multicast_t to) { SV_MulticastProtExt(origin, to, FULLDIMENSIONMASK, 0, 0); } /* ================== SV_StartSound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. (max 4 attenuation) ================== */ void SV_StartSound (int ent, vec3_t origin, int seenmask, int channel, char *sample, int volume, float attenuation, int pitchadj) { int sound_num; int extfield_mask; int qwflags; int i; qboolean use_phs; qboolean reliable; int requiredextensions = 0; if (channel & 256) { channel -= 256; reliable = true; } else reliable = false; if (volume < 0 || volume > 255) { Con_Printf ("SV_StartSound: volume = %i", volume); return; } if (attenuation < 0 || attenuation > 4) { Con_Printf ("SV_StartSound: attenuation = %f", attenuation); return; } if (channel < 0 || channel > 255) { Con_Printf ("SV_StartSound: channel = %i", channel); return; } // find precache number for sound if (!*sample) sound_num = 0; else { for (sound_num=1 ; sound_num= 8192 || channel >= 8) extfield_mask |= DPSND_LARGEENTITY; if (sound_num > 0xff) extfield_mask |= DPSND_LARGESOUND; if (pitchadj && (pitchadj != 100)) extfield_mask |= FTESND_PITCHADJ; #ifdef PEXT_SOUNDDBL if (channel >= 8 || ent >= 2048 || sound_num > 0xff || pitchadj) { //if any of the above conditions evaluates to true, then we can't use standard qw protocols MSG_WriteByte (&sv.multicast, svcfte_soundextended); MSG_WriteByte (&sv.multicast, extfield_mask); if (extfield_mask & NQSND_VOLUME) MSG_WriteByte (&sv.multicast, volume); if (extfield_mask & NQSND_ATTENUATION) MSG_WriteByte (&sv.multicast, attenuation*64); if (extfield_mask & FTESND_PITCHADJ) MSG_WriteByte (&sv.multicast, pitchadj); if (extfield_mask & DPSND_LARGEENTITY) { MSG_WriteEntity (&sv.multicast, ent); MSG_WriteByte (&sv.multicast, channel); } else MSG_WriteShort (&sv.multicast, (ent<<3) | channel); if (extfield_mask & DPSND_LARGESOUND) MSG_WriteShort (&sv.multicast, sound_num); else MSG_WriteByte (&sv.multicast, sound_num); for (i=0 ; i<3 ; i++) MSG_WriteCoord (&sv.multicast, origin[i]); requiredextensions |= PEXT_SOUNDDBL; if (ent > 512) requiredextensions |= PEXT_ENTITYDBL; if (ent > 1024) requiredextensions |= PEXT_ENTITYDBL2; } else #endif { qwflags = (ent<<3) | channel; if (volume != DEFAULT_SOUND_PACKET_VOLUME) qwflags |= SND_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) qwflags |= SND_ATTENUATION; MSG_WriteByte (&sv.multicast, svc_sound); MSG_WriteShort (&sv.multicast, qwflags); if (qwflags & SND_VOLUME) MSG_WriteByte (&sv.multicast, volume); if (qwflags & SND_ATTENUATION) MSG_WriteByte (&sv.multicast, attenuation*64); MSG_WriteByte (&sv.multicast, sound_num); for (i=0 ; i<3 ; i++) MSG_WriteCoord (&sv.multicast, origin[i]); if (ent > 512) requiredextensions |= PEXT_ENTITYDBL; if (ent > 1024) requiredextensions |= PEXT_ENTITYDBL2; } #ifdef NQPROT MSG_WriteByte (&sv.nqmulticast, svc_sound); MSG_WriteByte (&sv.nqmulticast, extfield_mask); if (extfield_mask & NQSND_VOLUME) MSG_WriteByte (&sv.nqmulticast, volume); if (extfield_mask & NQSND_ATTENUATION) MSG_WriteByte (&sv.nqmulticast, attenuation*64); if (extfield_mask & FTESND_PITCHADJ) MSG_WriteByte (&sv.nqmulticast, pitchadj); if (extfield_mask & DPSND_LARGEENTITY) { MSG_WriteEntity (&sv.nqmulticast, ent); MSG_WriteByte (&sv.nqmulticast, channel); } else MSG_WriteShort (&sv.nqmulticast, (ent<<3) | channel); if (extfield_mask & DPSND_LARGESOUND) MSG_WriteShort (&sv.nqmulticast, sound_num); else MSG_WriteByte (&sv.nqmulticast, sound_num); for (i=0 ; i<3 ; i++) MSG_WriteCoord (&sv.nqmulticast, origin[i]); #endif if (use_phs) SV_MulticastProtExt(origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS, seenmask, requiredextensions, 0); else SV_MulticastProtExt(origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL, seenmask, requiredextensions, 0); } void SVQ1_StartSound (float *origin, wedict_t *wentity, int channel, char *sample, int volume, float attenuation, int pitchadj) { edict_t *entity = (edict_t*)wentity; int i; vec3_t originbuf; if (!origin) { origin = originbuf; if (entity->v->solid == SOLID_BSP) { for (i=0 ; i<3 ; i++) origin[i] = entity->v->origin[i]+0.5*(entity->v->mins[i]+entity->v->maxs[i]); } else { VectorCopy (entity->v->origin, origin); } } SV_StartSound(NUM_FOR_EDICT(svprogfuncs, entity), origin, entity->xv->dimension_seen, channel, sample, volume, attenuation, pitchadj); } /* =============================================================================== FRAME UPDATES =============================================================================== */ int sv_nailmodel, sv_supernailmodel, sv_playermodel; void SV_FindModelNumbers (void) { int i; sv_nailmodel = -1; sv_supernailmodel = -1; sv_playermodel = -1; for (i=0 ; iedict; if (!ent) return; // send a damage message if the player got hit this frame if (ent->v->dmg_take || ent->v->dmg_save) { other = PROG_TO_EDICT(svprogfuncs, ent->v->dmg_inflictor); if (pnum) { MSG_WriteByte(msg, svcfte_choosesplitclient); MSG_WriteByte(msg, pnum); } MSG_WriteByte (msg, svc_damage); MSG_WriteByte (msg, ent->v->dmg_save); MSG_WriteByte (msg, ent->v->dmg_take); for (i=0 ; i<3 ; i++) MSG_WriteCoord (msg, other->v->origin[i] + 0.5*(other->v->mins[i] + other->v->maxs[i])); ent->v->dmg_take = 0; ent->v->dmg_save = 0; } // a fixangle might get lost in a dropped packet. Oh well. if (ent->v->fixangle) { if (pnum) { MSG_WriteByte(msg, svcfte_choosesplitclient); MSG_WriteByte(msg, pnum); } if (!client->lockangles && (client->fteprotocolextensions2 & PEXT2_SETANGLEDELTA) && client->delta_sequence != -1) { MSG_WriteByte (msg, svcfte_setangledelta); for (i=0 ; i < 3 ; i++) { newa = ent->v->angles[i] - SHORT2ANGLE(client->lastcmd.angles[i]); MSG_WriteAngle16 (msg, newa); client->lastcmd.angles[i] = ANGLE2SHORT(ent->v->angles[i]); } } else { MSG_WriteByte (msg, svc_setangle); for (i=0 ; i < 3 ; i++) MSG_WriteAngle (msg, ent->v->angles[i]); } ent->v->fixangle = 0; client->lockangles = true; } else client->lockangles = false; } /*sends the a centerprint string directly to the client*/ void SV_WriteCenterPrint(client_t *cl, char *s) { if (cl->controller) { //this is a slave client. //find the right number and send. int pnum = 0; client_t *sp; for (sp = cl->controller; sp; sp = sp->controlled) { if (sp == cl) break; pnum++; } cl = cl->controller; ClientReliableWrite_Begin (cl, svcfte_choosesplitclient, 4 + strlen(s)); ClientReliableWrite_Byte (cl, pnum); ClientReliableWrite_Byte (cl, svc_centerprint); } else { ClientReliableWrite_Begin (cl, svc_centerprint, 2 + strlen(s)); } ClientReliableWrite_String (cl, s); if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin (dem_single, cl - svs.clients, 2 + strlen(s)); MSG_WriteByte (msg, svc_centerprint); MSG_WriteString (msg, s); } } /* ================== SV_WriteClientdataToMessage ================== */ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) { #ifdef NQPROT int i; int bits, items; edict_t *ent; #endif client_t *split; int pnum=0; int weaponmodelindex = 0; qboolean nqjunk = true; // send the chokecount for r_netgraph if (ISQWCLIENT(client)) if (client->chokecount) { MSG_WriteByte (msg, svc_chokecount); MSG_WriteByte (msg, client->chokecount); client->chokecount = 0; } for (split = client; split; split=split->controlled, pnum++) { SV_WriteEntityDataToMessage(split, msg, pnum); if (split->centerprintstring && ! client->num_backbuf) { SV_WriteCenterPrint(split, split->centerprintstring); Z_Free(split->centerprintstring); split->centerprintstring = NULL; } } /* MSG_WriteByte (msg, svc_time); MSG_WriteFloat(msg, sv.physicstime); client->nextservertimeupdate = sv.physicstime; */ #ifdef NQPROT if (ISQWCLIENT(client)) return; ent = client->edict; if (!(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) { MSG_WriteByte (msg, svc_time); MSG_WriteFloat(msg, sv.world.physicstime); if (client->fteprotocolextensions2 & PEXT2_PREDINFO) MSG_WriteLong(msg, client->last_sequence); // Con_Printf("%f\n", sv.world.physicstime); } //predinfo extension reworks stats, making svc_clientdata redundant. if (client->fteprotocolextensions2 & PEXT2_PREDINFO) return; bits = 0; if (ent->v->view_ofs[2] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; // if (ent->v->idealpitch) // bits |= SU_IDEALPITCH; // stuff the sigil bits into the high bits of items for sbar, or else // mix in items2 // val = GetEdictFieldValue(ent, "items2", &items2cache); // if (val) // items = (int)ent->v->items | ((int)val->_float << 23); // else items = (int)ent->v->items | ((int)pr_global_struct->serverflags << 28); bits |= SU_ITEMS; if ( (int)ent->v->flags & FL_ONGROUND) bits |= SU_ONGROUND; if ( ent->v->waterlevel >= 2) bits |= SU_INWATER; for (i=0 ; i<3 ; i++) { // if (ent->v->punchangle[i]) // bits |= (SU_PUNCH1<v->velocity[i]) bits |= (SU_VELOCITY1<protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) { //bits &= ~SU_ITEMS; nqjunk = false; } else { nqjunk = true; if (ent->v->weaponframe) bits |= SU_WEAPONFRAME; if (ent->v->armorvalue) bits |= SU_ARMOR; weaponmodelindex = SV_ModelIndex(ent->v->weaponmodel + svprogfuncs->stringtable); if (weaponmodelindex) bits |= SU_WEAPONMODEL; if (client->protocol == SCP_FITZ666) { if (weaponmodelindex & 0xff00) bits |= FITZSU_WEAPONMODEL2; if ((int)ent->v->armorvalue & 0xff00) bits |= FITZSU_ARMOR2; if ((int)ent->v->currentammo & 0xff00) bits |= FITZSU_AMMO2; if ((int)ent->v->ammo_shells & 0xff00) bits |= FITZSU_SHELLS2; if ((int)ent->v->ammo_nails & 0xff00) bits |= FITZSU_NAILS2; if ((int)ent->v->ammo_rockets & 0xff00) bits |= FITZSU_ROCKETS2; if ((int)ent->v->ammo_cells & 0xff00) bits |= FITZSU_CELLS2; if ((int)ent->v->weaponframe & 0xff00) bits |= FITZSU_WEAPONFRAME2; if (ent->xv->alpha && ent->xv->alpha < 1) bits |= FITZSU_WEAPONALPHA; } } if (bits >= (1u<<16)) bits |= SU_EXTEND1; if (bits >= (1u<<24)) bits |= SU_EXTEND2; if (bits >= (1ull<<32)) bits |= SU_EXTEND3; // send the data MSG_WriteByte (msg, svcnq_clientdata); MSG_WriteShort (msg, bits); if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16); if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24); if (bits & SU_VIEWHEIGHT) MSG_WriteChar (msg, ent->v->view_ofs[2]); // if (bits & SU_IDEALPITCH) // MSG_WriteChar (msg, ent->v->idealpitch); for (i=0 ; i<3 ; i++) { // if (bits & (SU_PUNCH1<v->punchangle[i]); if (bits & (SU_VELOCITY1<protocol == SCP_DARKPLACES6 || client->protocol == SCP_DARKPLACES7) MSG_WriteCoord(msg, ent->v->velocity[i]); else MSG_WriteChar (msg, ent->v->velocity[i]/16); } } if (bits & SU_ITEMS) MSG_WriteLong (msg, items); if (bits & SU_WEAPONFRAME) MSG_WriteByte (msg, ent->v->weaponframe); if (bits & SU_ARMOR) { if (ent->v->armorvalue>255 && !(bits & FITZSU_ARMOR2)) MSG_WriteByte (msg, 255); else MSG_WriteByte (msg, ent->v->armorvalue); } if (bits & SU_WEAPONMODEL) MSG_WriteByte (msg, weaponmodelindex); if (nqjunk) { MSG_WriteShort (msg, ent->v->health); MSG_WriteByte (msg, ent->v->currentammo); MSG_WriteByte (msg, ent->v->ammo_shells); MSG_WriteByte (msg, ent->v->ammo_nails); MSG_WriteByte (msg, ent->v->ammo_rockets); MSG_WriteByte (msg, ent->v->ammo_cells); if (standard_quake) { MSG_WriteByte (msg, ent->v->weapon); } else { for(i=0;i<32;i++) { if ( ((int)ent->v->weapon) & (1<> 8); if (bits & FITZSU_ARMOR2) MSG_WriteByte (msg, (int)ent->v->armorvalue >> 8); if (bits & FITZSU_AMMO2) MSG_WriteByte (msg, (int)ent->v->currentammo >> 8); if (bits & FITZSU_SHELLS2) MSG_WriteByte (msg, (int)ent->v->ammo_shells >> 8); if (bits & FITZSU_NAILS2) MSG_WriteByte (msg, (int)ent->v->ammo_nails >> 8); if (bits & FITZSU_ROCKETS2) MSG_WriteByte (msg, (int)ent->v->ammo_rockets >> 8); if (bits & FITZSU_CELLS2) MSG_WriteByte (msg, (int)ent->v->ammo_cells >> 8); if (bits & FITZSU_WEAPONFRAME2) MSG_WriteByte (msg, (int)ent->v->weaponframe >> 8); if (bits & FITZSU_WEAPONALPHA) MSG_WriteByte (msg, ent->xv->alpha*255); // } #endif } typedef struct { int type; //negative means a global. char name[64]; union { evalc_t c; eval_t *g; //just store a pointer to it. } eval; int statnum; } qcstat_t; qcstat_t qcstats[MAX_CL_STATS-32]; int numqcstats; void SV_QCStatEval(int type, char *name, evalc_t *field, eval_t *global, int statnum) { int i; if (numqcstats == sizeof(qcstats)/sizeof(qcstats[0])) { Con_Printf("Too many stat types\n"); return; } for (i = 0; i < numqcstats; i++) { if (qcstats[i].statnum == statnum) break; } if (i == numqcstats) { if (i == sizeof(qcstats)/sizeof(qcstats[0])) { Con_Printf("Too many stats specified for csqc\n"); return; } numqcstats++; } qcstats[i].type = type; qcstats[i].statnum = statnum; Q_strncpyz(qcstats[i].name, name, sizeof(qcstats[i].name)); if (type < 0) qcstats[i].eval.g = global; else memcpy(&qcstats[i].eval.c, field, sizeof(evalc_t)); } void SV_QCStatGlobal(int type, char *globalname, int statnum) { eval_t *glob; if (type < 0) return; glob = svprogfuncs->FindGlobal(svprogfuncs, globalname, PR_ANY, NULL); if (!glob) { Con_Printf("couldn't find named global for csqc stat (%s)\n", globalname); return; } SV_QCStatEval(-type, globalname, NULL, glob, statnum); } void SV_QCStatPtr(int type, void *ptr, int statnum) { SV_QCStatEval(-type, "", NULL, ptr, statnum); } void SV_QCStatName(int type, char *name, int statnum) { evalc_t cache; if (type < 0) return; memset(&cache, 0, sizeof(cache)); if (!svprogfuncs->GetEdictFieldValue(svprogfuncs, NULL, name, &cache)) return; SV_QCStatEval(type, name, &cache, NULL, statnum); } void SV_QCStatFieldIdx(int type, unsigned int fieldindex, int statnum) { evalc_t cache; char *name; etype_t ftype; if (type < 0) return; if (!svprogfuncs->QueryField(svprogfuncs, fieldindex, &ftype, &name, &cache)) { Con_Printf("invalid field for csqc stat\n"); return; } SV_QCStatEval(type, name, &cache, NULL, statnum); } void SV_ClearQCStats(void) { numqcstats = 0; } extern cvar_t dpcompat_stats; void SV_UpdateQCStats(edict_t *ent, int *statsi, char **statss, float *statsf) { char *s; int i; int t; for (i = 0; i < numqcstats; i++) { eval_t *eval; t = qcstats[i].type; if (t < 0) { t = -t; eval = qcstats[i].eval.g; } else { eval = svprogfuncs->GetEdictFieldValue(svprogfuncs, ent, qcstats[i].name, &qcstats[i].eval.c); } if (!eval) continue; switch(t) { case ev_float: statsf[qcstats[i].statnum] = eval->_float; break; case ev_vector: statsf[qcstats[i].statnum+0] = eval->_vector[0]; statsf[qcstats[i].statnum+1] = eval->_vector[1]; statsf[qcstats[i].statnum+2] = eval->_vector[2]; break; case ev_integer: statsi[qcstats[i].statnum] = eval->_int; break; case ev_entity: statsi[qcstats[i].statnum] = NUM_FOR_EDICT(svprogfuncs, PROG_TO_EDICT(svprogfuncs, eval->edict)); break; case ev_string: s = PR_GetString(svprogfuncs, eval->string); statss[qcstats[i].statnum] = s; // statsi[qcstats[i].statnum+0] = LittleLong(((int*)s)[0]); //so the network is sent out correctly as a string. // statsi[qcstats[i].statnum+1] = LittleLong(((int*)s)[1]); // statsi[qcstats[i].statnum+2] = LittleLong(((int*)s)[2]); // statsi[qcstats[i].statnum+3] = LittleLong(((int*)s)[3]); break; } } } /*this function calculates the current stat values for the given client*/ void SV_CalcClientStats(client_t *client, int statsi[MAX_CL_STATS], float statsf[MAX_CL_STATS], char *statss[MAX_CL_STATS]) { extern qboolean pr_items2; edict_t *ent; ent = client->edict; memset (statsi, 0, sizeof(int)*MAX_CL_STATS); memset (statsf, 0, sizeof(float)*MAX_CL_STATS); memset (statss, 0, sizeof(char*)*MAX_CL_STATS); // if we are a spectator and we are tracking a player, we get his stats // so our status bar reflects his if (client->spectator && client->spec_track > 0) ent = EDICT_NUM(svprogfuncs, client->spec_track); #ifdef HLSERVER if (svs.gametype == GT_HALFLIFE) { SVHL_BuildStats(client, statsi, statsf, statss); } else #endif { statsf[STAT_HEALTH] = ent->v->health; //sorry, but mneh statsi[STAT_WEAPON] = SV_ModelIndex(PR_GetString(svprogfuncs, ent->v->weaponmodel)); if (client->fteprotocolextensions & PEXT_MODELDBL) { if ((unsigned)statsi[STAT_WEAPON] >= MAX_MODELS) statsi[STAT_WEAPON] = 0; } else { if ((unsigned)statsi[STAT_WEAPON] >= 256) statsi[STAT_WEAPON] = 0; } statsf[STAT_AMMO] = ent->v->currentammo; statsf[STAT_ARMOR] = ent->v->armorvalue; statsf[STAT_SHELLS] = ent->v->ammo_shells; statsf[STAT_NAILS] = ent->v->ammo_nails; statsf[STAT_ROCKETS] = ent->v->ammo_rockets; statsf[STAT_CELLS] = ent->v->ammo_cells; if (!client->spectator) { statsf[STAT_ACTIVEWEAPON] = ent->v->weapon; if ((client->csqcactive && !(client->fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS)) || client->protocol != SCP_QUAKEWORLD) statsf[STAT_WEAPONFRAME] = ent->v->weaponframe; } // stuff the sigil bits into the high bits of items for sbar if (pr_items2) statsi[STAT_ITEMS] = (int)ent->v->items | ((int)ent->xv->items2 << 23); else statsi[STAT_ITEMS] = (int)ent->v->items | ((int)pr_global_struct->serverflags << 28); statsf[STAT_VIEWHEIGHT] = ent->v->view_ofs[2]; #ifdef PEXT_VIEW2 if (ent->xv->view2) statsi[STAT_VIEW2] = NUM_FOR_EDICT(svprogfuncs, PROG_TO_EDICT(svprogfuncs, ent->xv->view2)); else statsi[STAT_VIEW2] = 0; #endif if (!ent->xv->viewzoom) statsi[STAT_VIEWZOOM] = 255; else statsi[STAT_VIEWZOOM] = ent->xv->viewzoom*255; if (client->protocol == SCP_DARKPLACES7 || (client->fteprotocolextensions2 & PEXT2_PREDINFO)) { float *statsfi; if (client->fteprotocolextensions2 & PEXT2_PREDINFO) statsfi = statsf; else statsfi = (float*)statsi; /*dp requires a union of ints and floats, which is rather hideous...*/ // statsfi[STAT_MOVEVARS_WALLFRICTION] = sv_wall statsfi[STAT_MOVEVARS_FRICTION] = sv_friction.value; statsfi[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value; statsfi[STAT_MOVEVARS_TICRATE] = 72; statsfi[STAT_MOVEVARS_TIMESCALE] = sv_gamespeed.value; statsfi[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; statsfi[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; statsfi[STAT_MOVEVARS_MAXSPEED] = host_client->maxspeed; statsfi[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_spectatormaxspeed.value; statsfi[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; statsfi[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value; statsfi[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value; statsfi[STAT_MOVEVARS_ENTGRAVITY] = host_client->entgravity/sv_gravity.value; statsfi[STAT_MOVEVARS_JUMPVELOCITY] = 270;//sv_jumpvelocity.value; //bah statsfi[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; statsfi[STAT_MOVEVARS_MAXAIRSPEED] = host_client->maxspeed; statsfi[STAT_MOVEVARS_STEPHEIGHT] = 18; statsfi[STAT_MOVEVARS_AIRACCEL_QW] = 1; statsfi[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_gravity.value; } SV_UpdateQCStats(ent, statsi, statss, statsf); } } /* ======================= SV_UpdateClientStats Performs a delta update of the stats array. This should only be performed when a reliable message can be delivered this frame. ======================= */ void SV_UpdateClientStats (client_t *client, int pnum) { int statsi[MAX_CL_STATS]; float statsf[MAX_CL_STATS]; char *statss[MAX_CL_STATS]; int i, m; /*figure out what the stat values should be*/ SV_CalcClientStats(client, statsi, statsf, statss); m = MAX_QW_STATS; if (client->fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC)) m = MAX_CL_STATS; for (i=0 ; ispec_track) { statsf[i] = 0; if (i == STAT_HEALTH) statsf[i] = 100; } else { statsf[i] = sv.recordedplayer[client->spec_track - 1].stats[i]; statsi[i] = sv.recordedplayer[client->spec_track - 1].stats[i]; } } #endif if (!ISQWCLIENT(client)) { if (!statsi[i]) statsi[i] = statsf[i]; if (statsi[i] != client->statsi[i]) { client->statsi[i] = statsi[i]; ClientReliableWrite_Begin(client, svcnq_updatestatlong, 6); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Long(client, statsi[i]); } } else { #ifdef PEXT_CSQC if (client->fteprotocolextensions & PEXT_CSQC) { if (statss[i] || client->statss[i]) if (strcmp(statss[i]?statss[i]:"", client->statss[i]?client->statss[i]:"")) { client->statss[i] = statss[i]; if (pnum) { ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5+strlen(statss[i])); ClientReliableWrite_Byte(client->controller, pnum); ClientReliableWrite_Byte(client->controller, svcfte_updatestatstring); ClientReliableWrite_Byte(client->controller, i); ClientReliableWrite_String(client->controller, statss[i]); } else { ClientReliableWrite_Begin(client, svcfte_updatestatstring, 3+strlen(statss[i])); ClientReliableWrite_Byte(client, i); ClientReliableWrite_String(client, statss[i]); } } } if (dpcompat_stats.ival) { if (statsf[i]) { statsi[i] = statsf[i]; statsf[i] = 0; } } #endif if (statsf[i]) { if (client->fteprotocolextensions & PEXT_CSQC) { if (statsf[i] != client->statsf[i]) { if (statsf[i] - (float)(int)statsf[i] == 0 && statsf[i] >= 0 && statsf[i] <= 255) { if (pnum) { ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5); ClientReliableWrite_Byte(client->controller, pnum); ClientReliableWrite_Byte(client->controller, svcqw_updatestatbyte); ClientReliableWrite_Byte(client->controller, i); ClientReliableWrite_Byte(client->controller, statsf[i]); } else { ClientReliableWrite_Begin(client, svcqw_updatestatbyte, 3); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Byte(client, statsf[i]); } } else { if (pnum) { ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 8); ClientReliableWrite_Byte(client->controller, pnum); ClientReliableWrite_Byte(client->controller, svcfte_updatestatfloat); ClientReliableWrite_Byte(client->controller, i); ClientReliableWrite_Float(client->controller, statsf[i]); } else { ClientReliableWrite_Begin(client, svcfte_updatestatfloat, 6); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Float(client, statsf[i]); } } client->statsf[i] = statsf[i]; /*make sure statsf is correct*/ client->statsi[i] = statsf[i]; } continue; } else { statsi[i] = statsf[i]; } } if (statsi[i] != client->statsi[i]) { client->statsi[i] = statsi[i]; client->statsf[i] = statsi[i]; if (statsi[i] >=0 && statsi[i] <= 255) { if (pnum) { ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 5); ClientReliableWrite_Byte(client->controller, pnum); ClientReliableWrite_Byte(client->controller, svcqw_updatestatbyte); ClientReliableWrite_Byte(client->controller, i); ClientReliableWrite_Byte(client->controller, statsi[i]); } else { ClientReliableWrite_Begin(client, svcqw_updatestatbyte, 3); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Byte(client, statsi[i]); } } else { if (pnum) { ClientReliableWrite_Begin(client->controller, svcfte_choosesplitclient, 8); ClientReliableWrite_Byte(client->controller, pnum); ClientReliableWrite_Byte(client->controller, svcqw_updatestatlong); ClientReliableWrite_Byte(client->controller, i); ClientReliableWrite_Long(client->controller, statsi[i]); } else { ClientReliableWrite_Begin(client, svcqw_updatestatlong, 6); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Long(client, statsi[i]); } } } } } } qboolean SV_CanTrack(client_t *client, int entity) { if (entity < 0 || entity > sv.allocated_client_slots || svs.clients[entity-1].state != cs_spawned || svs.clients[entity-1].spectator) return false; return true; } /* ======================= SV_SendClientDatagram ======================= */ qboolean SV_SendClientDatagram (client_t *client) { qbyte buf[MAX_OVERALLMSGLEN]; sizebuf_t msg; unsigned int sentbytes, fnum; msg.data = buf; msg.maxsize = sizeof(buf); msg.cursize = 0; msg.allowoverflow = true; msg.overflowed = false; msg.prim = client->datagram.prim; if (client->spec_track && !SV_CanTrack(client, client->spec_track)) { client->spec_track = 0; client->edict->v->goalentity = 0; } if (client->protocol != SCP_FITZ666 && !client->netchan.fragmentsize) msg.maxsize = MAX_DATAGRAM; if (sv.world.worldmodel && !client->controller) { #ifdef Q2SERVER if (ISQ2CLIENT(client)) { SVQ2_BuildClientFrame (client); // send over all the relevant entity_state_t // and the player_state_t SVQ2_WriteFrameToClient (client, &msg); } else #endif { // add the client specific data to the datagram SV_WriteClientdataToMessage (client, &msg); // send over all the objects that are in the PVS // this will include clients, a packetentities, and // possibly a nails update SV_WriteEntitiesToClient (client, &msg, false); } #ifdef VOICECHAT SV_VoiceSendPacket(client, &msg); #endif } // copy the accumulated multicast datagram // for this client out to the message if (client->datagram.overflowed) Con_Printf ("WARNING: datagram overflowed for %s\n", client->name); else SZ_Write (&msg, client->datagram.data, client->datagram.cursize); SZ_Clear (&client->datagram); // send deltas over reliable stream if (sv.world.worldmodel) if (!ISQ2CLIENT(client) && Netchan_CanReliable (&client->netchan, SV_RateForClient(client))) { int pnum=1; client_t *c; SV_UpdateClientStats (client, 0); for (c = client->controlled; c; c = c->controlled,pnum++) SV_UpdateClientStats(c, pnum); } if (msg.overflowed) { Con_Printf ("WARNING: msg overflowed for %s\n", client->name); SZ_Clear (&msg); } SV_DarkPlacesDownloadChunk(client, &msg); // send the datagram fnum = client->netchan.outgoing_sequence; sentbytes = Netchan_Transmit (&client->netchan, msg.cursize, buf, SV_RateForClient(client)); if (ISQWCLIENT(client) || ISNQCLIENT(client)) client->frameunion.frames[fnum & UPDATE_MASK].packetsizeout += sentbytes; return true; } client_t *SV_SplitClientDest(client_t *client, qbyte first, int size) { client_t *sp; if (client->controller) { //this is a slave client. //find the right number and send. int pnum = 0; for (sp = client->controller; sp; sp = sp->controlled) { if (sp == client) break; pnum++; } sp = client->controller; ClientReliableWrite_Begin (sp, svcfte_choosesplitclient, size+2); ClientReliableWrite_Byte (sp, pnum); ClientReliableWrite_Byte (sp, first); return sp; } else { ClientReliableWrite_Begin (client, first, size); return client; } } void SV_FlushBroadcasts (void) { client_t *client; int j; // append the broadcast messages to each client messages for (j=0, client = svs.clients ; jstate < cs_connected) continue; // reliables go to all connected or spawned if (client->controller) continue; //splitscreen if (client->protocol == SCP_BAD) continue; //botclient #ifdef Q2SERVER if (ISQ2CLIENT(client)) { ClientReliableCheckBlock(client, sv.q2reliable_datagram.cursize); ClientReliableWrite_SZ(client, sv.q2reliable_datagram.data, sv.q2reliable_datagram.cursize); if (client->state != cs_spawned) continue; // datagrams only go to spawned SZ_Write (&client->datagram , sv.q2datagram.data , sv.q2datagram.cursize); } else #endif #ifdef NQPROT if (!ISQWCLIENT(client)) { if (client->pextknown) { ClientReliableCheckBlock(client, sv.nqreliable_datagram.cursize); ClientReliableWrite_SZ(client, sv.nqreliable_datagram.data, sv.nqreliable_datagram.cursize); } if (client->state != cs_spawned) continue; // datagrams only go to spawned SZ_Write (&client->datagram , sv.nqdatagram.data , sv.nqdatagram.cursize); } else #endif { ClientReliableCheckBlock(client, sv.reliable_datagram.cursize); ClientReliableWrite_SZ(client, sv.reliable_datagram.data, sv.reliable_datagram.cursize); if (client->state != cs_spawned) continue; // datagrams only go to spawned SZ_Write (&client->datagram , sv.datagram.data , sv.datagram.cursize); } } SV_MVD_WriteReliables(); SZ_Clear (&sv.reliable_datagram); SZ_Clear (&sv.datagram); #ifdef NQPROT SZ_Clear (&sv.nqreliable_datagram); SZ_Clear (&sv.nqdatagram); #endif SZ_Clear (&sv.q2reliable_datagram); SZ_Clear (&sv.q2datagram); } /* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages (void) { int i, j; client_t *client, *sp; edict_t *ent; char *name; float curgrav; float curspeed; int curfrags; // check for changes to be sent over the reliable streams to all clients for (i=0, host_client = svs.clients ; istate == cs_spawned) { //DP_SV_CLIENTCOLORS if (host_client->edict->xv->clientcolors != host_client->playercolor) { Info_SetValueForKey(host_client->userinfo, "topcolor", va("%i", (int)host_client->edict->xv->clientcolors/16), sizeof(host_client->userinfo)); Info_SetValueForKey(host_client->userinfo, "bottomcolor", va("%i", (int)host_client->edict->xv->clientcolors&15), sizeof(host_client->userinfo)); { SV_ExtractFromUserinfo (host_client, true); //this will take care of nq for us anyway. MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, "topcolor"); MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "topcolor")); MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, "bottomcolor"); MSG_WriteString (&sv.reliable_datagram, Info_ValueForKey(host_client->userinfo, "bottomcolor")); } } name = PR_GetString(svprogfuncs, host_client->edict->v->netname); if (name != host_client->name) { if (strcmp(host_client->name, name)) { char oname[80]; Q_strncpyz(oname, host_client->name, sizeof(oname)); Con_DPrintf("Client %s programatically renamed to %s\n", host_client->name, name); Info_SetValueForKey(host_client->userinfo, "name", name, sizeof(host_client->userinfo)); SV_ExtractFromUserinfo (host_client, true); if (strcmp(oname, host_client->name)) { MSG_WriteByte (&sv.reliable_datagram, svc_setinfo); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, "name"); MSG_WriteString (&sv.reliable_datagram, host_client->name); } } host_client->edict->v->netname = PR_SetString(svprogfuncs, host_client->name); } } if (host_client->state != cs_spawned) { if (!host_client->state && host_client->name && host_client->name[0]) //if this is a writebyte bot { if (host_client->old_frags != (int)host_client->edict->v->frags) { for (j=0, client = svs.clients ; jstate < cs_connected) continue; ClientReliableWrite_Begin(client, svc_updatefrags, 4); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Short(client, host_client->edict->v->frags); } if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin(dem_all, 0, 4); MSG_WriteByte(msg, svc_updatefrags); MSG_WriteByte(msg, i); MSG_WriteShort(msg, host_client->edict->v->frags); } host_client->old_frags = host_client->edict->v->frags; } } continue; } if (svs.gametype == GT_PROGS || svs.gametype == GT_Q1QVM) { ent = host_client->edict; curfrags = host_client->edict->v->frags; curgrav = ent->xv->gravity*sv_gravity.value; curspeed = ent->xv->maxspeed; if (progstype != PROG_QW) { if (!curgrav) curgrav = sv_gravity.value; if (!curspeed) curspeed = sv_maxspeed.value; } if (ent->xv->hasted) curspeed*=ent->xv->hasted; } else { curgrav = sv_gravity.value; curspeed = sv_maxspeed.value; curfrags = 0; } #ifdef SVCHAT //enforce a no moving time when chatting. Prevent client prediction going mad. if (host_client->chat.active) curspeed = 0; #endif if (!ISQ2CLIENT(host_client)) { if (host_client->sendinfo) { host_client->sendinfo = false; SV_FullClientUpdate (host_client, NULL); } if (host_client->old_frags != curfrags) { for (j=0, client = svs.clients ; jstate < cs_connected) continue; if (client->controller) continue; ClientReliableWrite_Begin(client, svc_updatefrags, 4); ClientReliableWrite_Byte(client, i); ClientReliableWrite_Short(client, curfrags); } if (sv.mvdrecording) { sizebuf_t *msg = MVDWrite_Begin(dem_all, 0, 4); MSG_WriteByte(msg, svc_updatefrags); MSG_WriteByte(msg, i); MSG_WriteShort(msg, curfrags); } host_client->old_frags = curfrags; } { if (host_client->entgravity != curgrav) { if (ISQWCLIENT(host_client)) { sp = SV_SplitClientDest(host_client, svc_entgravity, 5); ClientReliableWrite_Float(sp, curgrav/movevars.gravity); //lie to the client in a cunning way } host_client->entgravity = curgrav; } if (host_client->maxspeed != curspeed) { //MSVC can really suck at times (optimiser bug) if (ISQWCLIENT(host_client)) { if (host_client->controller) { //this is a slave client. //find the right number and send. int pnum = 0; client_t *sp; for (sp = host_client->controller; sp; sp = sp->controlled) { if (sp == host_client) break; pnum++; } sp = host_client->controller; ClientReliableWrite_Begin (sp, svcfte_choosesplitclient, 7); ClientReliableWrite_Byte (sp, pnum); ClientReliableWrite_Byte (sp, svc_maxspeed); ClientReliableWrite_Float(sp, curspeed); } else { ClientReliableWrite_Begin(host_client, svc_maxspeed, 5); ClientReliableWrite_Float(host_client, curspeed); } } host_client->maxspeed = curspeed; } } } } if (sv.reliable_datagram.overflowed) { Con_Printf("WARNING: Reliable datagram overflowed\n"); SZ_Clear (&sv.reliable_datagram); } if (sv.datagram.overflowed) SZ_Clear (&sv.datagram); #ifdef NQPROT if (sv.nqdatagram.overflowed) SZ_Clear (&sv.nqdatagram); #endif #ifdef Q2SERVER if (sv.q2datagram.overflowed) SZ_Clear (&sv.q2datagram); #endif SV_FlushBroadcasts(); } #ifdef _MSC_VER #pragma optimize( "", off ) #endif /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages (void) { int i, j; client_t *c; int sentbytes, fnum; float pt = sv.paused?realtime:sv.world.physicstime; #ifdef Q3SERVER if (svs.gametype == GT_QUAKE3) { for (i=0, c = svs.clients ; istate <= cs_zombie) continue; if (c->drop) { SV_DropClient(c); c->drop = false; continue; } if (c->protocol == SCP_BAD) //this is a bot. { SZ_Clear (&c->netchan.message); SZ_Clear (&c->datagram); continue; } SVQ3_SendMessage(c); } return; } #endif // update frags, names, etc SV_UpdateToReliableMessages (); // build individual updates for (i=0, c = svs.clients ; istate <= cs_zombie) continue; if (c->drop) { SV_DropClient(c); c->drop = false; continue; } #ifdef SVCHAT SV_ChatThink(c); #endif if (c->wasrecorded) { c->netchan.message.cursize = 0; c->datagram.cursize = 0; continue; } if (c->istobeloaded && c->state == cs_zombie) { //not yet present. c->netchan.message.cursize = 0; c->datagram.cursize = 0; continue; } #ifdef Q3SERVER if (ISQ3CLIENT(c)) { //q3 protocols bypass backbuffering and pretty much everything else if (c->state <= cs_zombie) continue; SVQ3_SendMessage(c); continue; } #endif // check to see if we have a backbuf to stick in the reliable if (c->num_backbuf) { // will it fit? if (c->netchan.message.cursize + c->backbuf_size[0] < c->netchan.message.maxsize) { Con_DPrintf("%s: backbuf %d bytes\n", c->name, c->backbuf_size[0]); // it'll fit SZ_Write(&c->netchan.message, c->backbuf_data[0], c->backbuf_size[0]); //move along, move along for (j = 1; j < c->num_backbuf; j++) { memcpy(c->backbuf_data[j - 1], c->backbuf_data[j], c->backbuf_size[j]); c->backbuf_size[j - 1] = c->backbuf_size[j]; } c->num_backbuf--; if (c->num_backbuf) { memset(&c->backbuf, 0, sizeof(c->backbuf)); c->backbuf.data = c->backbuf_data[c->num_backbuf - 1]; c->backbuf.cursize = c->backbuf_size[c->num_backbuf - 1]; c->backbuf.maxsize = sizeof(c->backbuf_data[c->num_backbuf - 1]); } } } if (c->protocol == SCP_BAD) { SZ_Clear (&c->netchan.message); SZ_Clear (&c->datagram); c->num_backbuf = 0; continue; } // if the reliable message overflowed, // drop the client if (c->netchan.message.overflowed) { SZ_Clear (&c->netchan.message); SZ_Clear (&c->datagram); SV_BroadcastPrintf (PRINT_HIGH, "%s overflowed\n", c->name); Con_Printf ("WARNING: reliable overflow for %s\n",c->name); c->send_message = true; c->netchan.cleartime = 0; // don't choke this message SV_DropClient (c); continue; } #ifdef NQPROT // only send messages if the client has sent one // and the bandwidth is not choked if (ISNQCLIENT(c)) { //tread carefully with NQ: //while loading models etc, NQ will error out if it receives anything that it wasn't expecting. //we should still send unreliable nops whenever we want as a keepalive (and we may need to in order to wake up the client). //other unreliables are disallowed when connecting, due to sync issues. //reliables may be sent only if some other code has said that its okay (to avoid stray name changes killing clients). if (c->state == cs_connected) { if (c->nextservertimeupdate > pt + 6) c->nextservertimeupdate = 0; c->netchan.nqunreliableonly = !c->send_message; c->datagram.cursize = 0; if (!c->send_message && c->nextservertimeupdate < pt) { if (c->nextservertimeupdate) MSG_WriteByte(&c->datagram, svc_nop); c->nextservertimeupdate = pt+5; } c->send_message = true; //we can still send an outgoing packet if something set send_message. This should really only be svnq_new_f and friends. } else { if (c->nextservertimeupdate > pt + 0.1) c->nextservertimeupdate = 0; c->netchan.nqunreliableonly = false; c->send_message = false; //nq sends one packet only for each server physics frame if (c->nextservertimeupdate < pt && c->state != cs_zombie) { c->send_message = true; c->nextservertimeupdate = pt + 1.0/77; } } } //qw servers will set send_message on packet reception. #endif if (!c->send_message) continue; c->send_message = false; // try putting this after choke? if (c->controller) continue; /*shouldn't have been set*/ if (!sv.paused && !Netchan_CanPacket (&c->netchan, SV_RateForClient(c))) { c->chokecount++; c->waschoked = true; continue; // bandwidth choke } c->waschoked = false; if (sv.time > c->ratetime + 1) { c->inrate = c->netchan.bytesin / (sv.time - c->ratetime); c->outrate = c->netchan.bytesout / (sv.time - c->ratetime); c->netchan.bytesin = 0; c->netchan.bytesout = 0; c->ratetime = sv.time; } if (c->state == cs_spawned) SV_SendClientDatagram (c); else { SV_SendClientPrespawnInfo(c); SV_DarkPlacesDownloadChunk(c, &c->datagram); fnum = c->netchan.outgoing_sequence; sentbytes = Netchan_Transmit (&c->netchan, c->datagram.cursize, c->datagram.data, SV_RateForClient(c)); // just update reliable if (ISQWCLIENT(c) || ISNQCLIENT(c)) c->frameunion.frames[fnum & UPDATE_MASK].packetsizeout += sentbytes; c->datagram.cursize = 0; } } SV_CleanupEnts(); } #ifdef _MSC_VER #pragma optimize( "", on ) #endif void SV_WriteMVDMessage (sizebuf_t *msg, int type, int to, float time); void DemoWriteQTVTimePad(int msecs); #define Max(a, b) ((a>b)?a:b) void SV_SendMVDMessage(void) { int i, j, m, cls = 0; client_t *c; qbyte buf[MAX_DATAGRAM]; sizebuf_t msg; int statsi[MAX_CL_STATS]; float statsf[MAX_CL_STATS]; char *statss[MAX_CL_STATS]; float min_fps; extern cvar_t sv_demofps; extern cvar_t sv_demoPings; // extern cvar_t sv_demoMaxSize; sizebuf_t *dmsg; SV_MVD_RunPendingConnections(); if (!sv.mvdrecording) return; if (sv_demoPings.value) { if (sv.time - demo.pingtime > sv_demoPings.value) { SV_MVDPings(); demo.pingtime = sv.time; } } if (sv_demofps.value <= 1) min_fps = 30.0; else min_fps = sv_demofps.value; min_fps = Max(4, min_fps); if (sv.time - demo.time < 1.0/min_fps) return; for (i=0, c = svs.clients ; istate != cs_spawned) continue; // datagrams only go to spawned cls |= 1 << i; } if (!cls) { SZ_Clear (&demo.datagram); DemoWriteQTVTimePad((int)((sv.time - demo.time)*1000)); DestFlush(false); demo.time = sv.time; return; } msg.data = buf; msg.maxsize = sizeof(buf); msg.cursize = 0; msg.allowoverflow = true; msg.overflowed = false; m = MAX_QW_STATS; if (demo.recorder.fteprotocolextensions & (PEXT_HEXEN2|PEXT_CSQC)) m = MAX_CL_STATS; for (i=0, c = svs.clients ; istate != cs_spawned) continue; // datagrams only go to spawned if (c->spectator) continue; /*figure out what the stat values should be*/ SV_CalcClientStats(c, statsi, statsf, statss); //FIXME we should do something about the packet overhead here. each MVDWrite_Begin is a separate packet! for (j=0 ; j= 0 && statsf[j] <= 255) { dmsg = MVDWrite_Begin(dem_stats, i, 3); MSG_WriteByte(dmsg, svcqw_updatestatbyte); MSG_WriteByte(dmsg, j); MSG_WriteByte(dmsg, statsf[j]); } else { dmsg = MVDWrite_Begin(dem_stats, i, 6); MSG_WriteByte(dmsg, svcfte_updatestatfloat); MSG_WriteByte(dmsg, j); MSG_WriteFloat(dmsg, statsf[j]); } demo.statsf[i][j] = statsf[j]; /*make sure statsf is correct*/ demo.statsi[i][j] = statsf[j]; } continue; } else statsi[j] = statsf[j]; } if (statsi[j] != demo.statsi[i][j]) { demo.statsi[i][j] = statsi[j]; demo.statsf[i][j] = statsi[j]; if (statsi[j] >=0 && statsi[j] <= 255) { dmsg = MVDWrite_Begin(dem_stats, i, 3); MSG_WriteByte(dmsg, svcqw_updatestatbyte); MSG_WriteByte(dmsg, j); MSG_WriteByte(dmsg, statsi[j]); } else { dmsg = MVDWrite_Begin(dem_stats, i, 6); MSG_WriteByte(dmsg, svcqw_updatestatlong); MSG_WriteByte(dmsg, j); MSG_WriteLong(dmsg, statsi[j]); } } } } // send over all the objects that are in the PVS // this will include clients, a packetentities, and // possibly a nails update msg.cursize = 0; msg.prim = demo.recorder.netchan.netprim; if (!demo.recorder.delta_sequence) demo.recorder.delta_sequence = -1; // copy the accumulated multicast datagram // for this client out to the message if (demo.datagram.cursize && sv.mvdrecording) { dmsg = MVDWrite_Begin(dem_all, 0, demo.datagram.cursize); SZ_Write (dmsg, demo.datagram.data, demo.datagram.cursize); SZ_Clear (&demo.datagram); } while (demo.lastwritten < demo.parsecount-1 && sv.mvdrecording) { SV_MVDWritePackets(1); } demo.recorder.delta_sequence = demo.recorder.netchan.incoming_sequence&255; demo.recorder.netchan.incoming_sequence++; demo.frames[demo.parsecount&DEMO_FRAMES_MASK].time = demo.time = sv.time; if (sv.mvdrecording) { SV_WriteEntitiesToClient (&demo.recorder, &msg, true); SV_WriteMVDMessage(&msg, dem_all, 0, sv.time); // dmsg = MVDWrite_Begin(dem_all, 0, msg.cursize); // SZ_Write (dmsg, msg.data, msg.cursize); } demo.parsecount++; // MVDSetMsgBuf(demo.dbuf,&demo.frames[demo.parsecount&DEMO_FRAMES_MASK].buf); } /* ======================= SV_SendMessagesToAll FIXME: does this sequence right? ======================= */ void SV_SendMessagesToAll (void) { int i; client_t *c; for (i=0, c = svs.clients ; istate) // FIXME: should this only send to active? c->send_message = true; SV_SendClientMessages (); } #endif