newtree/source/sv_send.c
Jeff Teunissen 6f40c97643 New Cvars:
sv_minqfversion: Set to the minimum allowable QuakeForge version you want
to allow on a server. QF clients prior to current CVS _will_not_work_ with
this cvar, as the QF version was not reported previously.

sv_maxrate: Change maximum rate algorithm; if sv_maxrate is nonzero, it is
the maximum rate allowed by the server. If it is unset, the maximum is
10000 like it used to be. The rate is silently capped, so it won't kick.

Cvar fixed: sv_timekick and friends; Now kicks on too little time passed
as well.

Functionality removed: "Last message repeated X times". It causes too much
trouble.
2000-11-25 14:20:04 +00:00

858 lines
20 KiB
C

/*
sv_send.c
(description)
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "server.h"
#include "crc.h"
#include "msg.h"
#include "world.h"
#include "commdef.h"
#include "cmd.h"
#include "sys.h"
#include "pmove.h"
#include "bothdefs.h"
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define CHAN_AUTO 0
#define CHAN_WEAPON 1
#define CHAN_VOICE 2
#define CHAN_ITEM 3
#define CHAN_BODY 4
/*
=============================================================================
Con_Printf redirection
=============================================================================
*/
char outputbuf[8000];
redirect_t sv_redirected;
extern cvar_t *sv_phs;
extern cvar_t *sv_timestamps;
extern cvar_t *sv_timefmt;
/*
==================
SV_FlushRedirect
==================
*/
void SV_FlushRedirect (void)
{
char send[8000+6];
if (sv_redirected == RD_PACKET)
{
send[0] = 0xff;
send[1] = 0xff;
send[2] = 0xff;
send[3] = 0xff;
send[4] = A2C_PRINT;
memcpy (send+5, outputbuf, strlen(outputbuf)+1);
NET_SendPacket (strlen(send)+1, send, net_from);
}
else if (sv_redirected == RD_CLIENT)
{
ClientReliableWrite_Begin (host_client, svc_print, strlen(outputbuf)+3);
ClientReliableWrite_Byte (host_client, PRINT_HIGH);
ClientReliableWrite_String (host_client, outputbuf);
}
// clear it
outputbuf[0] = 0;
}
/*
==================
SV_BeginRedirect
Send Con_Printf data to the remote client
instead of the console
==================
*/
void SV_BeginRedirect (redirect_t rd)
{
sv_redirected = rd;
outputbuf[0] = 0;
}
void SV_EndRedirect (void)
{
SV_FlushRedirect ();
sv_redirected = RD_NONE;
}
/*
================
Con_Printf
Handles cursor positioning, line wrapping, etc
================
*/
#define MAXPRINTMSG 4096
void Con_Printf (char *fmt, ...)
{
va_list argptr;
char msg[MAXPRINTMSG];
char msg2[MAXPRINTMSG];
char msg3[MAXPRINTMSG];
time_t mytime = 0;
struct tm *local = NULL;
qboolean timestamps = false;
va_start (argptr, fmt);
vsnprintf (msg, sizeof (msg), fmt, argptr);
va_end (argptr);
if (sv_redirected) { // Add to redirected message
if (strlen (msg) + strlen (outputbuf) > sizeof (outputbuf) - 1)
SV_FlushRedirect ();
strcat (outputbuf, msg);
return;
} else { // We want to output to console and maybe logfile
if (sv_timestamps && sv_timefmt && sv_timefmt->string && sv_timestamps->int_val)
timestamps = true;
if (timestamps) {
mytime = time (NULL);
local = localtime (&mytime);
strftime (msg3, sizeof (msg3), sv_timefmt->string, local);
snprintf (msg2, sizeof (msg2), "%s%s", msg3, msg);
} else {
snprintf (msg2, sizeof (msg2), "%s", msg);
}
Sys_Printf ("%s", msg2); // also echo to debugging console
if (sv_logfile)
Qprintf (sv_logfile, "%s", msg2);
}
}
/*
================
Con_DPrintf
A Con_Printf that only shows up if the "developer" cvar is set
================
*/
void Con_DPrintf (char *fmt, ...)
{
va_list argptr;
char msg[MAXPRINTMSG];
if (!developer->int_val)
return;
va_start (argptr, fmt);
vsnprintf (msg, sizeof (msg), fmt, argptr);
va_end (argptr);
Con_Printf ("%s", msg);
}
/*
=============================================================================
EVENT MESSAGES
=============================================================================
*/
static void SV_PrintToClient(client_t *cl, int level, char *string)
{
ClientReliableWrite_Begin (cl, svc_print, strlen(string)+3);
ClientReliableWrite_Byte (cl, level);
ClientReliableWrite_String (cl, string);
}
/*
=================
SV_ClientPrintf
Sends text across to be displayed if the level passes
=================
*/
void SV_ClientPrintf (client_t *cl, int level, char *fmt, ...)
{
va_list argptr;
char string[1024];
if (level < cl->messagelevel)
return;
va_start (argptr, fmt);
vsnprintf (string, sizeof(string), fmt, argptr);
va_end (argptr);
SV_PrintToClient(cl, level, string);
}
/*
=================
SV_BroadcastPrintf
Sends text to all active clients
=================
*/
void SV_BroadcastPrintf (int level, char *fmt, ...)
{
va_list argptr;
char string[1024];
client_t *cl;
int i;
va_start (argptr, fmt);
vsnprintf (string, sizeof(string), fmt, argptr);
va_end (argptr);
Sys_Printf ("%s", string); // print to the console
for (i=0, cl = svs.clients ; i<MAX_CLIENTS ; i++, cl++)
{
if (level < cl->messagelevel)
continue;
if (!cl->state)
continue;
SV_PrintToClient(cl, level, string);
}
}
/*
=================
SV_BroadcastCommand
Sends text to all active clients
=================
*/
void SV_BroadcastCommand (char *fmt, ...)
{
va_list argptr;
char string[1024];
if (!sv.state)
return;
va_start (argptr, fmt);
vsnprintf (string, sizeof(string), fmt, argptr);
va_end (argptr);
MSG_WriteByte (&sv.reliable_datagram, svc_stufftext);
MSG_WriteString (&sv.reliable_datagram, string);
}
/*
=================
SV_Multicast
Sends the contents of sv.multicast to a subset of the clients,
then clears sv.multicast.
MULTICAST_ALL same as broadcast
MULTICAST_PVS send to clients potentially visible from org
MULTICAST_PHS send to clients potentially hearable from org
=================
*/
void SV_Multicast (vec3_t origin, int to)
{
client_t *client;
byte *mask;
mleaf_t *leaf;
int leafnum;
int j;
qboolean reliable;
leaf = Mod_PointInLeaf (origin, sv.worldmodel);
if (!leaf)
leafnum = 0;
else
leafnum = leaf - sv.worldmodel->leafs;
reliable = false;
switch (to)
{
case MULTICAST_ALL_R:
reliable = true; // intentional fallthrough
case MULTICAST_ALL:
mask = sv.pvs; // leaf 0 is everything;
break;
case MULTICAST_PHS_R:
reliable = true; // intentional fallthrough
case MULTICAST_PHS:
mask = sv.phs + leafnum * 4*((sv.worldmodel->numleafs+31)>>5);
break;
case MULTICAST_PVS_R:
reliable = true; // intentional fallthrough
case MULTICAST_PVS:
mask = sv.pvs + leafnum * 4*((sv.worldmodel->numleafs+31)>>5);
break;
default:
mask = NULL;
SV_Error ("SV_Multicast: bad to:%i", to);
}
// send the data to all relevent clients
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++)
{
if (client->state != cs_spawned)
continue;
if (to == MULTICAST_PHS_R || to == MULTICAST_PHS) {
vec3_t delta;
VectorSubtract(origin, client->edict->v.origin, delta);
if (Length(delta) <= 1024)
goto inrange;
}
leaf = Mod_PointInLeaf (client->edict->v.origin, sv.worldmodel);
if (leaf)
{
// -1 is because pvs rows are 1 based, not 0 based like leafs
leafnum = leaf - sv.worldmodel->leafs - 1;
if ( !(mask[leafnum>>3] & (1<<(leafnum&7)) ) )
{
// Con_Printf ("supressed multicast\n");
continue;
}
}
inrange:
if (reliable) {
ClientReliableCheckBlock(client, sv.multicast.cursize);
ClientReliableWrite_SZ(client, sv.multicast.data, sv.multicast.cursize);
} else
SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);
}
SZ_Clear (&sv.multicast);
}
/*
==================
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, int channel, char *sample, int volume,
float attenuation)
{
int sound_num;
int field_mask;
int i;
int ent;
vec3_t origin;
qboolean use_phs;
qboolean reliable = false;
if (volume < 0 || volume > 255)
SV_Error ("SV_StartSound: volume = %i", volume);
if (attenuation < 0 || attenuation > 4)
SV_Error ("SV_StartSound: attenuation = %f", attenuation);
if (channel < 0 || channel > 15)
SV_Error ("SV_StartSound: channel = %i", channel);
// 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);
if ((channel & 8) || !sv_phs->int_val) // no PHS flag
{
if (channel & 8)
reliable = true; // sounds that break the phs are reliable
use_phs = false;
channel &= 7;
}
else
use_phs = true;
// if (channel == CHAN_BODY || channel == CHAN_VOICE)
// reliable = true;
channel = (ent<<3) | channel;
field_mask = 0;
if (volume != DEFAULT_SOUND_PACKET_VOLUME)
channel |= SND_VOLUME;
if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
channel |= SND_ATTENUATION;
// use the entity origin unless it is a bmodel
if (entity->v.solid == SOLID_BSP)
{
for (i=0 ; i<3 ; i++)
origin[i] = entity->v.origin[i]+0.5*(entity->v.mins[i]+entity->v.maxs[i]);
}
else
{
VectorCopy (entity->v.origin, origin);
}
MSG_WriteByte (&sv.multicast, svc_sound);
MSG_WriteShort (&sv.multicast, channel);
if (channel & SND_VOLUME)
MSG_WriteByte (&sv.multicast, volume);
if (channel & SND_ATTENUATION)
MSG_WriteByte (&sv.multicast, attenuation*64);
MSG_WriteByte (&sv.multicast, sound_num);
for (i=0 ; i<3 ; i++)
MSG_WriteCoord (&sv.multicast, origin[i]);
if (use_phs)
SV_Multicast (origin, reliable ? MULTICAST_PHS_R : MULTICAST_PHS);
else
SV_Multicast (origin, reliable ? MULTICAST_ALL_R : MULTICAST_ALL);
}
/*
===============================================================================
FRAME UPDATES
===============================================================================
*/
int sv_nailmodel, sv_supernailmodel, sv_playermodel;
void SV_FindModelNumbers (void)
{
int i;
sv_nailmodel = -1;
sv_supernailmodel = -1;
sv_playermodel = -1;
for (i=0 ; i<MAX_MODELS ; i++)
{
if (!sv.model_precache[i])
break;
if (!strcmp(sv.model_precache[i],"progs/spike.mdl"))
sv_nailmodel = i;
if (!strcmp(sv.model_precache[i],"progs/s_spike.mdl"))
sv_supernailmodel = i;
if (!strcmp(sv.model_precache[i],"progs/player.mdl"))
sv_playermodel = i;
}
}
/*
==================
SV_WriteClientdataToMessage
==================
*/
void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg)
{
int i;
edict_t *other;
edict_t *ent;
ent = client->edict;
// send the chokecount for r_netgraph
if (client->chokecount)
{
MSG_WriteByte (msg, svc_chokecount);
MSG_WriteByte (msg, client->chokecount);
client->chokecount = 0;
}
// send a damage message if the player got hit this frame
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]));
ent->v.dmg_take = 0;
ent->v.dmg_save = 0;
}
// a fixangle might get lost in a dropped packet. Oh well.
if ( ent->v.fixangle )
{
MSG_WriteByte (msg, svc_setangle);
for (i=0 ; i < 3 ; i++)
MSG_WriteAngle (msg, ent->v.angles[i] );
ent->v.fixangle = 0;
}
}
/*
=======================
SV_UpdateClientStats
Performs a delta update of the stats array. This should only be performed
when a reliable message can be delivered this frame.
=======================
*/
void SV_UpdateClientStats (client_t *client)
{
edict_t *ent;
int stats[MAX_CL_STATS];
int i;
ent = client->edict;
memset (stats, 0, sizeof(stats));
// if we are a spectator and we are tracking a player, we get his stats
// so our status bar reflects his
if (client->spectator && client->spec_track > 0)
ent = svs.clients[client->spec_track - 1].edict;
stats[STAT_HEALTH] = ent->v.health;
stats[STAT_WEAPON] = SV_ModelIndex(PR_GetString(ent->v.weaponmodel));
stats[STAT_AMMO] = ent->v.currentammo;
stats[STAT_ARMOR] = ent->v.armorvalue;
stats[STAT_SHELLS] = ent->v.ammo_shells;
stats[STAT_NAILS] = ent->v.ammo_nails;
stats[STAT_ROCKETS] = ent->v.ammo_rockets;
stats[STAT_CELLS] = ent->v.ammo_cells;
if (!client->spectator)
stats[STAT_ACTIVEWEAPON] = ent->v.weapon;
// stuff the sigil bits into the high bits of items for sbar
stats[STAT_ITEMS] = (int)ent->v.items | ((int)pr_global_struct->serverflags << 28);
// Extensions to the QW 2.40 protocol for Mega2k --KB
stats[STAT_VIEWHEIGHT] = (int)ent->v.view_ofs[2];
// FIXME: this should become a * key! --KB
if (ent->v.movetype == MOVETYPE_FLY && !atoi(Info_ValueForKey
(svs.info, "playerfly")))
ent->v.movetype = MOVETYPE_WALK;
stats[STAT_FLYMODE] = (ent->v.movetype == MOVETYPE_FLY);
for (i=0 ; i<MAX_CL_STATS ; i++)
if (stats[i] != client->stats[i])
{
client->stats[i] = stats[i];
if (stats[i] >=0 && stats[i] <= 255)
{
ClientReliableWrite_Begin(client, svc_updatestat, 3);
ClientReliableWrite_Byte(client, i);
ClientReliableWrite_Byte(client, stats[i]);
}
else
{
ClientReliableWrite_Begin(client, svc_updatestatlong, 6);
ClientReliableWrite_Byte(client, i);
ClientReliableWrite_Long(client, stats[i]);
}
}
}
/*
=======================
SV_SendClientDatagram
=======================
*/
qboolean SV_SendClientDatagram (client_t *client)
{
byte buf[MAX_DATAGRAM];
sizebuf_t msg;
msg.data = buf;
msg.maxsize = sizeof(buf);
msg.cursize = 0;
msg.allowoverflow = true;
msg.overflowed = false;
// add the client specific data to the datagram
SV_WriteClientdataToMessage (client, &msg);
// send over all the objects that are in the PVS
// this will include clients, a packetentities, and
// possibly a nails update
SV_WriteEntitiesToClient (client, &msg);
// copy the accumulated multicast datagram
// for this client out to the message
if (client->datagram.overflowed)
Con_Printf ("WARNING: datagram overflowed for %s\n", client->name);
else
SZ_Write (&msg, client->datagram.data, client->datagram.cursize);
SZ_Clear (&client->datagram);
// send deltas over reliable stream
if (Netchan_CanReliable (&client->netchan))
SV_UpdateClientStats (client);
if (msg.overflowed)
{
Con_Printf ("WARNING: msg overflowed for %s\n", client->name);
SZ_Clear (&msg);
}
// send the datagram
Netchan_Transmit (&client->netchan, msg.cursize, buf);
return true;
}
/*
=======================
SV_UpdateToReliableMessages
=======================
*/
void SV_UpdateToReliableMessages (void)
{
int i, j;
client_t *client;
eval_t *val;
edict_t *ent;
// check for changes to be sent over the reliable streams to all clients
for (i=0, host_client = svs.clients ; i<MAX_CLIENTS ; i++, host_client++)
{
if (host_client->state != cs_spawned)
continue;
if (host_client->sendinfo)
{
host_client->sendinfo = false;
SV_FullClientUpdate (host_client, &sv.reliable_datagram);
}
if (host_client->old_frags != host_client->edict->v.frags)
{
for (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)
{
if (client->state < cs_connected)
continue;
ClientReliableWrite_Begin(client, svc_updatefrags, 4);
ClientReliableWrite_Byte(client, i);
ClientReliableWrite_Short(client, host_client->edict->v.frags);
}
host_client->old_frags = host_client->edict->v.frags;
}
// maxspeed/entgravity changes
ent = host_client->edict;
val = GetEdictFieldValue(ent, "gravity");
if (val && host_client->entgravity != val->_float) {
host_client->entgravity = val->_float;
ClientReliableWrite_Begin(host_client, svc_entgravity, 5);
ClientReliableWrite_Float(host_client, host_client->entgravity);
}
val = GetEdictFieldValue(ent, "maxspeed");
if (val && host_client->maxspeed != val->_float) {
host_client->maxspeed = val->_float;
ClientReliableWrite_Begin(host_client, svc_maxspeed, 5);
ClientReliableWrite_Float(host_client, host_client->maxspeed);
}
}
if (sv.datagram.overflowed)
SZ_Clear (&sv.datagram);
// append the broadcast messages to each client messages
for (j=0, client = svs.clients ; j<MAX_CLIENTS ; j++, client++)
{
if (client->state < cs_connected)
continue; // reliables go to all connected or spawned
ClientReliableCheckBlock(client, sv.reliable_datagram.cursize);
ClientReliableWrite_SZ(client, sv.reliable_datagram.data, sv.reliable_datagram.cursize);
if (client->state != cs_spawned)
continue; // datagrams only go to spawned
SZ_Write (&client->datagram
, sv.datagram.data
, sv.datagram.cursize);
}
SZ_Clear (&sv.reliable_datagram);
SZ_Clear (&sv.datagram);
}
#if defined(_WIN32) && !defined(__GNUC__)
#pragma optimize( "", off )
#endif
/*
=======================
SV_SendClientMessages
=======================
*/
void SV_SendClientMessages (void)
{
int i, j;
client_t *c;
// update frags, names, etc
SV_UpdateToReliableMessages ();
// build individual updates
for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
{
if (!c->state)
continue;
if (c->drop) {
SV_DropClient(c);
c->drop = false;
continue;
}
// check to see if we have a backbuf to stick in the reliable
if (c->num_backbuf) {
// will it fit?
if (c->netchan.message.cursize + c->backbuf_size[0] <
c->netchan.message.maxsize) {
Con_DPrintf("%s: backbuf %d bytes\n",
c->name, c->backbuf_size[0]);
// it'll fit
SZ_Write(&c->netchan.message, c->backbuf_data[0],
c->backbuf_size[0]);
//move along, move along
for (j = 1; j < c->num_backbuf; j++) {
memcpy(c->backbuf_data[j - 1], c->backbuf_data[j],
c->backbuf_size[j]);
c->backbuf_size[j - 1] = c->backbuf_size[j];
}
c->num_backbuf--;
if (c->num_backbuf) {
memset(&c->backbuf, 0, sizeof(c->backbuf));
c->backbuf.data = c->backbuf_data[c->num_backbuf - 1];
c->backbuf.cursize = c->backbuf_size[c->num_backbuf - 1];
c->backbuf.maxsize = sizeof(c->backbuf_data[c->num_backbuf - 1]);
}
}
}
// if the reliable message overflowed,
// drop the client
if (c->netchan.message.overflowed)
{
SZ_Clear (&c->netchan.message);
SZ_Clear (&c->datagram);
SV_BroadcastPrintf (PRINT_HIGH, "%s overflowed\n", c->name);
Con_Printf ("WARNING: reliable overflow for %s\n",c->name);
SV_DropClient (c);
c->send_message = true;
c->netchan.cleartime = 0; // don't choke this message
}
// only send messages if the client has sent one
// and the bandwidth is not choked
if (!c->send_message)
continue;
c->send_message = false; // try putting this after choke?
if (!sv.paused && !Netchan_CanPacket (&c->netchan))
{
c->chokecount++;
continue; // bandwidth choke
}
if (c->state == cs_spawned)
SV_SendClientDatagram (c);
else
Netchan_Transmit (&c->netchan, 0, NULL); // just update reliable
}
}
#if defined(_WIN32) && !defined(__GNUC__)
#pragma optimize( "", on )
#endif
/*
=======================
SV_SendMessagesToAll
FIXME: does this sequence right?
=======================
*/
void SV_SendMessagesToAll (void)
{
int i;
client_t *c;
for (i=0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++)
if (c->state) // FIXME: should this only send to active?
c->send_message = true;
SV_SendClientMessages ();
}