quakespasm/Quake/sv_main.c

3136 lines
90 KiB
C
Raw Normal View History

/*
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_FITZQUAKE; //johnfitz
unsigned int sv_protocol_pext2; //spike
//============================================================================
void SV_CalcStats(client_t *client, int *statsi, float *statsf)
{
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);
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.
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;
}
//#undef MSG_WriteEntity
void MSG_WriteEntity (sizebuf_t *sb, int c, unsigned int pext2)
{
//high short, low byte
if (c > 0x7fff && (pext2 & PEXT2_REPLACEMENTDELTAS))
{
MSG_WriteShort(sb, 0x8000|(c>>8));
MSG_WriteByte(sb, c&0xff);
}
else
MSG_WriteShort(sb, c);
}
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)
{
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->resendstats[i] |= frame->resendstats[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];
int i;
struct deltaframe_s *frame;
int sequence = NET_QSocketGetSequenceOut(client->netconnection);
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);
for (i = 0; i < MAX_CL_STATS; 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->resendstats[i/32] |= 1u<<(i&31);
}
//if its flagged then unflag it, log it, and send it
if (client->resendstats[i/32] & (1u<<(i&31)))
{
client->resendstats[i/32] &= ~(1u<<(i&31));
frame->resendstats[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]);
}
}
}
}
}
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 (state<stateend && state->num < entnum)
state++;
if (state<stateend && state->num == 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 = (entnum<client->numpendingentities?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 = GetEdictFieldValue(ent, qcvm->extfields.traileffectnum)->_float;
state->emiteffectnum = GetEdictFieldValue(ent, qcvm->extfields.emiteffectnum)->_float;
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 ; e<maxentities ; e++, ent = NEXT_EDICT(ent))
{
eflags = 0;
emiteffect = GetEdictFieldValue(ent, qcvm->extfields.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, pr_extfields.exteriormodelforclient);
// 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) //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 <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
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
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_MODELS ; i++)
sprintf (localmodels[i], "*%i", i);
sv_protocol_pext2 = PEXT2_SUPPORTED_SERVER;
i = COM_CheckParm ("-protocol");
if (i && i < com_argc - 1)
sv_protocol = atoi (com_argv[i + 1]);
switch (sv_protocol)
{
case PROTOCOL_NETQUAKE:
p = "NetQuake";
break;
case PROTOCOL_FITZQUAKE:
p = "FitzQuake";
break;
case PROTOCOL_RMQ:
p = "RMQ";
break;
default:
Sys_Error ("Bad protocol version request %i. Accepted values: %i, %i, %i.",
sv_protocol, PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE, PROTOCOL_RMQ);
return; /* silence compiler */
}
Sys_Printf ("Server using protocol %i%s (%s%s)\n", sv_protocol, sv_protocol_pext2?"+":"", sv_protocol_pext2?"FTE-":"", p);
SV_VoiceInit();
}
/*
=============================================================================
EVENT MESSAGES
=============================================================================
*/
/*
==================
SV_StartParticle
Make sure the event gets sent to all clients
==================
*/
void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count)
{
int i, v;
if (sv.datagram.cursize > 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_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 ; i<NUM_SPAWN_PARMS ; i++)
client->spawn_parms[i] = (&pr_global_struct->parm1)[i];
}
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 ; i<svs.maxclients ; i++)
if (!svs.clients[i].active)
break;
if (i == svs.maxclients)
Sys_Error ("Host_CheckForNewClients: no free clients");
svs.clients[i].netconnection = ret;
SV_ConnectClient (i);
}
}
/*
===============================================================================
FRAME UPDATES
===============================================================================
*/
/*
==================
SV_ClearDatagram
==================
*/
void SV_ClearDatagram (void)
{
SZ_Clear (&sv.datagram);
}
/*
=============================================================================
The PVS must include a small area around the client to allow head bobbing
or other small motion on the client side. Otherwise, a bob might cause an
entity that should be visible to not show up, especially when the bob
crosses a waterline.
=============================================================================
*/
static int fatbytes;
static byte *fatpvs;
static int fatpvs_capacity;
void SV_AddToFatPVS (vec3_t org, mnode_t *node, qmodel_t *worldmodel) //johnfitz -- added worldmodel as a parameter
{
int i;
byte *pvs;
mplane_t *plane;
float d;
while (1)
{
// if this is a leaf, accumulate the pvs bits
if (node->contents < 0)
{
if (node->contents != CONTENTS_SOLID)
{
pvs = Mod_LeafPVS ( (mleaf_t *)node, worldmodel); //johnfitz -- worldmodel as a parameter
for (i=0 ; i<fatbytes ; i++)
fatpvs[i] |= pvs[i];
}
return;
}
plane = node->plane;
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 ; e<maxedict ; e++, ent = NEXT_EDICT(ent))
{
if (ent != clent) // clent is ALLWAYS sent
{
// ignore ents without visible models
64 bit compatibility effort, 4/nn: x86_64 works just fine now, yey! the QuakeC interpreter used to use string pointer offsets from pr_strings even when the pointers lead to engine data which is often well out of 32bit range on a 64bit architecture and they lead to crashes. they now go through the new PR_SetEngineString and PR_GetString functions which turn any address outside the pr_strings area into an index into a table of engine string addresses, adding new string addresses to the table as needed. the engine strings table is allocated with 256 entries at first (see the PR_STRING_ALLOCSLOTS definition in pr_edict.c) and its size is incremented by 256 as needed and re-allocated on the zone. managing that allocation and reallocation is accomplished by the recently added Z_Realloc function. implementation based on the uhexen2 (hexen2: hammer of thyrion) engine which, in turn, is loosely based on twilight and quakeforge engines. pr_strings range check is from tyrquake. pr_edict.c: added the new PR_SetEngineString, PR_GetString, PR_AllocString public functions and the new private PR_AllocStringSlots function. made ED_NewString private to pr_edict.c and reworked it to return an index to a newly allocated string. progs.h: added prototypes for the new public PR_SetEngineString, PR_GetString and PR_AllocString functions. host_cmd.c, pr_cmds.c, pr_edict.c, pr_exec.c, progs.h, sv_main.c, sv_phys.c: modifed to use the new PR_SetEngineString and PR_GetString functions. git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@38 af15c1b1-3010-417e-b628-4374ebc0bcbd
2010-02-17 15:04:50 +00:00
if (!ent->v.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<<i;
}
if ( ent->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 ; e<qcvm->num_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<<i);
if (ent->v.velocity[i])
bits |= (SU_VELOCITY1<<i);
}
if (ent->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<<i))
MSG_WriteChar (msg, ent->v.punchangle[i]);
if (bits & (SU_VELOCITY1<<i))
MSG_WriteChar (msg, ent->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<<i) )
{
MSG_WriteByte (msg, i);
break;
}
}
}
//johnfitz -- PROTOCOL_FITZQUAKE
if (bits & SU_WEAPON2)
MSG_WriteByte (msg, weaponmodelindex >> 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 ; i<svs.maxclients ; i++, host_client++)
{
if (host_client->old_frags != host_client->edict->v.frags)
{
for (j=0, client = svs.clients ; j<svs.maxclients ; j++, client++)
{
if (!client->knowntoqc)
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 ; j<svs.maxclients ; j++, client++)
{
if (!client->active)
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 ; i<svs.maxclients ; i++, host_client++)
{
if (!host_client->active)
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 ; i<MAX_MODELS && sv.model_precache[i] ; i++)
if (!strcmp(sv.model_precache[i], name))
return i;
if (i==MAX_MODELS || !sv.model_precache[i])
Sys_Error ("SV_ModelIndex: model %s not precached", name);
return i;
}
/*
================
SV_CreateBaseline
================
*/
void SV_CreateBaseline (void)
{
edict_t *svent;
int entnum;
eval_t *val;
for (entnum = 0; entnum < qcvm->num_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;
64 bit compatibility effort, 4/nn: x86_64 works just fine now, yey! the QuakeC interpreter used to use string pointer offsets from pr_strings even when the pointers lead to engine data which is often well out of 32bit range on a 64bit architecture and they lead to crashes. they now go through the new PR_SetEngineString and PR_GetString functions which turn any address outside the pr_strings area into an index into a table of engine string addresses, adding new string addresses to the table as needed. the engine strings table is allocated with 256 entries at first (see the PR_STRING_ALLOCSLOTS definition in pr_edict.c) and its size is incremented by 256 as needed and re-allocated on the zone. managing that allocation and reallocation is accomplished by the recently added Z_Realloc function. implementation based on the uhexen2 (hexen2: hammer of thyrion) engine which, in turn, is loosely based on twilight and quakeforge engines. pr_strings range check is from tyrquake. pr_edict.c: added the new PR_SetEngineString, PR_GetString, PR_AllocString public functions and the new private PR_AllocStringSlots function. made ED_NewString private to pr_edict.c and reworked it to return an index to a newly allocated string. progs.h: added prototypes for the new public PR_SetEngineString, PR_GetString and PR_AllocString functions. host_cmd.c, pr_cmds.c, pr_edict.c, pr_exec.c, progs.h, sv_main.c, sv_phys.c: modifed to use the new PR_SetEngineString and PR_GetString functions. git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@38 af15c1b1-3010-417e-b628-4374ebc0bcbd
2010-02-17 15:04:50 +00:00
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 ; i<svs.maxclients ; i++, host_client++)
{
if (!host_client->active)
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 ; j<NUM_SPAWN_PARMS ; j++)
host_client->spawn_parms[j] = (&pr_global_struct->parm1)[j];
}
}
//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)
sv.protocolflags = PRFL_INT32COORD | PRFL_SHORTANGLE;
}
else sv.protocolflags = 0;
// load progs to get entity field count
PR_LoadProgs ("progs.dat", true, 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 ; i<svs.maxclients ; i++)
{
ent = EDICT_NUM(i+1);
svs.clients[i].edict = ent;
}
sv.state = ss_loading;
sv.paused = false;
qcvm->time = 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 ; i<qcvm->worldmodel->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;
64 bit compatibility effort, 4/nn: x86_64 works just fine now, yey! the QuakeC interpreter used to use string pointer offsets from pr_strings even when the pointers lead to engine data which is often well out of 32bit range on a 64bit architecture and they lead to crashes. they now go through the new PR_SetEngineString and PR_GetString functions which turn any address outside the pr_strings area into an index into a table of engine string addresses, adding new string addresses to the table as needed. the engine strings table is allocated with 256 entries at first (see the PR_STRING_ALLOCSLOTS definition in pr_edict.c) and its size is incremented by 256 as needed and re-allocated on the zone. managing that allocation and reallocation is accomplished by the recently added Z_Realloc function. implementation based on the uhexen2 (hexen2: hammer of thyrion) engine which, in turn, is loosely based on twilight and quakeforge engines. pr_strings range check is from tyrquake. pr_edict.c: added the new PR_SetEngineString, PR_GetString, PR_AllocString public functions and the new private PR_AllocStringSlots function. made ED_NewString private to pr_edict.c and reworked it to return an index to a newly allocated string. progs.h: added prototypes for the new public PR_SetEngineString, PR_GetString and PR_AllocString functions. host_cmd.c, pr_cmds.c, pr_edict.c, pr_exec.c, progs.h, sv_main.c, sv_phys.c: modifed to use the new PR_SetEngineString and PR_GetString functions. git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@38 af15c1b1-3010-417e-b628-4374ebc0bcbd
2010-02-17 15:04:50 +00:00
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;
2018-07-07 14:05:34 +00:00
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 ; i<svs.maxclients ; i++, host_client++)
{
host_client->knowntoqc = false;
if (host_client->active)
SV_SendServerinfo (host_client);
}
Con_DPrintf ("Server spawned.\n");
}