/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2010-2014 QuakeSpasm developers Copyright (C) 2016 Spike 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" server_t sv; server_static_t svs; static char localmodels[MAX_MODELS][8]; // inline model names for precache int sv_protocol = PROTOCOL_RMQ;//spike -- enough maps need this now that we can probably afford incompatibility with engines that still don't support 999 (vanilla was already broken) -- PROTOCOL_FITZQUAKE; //johnfitz unsigned int sv_protocol_pext2 = PEXT2_SUPPORTED_SERVER; //spike //============================================================================ void SV_CalcStats(client_t *client, int *statsi, float *statsf, const char **statss) { size_t i; edict_t *ent = client->edict; //FIXME: string stats! int items; eval_t *val = GetEdictFieldValue(ent, qcvm->extfields.items2); if (val) items = (int)ent->v.items | ((int)val->_float << 23); else items = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28); memset(statsi, 0, sizeof(*statsi)*MAX_CL_STATS); memset(statsf, 0, sizeof(*statsf)*MAX_CL_STATS); memset(statss, 0, sizeof(*statss)*MAX_CL_STATS); statsf[STAT_HEALTH] = ent->v.health; // statsf[STAT_FRAGS] = ent->v.frags; //obsolete statsi[STAT_WEAPON] = SV_ModelIndex(PR_GetString(ent->v.weaponmodel)); if ((unsigned int)statsi[STAT_WEAPON] >= client->limit_models) statsi[STAT_WEAPON] = 0; statsf[STAT_AMMO] = ent->v.currentammo; statsf[STAT_ARMOR] = ent->v.armorvalue; statsf[STAT_WEAPONFRAME] = ent->v.weaponframe; 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; statsf[STAT_ACTIVEWEAPON] = ent->v.weapon; //sent in a way that does NOT depend upon the current mod... // statsf[STAT_TOTALSECRETS] = pr_global_struct->total_secrets; //don't bother with these, the qc sends extra updates and we don't want to end up with a race condition. // statsf[STAT_TOTALMONSTERS] = pr_global_struct->total_monsters; // statsf[STAT_SECRETS] = pr_global_struct->found_secrets; // statsf[STAT_MONSTERS] = pr_global_struct->killed_monsters; // statsi[STAT_TIME] = sv.time*1000; //in ms, this was a hack to work around vanilla QW clients not having any concept of serverside time. // statsi[STAT_MATCHSTARTTIME] = 0*1000; //in ms, set by the mod to when the current match actually starts (so pregame stuff doesn't interfere with game clocks). // stats[STAT_VIEW2] = NUM_FOR_EDICT(PROG_TO_EDICT(ent->v.view2)); if ((val = GetEdictFieldValue(ent, qcvm->extfields.viewzoom)) && val->_float) { statsf[STAT_VIEWZOOM] = val->_float*255; if (statsf[STAT_VIEWZOOM] < 1) statsf[STAT_VIEWZOOM] = 1; } //FIXME: add support for clientstat/globalstat qc builtins. if (client->protocol_pext2 & PEXT2_PREDINFO) { //predinfo also kills clc_clientdata statsi[STAT_ITEMS] = items; statsf[STAT_VIEWHEIGHT] = ent->v.view_ofs[2]; statsf[STAT_IDEALPITCH] = ent->v.idealpitch; statsf[STAT_PUNCHANGLE_X] = ent->v.punchangle[0]; statsf[STAT_PUNCHANGLE_Y] = ent->v.punchangle[1]; statsf[STAT_PUNCHANGLE_Z] = ent->v.punchangle[2]; } /* if (client->protocol_pext2 & PEXT2_PREDINFO) { //prediction needs some info on the server's rules statsf[STAT_MOVEVARS_FRICTION] = sv_friction.value; statsf[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value; statsf[STAT_MOVEVARS_TICRATE] = host_maxfps.value; statsf[STAT_MOVEVARS_TIMESCALE] = sv_gamespeed.value; statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; statsf[STAT_MOVEVARS_MAXSPEED] = client->maxspeed; statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_spectatormaxspeed.value; statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value; statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value; statsf[STAT_MOVEVARS_ENTGRAVITY] = client->entgravity/sv_gravity.value; statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; //bah statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; statsf[STAT_MOVEVARS_MAXAIRSPEED] = client->maxspeed; statsf[STAT_MOVEVARS_STEPHEIGHT] = 18; // statsf[STAT_MOVEVARS_AIRACCEL_QW] = 0; // statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_gravity.value; } */ for (i = 0; i < sv.numcustomstats; i++) { eval_t *eval = sv.customstats[i].ptr; if (!eval) eval = GetEdictFieldValue(ent, sv.customstats[i].fld); switch(sv.customstats[i].type) { case ev_ext_integer: statsi[sv.customstats[i].idx] = eval->_int; break; case ev_entity: statsi[sv.customstats[i].idx] = NUM_FOR_EDICT(PROG_TO_EDICT(eval->edict)); break; case ev_float: statsf[sv.customstats[i].idx] = eval->_float; break; case ev_vector: statsf[sv.customstats[i].idx+0] = eval->vector[0]; statsf[sv.customstats[i].idx+1] = eval->vector[1]; statsf[sv.customstats[i].idx+2] = eval->vector[2]; break; case ev_string: //not supported in this build... send with svcfte_updatestatstring on change, which is annoying. statss[sv.customstats[i].idx] = PR_GetString(eval->string); break; case ev_void: //nothing... case ev_field: //panic! everyone panic! case ev_function: //doesn't make much sense case ev_pointer: //doesn't make sense default: break; } } } /*server-side-only flags that re-use encoding bits*/ #define UF_REMOVE UF_16BIT /*says we removed the entity in this frame*/ #define UF_MOVETYPE UF_EFFECTS2 /*this flag isn't present in the header itself*/ #define UF_RESET2 UF_EXTEND1 /*so new ents are reset multiple times to avoid weird baselines*/ //#define UF_UNUSED UF_EXTEND2 /**/ #define UF_WEAPONFRAME_OLD UF_EXTEND2 #define UF_VIEWANGLES UF_EXTEND3 /**/ static unsigned int SVFTE_DeltaPredCalcBits(entity_state_t *from, entity_state_t *to) { unsigned int bits = 0; // if (from && from->pmovetype != to->pmovetype) // bits |= UFP_MOVETYPE; // if (to->movement[0]) // bits |= UFP_FORWARD; // if (to->movement[1]) // bits |= UFP_SIDE; // if (to->movement[2]) // bits |= UFP_UP; if (to->velocity[0]) bits |= UFP_VELOCITYXY; if (to->velocity[1]) bits |= UFP_VELOCITYXY; if (to->velocity[2]) bits |= UFP_VELOCITYZ; // if (to->msec) // bits |= UFP_MSEC; return bits; } static unsigned int MSGFTE_DeltaCalcBits(entity_state_t *from, entity_state_t *to) { unsigned int bits = 0; if (from->pmovetype != to->pmovetype) bits |= UF_PREDINFO|UF_MOVETYPE; /* if (from->weaponframe != to->weaponframe) bits |= UF_PREDINFO|UF_WEAPONFRAME_OLD; */// if (to->pmovetype) //we don't support prediction, but we still need to network the player's velocity for bob etc. { if (SVFTE_DeltaPredCalcBits(from, to)) bits |= UF_PREDINFO; //moving players get extra data forced upon them which is not deltatracked if ((bits & UF_PREDINFO) && (from->velocity[0] || from->velocity[1] || from->velocity[2])) { //if we've got player movement then write the origin anyway, to cover packetloss bits |= UF_ORIGINXY | UF_ORIGINZ; //and force angles too, if its not us // if (host_client != svs.clients + to->number-1) // bits |= UF_ANGLESXZ | UF_ANGLESY; } } if (to->origin[0] != from->origin[0]) bits |= UF_ORIGINXY; if (to->origin[1] != from->origin[1]) bits |= UF_ORIGINXY; if (to->origin[2] != from->origin[2]) bits |= UF_ORIGINZ; if (to->angles[0] != from->angles[0]) bits |= UF_ANGLESXZ; if (to->angles[1] != from->angles[1]) bits |= UF_ANGLESY; if (to->angles[2] != from->angles[2]) bits |= UF_ANGLESXZ; if (to->modelindex != from->modelindex) bits |= UF_MODEL; if (to->frame != from->frame) bits |= UF_FRAME; if (to->skin != from->skin) bits |= UF_SKIN; if (to->colormap != from->colormap) bits |= UF_COLORMAP; if (to->effects != from->effects) bits |= UF_EFFECTS; if (to->eflags != from->eflags) bits |= UF_FLAGS; // if (to->solidsize != from->solidsize) // bits |= UF_SOLID; if (to->scale != from->scale) bits |= UF_SCALE; if (to->alpha != from->alpha) bits |= UF_ALPHA; // if (to->fatness != from->fatness) // bits |= UF_FATNESS; // if (to->hexen2flags != from->hexen2flags || to->abslight != from->abslight) // bits |= UF_DRAWFLAGS; // if (to->bonecount != from->bonecount || (to->bonecount && memcmp(frombonedata+from->boneoffset, tobonedata+to->boneoffset, to->bonecount*sizeof(short)*7))) // bits |= UF_BONEDATA; // if (!to->bonecount && (to->basebone != from->basebone || to->baseframe != from->baseframe)) // bits |= UF_BONEDATA; if (to->colormod[0]!=from->colormod[0]||to->colormod[1]!=from->colormod[1]||to->colormod[2]!=from->colormod[2]) bits |= UF_COLORMOD; // if (to->glowsize!=from->glowsize||to->glowcolour!=from->glowcolour||to->glowmod[0]!=from->glowmod[0]||to->glowmod[1]!=from->glowmod[1]||to->glowmod[2]!=from->glowmod[2]) // bits |= UF_GLOW; if (to->tagentity != from->tagentity || to->tagindex != from->tagindex) bits |= UF_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->lightpflags) // bits |= UF_LIGHT; if (to->traileffectnum != from->traileffectnum || to->emiteffectnum != from->emiteffectnum) bits |= UF_TRAILEFFECT; // if (to->modelindex2 != from->modelindex2) // bits |= UF_MODELINDEX2; // if (to->gravitydir[0] != from->gravitydir[0] || to->gravitydir[1] != from->gravitydir[1]) // bits |= UF_GRAVITYDIR; return bits; } static void MSGFTE_WriteEntityUpdate(unsigned int bits, entity_state_t *state, sizebuf_t *msg, unsigned int pext2, unsigned int protocolflags) { unsigned int predbits = 0; if (bits & UF_MOVETYPE) { bits &= ~UF_MOVETYPE; predbits |= UFP_MOVETYPE; } if (pext2 & PEXT2_PREDINFO) { if (bits & UF_VIEWANGLES) { bits &= ~UF_VIEWANGLES; bits |= UF_PREDINFO; predbits |= UFP_VIEWANGLE; } } else { if (bits & UF_VIEWANGLES) { bits &= ~UF_VIEWANGLES; bits |= UF_PREDINFO; } if (bits & UF_WEAPONFRAME_OLD) { bits &= ~UF_WEAPONFRAME_OLD; predbits |= UFP_WEAPONFRAME_OLD; } } // if (!(pext2 & PEXT2_NEWSIZEENCODING)) //was added at the same time bits &= ~UF_BONEDATA; /*check if we need more precision for some things*/ if ((bits & UF_MODEL) && state->modelindex > 255) bits |= UF_16BIT; // if ((bits & UF_SKIN) && state->skin > 255) // bits |= UF_16BIT; if ((bits & UF_FRAME) && state->frame > 255) bits |= UF_16BIT; /*convert effects bits to higher lengths if needed*/ if (bits & UF_EFFECTS) { if (state->effects & 0xffff0000) /*both*/ bits |= UF_EFFECTS | UF_EFFECTS2; else if (state->effects & 0x0000ff00) /*2 only*/ bits = (bits & ~UF_EFFECTS) | UF_EFFECTS2; } if (bits & 0xff000000) bits |= UF_EXTEND3; if (bits & 0x00ff0000) bits |= UF_EXTEND2; if (bits & 0x0000ff00) bits |= UF_EXTEND1; MSG_WriteByte(msg, (bits>>0) & 0xff); if (bits & UF_EXTEND1) MSG_WriteByte(msg, (bits>>8) & 0xff); if (bits & UF_EXTEND2) MSG_WriteByte(msg, (bits>>16) & 0xff); if (bits & UF_EXTEND3) MSG_WriteByte(msg, (bits>>24) & 0xff); if (bits & UF_FRAME) { if (bits & UF_16BIT) MSG_WriteShort(msg, state->frame); else MSG_WriteByte(msg, state->frame); } if (bits & UF_ORIGINXY) { MSG_WriteCoord(msg, state->origin[0], protocolflags); MSG_WriteCoord(msg, state->origin[1], protocolflags); } if (bits & UF_ORIGINZ) MSG_WriteCoord(msg, state->origin[2], protocolflags); if ((bits & UF_PREDINFO) && !(pext2 & PEXT2_PREDINFO)) { /*if we have pred info, (always) use more precise angles*/ if (bits & UF_ANGLESXZ) { MSG_WriteAngle16(msg, state->angles[0], protocolflags); MSG_WriteAngle16(msg, state->angles[2], protocolflags); } if (bits & UF_ANGLESY) MSG_WriteAngle16(msg, state->angles[1], protocolflags); } else { if (bits & UF_ANGLESXZ) { MSG_WriteAngle(msg, state->angles[0], protocolflags); MSG_WriteAngle(msg, state->angles[2], protocolflags); } if (bits & UF_ANGLESY) MSG_WriteAngle(msg, state->angles[1], protocolflags); } if ((bits & (UF_EFFECTS|UF_EFFECTS2)) == (UF_EFFECTS|UF_EFFECTS2)) MSG_WriteLong(msg, state->effects); else if (bits & UF_EFFECTS2) MSG_WriteShort(msg, state->effects); else if (bits & UF_EFFECTS) MSG_WriteByte(msg, state->effects); if (bits & UF_PREDINFO) { /*movetype is set above somewhere*/ predbits |= SVFTE_DeltaPredCalcBits(NULL, state); MSG_WriteByte(msg, predbits); /* if (predbits & UFP_FORWARD) MSG_WriteShort(msg, state->movement[0]); if (predbits & UFP_SIDE) MSG_WriteShort(msg, state->movement[1]); if (predbits & UFP_UP) MSG_WriteShort(msg, state->movement[2]); */ if (predbits & UFP_MOVETYPE) MSG_WriteByte(msg, state->pmovetype); if (predbits & UFP_VELOCITYXY) { MSG_WriteShort(msg, state->velocity[0]); MSG_WriteShort(msg, state->velocity[1]); } if (predbits & UFP_VELOCITYZ) MSG_WriteShort(msg, state->velocity[2]); /* if (predbits & UFP_MSEC) MSG_WriteByte(msg, state->msec); if (pext2 & PEXT2_PREDINFO) { if (predbits & UFP_VIEWANGLE) { //if we have pred info, use more precise angles if (bits & UF_ANGLESXZ) { MSG_WriteShort(msg, state->vangle[0]); MSG_WriteShort(msg, state->vangle[2]); } if (bits & UF_ANGLESY) MSG_WriteShort(msg, state->vangle[1]); } } else { if (predbits & UFP_WEAPONFRAME_OLD) { if (state->weaponframe > 127) { MSG_WriteByte(msg, 128 | (state->weaponframe & 127)); MSG_WriteByte(msg, state->weaponframe>>7); } else MSG_WriteByte(msg, state->weaponframe); } } */ } if (bits & UF_MODEL) { if (bits & UF_16BIT) MSG_WriteShort(msg, state->modelindex); else MSG_WriteByte(msg, state->modelindex); } if (bits & UF_SKIN) { if (bits & UF_16BIT) MSG_WriteShort(msg, state->skin); else MSG_WriteByte(msg, state->skin); } if (bits & UF_COLORMAP) MSG_WriteByte(msg, state->colormap & 0xff); if (bits & UF_SOLID) { /* if (pext2 & PEXT2_NEWSIZEENCODING) { if (!state->solidsize) MSG_WriteByte(msg, 0); else if (state->solidsize == ES_SOLID_BSP) MSG_WriteByte(msg, 1); else if (state->solidsize == ES_SOLID_HULL1) MSG_WriteByte(msg, 2); else if (state->solidsize == ES_SOLID_HULL2) MSG_WriteByte(msg, 3); else if (!ES_SOLID_HAS_EXTRA_BITS(state->solidsize)) { MSG_WriteByte(msg, 16); MSG_WriteSize16(msg, state->solidsize); } else { MSG_WriteByte(msg, 32); MSG_WriteLong(msg, state->solidsize); } } else MSG_WriteSize16(msg, state->solidsize); */ } if (bits & UF_FLAGS) MSG_WriteByte(msg, state->eflags); if (bits & UF_ALPHA) MSG_WriteByte(msg, (state->alpha-1)&0xff); if (bits & UF_SCALE) MSG_WriteByte(msg, state->scale); /* if (bits & UF_BONEDATA) { short *bonedata; int i; byte bfl = 0; if (state->bonecount && boneptr) bfl |= 0x80; if (state->basebone || state->baseframe) bfl |= 0x40; MSG_WriteByte(msg, bfl); if (bfl & 0x80) { //this is NOT finalized MSG_WriteByte(msg, state->bonecount); bonedata = (short*)(boneptr + state->boneoffset); for (i = 0; i < state->bonecount*7; i++) MSG_WriteShort(msg, bonedata[i]); } if (bfl & 0x40) { MSG_WriteByte(msg, state->basebone); MSG_WriteShort(msg, state->baseframe); } } if (bits & UF_DRAWFLAGS) { MSG_WriteByte(msg, state->drawflags); if ((state->drawflags & MLS_MASK) == MLS_ABSLIGHT) MSG_WriteByte(msg, state->abslight); } */ if (bits & UF_TAGINFO) { MSG_WriteEntity(msg, state->tagentity, pext2); MSG_WriteByte(msg, state->tagindex); } /* if (bits & UF_LIGHT) { MSG_WriteShort (msg, state->light[0]); MSG_WriteShort (msg, state->light[1]); MSG_WriteShort (msg, state->light[2]); MSG_WriteShort (msg, state->light[3]); MSG_WriteByte (msg, state->lightstyle); MSG_WriteByte (msg, state->lightpflags); } */ if (bits & UF_TRAILEFFECT) { if (state->emiteffectnum) { //3 spare bits. so that's nice (this is guarenteed to be 14 bits max due to precaches using the upper two bits). MSG_WriteShort(msg, (state->traileffectnum&0x3fff)|0x8000); MSG_WriteShort(msg, state->emiteffectnum&0x3fff); } else MSG_WriteShort(msg, state->traileffectnum&0x3fff); } if (bits & UF_COLORMOD) { MSG_WriteByte(msg, state->colormod[0]); MSG_WriteByte(msg, state->colormod[1]); MSG_WriteByte(msg, state->colormod[2]); } /* if (bits & UF_GLOW) { MSG_WriteByte(msg, state->glowsize); MSG_WriteByte(msg, state->glowcolour); MSG_WriteByte(msg, state->glowmod[0]); MSG_WriteByte(msg, state->glowmod[1]); MSG_WriteByte(msg, state->glowmod[2]); } if (bits & UF_FATNESS) MSG_WriteByte(msg, state->fatness); if (bits & UF_MODELINDEX2) { if (bits & UF_16BIT) MSG_WriteShort(msg, state->modelindex2); else MSG_WriteByte(msg, state->modelindex2); } if (bits & UF_GRAVITYDIR) { MSG_WriteByte(msg, state->gravitydir[0]); MSG_WriteByte(msg, state->gravitydir[1]); } */ } static struct entity_num_state_s *snapshot_entstate; static size_t snapshot_numents; static size_t snapshot_maxents; void SVFTE_DestroyFrames(client_t *client) { int i; for (i = 0; i < MAX_CL_STATS; i++) { if (!client->oldstats_s[i]) continue; free(client->oldstats_s[i]); client->oldstats_s[i] = 0; } if (client->previousentities) free(client->previousentities); client->previousentities = NULL; client->numpreviousentities = 0; client->maxpreviousentities = 0; if (client->pendingentities_bits) free(client->pendingentities_bits); client->pendingentities_bits = NULL; client->numpendingentities = 0; while(client->numframes > 0) { client->numframes--; free(client->frames[client->numframes].ents); } if (client->frames) free(client->frames); client->frames = NULL; client->lastacksequence = 0; } static void SVFTE_SetupFrames(client_t *client) { size_t fr; //the client will clear out their stats on receipt of the svc_serverinfo packet. //we won't send any reliables until they receive it //so it should be enough to just clear these here, and they'll get their new stats with the first entity update once they're spawned memset(client->oldstats_i, 0, sizeof(client->oldstats_i)); memset(client->oldstats_f, 0, sizeof(client->oldstats_f)); client->lastmovemessage = 0; //it'll clear this too if (!client->protocol_pext2) { SVFTE_DestroyFrames(client); return; } client->numframes = 64; //must be power-of-two client->frames = malloc(sizeof(*client->frames) * client->numframes); client->lastacksequence = (int)0x80000000; memset(client->frames, 0, sizeof(*client->frames) * client->numframes); for (fr = 0; fr < client->numframes; fr++) client->frames[fr].sequence = client->lastacksequence; client->numpendingentities = qcvm->num_edicts; client->pendingentities_bits = calloc(client->numpendingentities, sizeof(*client->pendingentities_bits)); client->pendingentities_bits[0] = UF_REMOVE; } static void SVFTE_DroppedFrame(client_t *client, int sequence) { int i; struct deltaframe_s *frame = &client->frames[sequence&(client->numframes-1)]; if (frame->sequence != sequence) return; //this frame was stale... client is running too far behind. we'll probably be spamming resends as a result. frame->sequence = -1; //flag their stats for resend for (i = 0; i < MAX_CL_STATS/32; i++) { client->resendstatsnum[i] |= frame->resendstatsnum[i]; client->resendstatsstr[i] |= frame->resendstatsstr[i]; } //flag their various entities as needing a resend too. for (i = 0; i < frame->numents; i++) client->pendingentities_bits[frame->ents[i].num] |= frame->ents[i].bits; } void SVFTE_Ack(client_t *client, int sequence) { //any gaps in the sequence need to considered dropped struct deltaframe_s *frame; int dropseq = client->lastacksequence+1; if (!client->numframes) return; //client shouldn't be using this. if (sequence == -1) client->pendingentities_bits[0] |= UF_REMOVE; //client wants a full resend. which might happen from it just starting to record a demo, saving it from writing all the deltas out. if (sequence < client->lastacksequence) { // else Con_SafePrintf("dupe or stale ack (%s, %i->%i)\n", client->name, client->lastacksequence, sequence); return; //panic } if ((unsigned)(dropseq-sequence) >= client->numframes) dropseq = sequence - client->numframes; while(dropseq < sequence) SVFTE_DroppedFrame(client, dropseq++); client->lastacksequence = sequence; frame = &client->frames[sequence&(client->numframes-1)]; if (frame->sequence >= 0) { frame->sequence = -1; host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] = qcvm->time - frame->timestamp; host_client->num_pings++; } } static void SVFTE_WriteStats(client_t *client, sizebuf_t *msg) { int statsi[MAX_CL_STATS]; float statsf[MAX_CL_STATS]; const char *statss[MAX_CL_STATS]; int i; struct deltaframe_s *frame; int sequence = NET_QSocketGetSequenceOut(client->netconnection); int maxstats; if (client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) maxstats = MAX_CL_STATS; else maxstats = 32; frame = &client->frames[sequence&(client->numframes-1)]; if (frame->sequence == sequence-(int)client->numframes) //client is getting behind... this may get really spammy, lets hope it clears up at some point SVFTE_DroppedFrame(client, frame->sequence); //figure out the current values in a nice easy way (yay for copying to make arrays easier!) SV_CalcStats(client, statsi, statsf, statss); for (i = 0; i < maxstats; i++) { //small cleanup if (!statsi[i]) statsi[i] = statsf[i]; else statsf[i] = 0;//statsi[i]; //if it changed flag for sending if (statsi[i] != client->oldstats_i[i] || statsf[i] != client->oldstats_f[i]) { client->oldstats_i[i] = statsi[i]; client->oldstats_f[i] = statsf[i]; client->resendstatsnum[i/32] |= 1u<<(i&31); } if (statss[i] || client->oldstats_s[i]) { const char *os = client->oldstats_s[i]; const char *ns = statss[i]; if (!ns) ns=""; if (!os) os=""; if (strcmp(os,ns)) { client->resendstatsstr[i/32] |= 1u<<(i&31); free(client->oldstats_s[i]); client->oldstats_s[i] = strdup(ns); } } //if its flagged then unflag it, log it, and send it if (client->resendstatsnum[i/32] & (1u<<(i&31))) { client->resendstatsnum[i/32] &= ~(1u<<(i&31)); frame->resendstatsnum[i/32] |= 1u<<(i&31); if ((double)statsi[i] != statsf[i] && statsf[i]) { //didn't round nicely, so send as a float MSG_WriteByte(msg, svcfte_updatestatfloat); MSG_WriteByte(msg, i); MSG_WriteFloat(msg, statsf[i]); } else { if (statsi[i] < 0 || statsi[i] > 255) { //needs to be big MSG_WriteByte(msg, svc_updatestat); MSG_WriteByte(msg, i); MSG_WriteLong(msg, statsi[i]); } else { //can be fairly small MSG_WriteByte(msg, svcdp_updatestatbyte); MSG_WriteByte(msg, i); MSG_WriteByte(msg, statsi[i]); } } } //if its flagged then unflag it, log it, and send it if (client->resendstatsstr[i/32] & (1u<<(i&31))) { client->resendstatsstr[i/32] &= ~(1u<<(i&31)); frame->resendstatsstr[i/32] |= 1u<<(i&31); MSG_WriteByte(msg, svcfte_updatestatstring); MSG_WriteByte(msg, i); if (statss[i]) MSG_WriteString(msg, statss[i]); else MSG_WriteString(msg, NULL); } } } static void SVFTE_CalcEntityDeltas(client_t *client) { struct entity_num_state_s *olds, *news, *oldstop, *newstop; if ((int)client->numpendingentities < qcvm->num_edicts) { int newmax = qcvm->num_edicts+64; client->pendingentities_bits = realloc(client->pendingentities_bits, sizeof(*client->pendingentities_bits) * newmax); memset(client->pendingentities_bits+client->numpendingentities, 0, sizeof(*client->pendingentities_bits)*(newmax-client->numpendingentities)); client->numpendingentities = newmax; } //if we're clearing the list and starting from scratch, just wipe all lingering state if (client->pendingentities_bits[0] & UF_REMOVE) { client->numpreviousentities = 0; client->pendingentities_bits[0] = UF_REMOVE; } news = snapshot_entstate; newstop = news + snapshot_numents; olds = client->previousentities; oldstop = olds+client->numpreviousentities; //we have two sets of entity state, pvs culled etc already. //figure out which flags changed, for (;;) { if (olds==oldstop && news==newstop) break; if (news==newstop || (olds!=oldstop && olds->num < news->num)) { //old ent is no longer visible, so flag for removal. client->pendingentities_bits[olds->num] = UF_REMOVE; olds++; } else if (olds==oldstop || (news!=newstop && news->num < olds->num)) { //new ent is new this frame, so reset everything. client->pendingentities_bits[news->num] = UF_RESET | UF_RESET2; //don't need to calc the other bits here, resets are enough news++; } else { //simple entity delta //its flagged for removing, that's weird... must be some killer packetloss. turn that back into a reset or something if (client->pendingentities_bits[news->num] & UF_REMOVE) client->pendingentities_bits[news->num] = (client->pendingentities_bits[news->num] & ~UF_REMOVE) | UF_RESET2; client->pendingentities_bits[news->num] |= MSGFTE_DeltaCalcBits(&olds->state, &news->state); news++; olds++; } } //now we know what flags to apply, the client needs a copy of that state for the next frame too. //outgoing data can just read off these states too, instead of needing to hit the edicts memory (which may be spread over multiple allocations, yay cache). //to avoid a potentially large memcopy, I'm just going to swap these buffers. olds = client->previousentities; oldstop = olds + client->maxpreviousentities; client->previousentities = snapshot_entstate; client->numpreviousentities = snapshot_numents; client->maxpreviousentities = snapshot_maxents; snapshot_entstate = olds; snapshot_numents = 0; snapshot_maxents = oldstop-olds; } static void SVFTE_WriteEntitiesToClient(client_t *client, sizebuf_t *msg, size_t overflowsize) { struct entity_num_state_s *state, *stateend; unsigned int bits, logbits; size_t entnum; int sequence = NET_QSocketGetSequenceOut(client->netconnection); size_t origmaxsize = msg->maxsize; size_t rollbacksize; //I'm too lazy to figure out sizes (especially if someone updates this for bone states or whatever) struct deltaframe_s *frame = &client->frames[sequence&(client->numframes-1)]; frame->sequence = sequence; //so we know that it wasn't stale later. frame->timestamp = qcvm->time; msg->maxsize = overflowsize; state = client->previousentities; stateend = state + client->numpreviousentities; MSG_WriteByte(msg, svcfte_updateentities); frame->numents = 0; if (client->protocol_pext2 & PEXT2_PREDINFO) MSG_WriteShort(msg, (client->lastmovemessage&0xffff)); MSG_WriteFloat(msg, qcvm->time); //should be the time the last physics frame was run. for (entnum = client->snapshotresume; entnum < client->numpendingentities; entnum++) { bits = client->pendingentities_bits[entnum]; if (!(bits & ~UF_RESET2)) continue; //nothing to send (if reset2 is still set, then leave it pending until there's more data rollbacksize = msg->cursize; client->pendingentities_bits[entnum] = 0; logbits = 0; if (bits & UF_REMOVE) { if (entnum > 0x3fff) { MSG_WriteShort(msg, 0xc000|(entnum&0x3fff)); MSG_WriteShort(msg, entnum>>14); } else MSG_WriteShort(msg, 0x8000|entnum); logbits = UF_REMOVE; } else { while (statenum < entnum) state++; if (statenum == entnum) { if (bits & UF_RESET2) { /*if reset2, then this is the second packet sent to the client and should have a forced reset (but which isn't tracked)*/ logbits = bits & ~(UF_RESET|UF_RESET2); bits = UF_RESET | MSGFTE_DeltaCalcBits(&EDICT_NUM(entnum)->baseline, &state->state); // Con_Printf("RESET2 %i @ %i\n", j, sequence); } else if (bits & UF_RESET) { /*flag the entity for the next packet, so we always get two resets when it appears, to reduce the effects of packetloss on seeing rockets etc*/ client->pendingentities_bits[entnum] = UF_RESET2; bits = UF_RESET | MSGFTE_DeltaCalcBits(&EDICT_NUM(entnum)->baseline, &state->state); logbits = UF_RESET; // Con_Printf("RESET %i @ %i\n", j, sequence); } else logbits = bits; if (entnum >= 0x4000) { MSG_WriteShort(msg, 0x4000|(entnum&0x3fff)); MSG_WriteShort(msg, entnum>>14); } else MSG_WriteShort(msg, entnum); // SV_EmitDeltaEntIndex(msg, j, false, true); MSGFTE_WriteEntityUpdate(bits, &state->state, msg, client->protocol_pext2, sv.protocolflags); } } if ((size_t)msg->cursize + 2 > origmaxsize) { msg->cursize = rollbacksize; //roll back client->pendingentities_bits[entnum] |= logbits; //make sure those bits get re-applied later. break; } if (frame->numents == frame->maxents) { frame->maxents += 64; frame->ents = realloc(frame->ents, sizeof(*frame->ents)*frame->maxents); } frame->ents[frame->numents].num = entnum; frame->ents[frame->numents].bits = logbits; frame->numents++; } msg->maxsize = origmaxsize; MSG_WriteShort(msg, 0); //eom //remember how far we got, so we can keep things flushed, instead of only updating the first N entities. client->snapshotresume = (entnumnumpendingentities?entnum:0); if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) Con_DWarning ("%i byte packet exceeds standard limit of 1024.\n", msg->cursize); dev_stats.packetsize = msg->cursize; dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); } /* SV_BuildEntityState copies edict state into a more compact entity_state_t with all the extension fields etc sorted out and neatened up for network precision. note: ignores viewmodelforclient and other client-specific stuff. */ void SV_BuildEntityState(edict_t *ent, entity_state_t *state) { eval_t *val; state->eflags = 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->skin = ent->v.skin; if ((val = GetEdictFieldValue(ent, qcvm->extfields.scale)) && val->_float) state->scale = val->_float*16; else state->scale = 16; if ((val = GetEdictFieldValue(ent, qcvm->extfields.alpha))) state->alpha = ENTALPHA_ENCODE(val->_float); else state->alpha = ent->alpha; if ((val = GetEdictFieldValue(ent, qcvm->extfields.colormod)) && (val->vector[0]||val->vector[1]||val->vector[2])) { state->colormod[0] = val->vector[0]*32; state->colormod[1] = val->vector[1]*32; state->colormod[2] = val->vector[2]*32; } else state->colormod[0] = state->colormod[1] = state->colormod[2] = 32; state->traileffectnum = qcvm->extfields.traileffectnum>=0?GetEdictFieldValue(ent, qcvm->extfields.traileffectnum)->_float:0; state->emiteffectnum = qcvm->extfields.emiteffectnum>=0?GetEdictFieldValue(ent, qcvm->extfields.emiteffectnum)->_float:0; if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_entity)) && val->edict) state->tagentity = NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)); else state->tagentity = 0; if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_index))) state->tagindex = val->_float; else state->tagindex = 0; state->effects = ent->v.effects; if ((val = GetEdictFieldValue(ent, qcvm->extfields.modelflags))) state->effects |= ((unsigned int)val->_float)<<24; if (!ent->v.movetype || ent->v.movetype == MOVETYPE_STEP) state->eflags |= EFLAGS_STEP; state->pmovetype = 0; state->velocity[0] = state->velocity[1] = state->velocity[2] = 0; } byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel); static void SVFTE_BuildSnapshotForClient (client_t *client) { unsigned int e, i; byte *pvs; vec3_t org; edict_t *ent, *parent; unsigned int maxentities = client->limit_entities; edict_t *clent = client->edict; eval_t *val; unsigned char eflags; int proged = EDICT_TO_PROG(clent); struct entity_num_state_s *ents = snapshot_entstate; size_t numents = 0; size_t maxents = snapshot_maxents; int emiteffect; // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); pvs = SV_FatPVS (org, qcvm->worldmodel); if (maxentities > (unsigned int)qcvm->num_edicts) maxentities = (unsigned int)qcvm->num_edicts; // send over all entities (excpet the client) that touch the pvs ent = NEXT_EDICT(qcvm->edicts); for (e=1 ; eextfields.emiteffectnum)->_float; if (ent != clent) // clent is ALLWAYS sent { // ignore ents without visible models if ((!ent->v.modelindex || !PR_GetString(ent->v.model)[0]) && !emiteffect) continue; val = GetEdictFieldValue(ent, qcvm->extfields.viewmodelforclient); if (val && val->edict == proged) eflags |= EFLAGS_VIEWMODEL; else if (val && val->edict) continue; else { //attached entities should use the pvs of the parent rather than the child (because the child will typically be bugging out around '0 0 0', so won't be useful) parent = ent; while ((val = GetEdictFieldValue(parent, qcvm->extfields.tag_entity)) && val->edict) parent = PROG_TO_EDICT(val->edict); if (parent->num_leafs) { // ignore if not touching a PV leaf for (i=0 ; i < parent->num_leafs ; i++) if (pvs[parent->leafnums[i] >> 3] & (1 << (parent->leafnums[i]&7) )) break; // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. // // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs // for us to say whether it's in the PVS, so don't try to vis cull it. // this commonly happens with rotators, because they often have huge bboxes // spanning the entire map, or really tall lifts, etc. if (i == parent->num_leafs && parent->num_leafs < MAX_ENT_LEAFS) continue; // not visible } } } //okay, we care about this entity. if (numents == maxents) { maxents += 64; ents = realloc(ents, maxents*sizeof(*ents)); } ents[numents].num = e; SV_BuildEntityState(ent, &ents[numents].state); if ((unsigned int)ents[numents].state.modelindex >= client->limit_models) ents[numents].state.modelindex = 0; if (ent == clent) //add velocity, but we only care for the local player (should add prediction for other entities some time too). { ents[numents].state.pmovetype = 0;//ent->v.movetype; //fixme: we don't do prediction, so don't tell the client that it can try if ((int)ent->v.flags & FL_ONGROUND) eflags |= EFLAGS_ONGROUND; ents[numents].state.velocity[0] = ent->v.velocity[0]*8; ents[numents].state.velocity[1] = ent->v.velocity[1]*8; ents[numents].state.velocity[2] = ent->v.velocity[2]*8; } else if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) //don't send invisible entities unless they have effects continue; val = GetEdictFieldValue(ent, qcvm->extfields.exteriormodeltoclient); if (val && val->edict == proged) eflags |= EFLAGS_EXTERIORMODEL; //EFLAGS_VIEWMODEL was handled above ents[numents].state.eflags |= eflags; numents++; } snapshot_entstate = ents; snapshot_numents = numents; snapshot_maxents = maxents; } void MSG_WriteStaticOrBaseLine(sizebuf_t *buf, int idx, entity_state_t *state, unsigned int protocol_pext2, unsigned int protocol, unsigned int protocolflags) { int i; if (protocol_pext2 & PEXT2_REPLACEMENTDELTAS) { if (idx>=0) { MSG_WriteByte(buf, svcfte_spawnbaseline2); MSG_WriteShort(buf, idx); } else MSG_WriteByte(buf, svcfte_spawnstatic2); MSGFTE_WriteEntityUpdate(MSGFTE_DeltaCalcBits(&nullentitystate, state), state, buf, protocol_pext2, protocolflags); } else { int bits = 0; if (protocol == PROTOCOL_VERSION_DP7) { if ((state->modelindex & 0xFF00) || (state->frame & 0xFF00)) bits = B_LARGEMODEL|B_LARGEFRAME; //no alpha etc, no idea how that's meant to work with static ents. if (idx>=0) { MSG_WriteByte (buf, bits?svcdp_spawnbaseline2:svc_spawnbaseline); MSG_WriteEntity (buf, idx, protocol_pext2); } else MSG_WriteByte (buf, bits?svcdp_spawnstatic2:svc_spawnstatic); } else { if (protocol == PROTOCOL_FITZQUAKE || protocol == PROTOCOL_RMQ) //still want to send baseline in PROTOCOL_NETQUAKE, so reset these values { if (state->modelindex & 0xFF00) bits |= B_LARGEMODEL; if (state->frame & 0xFF00) bits |= B_LARGEFRAME; if (state->alpha != ENTALPHA_DEFAULT) bits |= B_ALPHA; } if (idx>=0) { MSG_WriteByte (buf, bits?svc_spawnbaseline2:svc_spawnbaseline); MSG_WriteEntity (buf, idx, protocol_pext2); } else MSG_WriteByte (buf, bits?svc_spawnstatic2:svc_spawnstatic); if (bits) MSG_WriteByte (buf, bits); } if ((bits & B_LARGEMODEL) || protocol == PROTOCOL_VERSION_BJP3) MSG_WriteShort (buf, state->modelindex); else MSG_WriteByte (buf, state->modelindex); if (bits & B_LARGEFRAME) MSG_WriteShort (buf, state->frame); else MSG_WriteByte (buf, state->frame); MSG_WriteByte (buf, state->colormap); MSG_WriteByte (buf, state->skin); for (i=0 ; i<3 ; i++) { MSG_WriteCoord(buf, state->origin[i], protocolflags); MSG_WriteAngle(buf, state->angles[i], protocolflags); } if (bits & B_ALPHA) MSG_WriteByte (buf, state->alpha); } } static void SV_Pext_f(void); /* =============== SV_Protocol_f =============== */ static void SV_Protocol_f (void) { int i; const char *s; int prot, pext2; prot = sv_protocol; pext2 = sv_protocol_pext2; switch (Cmd_Argc()) { case 1: //"FTE+15" or "15", just to be explicit about it Con_Printf ("\"sv_protocol\" is \"%s%i\"\n", sv_protocol_pext2?"fte":"", sv_protocol); break; case 2: s = Cmd_Argv(1); if (!q_strncasecmp(s, "FTE", 3)) { s += 3; if (*s == '+' || *s == '-') s++; pext2 = PEXT2_SUPPORTED_SERVER; } else if (!q_strncasecmp(s, "+", 3)) { s += 1; pext2 = PEXT2_SUPPORTED_SERVER; } else if (!q_strncasecmp(s, "Base", 4)) { s+= 4; if (*s == '+' || *s == '-') s++; pext2 = 0; } else if (*s == '-') { s++; pext2 = 0; } i = strtol(s, (char**)&s, 0); if (*s == '-') pext2 = 0; else if (*s == '+') pext2 = PEXT2_SUPPORTED_SERVER; if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE && i != PROTOCOL_RMQ && i != PROTOCOL_VERSION_BJP3) Con_Printf ("sv_protocol must be %i or %i or %i or %i.\nProtocol may be prefixed with FTE+ or Base- to enable/disable FTE extensions.\n", PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ, PROTOCOL_VERSION_BJP3); else { sv_protocol = i; sv_protocol_pext2 = pext2; if (sv.active) { if (prot == sv_protocol && pext2 == sv_protocol_pext2) Con_Printf ("specified protocol already active.\n"); else Con_Printf ("changes will not take effect until the next level load.\n"); } } break; default: Con_SafePrintf ("usage: sv_protocol \n"); break; } } /* =============== SV_Init =============== */ void SV_Init (void) { int i; const char *p; extern cvar_t sv_maxvelocity; extern cvar_t sv_gravity; extern cvar_t sv_nostep; extern cvar_t sv_freezenonclients; extern cvar_t sv_gameplayfix_spawnbeforethinks; extern cvar_t sv_gameplayfix_setmodelrealbox; //spike: 1 to replicate a quakespasm bug, 0 for actual vanilla compat. extern cvar_t sv_friction; extern cvar_t sv_edgefriction; extern cvar_t sv_stopspeed; extern cvar_t sv_maxspeed; extern cvar_t sv_accelerate; extern cvar_t sv_idealpitchscale; extern cvar_t sv_aim; extern cvar_t sv_altnoclip; //johnfitz extern cvar_t sv_public; //spike extern cvar_t sv_reportheartbeats; //spike extern cvar_t com_protocolname; //spike extern cvar_t net_masters[]; //spike extern cvar_t rcon_password; //spike, proquake-compatible rcon extern cvar_t sv_sound_watersplash; //spike - making these changable is handy... extern cvar_t sv_sound_land; //spike - and also mutable... Cvar_RegisterVariable (&sv_maxvelocity); Cvar_RegisterVariable (&sv_gravity); Cvar_RegisterVariable (&sv_friction); Cvar_SetCallback (&sv_gravity, Host_Callback_Notify); Cvar_SetCallback (&sv_friction, Host_Callback_Notify); Cvar_RegisterVariable (&sv_edgefriction); Cvar_RegisterVariable (&sv_stopspeed); Cvar_RegisterVariable (&sv_maxspeed); Cvar_SetCallback (&sv_maxspeed, Host_Callback_Notify); Cvar_RegisterVariable (&sv_accelerate); Cvar_RegisterVariable (&sv_idealpitchscale); Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_freezenonclients); Cvar_RegisterVariable (&sv_gameplayfix_spawnbeforethinks); Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&sv_altnoclip); //johnfitz Cvar_RegisterVariable (&sv_sound_watersplash); //spike Cvar_RegisterVariable (&sv_sound_land); //spike if (isDedicated) sv_public.string = "1"; else sv_public.string = "0"; Cvar_RegisterVariable (&sv_public); Cvar_RegisterVariable (&sv_reportheartbeats); Cvar_RegisterVariable (&com_protocolname); for (i = 0; net_masters[i].name; i++) Cvar_RegisterVariable (&net_masters[i]); Cvar_RegisterVariable (&rcon_password); Cmd_AddCommand_ClientCommand("pext", SV_Pext_f); Cmd_AddCommand ("sv_protocol", &SV_Protocol_f); //johnfitz for (i=0 ; i MAX_DATAGRAM-16) return; MSG_WriteByte (&sv.datagram, svc_particle); MSG_WriteCoord (&sv.datagram, org[0], sv.protocolflags); MSG_WriteCoord (&sv.datagram, org[1], sv.protocolflags); MSG_WriteCoord (&sv.datagram, org[2], sv.protocolflags); for (i=0 ; i<3 ; i++) { v = dir[i]*16; if (v > 127) v = 127; else if (v < -128) v = -128; MSG_WriteChar (&sv.datagram, v); } MSG_WriteByte (&sv.datagram, count); MSG_WriteByte (&sv.datagram, color); } /* ================== 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 allready 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 (edict_t *entity, float *origin, int channel, const char *sample, int volume, float attenuation) { unsigned int sound_num, ent; int i, field_mask; int p; client_t *cl; if (volume < 0) Host_Error ("SV_StartSound: volume = %i", volume); else if (volume > 255) { volume = 255; Con_Printf ("SV_StartSound: volume = %i\n", volume); } if (attenuation < 0 || attenuation > 4) Host_Error ("SV_StartSound: attenuation = %f", attenuation); if (channel < 0 || channel > 255) Host_Error ("SV_StartSound: channel = %i", channel); else if (channel > 7) Con_DPrintf ("SV_StartSound: channel = %i\n", channel); if (sv.datagram.cursize > MAX_DATAGRAM-16) return; // find precache number for sound for (sound_num = 1; sound_num < MAX_SOUNDS && sv.sound_precache[sound_num]; sound_num++) { if (!strcmp(sample, sv.sound_precache[sound_num])) break; } if (sound_num == MAX_SOUNDS || !sv.sound_precache[sound_num]) { Con_Printf ("SV_StartSound: %s not precacheed\n", sample); return; } ent = NUM_FOR_EDICT(entity); field_mask = 0; if (volume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= SND_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) field_mask |= SND_ATTENUATION; //johnfitz -- PROTOCOL_FITZQUAKE if (ent >= 8192 || channel >= 8) field_mask |= SND_LARGEENTITY; if (sound_num >= 256) field_mask |= SND_LARGESOUND; //johnfitz for (p = 0; p < svs.maxclients; p++) { cl = &svs.clients[p]; if (!cl->active || !cl->spawned) continue; if (ent >= cl->limit_entities) continue; if (sound_num >= cl->limit_sounds) continue; if ((field_mask & (SND_LARGEENTITY|SND_LARGESOUND)) && (!cl->protocol_pext2 || sv.protocol == PROTOCOL_NETQUAKE)) continue; // directed messages go only to the entity the are targeted on MSG_WriteByte (&cl->datagram, svc_sound); MSG_WriteByte (&cl->datagram, field_mask); if (field_mask & SND_VOLUME) MSG_WriteByte (&cl->datagram, volume); if (field_mask & SND_ATTENUATION) MSG_WriteByte (&cl->datagram, attenuation*64); //johnfitz -- PROTOCOL_FITZQUAKE if (field_mask & SND_LARGEENTITY) { if ((cl->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) && ent > 0x7fff) { MSG_WriteShort(&cl->datagram, (ent>>8) | 0x8000); MSG_WriteByte(&cl->datagram, ent & 0xff); } else MSG_WriteShort (&cl->datagram, ent); MSG_WriteByte (&cl->datagram, channel); } else MSG_WriteShort (&cl->datagram, (ent<<3) | channel); if ((field_mask & SND_LARGESOUND) || sv.protocol == PROTOCOL_VERSION_BJP3) MSG_WriteShort (&cl->datagram, sound_num); else MSG_WriteByte (&cl->datagram, sound_num); //johnfitz for (i = 0; i < 3; i++) { if (origin) MSG_WriteCoord (&cl->datagram, origin[i], sv.protocolflags); else MSG_WriteCoord (&cl->datagram, entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]), sv.protocolflags); } } } /* ============================================================================== CLIENT SPAWNING ============================================================================== */ /* ================ SV_SendServerinfo Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each server load. ================ */ void SV_SendServerinfo (client_t *client) { const char **s; char message[2048]; unsigned int i; //johnfitz qboolean cantruncate; SV_VoiceInitClient(client); client->spawned = false; // need prespawn, spawn, etc //assume some safe defaults if we early out. client->limit_unreliable = 1024; client->limit_reliable = 8192; client->limit_entities = 0; client->limit_models = 0; client->limit_sounds = 0; if (!sv_protocol_pext2) { //server disabled pext completely, don't bother trying. //make sure we try reenabling it again on the next map though. mwahaha. client->pextknown = false; } else if (!client->pextknown) { MSG_WriteByte (&client->message, svc_stufftext); MSG_WriteString (&client->message, "cmd pext\n"); client->sendsignon = true; return; } client->protocol_pext2 &= sv_protocol_pext2; if (!(client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) client->protocol_pext2 &= ~PEXT2_PREDINFO; //stats can't be deltaed if there's no deltas, so just pretend its not supported on its own. client->limit_entities = (sv_protocol_pext2&&NET_QSocketGetProQuakeAngleHack(client->netconnection))?2048:600; //vanilla sucks. proquake supports more so assume we can use that limit if angles are also available (but only if we're allowing other non-vanilla extensions) client->limit_models = 256; //single byte client->limit_sounds = 256; //single byte //now we know their protocol, pick some real defaults if (sv.protocol != PROTOCOL_NETQUAKE || client->protocol_pext2) { client->limit_unreliable = DATAGRAM_MTU;//some safe ethernet limit. these clients should accept pretty much anything, but any routers will not. client->limit_reliable = MAX_DATAGRAM; //quite large, ip allows 16 bits client->limit_entities = qcvm->max_edicts; //we don't really know, 8k is probably a save guess but could be 32k, 65k, or even more... client->limit_models = MAX_MODELS; //not really sure, client's problem until >14bits client->limit_sounds = MAX_SOUNDS; //not really sure, client's problem until >14bits if (!Q_strcmp(NET_QSocketGetTrueAddressString(client->netconnection), "LOCAL")) { //override some other limits for localhost, because we can probably get away with it. //only do this if we're using extensions, so we don't break demos client->limit_unreliable = client->limit_reliable = MAX_DATAGRAM; } } if (client->limit_entities > 0x8000 && !(client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) client->limit_entities = 0x8000; //pext2 changes the encoding of entities to support 23 bits instead of dpp7's 15bits or vanilla's 16bits, but our writeentity is lazy. //unfortunately we can't split this up, so if its oversized, we'll just let the client complain instead of always kicking them client->message.maxsize = sizeof(client->msgbuf); if (client->message.maxsize > (int)client->limit_reliable) client->message.maxsize = client->limit_reliable; NET_QSocketSetMSS(client->netconnection, client->limit_unreliable); if (client->message.cursize) { //try and flush the reliable NOW, in case the qc is evil if (NET_CanSendMessage (host_client->netconnection)) { if (NET_SendMessage (host_client->netconnection, &host_client->message) != -1) { SZ_Clear (&host_client->message); host_client->last_message = realtime; } } } cantruncate = client->message.cursize == 0; retry: MSG_WriteByte (&client->message, svc_print); // sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version sprintf (message, "%c\n"ENGINE_NAME_AND_VER" Server (%i CRC)\n", 2, qcvm->crc); //spike -- quakespasm has moved on, and has its own server capabilities now. Advertising = good, right? MSG_WriteString (&client->message,message); // lack of serverinfo means any csqc info might as well be sent the lame dp way /* if (sv.csqc_progsize) { MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va("csqc_progname %s\n", sv.csqc_progname)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va("csqc_progsize %i\n", sv.csqc_progsize)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va("csqc_progcrc %i\n", sv.csqc_progcrc)); }*/ //let clients know that we support downloads //if (client->protocol_pext2 || sv.protocol == PROTOCOL_VERSION_DP7) { MSG_WriteByte (&client->message, svc_stufftext); MSG_WriteString (&client->message, "cl_serverextension_download 1\n"); } MSG_WriteByte (&client->message, svc_serverinfo); if (client->protocol_pext2) { //pext stuff takes the form of modifiers to an underlaying protocol MSG_WriteLong (&client->message, PROTOCOL_FTE_PEXT2); MSG_WriteLong (&client->message, client->protocol_pext2); //active extensions that the client needs to look out for } MSG_WriteLong (&client->message, sv.protocol); //johnfitz -- sv.protocol instead of PROTOCOL_VERSION if (sv.protocol == PROTOCOL_RMQ) { // mh - now send protocol flags so that the client knows the protocol features to expect MSG_WriteLong (&client->message, sv.protocolflags); } if (client->protocol_pext2 & PEXT2_PREDINFO) { //if multiple gamedirs were used, we should list all the active ones eg: "id1;hipnotic;rogue;quoth;mod". //fixme: engine-specific forced gamedirs like id1/ or qw/ or fte/ are redundant, so don't bother listing them //we don't really track that stuff, so I'm just going to report the last one MSG_WriteString(&client->message, COM_GetGameNames(false)); } MSG_WriteByte (&client->message, svs.maxclients); if (!coop.value && deathmatch.value) MSG_WriteByte (&client->message, GAME_DEATHMATCH); else MSG_WriteByte (&client->message, GAME_COOP); MSG_WriteString (&client->message, PR_GetString(qcvm->edicts->v.message)); //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 for (i=1,s = sv.model_precache+1 ; *s && i < client->limit_models; s++,i++) MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); for (i=1,s = sv.sound_precache+1 ; *s && i < client->limit_sounds; s++,i++) MSG_WriteString (&client->message, *s); MSG_WriteByte (&client->message, 0); //johnfitz // send music MSG_WriteByte (&client->message, svc_cdtrack); MSG_WriteByte (&client->message, qcvm->edicts->v.sounds); MSG_WriteByte (&client->message, qcvm->edicts->v.sounds); // set view MSG_WriteByte (&client->message, svc_setview); MSG_WriteShort (&client->message, NUM_FOR_EDICT(client->edict)); MSG_WriteByte (&client->message, svc_signonnum); MSG_WriteByte (&client->message, 1); client->sendsignon = true; SVFTE_SetupFrames(client); if (client->message.overflowed && client->limit_sounds > 64 && cantruncate) { if (client->limit_models > client->limit_sounds) client->limit_models /= 2; else client->limit_sounds /= 2; SZ_Clear(&client->message); Con_Printf("Serverinfo too large for %s, truncating.\n", NET_QSocketGetTrueAddressString(client->netconnection)); goto retry; } //try and flush the reliable NOW, in case the qc is evil if (NET_CanSendMessage (client->netconnection)) { if (NET_SendMessage (client->netconnection, &client->message) != -1) { SZ_Clear (&client->message); client->last_message = realtime; client->sendsignon = false; } } //protocol 15 is too limited. let people know that they'll get a crappy experience. if (client->limit_entities <= (unsigned)qcvm->num_edicts) { Con_Warning("Protocol limitation (entities) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); MSG_WriteByte (&client->message, svc_print); MSG_WriteByte (&client->message, 2); MSG_WriteString (&client->message, "WARNING: "); MSG_WriteByte (&client->message, svc_print); MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to see all entities\n"); } if (client->limit_models < MAX_MODELS && sv.model_precache[client->limit_models]) { Con_Warning("Protocol limitation (models) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); MSG_WriteByte (&client->message, svc_print); MSG_WriteByte (&client->message, 2); MSG_WriteString (&client->message, "WARNING: "); MSG_WriteByte (&client->message, svc_print); MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to see all models\n"); } if (client->limit_sounds < MAX_SOUNDS && sv.sound_precache[client->limit_sounds]) { Con_Warning("Protocol limitation (sounds) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); MSG_WriteByte (&client->message, svc_print); MSG_WriteByte (&client->message, 2); MSG_WriteString (&client->message, "WARNING: "); MSG_WriteByte (&client->message, svc_print); MSG_WriteString (&client->message, "The protocol in use is too limited. You will not be able to hear all sounds\n"); } } void SV_Pext_f(void) { //this only makes sense on the server. the clientside part only takes the form of 'cmd pext', for compat with clients that don't support this. if (cmd_source == src_command) { if (!cls.state) { Con_Printf ("Not connected\n"); return; } Con_Printf ("Current Protocols:\n"); if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) Con_Printf (" Replacement Entity Deltas\n"); if (cl.protocol_pext2 & PEXT2_PREDINFO) Con_Printf (" Replacement Stats ('predinfo')\n"); if (cl.protocol == PROTOCOL_NETQUAKE) Con_Printf (" vanilla(15)\n"); else if (cl.protocol == PROTOCOL_FITZQUAKE) Con_Printf (" fitzquake(666)\n"); else if (cl.protocol == PROTOCOL_RMQ) Con_Printf (" rmq(999)\n"); else Con_Printf (" unknown protocol(%i)\n", cl.protocol); return; } if (!host_client->pextknown && !host_client->spawned) { int i; int key; int value; for (i = 1; i < Cmd_Argc(); i+=2) { key = strtoul(Cmd_Argv(i), NULL, 0); value = strtoul(Cmd_Argv(i+1), NULL, 0); if (key == PROTOCOL_FTE_PEXT2) host_client->protocol_pext2 = value & PEXT2_SUPPORTED_SERVER; //else some other extension that we don't know } host_client->pextknown = true; SV_SendServerinfo(host_client); } } /* ================ SV_ConnectClient Initializes a client_t for a new net connection. This will only be called once for a player each game, not once for each level change. ================ */ void SV_ConnectClient (int clientnum) { edict_t *ent; client_t *client; int edictnum; struct qsocket_s *netconnection; int i; float spawn_parms[NUM_TOTAL_SPAWN_PARMS]; client = svs.clients + clientnum; if (client->netconnection) Con_DPrintf ("Client %s connected\n", NET_QSocketGetTrueAddressString(client->netconnection)); else Con_DPrintf ("Bot connected\n"); edictnum = clientnum+1; ent = EDICT_NUM(edictnum); // set up the client_t netconnection = client->netconnection; net_activeconnections++; if (sv.loadgame) memcpy (spawn_parms, client->spawn_parms, sizeof(spawn_parms)); memset (client, 0, sizeof(*client)); client->netconnection = netconnection; strcpy (client->name, "unconnected"); client->active = true; client->spawned = false; client->edict = ent; client->message.data = client->msgbuf; client->message.maxsize = sizeof(client->msgbuf); client->message.allowoverflow = true; // we can catch it client->datagram.data = client->datagram_buf; client->datagram.maxsize = sizeof(client->datagram_buf); client->datagram.allowoverflow = true; //simply ignored on overflow client->pextknown = false; client->protocol_pext2 = 0; if (sv.loadgame) memcpy (client->spawn_parms, spawn_parms, sizeof(spawn_parms)); else { // call the progs to get default spawn parms for the new client PR_ExecuteProgram (pr_global_struct->SetNewParms); for (i=0 ; ispawn_parms[i] = (&pr_global_struct->parm1)[i]; for ( ; i< NUM_TOTAL_SPAWN_PARMS ; i++) { ddef_t *g = ED_FindGlobal(va("parm%i", i+1)); client->spawn_parms[i] = g?qcvm->globals[g->ofs]:0; } } SV_SendServerinfo (client); } /* =================== SV_CheckForNewClients =================== */ void SV_CheckForNewClients (void) { struct qsocket_s *ret; int i; // // check for new connections // while (1) { ret = NET_CheckNewConnections (); if (!ret) break; // // init a new client structure // for (i=0 ; icontents < 0) { if (node->contents != CONTENTS_SOLID) { pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter for (i=0 ; iplane; d = DotProduct (org, plane->normal) - plane->dist; if (d > 8) node = node->children[0]; else if (d < -8) node = node->children[1]; else { // go down both SV_AddToFatPVS (org, node->children[0], worldmodel); //johnfitz -- worldmodel as a parameter node = node->children[1]; } } } /* ============= SV_FatPVS Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the given point. ============= */ byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter { fatbytes = (worldmodel->numleafs+7)>>3; // ericw -- was +31, assumed to be a bug/typo if (fatpvs == NULL || fatbytes > fatpvs_capacity) { fatpvs_capacity = fatbytes; fatpvs = (byte *) realloc (fatpvs, fatpvs_capacity); if (!fatpvs) Sys_Error ("SV_FatPVS: realloc() failed on %d bytes", fatpvs_capacity); } Q_memset (fatpvs, 0, fatbytes); SV_AddToFatPVS (org, worldmodel->nodes, worldmodel); //johnfitz -- worldmodel as a parameter return fatpvs; } /* ============= SV_VisibleToClient -- johnfitz PVS test encapsulated in a nice function ============= */ qboolean SV_VisibleToClient (edict_t *client, edict_t *test, qmodel_t *worldmodel) { byte *pvs; vec3_t org; unsigned int i; VectorAdd (client->v.origin, client->v.view_ofs, org); pvs = SV_FatPVS (org, worldmodel); for (i=0 ; i < test->num_leafs ; i++) if (pvs[test->leafnums[i] >> 3] & (1 << (test->leafnums[i]&7) )) return true; return false; } //============================================================================= /* ============= SV_WriteEntitiesToClient ============= */ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg) { edict_t *clent = client->edict; unsigned int e, i, maxedict=qcvm->num_edicts; int bits; byte *pvs; vec3_t org; float miss; edict_t *ent; eval_t *val; int maxsize = msg->maxsize; //try to avoid sounds getting lost. flickering entities are weird, but missing sounds+particles are just eerie. maxsize -= client->datagram.cursize; maxsize -= sv.datagram.cursize; if (maxedict > client->limit_entities) maxedict = client->limit_entities; // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); pvs = SV_FatPVS (org, qcvm->worldmodel); // send over all entities (excpet the client) that touch the pvs ent = NEXT_EDICT(qcvm->edicts); for (e=1 ; ev.modelindex || !PR_GetString(ent->v.model)[0]) continue; //johnfitz -- don't send model>255 entities if protocol is 15 if ((unsigned int)ent->v.modelindex >= client->limit_models) continue; // ignore if not touching a PV leaf for (i=0 ; i < ent->num_leafs ; i++) if (pvs[ent->leafnums[i] >> 3] & (1 << (ent->leafnums[i]&7) )) break; // ericw -- added ent->num_leafs < MAX_ENT_LEAFS condition. // // if ent->num_leafs == MAX_ENT_LEAFS, the ent is visible from too many leafs // for us to say whether it's in the PVS, so don't try to vis cull it. // this commonly happens with rotators, because they often have huge bboxes // spanning the entire map, or really tall lifts, etc. if (i == ent->num_leafs && ent->num_leafs < MAX_ENT_LEAFS) continue; // not visible } //johnfitz -- max size for protocol 15 is 18 bytes, not 16 as originally //assumed here. And, for protocol 85 the max size is actually 24 bytes. if (msg->cursize + 24 > maxsize) { //johnfitz -- less spammy overflow message if (!dev_overflows.packetsize || dev_overflows.packetsize + CONSOLE_RESPAM_TIME < realtime ) { Con_Printf ("Packet overflow!\n"); dev_overflows.packetsize = realtime; } goto stats; //johnfitz } // send an update bits = 0; for (i=0 ; i<3 ; i++) { miss = ent->v.origin[i] - ent->baseline.origin[i]; if ( miss < -0.1 || miss > 0.1 ) bits |= U_ORIGIN1<v.angles[0] != ent->baseline.angles[0] ) bits |= U_ANGLE1; if ( ent->v.angles[1] != ent->baseline.angles[1] ) bits |= U_ANGLE2; if ( ent->v.angles[2] != ent->baseline.angles[2] ) bits |= U_ANGLE3; if (ent->v.movetype == MOVETYPE_STEP) bits |= U_STEP; // don't mess up the step animation if (ent->baseline.colormap != ent->v.colormap) bits |= U_COLORMAP; if (ent->baseline.skin != ent->v.skin) bits |= U_SKIN; if (ent->baseline.frame != ent->v.frame) bits |= U_FRAME; if (ent->baseline.effects != ent->v.effects) bits |= U_EFFECTS; if (ent->baseline.modelindex != ent->v.modelindex) bits |= U_MODEL; //johnfitz -- alpha // TODO: find a cleaner place to put this code val = GetEdictFieldValue(ent, qcvm->extfields.alpha); if (val) ent->alpha = ENTALPHA_ENCODE(val->_float); //don't send invisible entities unless they have effects if (ent->alpha == ENTALPHA_ZERO && !ent->v.effects) continue; //johnfitz //spike -- PROTOCOL_VERSION_BJP3 if (sv.protocol == PROTOCOL_VERSION_BJP3) { //alpha+fullbright can be sent, but they're too hideous... if (ent->baseline.alpha != ent->alpha) bits |= U_TRANS; } else //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { if (ent->baseline.alpha != ent->alpha) bits |= U_ALPHA; if (bits & U_FRAME && (int)ent->v.frame & 0xFF00) bits |= U_FRAME2; if (bits & U_MODEL && (int)ent->v.modelindex & 0xFF00) bits |= U_MODEL2; if (ent->sendinterval) bits |= U_LERPFINISH; if (bits >= 65536) bits |= U_EXTEND1; if (bits >= 16777216) bits |= U_EXTEND2; } //johnfitz if (e >= 256) bits |= U_LONGENTITY; if (bits >= 256) bits |= U_MOREBITS; // // write the message // MSG_WriteByte (msg, bits | U_SIGNAL); if (bits & U_MOREBITS) MSG_WriteByte (msg, bits>>8); //spike -- nehahra protocols are awkward if (sv.protocol == PROTOCOL_VERSION_BJP3) ; else { //johnfitz -- PROTOCOL_FITZQUAKE if (bits & U_EXTEND1) MSG_WriteByte(msg, bits>>16); if (bits & U_EXTEND2) MSG_WriteByte(msg, bits>>24); //johnfitz } if (bits & U_LONGENTITY) MSG_WriteShort (msg,e); else MSG_WriteByte (msg,e); if (bits & U_MODEL) { if (sv.protocol == PROTOCOL_VERSION_BJP3) MSG_WriteShort(msg, ent->v.modelindex); else MSG_WriteByte (msg, ent->v.modelindex); } if (bits & U_FRAME) MSG_WriteByte (msg, ent->v.frame); if (bits & U_COLORMAP) MSG_WriteByte (msg, ent->v.colormap); if (bits & U_SKIN) MSG_WriteByte (msg, ent->v.skin); if (bits & U_EFFECTS) MSG_WriteByte (msg, ent->v.effects); if (bits & U_ORIGIN1) MSG_WriteCoord (msg, ent->v.origin[0], sv.protocolflags); if (bits & U_ANGLE1) MSG_WriteAngle(msg, ent->v.angles[0], sv.protocolflags); if (bits & U_ORIGIN2) MSG_WriteCoord (msg, ent->v.origin[1], sv.protocolflags); if (bits & U_ANGLE2) MSG_WriteAngle(msg, ent->v.angles[1], sv.protocolflags); if (bits & U_ORIGIN3) MSG_WriteCoord (msg, ent->v.origin[2], sv.protocolflags); if (bits & U_ANGLE3) MSG_WriteAngle(msg, ent->v.angles[2], sv.protocolflags); //spike -- nehahra protocols are awkward if (sv.protocol == PROTOCOL_VERSION_BJP3) { //this protocol is shite if ((int)ent->v.effects & EF_FULLBRIGHT) { MSG_WriteFloat(msg, 2); MSG_WriteFloat(msg, ENTALPHA_DECODE(ent->alpha)); MSG_WriteFloat(msg, 1); } else if (bits & U_TRANS) { MSG_WriteFloat(msg, 1); MSG_WriteFloat(msg, ENTALPHA_DECODE(ent->alpha)); } } else { //johnfitz -- PROTOCOL_FITZQUAKE if (bits & U_ALPHA) MSG_WriteByte(msg, ent->alpha); if (bits & U_FRAME2) MSG_WriteByte(msg, (int)ent->v.frame >> 8); if (bits & U_MODEL2) MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); if (bits & U_LERPFINISH) MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-qcvm->time)*255))); //johnfitz } } //johnfitz -- devstats stats: if (msg->cursize > 1024 && dev_peakstats.packetsize <= 1024) Con_DWarning ("%i byte packet exceeds standard limit of 1024 (max = %d).\n", msg->cursize, msg->maxsize); dev_stats.packetsize = msg->cursize; dev_peakstats.packetsize = q_max(msg->cursize, dev_peakstats.packetsize); //johnfitz } /* ============= SV_CleanupEnts ============= */ void SV_CleanupEnts (void) { int e; edict_t *ent; ent = NEXT_EDICT(qcvm->edicts); for (e=1 ; enum_edicts ; e++, ent = NEXT_EDICT(ent)) { ent->v.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH; } } /* ================== SV_WriteDamageToMessage ================== */ void SV_WriteDamageToMessage(edict_t *ent, sizebuf_t *msg) { edict_t *other; int i; // // send a damage message // if (ent->v.dmg_take || ent->v.dmg_save) { other = PROG_TO_EDICT(ent->v.dmg_inflictor); 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]), sv.protocolflags ); ent->v.dmg_take = 0; ent->v.dmg_save = 0; } // // send the current viewpos offset from the view entity // SV_SetIdealPitch (); // how much to look up / down ideally // a fixangle might get lost in a dropped packet. Oh well. if ( ent->v.fixangle ) { MSG_WriteByte (msg, svc_setangle); for (i=0 ; i < 3 ; i++) MSG_WriteAngle (msg, ent->v.angles[i], sv.protocolflags ); ent->v.fixangle = 0; } } /* ================== SV_WriteClientdataToMessage ================== */ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) { edict_t *ent = client->edict; int bits; int i; int items; eval_t *val; unsigned int weaponmodelindex = SV_ModelIndex(PR_GetString(ent->v.weaponmodel)); if (weaponmodelindex >= client->limit_models) weaponmodelindex = 0; 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, qcvm->extfields.items2); 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<v.weaponframe) bits |= SU_WEAPONFRAME; if (ent->v.armorvalue) bits |= SU_ARMOR; // if (ent->v.weapon) bits |= SU_WEAPON; //johnfitz -- PROTOCOL_FITZQUAKE if (sv.protocol != PROTOCOL_NETQUAKE) { if (bits & SU_WEAPON && weaponmodelindex & 0xFF00) bits |= SU_WEAPON2; if ((int)ent->v.armorvalue & 0xFF00) bits |= SU_ARMOR2; if ((int)ent->v.currentammo & 0xFF00) bits |= SU_AMMO2; if ((int)ent->v.ammo_shells & 0xFF00) bits |= SU_SHELLS2; if ((int)ent->v.ammo_nails & 0xFF00) bits |= SU_NAILS2; if ((int)ent->v.ammo_rockets & 0xFF00) bits |= SU_ROCKETS2; if ((int)ent->v.ammo_cells & 0xFF00) bits |= SU_CELLS2; if (bits & SU_WEAPONFRAME && (int)ent->v.weaponframe & 0xFF00) bits |= SU_WEAPONFRAME2; if (bits & SU_WEAPON && ent->alpha != ENTALPHA_DEFAULT) bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha if (bits >= 65536) bits |= SU_EXTEND1; if (bits >= 16777216) bits |= SU_EXTEND2; } //johnfitz // send the data MSG_WriteByte (msg, svc_clientdata); MSG_WriteShort (msg, bits); //johnfitz -- PROTOCOL_FITZQUAKE if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits>>16); if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits>>24); //johnfitz 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<v.velocity[i]/16); } // [always sent] if (bits & SU_ITEMS) MSG_WriteLong (msg, items); if (bits & SU_WEAPONFRAME) MSG_WriteByte (msg, ent->v.weaponframe); if (bits & SU_ARMOR) MSG_WriteByte (msg, ent->v.armorvalue); if (bits & SU_WEAPON) { if (sv.protocol == PROTOCOL_VERSION_BJP3) MSG_WriteShort (msg, weaponmodelindex); else MSG_WriteByte (msg, weaponmodelindex); } 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 & SU_ARMOR2) MSG_WriteByte (msg, (int)ent->v.armorvalue >> 8); if (bits & SU_AMMO2) MSG_WriteByte (msg, (int)ent->v.currentammo >> 8); if (bits & SU_SHELLS2) MSG_WriteByte (msg, (int)ent->v.ammo_shells >> 8); if (bits & SU_NAILS2) MSG_WriteByte (msg, (int)ent->v.ammo_nails >> 8); if (bits & SU_ROCKETS2) MSG_WriteByte (msg, (int)ent->v.ammo_rockets >> 8); if (bits & SU_CELLS2) MSG_WriteByte (msg, (int)ent->v.ammo_cells >> 8); if (bits & SU_WEAPONFRAME2) MSG_WriteByte (msg, (int)ent->v.weaponframe >> 8); if (bits & SU_WEAPONALPHA) MSG_WriteByte (msg, ent->alpha); //for now, weaponalpha = client entity alpha //johnfitz } /* ======================= SV_SendClientDatagram ======================= */ qboolean SV_SendClientDatagram (client_t *client) { byte buf[MAX_DATAGRAM+1000]; sizebuf_t msg; if (!client->netconnection) { //botclient, shouldn't be sent anything. SZ_Clear(&client->datagram); return true; } msg.allowoverflow = false; msg.data = buf; msg.maxsize = client->limit_unreliable; msg.cursize = 0; if (client->download.file) msg.maxsize /= 2; //make sure there's space for download data host_client = client; if (client->spawned) { sv_player = client->edict; if (client->protocol_pext2 & PEXT2_REPLACEMENTDELTAS) { SV_WriteDamageToMessage(client->edict, &msg); if (!(client->protocol_pext2 & PEXT2_PREDINFO)) SV_WriteClientdataToMessage (client, &msg); else SVFTE_WriteStats(client, &msg); if (!client->snapshotresume) { SVFTE_BuildSnapshotForClient(client); SVFTE_CalcEntityDeltas(client); } SVFTE_WriteEntitiesToClient(client, &msg, sizeof(buf)); //must always write some data, or the stats will break //this delta protocol doesn't wipe old state just because there's a new packet. //the server isn't required to sync with the client frames either //so we can just spam multiple packets to keep our udp data under the MTU while (client->snapshotresume) { NET_SendUnreliableMessage (client->netconnection, &msg); SZ_Clear(&msg); SVFTE_WriteEntitiesToClient(client, &msg, sizeof(buf)); } } else { MSG_WriteByte (&msg, svc_time); MSG_WriteFloat (&msg, qcvm->time); if (client->protocol_pext2 & PEXT2_PREDINFO) MSG_WriteShort(&msg, (client->lastmovemessage&0xffff)); // add the client specific data to the datagram SV_WriteDamageToMessage (client->edict, &msg); SV_WriteClientdataToMessage (client, &msg); SV_WriteEntitiesToClient (client, &msg); } // copy the private datagram if there is space if (msg.cursize + client->datagram.cursize < msg.maxsize && !client->datagram.overflowed) SZ_Write(&msg, client->datagram.data, client->datagram.cursize); client->datagram.overflowed = false; SZ_Clear(&client->datagram); // copy the server datagram if there is space if (msg.cursize + sv.datagram.cursize < msg.maxsize) SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize); } SV_VoiceSendPacket(client, &msg); msg.maxsize = client->limit_unreliable; Host_AppendDownloadData(client, &msg); // send the datagram if (msg.cursize && NET_SendUnreliableMessage (client->netconnection, &msg) == -1) { SV_DropClient (false);// if the message couldn't send, kick off return false; } return true; } /* ======================= SV_UpdateToReliableMessages ======================= */ void SV_UpdateToReliableMessages (void) { int i, j; client_t *client; // check for changes to be sent over the reliable streams for (i=0, host_client = svs.clients ; iold_frags != host_client->edict->v.frags) { for (j=0, client = svs.clients ; jknowntoqc) continue; MSG_WriteByte (&client->message, svc_updatefrags); MSG_WriteByte (&client->message, i); MSG_WriteShort (&client->message, host_client->edict->v.frags); } host_client->old_frags = host_client->edict->v.frags; } } for (j=0, client = svs.clients ; jactive) continue; SZ_Write (&client->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); } SZ_Clear (&sv.reliable_datagram); } /* ======================= SV_SendNop Send a nop message without trashing or sending the accumulated client message buffer ======================= */ void SV_SendNop (client_t *client) { sizebuf_t msg; byte buf[4]; msg.data = buf; msg.maxsize = sizeof(buf); msg.cursize = 0; MSG_WriteChar (&msg, svc_nop); if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) SV_DropClient (false); // if the message couldn't send, kick off client->last_message = realtime; } int SV_SendPrespawnParticlePrecaches(int idx) { size_t maxsize = host_client->message.maxsize; //we can go quite large if (!host_client->protocol_pext2) return -1; //unsupported by this client. for (; idx < MAX_PARTICLETYPES; idx++) { if (!sv.particle_precache[idx]) continue; if (host_client->message.cursize + 4+strlen(sv.particle_precache[idx]) > maxsize) break; MSG_WriteByte(&host_client->message, svcdp_precache); MSG_WriteShort(&host_client->message, 0x4000 | idx); MSG_WriteString(&host_client->message, sv.particle_precache[idx]); } if (idx == MAX_PARTICLETYPES) return -1; return idx; } int SV_SendPrespawnStatics(int idx) { entity_state_t *svent; int maxsize = host_client->message.maxsize - 128; //we can go quite large while (1) { if (idx >= sv.num_statics) return -1; svent = &sv.static_entities[idx]; if (host_client->message.cursize > maxsize) break; idx++; if (svent->modelindex >= host_client->limit_models) continue; if (memcmp(&nullentitystate, svent, sizeof(nullentitystate))) MSG_WriteStaticOrBaseLine(&host_client->message, -1, svent, host_client->protocol_pext2, sv.protocol, sv.protocolflags); } return idx; } int SV_SendAmbientSounds(int idx) { struct ambientsound_s *snd; int maxsize = host_client->message.maxsize - 128; //we can go quite large qboolean large; size_t i; while (1) { if (idx >= sv.num_ambients) return -1; snd = &sv.ambientsounds[idx]; if (host_client->message.cursize > maxsize) break; idx++; if (snd->soundindex >= host_client->limit_sounds) continue; large = (snd->soundindex > 255); if (large) MSG_WriteByte (&host_client->message,svc_spawnstaticsound2); //johnfitz -- PROTOCOL_FITZQUAKE else MSG_WriteByte (&host_client->message,svc_spawnstaticsound); for (i = 0; i < 3; i++) MSG_WriteCoord(&host_client->message, snd->origin[i], sv.protocolflags); if (large) MSG_WriteShort(&host_client->message, snd->soundindex); else MSG_WriteByte (&host_client->message, snd->soundindex); MSG_WriteByte (&host_client->message, snd->volume*255); MSG_WriteByte (&host_client->message, snd->attenuation*64); } return idx; } int SV_SendPrespawnBaselines(int idx) { edict_t *svent; int maxsize = host_client->message.maxsize - 128; //we can go quite large while (1) { if (idx >= qcvm->num_edicts) return -1; svent = EDICT_NUM(idx); if (host_client->message.cursize > maxsize) break; if (memcmp(&nullentitystate, &svent->baseline, sizeof(nullentitystate))) MSG_WriteStaticOrBaseLine(&host_client->message, idx, &svent->baseline, host_client->protocol_pext2, sv.protocol, sv.protocolflags); idx++; } return idx; } /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages (void) { int i; // update frags, names, etc SV_UpdateToReliableMessages (); // build individual updates for (i=0, host_client = svs.clients ; iactive) continue; if (!SV_SendClientDatagram (host_client)) continue; if (!host_client->spawned) { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed // send a full message when the next signon stage has been requested // some other message data (name changes, etc) may accumulate // between signon stages if (!host_client->sendsignon) { if (realtime - host_client->last_message > 5) SV_SendNop (host_client); continue; // don't send out non-signon messages } if (host_client->sendsignon == 2) { host_client->signonidx = SV_SendPrespawnParticlePrecaches(host_client->signonidx); if (host_client->signonidx < 0) { host_client->signonidx = 0; host_client->sendsignon++; } } if (host_client->sendsignon == 3) { host_client->signonidx = SV_SendPrespawnBaselines(host_client->signonidx); if (host_client->signonidx < 0) { host_client->signonidx = 0; host_client->sendsignon++; } } if (host_client->sendsignon == 4) { host_client->signonidx = SV_SendPrespawnStatics(host_client->signonidx); if (host_client->signonidx < 0) { host_client->signonidx = 0; host_client->sendsignon++; } } if (host_client->sendsignon == 5) { host_client->signonidx = SV_SendAmbientSounds(host_client->signonidx); if (host_client->signonidx < 0) { host_client->signonidx = 0; host_client->sendsignon++; } } if (host_client->sendsignon == 6) { if (host_client->message.cursize+sv.signon.cursize+2 < host_client->message.maxsize) { SZ_Write (&host_client->message, sv.signon.data, sv.signon.cursize); MSG_WriteByte (&host_client->message, svc_signonnum); MSG_WriteByte (&host_client->message, 2); host_client->sendsignon = true; } } } // check for an overflowed message. Should only happen // on a very fucked up connection that backs up a lot, then // changes level if (host_client->message.overflowed) { SZ_Clear(&host_client->message); SV_DropClient (false); continue; } if (host_client->message.cursize || host_client->dropasap) { if (!NET_CanSendMessage (host_client->netconnection)) { // I_Printf ("can't write\n"); continue; } if (host_client->dropasap) SV_DropClient (false); // went to another level else { if (NET_SendMessage (host_client->netconnection , &host_client->message) == -1) SV_DropClient (false); // if the message couldn't send, kick off SZ_Clear (&host_client->message); host_client->last_message = realtime; if (host_client->sendsignon == true) host_client->sendsignon = false; } } } // clear muzzle flashes SV_CleanupEnts (); } /* ============================================================================== SERVER SPAWNING ============================================================================== */ /* ================ SV_ModelIndex ================ */ int SV_ModelIndex (const char *name) { int i; if (!name || !name[0]) return 0; for (i=0 ; inum_edicts; entnum++) { // get the current server version svent = EDICT_NUM(entnum); if (svent->free) continue; if (entnum > svs.maxclients && !svent->v.modelindex) continue; // // create entity baseline // VectorCopy (svent->v.origin, svent->baseline.origin); VectorCopy (svent->v.angles, svent->baseline.angles); svent->baseline.frame = svent->v.frame; svent->baseline.skin = svent->v.skin; if (entnum > 0 && entnum <= svs.maxclients) { svent->baseline.colormap = entnum; svent->baseline.modelindex = SV_ModelIndex("progs/player.mdl"); svent->baseline.alpha = ENTALPHA_DEFAULT; //johnfitz -- alpha support } else { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); val = GetEdictFieldValue(svent, qcvm->extfields.alpha); if (val) svent->baseline.alpha = ENTALPHA_ENCODE(val->_float); else svent->baseline.alpha = svent->alpha; //johnfitz -- alpha support } //Spike -- baselines are now generated on a per-client basis. //FIXME: should merge the above with other edict->entity_state copies (updates, baselines, spawnstatics) //1) this allows per-client extensions. //2) this avoids pre-generating a single signon buffer, splitting it over multiple packets. // thereby allowing more than 3k or so entities } } /* ================ SV_SendReconnect Tell all the clients that the server is changing levels ================ */ void SV_SendReconnect (void) { byte data[128]; sizebuf_t msg; msg.data = data; msg.cursize = 0; msg.maxsize = sizeof(data); MSG_WriteChar (&msg, svc_stufftext); MSG_WriteString (&msg, "reconnect\n"); NET_SendToAll (&msg, 5.0); if (!isDedicated) Cmd_ExecuteString ("reconnect\n", src_server); } /* ================ SV_SaveSpawnparms Grabs the current state of each client for saving across the transition to another level ================ */ void SV_SaveSpawnparms (void) { int i, j; svs.serverflags = pr_global_struct->serverflags; for (i=0, host_client = svs.clients ; iactive) continue; // call the progs to get default spawn parms for the new client pr_global_struct->self = EDICT_TO_PROG(host_client->edict); PR_ExecuteProgram (pr_global_struct->SetChangeParms); for (j=0 ; jspawn_parms[j] = (&pr_global_struct->parm1)[j]; for ( ; j< NUM_TOTAL_SPAWN_PARMS ; j++) { ddef_t *g = ED_FindGlobal(va("parm%i", j+1)); host_client->spawn_parms[j] = g?qcvm->globals[g->ofs]:0; } } } //used for sv.qcvm.GetModel (so ssqc+csqc can share builtins) qmodel_t *SV_ModelForIndex(int index) { if (index < 0 || index >= MAX_MODELS) return NULL; return sv.models[index]; } /* ================ SV_SpawnServer This is called at the start of each level ================ */ extern float scr_centertime_off; void SV_SpawnServer (const char *server) { static char dummy[8] = { 0,0,0,0,0,0,0,0 }; edict_t *ent; int i; // let's not have any servers with no name if (hostname.string[0] == 0) Cvar_Set ("hostname", "UNNAMED"); scr_centertime_off = 0; Con_DPrintf ("SpawnServer: %s\n",server); svs.changelevel_issued = false; // now safe to issue another // // tell all connected clients that we are going to a new level // if (sv.active) { PR_SwitchQCVM(NULL); SV_SendReconnect (); PR_SwitchQCVM(&sv.qcvm); } // // make cvars consistant // if (coop.value) Cvar_Set ("deathmatch", "0"); current_skill = (int)(skill.value + 0.5); if (current_skill < 0) current_skill = 0; if (current_skill > 3) current_skill = 3; Cvar_SetValue ("skill", (float)current_skill); // // set up the new server // //memset (&sv, 0, sizeof(sv)); Host_ClearMemory (); q_strlcpy (sv.name, server, sizeof(sv.name)); sv.protocol = sv_protocol; // johnfitz if (sv.protocol == PROTOCOL_RMQ) { // set up the protocol flags used by this server // (note - these could be cvar-ised so that server admins could choose the protocol features used by their servers) if (sv_protocol_pext2) //spike: I don't really want to step on anyone's toes, but floats have the exact same precision as qc does. sv.protocolflags = PRFL_FLOATCOORD | PRFL_SHORTANGLE; else //spike: purists might want to preserve the inprecision and just extend the range though. This matches vanilla QS. should compress a bit better too. sv.protocolflags = PRFL_INT32COORD | PRFL_SHORTANGLE; } else sv.protocolflags = 0; // load progs to get entity field count PR_LoadProgs ("progs.dat", true, PROGHEADER_CRC, pr_ssqcbuiltins, pr_ssqcnumbuiltins); // allocate server memory /* Host_ClearMemory() called above already cleared the whole sv structure */ qcvm->max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar qcvm->edicts = (edict_t *) malloc (qcvm->max_edicts*qcvm->edict_size); // ericw -- sv.edicts switched to use malloc() sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.cursize = 0; sv.datagram.data = sv.datagram_buf; sv.multicast.maxsize = sizeof(sv.multicast_buf); sv.multicast.cursize = 0; sv.multicast.data = sv.multicast_buf; sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); sv.reliable_datagram.cursize = 0; sv.reliable_datagram.data = sv.reliable_datagram_buf; sv.signon.maxsize = sizeof(sv.signon_buf); sv.signon.cursize = 0; sv.signon.data = sv.signon_buf; // leave slots at start for clients only qcvm->num_edicts = qcvm->reserved_edicts = svs.maxclients+1; memset(qcvm->edicts, 0, qcvm->num_edicts*qcvm->edict_size); // ericw -- sv.edicts switched to use malloc() for (i=0 ; itime = 1.0; q_strlcpy (sv.name, server, sizeof(sv.name)); q_snprintf (sv.modelname, sizeof(sv.modelname), "maps/%s.bsp", server); qcvm->worldmodel = Mod_ForName (sv.modelname, false); if (!qcvm->worldmodel || qcvm->worldmodel->type != mod_brush) { Con_Printf ("Couldn't spawn server %s\n", sv.modelname); sv.active = false; return; } sv.models[1] = qcvm->worldmodel; qcvm->GetModel = SV_ModelForIndex; // // clear world interaction links // SV_ClearWorld (); sv.sound_precache[0] = dummy; sv.model_precache[0] = dummy; sv.model_precache[1] = sv.modelname; if (qcvm->worldmodel->numsubmodels > MAX_MODELS) { Con_Printf ("too many inline models %s\n", sv.modelname); sv.active = false; return; } for (i=1 ; iworldmodel->numsubmodels ; i++) { sv.model_precache[1+i] = localmodels[i]; sv.models[i+1] = Mod_ForName (localmodels[i], false); } // // load the rest of the entities // ent = EDICT_NUM(0); memset (&ent->v, 0, qcvm->progs->entityfields * 4); ent->free = false; ent->v.model = PR_SetEngineString(qcvm->worldmodel->name); ent->v.modelindex = 1; // world model ent->v.solid = SOLID_BSP; ent->v.movetype = MOVETYPE_PUSH; if (coop.value) pr_global_struct->coop = coop.value; else pr_global_struct->deathmatch = deathmatch.value; pr_global_struct->mapname = PR_SetEngineString(sv.name); // serverflags are for cross level information (sigils) pr_global_struct->serverflags = svs.serverflags; ED_LoadFromFile (qcvm->worldmodel->entities); sv.active = true; SV_Precache_Model("progs/player.mdl"); //Spike -- SV_CreateBaseline depends on this model. // all setup is completed, any further precache statements are errors sv.state = ss_active; // run two frames to allow everything to settle host_frametime = 0.1; SV_Physics (); SV_Physics (); // create a baseline for more efficient communications SV_CreateBaseline (); //johnfitz -- warn if signon buffer larger than standard server can handle if (sv.signon.cursize > 8000-2) //max size that will fit into 8000-sized client->message buffer with 2 extra bytes on the end Con_DWarning ("%i byte signon buffer exceeds standard limit of 7998 (max = %d).\n", sv.signon.cursize, sv.signon.maxsize); //johnfitz // send serverinfo to all connected clients for (i=0,host_client = svs.clients ; iknowntoqc = false; if (host_client->active) SV_SendServerinfo (host_client); } Con_DPrintf ("Server spawned.\n"); }