void SV_VM_Init();
void SV_VM_Setup();
-void VM_AutoSentStats_Clear (void);
+void VM_CustomStats_Clear (void);
void EntityFrameCSQC_ClearVersions (void);
void EntityFrameCSQC_InitClientVersions (int client, qboolean clear);
-void VM_SV_WriteAutoSentStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats);
+void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats);
void EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int numstates, const entity_state_t *states);
Cvar_RegisterVariable (&sv_accelerate);
Cvar_RegisterVariable (&sv_airaccelerate);
Cvar_RegisterVariable (&sv_wateraccelerate);
+ Cvar_RegisterVariable (&sv_jumpvelocity);
+ Cvar_RegisterVariable (&sv_airaccel_qw);
+ Cvar_RegisterVariable (&sv_airaccel_sideways_friction);
Cvar_RegisterVariable (&sv_clmovement_enable);
Cvar_RegisterVariable (&sv_clmovement_minping);
Cvar_RegisterVariable (&sv_clmovement_minping_disabletime);
MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127));
MSG_WriteByte (&sv.datagram, count);
MSG_WriteByte (&sv.datagram, color);
+ SV_FlushBroadcastMessages();
}
/*
MSG_WriteByte (&sv.datagram, framecount);
MSG_WriteByte (&sv.datagram, framerate);
}
+ SV_FlushBroadcastMessages();
}
/*
MSG_WriteByte (&sv.datagram, sound_num);
for (i = 0;i < 3;i++)
MSG_WriteCoord (&sv.datagram, entity->fields.server->origin[i]+0.5*(entity->fields.server->mins[i]+entity->fields.server->maxs[i]), sv.protocol);
+ SV_FlushBroadcastMessages();
}
/*
if (client->entitydatabase5)
EntityFrame5_FreeDatabase(client->entitydatabase5);
+ memset(client->stats, 0, sizeof(client->stats));
+ memset(client->statsdeltabits, 0, sizeof(client->statsdeltabits));
+
if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE)
{
if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3)
client->edict = PRVM_EDICT_NUM(clientnum+1);
if (client->netconnection)
client->netconnection->message.allowoverflow = true; // we can catch it
+ // prepare the unreliable message buffer
+ client->unreliablemsg.data = client->unreliablemsg_data;
+ client->unreliablemsg.maxsize = sizeof(client->unreliablemsg_data);
// updated by receiving "rate" command from client
client->rate = NET_MINRATE;
// no limits for local player
===============================================================================
*/
-/*
-==================
-SV_ClearDatagram
-
-==================
-*/
-void SV_ClearDatagram (void)
-{
- SZ_Clear (&sv.datagram);
-}
-
/*
=============================================================================
static entity_state_t sendentities[MAX_EDICTS];
static entity_state_t *sendentitiesindex[MAX_EDICTS];
+static int sententitiesmark = 0;
+static int sententities[MAX_EDICTS];
+static int sententitiesconsideration[MAX_EDICTS];
+static int sv_writeentitiestoclient_culled_pvs;
+static int sv_writeentitiestoclient_culled_trace;
+static int sv_writeentitiestoclient_visibleentities;
+static int sv_writeentitiestoclient_totalentities;
+//static entity_frame_t sv_writeentitiestoclient_entityframe;
+static int sv_writeentitiestoclient_clentnum;
+static vec3_t sv_writeentitiestoclient_testeye;
+static client_t *sv_writeentitiestoclient_client;
+
qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int e)
{
int i;
if (PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.fullbright)->_float)
cs->effects |= EF_FULLBRIGHT;
+ val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.modelflags);
+ if (val && val->_float)
+ cs->effects |= ((unsigned int)val->_float & 0xff) << 24;
+
if (ent->fields.server->movetype == MOVETYPE_STEP)
cs->flags |= RENDER_STEP;
- if ((cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767)
+ if (cs->number != sv_writeentitiestoclient_clentnum && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767)
cs->flags |= RENDER_LOWPRECISION;
if (ent->fields.server->colormap >= 1024)
cs->flags |= RENDER_COLORMAPPED;
}
}
-static int sententitiesmark = 0;
-static int sententities[MAX_EDICTS];
-static int sententitiesconsideration[MAX_EDICTS];
-static int sv_writeentitiestoclient_culled_pvs;
-static int sv_writeentitiestoclient_culled_trace;
-static int sv_writeentitiestoclient_visibleentities;
-static int sv_writeentitiestoclient_totalentities;
-//static entity_frame_t sv_writeentitiestoclient_entityframe;
-static int sv_writeentitiestoclient_clentnum;
-static vec3_t sv_writeentitiestoclient_testeye;
-static client_t *sv_writeentitiestoclient_client;
-
void SV_MarkWriteEntityStateToClient(entity_state_t *s)
{
int isbmodel;
entity_state_t sendstates[MAX_EDICTS];
extern int csqc_clientnum;
-void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int *stats)
+void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg)
{
int i, numsendstates;
entity_state_t *s;
EntityFrameCSQC_WriteFrame(msg, numsendstates, sendstates);
if (client->entitydatabase5)
- EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sendstates, client - svs.clients + 1, stats, client->movesequence);
+ EntityFrame5_WriteFrame(msg, client->entitydatabase5, numsendstates, sendstates, client - svs.clients + 1, client->movesequence);
else if (client->entitydatabase4)
+ {
EntityFrame4_WriteFrame(msg, client->entitydatabase4, numsendstates, sendstates);
+ Protocol_WriteStatsReliable();
+ }
else if (client->entitydatabase)
+ {
EntityFrame_WriteFrame(msg, client->entitydatabase, numsendstates, sendstates, client - svs.clients + 1);
+ Protocol_WriteStatsReliable();
+ }
else
+ {
EntityFrameQuake_WriteFrame(msg, numsendstates, sendstates);
+ Protocol_WriteStatsReliable();
+ }
}
/*
vec3_t punchvector;
int viewzoom;
const char *s;
+ float *statsf = (float *)stats;
+extern cvar_t slowmo;
//
// send a damage message
SV_SetIdealPitch (); // how much to look up / down ideally
// a fixangle might get lost in a dropped packet. Oh well.
- if ( ent->fields.server->fixangle )
+ if(ent->fields.server->fixangle)
+ {
+ // angle fixing was requested by global thinking code...
+ // so store the current angles for later use
+ memcpy(host_client->fixangle_angles, ent->fields.server->angles, sizeof(host_client->fixangle_angles));
+ host_client->fixangle_angles_set = TRUE;
+
+ // and clear fixangle for the next frame
+ ent->fields.server->fixangle = 0;
+ }
+
+ if (host_client->fixangle_angles_set)
{
MSG_WriteByte (msg, svc_setangle);
for (i=0 ; i < 3 ; i++)
- MSG_WriteAngle (msg, ent->fields.server->angles[i], sv.protocol);
- // LordHavoc: moved fixangle = 0 to the physics code so it is
- // repeatedly sent to predicted clients even though they don't always
- // move each frame
- //ent->fields.server->fixangle = 0;
+ MSG_WriteAngle (msg, host_client->fixangle_angles[i], sv.protocol);
+ host_client->fixangle_angles_set = FALSE;
}
// stuff the sigil bits into the high bits of items for sbar, or else
//stats[STAT_SECRETS] = prog->globals.server->found_secrets;
//stats[STAT_MONSTERS] = prog->globals.server->killed_monsters;
+ // movement settings for prediction
+ statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value;
+ statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value;
+ statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value;
+ statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value;
+ statsf[STAT_MOVEVARS_MAXSPEED] = sv_maxspeed.value;
+ statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_maxspeed.value; // FIXME: QW has a separate cvar for this
+ statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value;
+ statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value >= 0 ? sv_airaccelerate.value : sv_accelerate.value;
+ statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value >= 0 ? sv_wateraccelerate.value : sv_accelerate.value;
+ val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.gravity);
+ statsf[STAT_MOVEVARS_ENTGRAVITY] = val ? val->_float : 1.0f;
+ statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value;
+ statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value;
+ statsf[STAT_MOVEVARS_MAXAIRSPEED] = sv_maxairspeed.value;
+ statsf[STAT_MOVEVARS_STEPHEIGHT] = sv_stepheight.value;
+ statsf[STAT_MOVEVARS_AIRACCEL_QW] = sv_airaccel_qw.value;
+ statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_airaccel_sideways_friction.value;
+
if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)
{
if (stats[STAT_VIEWHEIGHT] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT;
}
}
+void SV_FlushBroadcastMessages(void)
+{
+ int i;
+ client_t *client;
+ if (sv.datagram.cursize <= 0)
+ return;
+ for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
+ {
+ if (!client->spawned || !client->netconnection || client->unreliablemsg.cursize + sv.datagram.cursize > client->unreliablemsg.maxsize || client->unreliablemsg_splitpoints >= (int)(sizeof(client->unreliablemsg_splitpoint)/sizeof(client->unreliablemsg_splitpoint[0])))
+ continue;
+ SZ_Write(&client->unreliablemsg, sv.datagram.data, sv.datagram.cursize);
+ client->unreliablemsg_splitpoint[client->unreliablemsg_splitpoints++] = client->unreliablemsg.cursize;
+ }
+ SZ_Clear(&sv.datagram);
+}
+
+void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg)
+{
+ // scan the splitpoints to find out how many we can fit in
+ int numsegments, j, split;
+ if (!client->unreliablemsg_splitpoints)
+ return;
+ // always accept the first one if it's within 1400 bytes, this ensures
+ // that very big datagrams which are over the rate limit still get
+ // through, just to keep it working
+ if (msg->cursize + client->unreliablemsg_splitpoint[0] > msg->maxsize && msg->maxsize < 1400)
+ {
+ numsegments = 1;
+ msg->maxsize = 1400;
+ }
+ else
+ for (numsegments = 0;numsegments < client->unreliablemsg_splitpoints;numsegments++)
+ if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > msg->maxsize)
+ break;
+ if (numsegments > 0)
+ {
+ // some will fit, so add the ones that will fit
+ split = client->unreliablemsg_splitpoint[numsegments-1];
+ // note this discards ones that were accepted by the segments scan but
+ // can not fit, such as a really huge first one that will never ever
+ // fit in a packet...
+ if (msg->cursize + split <= msg->maxsize)
+ SZ_Write(msg, client->unreliablemsg.data, split);
+ // remove the part we sent, keeping any remaining data
+ client->unreliablemsg.cursize -= split;
+ if (client->unreliablemsg.cursize > 0)
+ memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize);
+ // adjust remaining splitpoints
+ client->unreliablemsg_splitpoints -= numsegments;
+ for (j = 0;j < client->unreliablemsg_splitpoints;j++)
+ client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split;
+ }
+}
+
/*
=======================
SV_SendClientDatagram
static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; // FIXME?
void SV_SendClientDatagram (client_t *client)
{
- int rate, maxrate, maxsize, maxsize2, downloadsize;
+ int clientrate, maxrate, maxsize, maxsize2, downloadsize;
sizebuf_t msg;
int stats[MAX_CL_STATS];
+ // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates
+ maxrate = max(NET_MINRATE, sv_maxrate.integer);
+ if (sv_maxrate.integer != maxrate)
+ Cvar_SetValueQuick(&sv_maxrate, maxrate);
+ // clientrate determines the 'cleartime' of a packet
+ // (how long to wait before sending another, based on this packet's size)
+ clientrate = bound(NET_MINRATE, client->rate, maxrate);
+
if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer)
{
- // for good singleplayer, send huge packets
+ // for good singleplayer, send huge packets and never limit frequency
+ clientrate = 1000000000;
maxsize = sizeof(sv_sendclientdatagram_buf);
maxsize2 = sizeof(sv_sendclientdatagram_buf);
}
else if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4)
{
- // no rate limiting support on older protocols because dp protocols
- // 1-4 kick the client off if they overflow, and quake protocol shows
- // less than the full entity set if rate limited
+ // no packet size limit support on older protocols because DP1-4 kick
+ // the client off if they overflow, and quake protocol shows less than
+ // the full entity set if rate limited
maxsize = 1400;
maxsize2 = 1400;
}
else
{
- // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates
- maxrate = max(NET_MINRATE, sv_maxrate.integer);
- if (sv_maxrate.integer != maxrate)
- Cvar_SetValueQuick(&sv_maxrate, maxrate);
-
+ // DP5 and later protocols support packet size limiting which is a
+ // better method than limiting packet frequency as QW does
+ //
// this rate limiting does not understand sys_ticrate 0
// (but no one should be running that on a server!)
- rate = bound(NET_MINRATE, client->rate, maxrate);
- rate = (int)(rate * sys_ticrate.value);
- maxsize = bound(50, rate, 1400);
+ maxsize = (int)(clientrate * sys_ticrate.value);
+ maxsize = bound(100, maxsize, 1400);
maxsize2 = 1400;
}
msg.maxsize = maxsize;
msg.cursize = 0;
- if (host_client->spawned)
+ // obey rate limit by limiting packet frequency if the packet size
+ // limiting fails
+ // (usually this is caused by reliable messages)
+ if (!NetConn_CanSend(client->netconnection))
+ {
+ // send the datagram
+ //NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate);
+ return;
+ }
+ else if (host_client->spawned)
{
MSG_WriteByte (&msg, svc_time);
MSG_WriteFloat (&msg, sv.time);
// add the client specific data to the datagram
SV_WriteClientdataToMessage (client, client->edict, &msg, stats);
- VM_SV_WriteAutoSentStats (client, client->edict, &msg, stats);
- SV_WriteEntitiesToClient (client, client->edict, &msg, stats);
+ // now update the stats[] array using any registered custom fields
+ VM_SV_UpdateCustomStats (client, client->edict, &msg, stats);
+ // set host_client->statsdeltabits
+ Protocol_UpdateClientStats (stats);
- // expand packet size to allow effects to go over the rate limit
- // (dropping them is FAR too ugly)
- msg.maxsize = maxsize2;
+ // add as many queued unreliable messages (effects) as we can fit
+ // limit effects to half of the remaining space
+ msg.maxsize -= (msg.maxsize - msg.cursize) / 2;
+ if (client->unreliablemsg.cursize)
+ SV_WriteUnreliableMessages (client, &msg);
- // copy the server datagram if there is space
- // FIXME: put in delayed queue of effects to send
- if (sv.datagram.cursize > 0 && msg.cursize + sv.datagram.cursize <= msg.maxsize)
- SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize);
+ msg.maxsize = maxsize;
+
+ // now write as many entities as we can fit, and also sends stats
+ SV_WriteEntitiesToClient (client, client->edict, &msg);
}
else if (realtime > client->keepalivetime)
{
}
// send the datagram
- NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol);
+ NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate);
}
/*
if (strcmp(host_client->old_name, host_client->name))
{
if (host_client->spawned)
- SV_BroadcastPrintf("%s changed name to %s\n", host_client->old_name, host_client->name);
+ SV_BroadcastPrintf("%s^%i changed name to %s\n", host_client->old_name, STRING_COLOR_DEFAULT, host_client->name);
strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
// send notification to all clients
MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
if (sv.protocol == PROTOCOL_QUAKEWORLD)
Sys_Error("SV_SendClientMessages: no quakeworld support\n");
+ SV_FlushBroadcastMessages();
+
// update frags, names, etc
SV_UpdateToReliableMessages();
PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.playermodel)->string = PRVM_SetEngineString(svs.clients[num].playermodel);
if( prog->fieldoffsets.playerskin >= 0 )
PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.playerskin)->string = PRVM_SetEngineString(svs.clients[num].playerskin);
+ // Assign netaddress (IP Address, etc)
+ if(prog->fieldoffsets.netaddress >= 0)
+ { // Valid Field; Process
+ if(svs.clients[num].netconnection != NULL)
+ {// Valid Address; Assign
+ // Acquire Readable Address
+ LHNETADDRESS_ToString(&svs.clients[num].netconnection->peeraddress, svs.clients[num].netaddress, sizeof(svs.clients[num].netaddress), false);
+ PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.netaddress)->string = PRVM_SetEngineString(svs.clients[num].netaddress);
+ }
+ else
+ // Invalid / Bot
+ PRVM_EDICTFIELDVALUE(e, prog->fieldoffsets.netaddress)->string = PRVM_SetEngineString("null/botclient");
+ }
}
}
{ev_entity, "nodrawtoclient"},
{ev_entity, "tag_entity"},
{ev_entity, "viewmodelforclient"},
+ {ev_float, "Version"},
{ev_float, "alpha"},
{ev_float, "ammo_cells1"},
{ev_float, "ammo_lava_nails"},
{ev_float, "buttonuse"},
{ev_float, "clientcolors"},
{ev_float, "cursor_active"},
+ {ev_float, "disableclientprediction"},
{ev_float, "fullbright"},
{ev_float, "glow_color"},
{ev_float, "glow_size"},
{ev_float, "idealpitch"},
{ev_float, "items2"},
{ev_float, "light_lev"},
+ {ev_float, "modelflags"},
{ev_float, "pflags"},
{ev_float, "ping"},
{ev_float, "pitch_speed"},
{ev_float, "scale"},
{ev_float, "style"},
{ev_float, "tag_index"},
- {ev_float, "Version"},
{ev_float, "viewzoom"},
+ {ev_function, "SendEntity"},
+ {ev_function, "contentstransition"}, // DRESK - Support for Entity Contents Transition Event
+ {ev_function, "customizeentityforclient"},
+ {ev_string, "netaddress"},
+ {ev_string, "playermodel"},
+ {ev_string, "playerskin"},
{ev_vector, "color"},
{ev_vector, "colormod"},
{ev_vector, "cursor_screen"},
{ev_vector, "cursor_trace_start"},
{ev_vector, "movement"},
{ev_vector, "punchvector"},
- {ev_string, "playermodel"},
- {ev_string, "playerskin"},
- {ev_function, "SendEntity"},
- {ev_function, "customizeentityforclient"},
- // DRESK - Support for Entity Contents Transition Event
- {ev_function, "contentstransition"},
};
void SV_VM_Setup(void)
// OP_STATE is always supported on server (due to entvars_t)
prog->flag |= PRVM_OP_STATE;
- VM_AutoSentStats_Clear();//[515]: csqc
+ VM_CustomStats_Clear();//[515]: csqc
EntityFrameCSQC_ClearVersions();//[515]: csqc
PRVM_End;