mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-23 21:02:50 +00:00
f47e03e606
Modern maps can have many more leafs (eg, ad_tears has 98983 leafs). Using set_t makes dynamic leaf counts easy to support and the code much easier to read (though set_is_member and the iterators are a little slower). The main thing to watch out for is the novis set and the set returned by Mod_LeafPVS never shrink, and may have excess elements (ie, indicate that nonexistent leafs are visible).
937 lines
23 KiB
C
937 lines
23 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
|
|
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_STRING_H
|
|
# include <string.h>
|
|
#endif
|
|
#ifdef HAVE_STRINGS_H
|
|
# include <strings.h>
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#include "QF/cvar.h"
|
|
#include "QF/console.h"
|
|
#include "QF/dstring.h"
|
|
#include "QF/msg.h"
|
|
#include "QF/set.h"
|
|
#include "QF/sound.h" // FIXME: DEFAULT_SOUND_PACKET_*
|
|
#include "QF/sys.h"
|
|
|
|
#include "compat.h"
|
|
|
|
#include "qw/bothdefs.h"
|
|
#include "qw/include/server.h"
|
|
#include "qw/include/sv_progs.h"
|
|
#include "qw/include/sv_recorder.h"
|
|
|
|
#define CHAN_AUTO 0
|
|
#define CHAN_WEAPON 1
|
|
#define CHAN_VOICE 2
|
|
#define CHAN_ITEM 3
|
|
#define CHAN_BODY 4
|
|
|
|
/* SV_Printf redirection */
|
|
|
|
dstring_t outputbuf = {&dstring_default_mem};
|
|
int con_printf_no_log;
|
|
redirect_t sv_redirected;
|
|
|
|
|
|
static void
|
|
SV_FlushRedirect (void)
|
|
{
|
|
char send[8000 + 6];
|
|
size_t count;
|
|
int bytes;
|
|
const char *p;
|
|
|
|
if (!outputbuf.size)
|
|
return;
|
|
|
|
count = strlen (outputbuf.str);
|
|
if (sv_redirected == RD_PACKET) {
|
|
|
|
send[0] = 0xff;
|
|
send[1] = 0xff;
|
|
send[2] = 0xff;
|
|
send[3] = 0xff;
|
|
send[4] = A2C_PRINT;
|
|
|
|
p = outputbuf.str;
|
|
while (count) {
|
|
bytes = min (count, sizeof (send) - 5);
|
|
memcpy (send + 5, p, bytes);
|
|
Netchan_SendPacket (bytes + 5, send, net_from);
|
|
p += bytes;
|
|
count -= bytes;
|
|
}
|
|
} else if (sv_redirected == RD_CLIENT || sv_redirected > RD_MOD) {
|
|
client_t *cl;
|
|
|
|
if (sv_redirected > RD_MOD) {
|
|
cl = svs.clients + sv_redirected - RD_MOD - 1;
|
|
if (cl->state != cs_spawned) //FIXME record to mvd?
|
|
count = 0;
|
|
} else {
|
|
cl = host_client;
|
|
}
|
|
p = outputbuf.str;
|
|
while (count) {
|
|
// +/- 3 for svc_print, PRINT_HIGH and nul byte
|
|
// min of 4 because we don't want to send an effectively empty
|
|
// message
|
|
bytes = MSG_ReliableCheckSize (&cl->backbuf, count + 3, 4) - 3;
|
|
// if writing another packet would overflow the client, just drop
|
|
// the rest of the data. getting rudely disconnected would be much
|
|
// more annoying than losing the tail end of the output
|
|
if (bytes <= 0)
|
|
break;
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_print, bytes + 3);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, PRINT_HIGH);
|
|
MSG_ReliableWrite_SZ (&cl->backbuf, p, bytes);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, 0);
|
|
p += bytes;
|
|
count -= bytes;
|
|
}
|
|
}
|
|
// RD_MOD doesn't do anything :)
|
|
// clear it
|
|
dstring_clear (&outputbuf);
|
|
}
|
|
|
|
/*
|
|
SV_BeginRedirect
|
|
|
|
Send SV_Printf data to the remote client
|
|
instead of the console
|
|
*/
|
|
void
|
|
SV_BeginRedirect (redirect_t rd)
|
|
{
|
|
sv_redirected = rd;
|
|
dstring_clear (&outputbuf);
|
|
}
|
|
|
|
void
|
|
SV_EndRedirect (void)
|
|
{
|
|
SV_FlushRedirect ();
|
|
sv_redirected = RD_NONE;
|
|
}
|
|
|
|
#define MAXPRINTMSG 4096
|
|
static int
|
|
find_userid (const char *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
if (!svs.clients[i].state)
|
|
continue;
|
|
if (!strcmp (svs.clients[i].name, name)) {
|
|
return svs.clients[i].userid;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define hstrftime ((size_t (*)(char *s, size_t, const char *, \
|
|
const struct tm*))strftime)
|
|
|
|
/*
|
|
SV_Printf
|
|
|
|
Handles cursor positioning, line wrapping, etc
|
|
*/
|
|
// FIXME: the msg variables need to be renamed/cleaned up
|
|
void
|
|
SV_Print (const char *fmt, va_list args)
|
|
{
|
|
static dstring_t *premsg;
|
|
static dstring_t *msg;
|
|
static dstring_t *msg2;
|
|
static int pending = 0; // partial line being printed
|
|
|
|
char msg3[MAXPRINTMSG];
|
|
|
|
time_t mytime = 0;
|
|
struct tm *local = NULL;
|
|
qboolean timestamps = false;
|
|
|
|
char *in;
|
|
|
|
if (!premsg) {
|
|
premsg = dstring_newstr ();
|
|
msg = dstring_new ();
|
|
msg2 = dstring_new ();
|
|
}
|
|
dstring_clearstr (msg);
|
|
|
|
dvsprintf (premsg, fmt, args);
|
|
in = premsg->str;
|
|
|
|
if (!*premsg->str)
|
|
return;
|
|
// expand FFnickFF to nick <userid>
|
|
do {
|
|
char *beg = strchr (in, 0xFF);
|
|
if (beg) {
|
|
char *name = beg + 1;
|
|
char *end = strchr (name, 0xFF);
|
|
if (!end) {
|
|
end = beg + strlen (name);
|
|
}
|
|
*end = 0;
|
|
dstring_appendsubstr (msg, in, beg - in);
|
|
dasprintf (msg, "%s <%d>", name, find_userid (name));
|
|
in = end + 1;
|
|
} else {
|
|
dstring_appendstr (msg, in);
|
|
break;
|
|
}
|
|
} while (*in);
|
|
|
|
if (sv_redirected) { // Add to redirected message
|
|
dstring_appendstr (&outputbuf, msg->str);
|
|
}
|
|
if (*msg->str && !con_printf_no_log) {
|
|
// We want to output to console and maybe logfile
|
|
if (sv_timestamps && sv_timefmt && sv_timefmt->string
|
|
&& sv_timestamps->int_val && !pending)
|
|
timestamps = true;
|
|
|
|
if (timestamps) {
|
|
mytime = time (NULL);
|
|
local = localtime (&mytime);
|
|
hstrftime (msg3, sizeof (msg3), sv_timefmt->string, local);
|
|
|
|
dsprintf (msg2, "%s%s", msg3, msg->str);
|
|
} else {
|
|
dsprintf (msg2, "%s", msg->str);
|
|
}
|
|
if (msg2->str[strlen (msg2->str) - 1] != '\n') {
|
|
pending = 1;
|
|
} else {
|
|
pending = 0;
|
|
}
|
|
|
|
Con_Printf ("%s", msg2->str); // also echo to debugging console
|
|
}
|
|
}
|
|
|
|
/* EVENT MESSAGES */
|
|
|
|
static void
|
|
SV_PrintToClient (client_t *cl, int level, const char *string)
|
|
{
|
|
static char *buffer;
|
|
const char *a;
|
|
unsigned char *b;
|
|
int size;
|
|
static int buffer_size;
|
|
|
|
size = strlen (string) + 1;
|
|
if (size > buffer_size) {
|
|
buffer_size = (size + 1023) & ~1023; // 1k multiples
|
|
if (buffer)
|
|
free (buffer);
|
|
buffer = malloc (buffer_size);
|
|
if (!buffer)
|
|
Sys_Error ("SV_PrintToClient: could not allocate %d bytes",
|
|
buffer_size);
|
|
}
|
|
|
|
a = string;
|
|
b = (byte *) buffer;
|
|
// strip 0xFFs
|
|
while ((*b = *a++))
|
|
if (*b != 0xFF)
|
|
b++;
|
|
|
|
MSG_ReliableWrite_Begin (&cl->backbuf, svc_print, strlen (buffer) + 3);
|
|
MSG_ReliableWrite_Byte (&cl->backbuf, level);
|
|
MSG_ReliableWrite_String (&cl->backbuf, buffer);
|
|
}
|
|
|
|
/*
|
|
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 (const vec3_t origin, int to)
|
|
{
|
|
set_t *mask;
|
|
client_t *client;
|
|
int leafnum, j;
|
|
mleaf_t *leaf;
|
|
qboolean reliable;
|
|
mod_brush_t *brush = &sv.worldmodel->brush;
|
|
|
|
leaf = Mod_PointInLeaf (origin, sv.worldmodel);
|
|
if (!leaf)
|
|
leafnum = 0;
|
|
else
|
|
leafnum = leaf - sv.worldmodel->brush.leafs;
|
|
|
|
reliable = false;
|
|
|
|
switch (to) {
|
|
case MULTICAST_ALL_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MULTICAST_ALL:
|
|
mask = &sv.pvs[0]; // leaf 0 is everything;
|
|
break;
|
|
|
|
case MULTICAST_PHS_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MULTICAST_PHS:
|
|
mask = &sv.phs[leafnum];
|
|
break;
|
|
|
|
case MULTICAST_PVS_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MULTICAST_PVS:
|
|
mask = &sv.pvs[leafnum];
|
|
break;
|
|
|
|
default:
|
|
Sys_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, SVvector (client->edict, origin), delta);
|
|
if (VectorLength (delta) <= 1024)
|
|
goto inrange;
|
|
}
|
|
|
|
leaf = Mod_PointInLeaf (SVvector (client->edict, origin),
|
|
sv.worldmodel);
|
|
if (leaf) {
|
|
// -1 is because pvs rows are 1 based, not 0 based like leafs
|
|
leafnum = leaf - brush->leafs - 1;
|
|
if (!set_is_member (mask, leafnum)) {
|
|
// SV_Printf ("supressed multicast\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
inrange:
|
|
if (reliable) {
|
|
MSG_ReliableCheckBlock (&client->backbuf, sv.multicast.cursize);
|
|
MSG_ReliableWrite_SZ (&client->backbuf, sv.multicast.data,
|
|
sv.multicast.cursize);
|
|
} else
|
|
SZ_Write (&client->datagram, sv.multicast.data,
|
|
sv.multicast.cursize);
|
|
}
|
|
|
|
if (sv.recorders) {
|
|
if (reliable) {
|
|
sizebuf_t *dbuf = SVR_WriteBegin (dem_all, 0,
|
|
sv.multicast.cursize);
|
|
SZ_Write (dbuf, sv.multicast.data, sv.multicast.cursize);
|
|
} else
|
|
SZ_Write (SVR_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
|
|
already running on that entity/channel pair.
|
|
|
|
An attenuation of 0 will play full volume everywhere in the level.
|
|
Larger attenuations will drop off. (max 4 attenuation)
|
|
*/
|
|
void
|
|
SV_StartSound (edict_t *entity, int channel, const char *sample, int volume,
|
|
float attenuation)
|
|
{
|
|
int ent, sound_num, i;
|
|
qboolean use_phs;
|
|
qboolean reliable = false;
|
|
vec3_t origin;
|
|
|
|
if (volume < 0 || volume > 255)
|
|
Sys_Error ("SV_StartSound: volume = %i", volume);
|
|
|
|
if (attenuation < 0 || attenuation > 4)
|
|
Sys_Error ("SV_StartSound: attenuation = %f", attenuation);
|
|
|
|
if (channel < 0 || channel > 15)
|
|
Sys_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]) {
|
|
SV_Printf ("SV_StartSound: %s not precacheed\n", sample);
|
|
return;
|
|
}
|
|
|
|
ent = NUM_FOR_EDICT (&sv_pr_state, 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;
|
|
|
|
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 (SVfloat (entity, solid) == SOLID_BSP) {
|
|
for (i = 0; i < 3; i++)
|
|
origin[i] = SVvector (entity, origin)[i] + 0.5 *
|
|
(SVvector (entity, mins)[i] + SVvector (entity, maxs)[i]);
|
|
} else {
|
|
VectorCopy (SVvector (entity, 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);
|
|
MSG_WriteCoordV (&sv.multicast, origin);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void
|
|
SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg)
|
|
{
|
|
edict_t *ent, *other;
|
|
int i, clnum;
|
|
sizebuf_t *dbuf;
|
|
|
|
ent = client->edict;
|
|
|
|
clnum = NUM_FOR_EDICT (&sv_pr_state, ent) - 1;
|
|
|
|
// 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 (SVfloat (ent, dmg_take) || SVfloat (ent, dmg_save)) {
|
|
other = PROG_TO_EDICT (&sv_pr_state, SVentity (ent, dmg_inflictor));
|
|
MSG_WriteByte (msg, svc_damage);
|
|
MSG_WriteByte (msg, SVfloat (ent, dmg_save));
|
|
MSG_WriteByte (msg, SVfloat (ent, dmg_take));
|
|
for (i = 0; i < 3; i++)
|
|
MSG_WriteCoord (msg, SVvector (other, origin)[i] + 0.5 *
|
|
(SVvector (other, mins)[i] +
|
|
SVvector (other, maxs)[i]));
|
|
SVfloat (ent, dmg_take) = 0;
|
|
SVfloat (ent, dmg_save) = 0;
|
|
}
|
|
|
|
// add this to server demo
|
|
if (sv.recorders && msg->cursize) {
|
|
dbuf = SVR_WriteBegin (dem_single, clnum, msg->cursize);
|
|
SZ_Write (dbuf, msg->data, msg->cursize);
|
|
}
|
|
|
|
// a fixangle might get lost in a dropped packet. Oh well.
|
|
if (SVfloat (ent, fixangle)) {
|
|
vec_t *angles = SVvector (ent, angles);
|
|
MSG_WriteByte (msg, svc_setangle);
|
|
MSG_WriteAngleV (msg, angles);
|
|
SVfloat (ent, fixangle) = 0;
|
|
|
|
if (sv.recorders) {
|
|
dbuf = SVR_Datagram ();
|
|
MSG_WriteByte (dbuf, svc_setangle);
|
|
MSG_WriteByte (dbuf, clnum);
|
|
MSG_WriteAngleV (dbuf, angles);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
SV_GetStats (edict_t *ent, int spectator, int stats[])
|
|
{
|
|
memset (stats, 0, sizeof (int) * MAX_CL_STATS);
|
|
|
|
stats[STAT_HEALTH] = SVfloat (ent, health);
|
|
stats[STAT_WEAPON] = SV_ModelIndex (PR_GetString (&sv_pr_state,
|
|
SVstring (ent, weaponmodel)));
|
|
stats[STAT_AMMO] = SVfloat (ent, currentammo);
|
|
stats[STAT_ARMOR] = SVfloat (ent, armorvalue);
|
|
stats[STAT_SHELLS] = SVfloat (ent, ammo_shells);
|
|
stats[STAT_NAILS] = SVfloat (ent, ammo_nails);
|
|
stats[STAT_ROCKETS] = SVfloat (ent, ammo_rockets);
|
|
stats[STAT_CELLS] = SVfloat (ent, ammo_cells);
|
|
if (!spectator)
|
|
stats[STAT_ACTIVEWEAPON] = SVfloat (ent, weapon);
|
|
// stuff the sigil bits into the high bits of items for sbar
|
|
stats[STAT_ITEMS] = ((int) SVfloat (ent, items)
|
|
| ((int) *sv_globals.serverflags << 28));
|
|
|
|
// Extensions to the QW 2.40 protocol for Mega2k --KB
|
|
stats[STAT_VIEWHEIGHT] = (int) SVvector (ent, view_ofs)[2];
|
|
|
|
// FIXME: this should become a * key! --KB
|
|
if (SVfloat (ent, movetype) == MOVETYPE_FLY
|
|
&& !atoi (Info_ValueForKey (svs.info, "playerfly")))
|
|
SVfloat (ent, movetype) = MOVETYPE_WALK;
|
|
|
|
stats[STAT_FLYMODE] = (SVfloat (ent, movetype) == MOVETYPE_FLY);
|
|
}
|
|
|
|
/*
|
|
SV_UpdateClientStats
|
|
|
|
Performs a delta update of the stats array. This should be performed only
|
|
when a reliable message can be delivered this frame.
|
|
*/
|
|
static void
|
|
SV_UpdateClientStats (client_t *client)
|
|
{
|
|
edict_t *ent;
|
|
int i;
|
|
int stats[MAX_CL_STATS];
|
|
|
|
ent = client->edict;
|
|
|
|
// 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;
|
|
|
|
SV_GetStats (ent, client->spectator, stats);
|
|
|
|
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) {
|
|
MSG_ReliableWrite_Begin (&client->backbuf, svc_updatestat, 3);
|
|
MSG_ReliableWrite_Byte (&client->backbuf, i);
|
|
MSG_ReliableWrite_Byte (&client->backbuf, stats[i]);
|
|
} else {
|
|
MSG_ReliableWrite_Begin (&client->backbuf,
|
|
svc_updatestatlong, 6);
|
|
MSG_ReliableWrite_Byte (&client->backbuf, i);
|
|
MSG_ReliableWrite_Long (&client->backbuf, stats[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static 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);
|
|
|
|
if (client->state == cs_server)
|
|
return true;
|
|
|
|
// send over all the objects that are in the PVS
|
|
// this will include clients, a packetentities, and
|
|
// possibly a nails update
|
|
SV_WriteEntitiesToClient (&client->delta, &msg);
|
|
|
|
// copy the accumulated multicast datagram
|
|
// for this client out to the message
|
|
if (client->datagram.overflowed)
|
|
SV_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) {
|
|
SV_Printf ("WARNING: msg overflowed for %s\n", client->name);
|
|
SZ_Clear (&msg);
|
|
}
|
|
// send the datagram
|
|
Netchan_Transmit (&client->netchan, msg.cursize, msg.data);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
SV_UpdateToReliableMessages (void)
|
|
{
|
|
client_t *client;
|
|
edict_t *ent;
|
|
int i, j;
|
|
sizebuf_t *dbuf;
|
|
|
|
// 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 && host_client->state != cs_server)
|
|
continue;
|
|
if (host_client->sendinfo) {
|
|
host_client->sendinfo = false;
|
|
SV_FullClientUpdate (host_client, &sv.reliable_datagram);
|
|
}
|
|
if (host_client->old_frags != (int) SVfloat (host_client->edict, frags)) {
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
|
|
if (client->state < cs_connected)
|
|
continue;
|
|
MSG_ReliableWrite_Begin (&client->backbuf, svc_updatefrags, 4);
|
|
MSG_ReliableWrite_Byte (&client->backbuf, i);
|
|
MSG_ReliableWrite_Short (&client->backbuf,
|
|
SVfloat (host_client->edict, frags));
|
|
}
|
|
|
|
if (sv.recorders) {
|
|
dbuf = SVR_WriteBegin (dem_all, 0, 4);
|
|
MSG_WriteByte (dbuf, svc_updatefrags);
|
|
MSG_WriteByte (dbuf, i);
|
|
MSG_WriteShort (dbuf, SVfloat (host_client->edict, frags));
|
|
}
|
|
|
|
host_client->old_frags = SVfloat (host_client->edict, frags);
|
|
}
|
|
// maxspeed/entgravity changes
|
|
ent = host_client->edict;
|
|
|
|
if (sv_fields.gravity != -1
|
|
&& host_client->entgravity != SVfloat (ent, gravity)) {
|
|
host_client->entgravity = SVfloat (ent, gravity);
|
|
if (host_client->state != cs_server) {
|
|
MSG_ReliableWrite_Begin (&host_client->backbuf,
|
|
svc_entgravity, 5);
|
|
MSG_ReliableWrite_Float (&host_client->backbuf,
|
|
host_client->entgravity);
|
|
}
|
|
if (sv.recorders) {
|
|
dbuf = SVR_WriteBegin (dem_single, i, 5);
|
|
MSG_WriteByte (dbuf, svc_entgravity);
|
|
MSG_WriteFloat (dbuf, host_client->entgravity);
|
|
}
|
|
}
|
|
if (sv_fields.maxspeed != -1
|
|
&& host_client->maxspeed != SVfloat (ent, maxspeed)) {
|
|
host_client->maxspeed = SVfloat (ent, maxspeed);
|
|
if (host_client->state != cs_server) {
|
|
MSG_ReliableWrite_Begin (&host_client->backbuf,
|
|
svc_maxspeed, 5);
|
|
MSG_ReliableWrite_Float (&host_client->backbuf,
|
|
host_client->maxspeed);
|
|
}
|
|
if (sv.recorders) {
|
|
dbuf = SVR_WriteBegin (dem_single, i, 5);
|
|
MSG_WriteByte (dbuf, svc_maxspeed);
|
|
MSG_WriteFloat (dbuf, 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
|
|
|
|
MSG_ReliableCheckBlock (&client->backbuf,
|
|
sv.reliable_datagram.cursize);
|
|
MSG_ReliableWrite_SZ (&client->backbuf, sv.reliable_datagram.data,
|
|
sv.reliable_datagram.cursize);
|
|
|
|
if (client->state != cs_spawned)
|
|
continue; // datagrams go to only spawned
|
|
SZ_Write (&client->datagram, sv.datagram.data, sv.datagram.cursize);
|
|
}
|
|
|
|
if (sv.recorders && sv.reliable_datagram.cursize) {
|
|
dbuf = SVR_WriteBegin (dem_all, 0, sv.reliable_datagram.cursize);
|
|
SZ_Write (dbuf, sv.reliable_datagram.data,
|
|
sv.reliable_datagram.cursize);
|
|
}
|
|
if (sv.recorders)
|
|
SZ_Write (SVR_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
|
|
|
|
void
|
|
SV_SendClientMessages (void)
|
|
{
|
|
client_t *c;
|
|
int i;
|
|
|
|
// update frags, names, etc
|
|
SV_UpdateToReliableMessages ();
|
|
|
|
// build individual updates
|
|
for (i = 0, c = svs.clients; i < MAX_CLIENTS; i++, c++) {
|
|
if (c->state != cs_server) {
|
|
if (c->state < cs_zombie)
|
|
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->backbuf.num_backbuf)
|
|
MSG_Reliable_Send (&c->backbuf);
|
|
|
|
// 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);
|
|
SV_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
|
|
}
|
|
// send messages only 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 || c->state == cs_server)
|
|
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)
|
|
{
|
|
client_t *c;
|
|
int i;
|
|
|
|
for (i = 0, c = svs.clients; i < MAX_CLIENTS; i++, c++)
|
|
if (c->state < cs_zombie) // FIXME: should this send to only active?
|
|
c->send_message = true;
|
|
|
|
SV_SendClientMessages ();
|
|
}
|
|
|
|
void
|
|
SV_Printf (const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
SV_Print (fmt, argptr);
|
|
va_end (argptr);
|
|
}
|
|
|
|
/*
|
|
SV_ClientPrintf
|
|
|
|
Sends text across to be displayed if the level passes
|
|
*/
|
|
void
|
|
SV_ClientPrintf (int recorder, client_t *cl, int level, const char *fmt, ...)
|
|
{
|
|
char string[1024];
|
|
va_list argptr;
|
|
|
|
if (level < cl->messagelevel)
|
|
return;
|
|
|
|
va_start (argptr, fmt);
|
|
vsnprintf (string, sizeof (string), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
if (recorder && sv.recorders) {
|
|
sizebuf_t *dbuf = SVR_WriteBegin (dem_single, cl - svs.clients,
|
|
strlen (string) + 3);
|
|
MSG_WriteByte (dbuf, svc_print);
|
|
MSG_WriteByte (dbuf, level);
|
|
MSG_WriteString (dbuf, string);
|
|
}
|
|
|
|
SV_PrintToClient (cl, level, string);
|
|
}
|
|
|
|
/*
|
|
SV_BroadcastPrintf
|
|
|
|
Sends text to all active clients
|
|
*/
|
|
void
|
|
SV_BroadcastPrintf (int level, const char *fmt, ...)
|
|
{
|
|
char string[1024];
|
|
client_t *cl;
|
|
int i;
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
vsnprintf (string, sizeof (string), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
SV_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 < cs_zombie) //FIXME record to mvd
|
|
continue;
|
|
|
|
SV_PrintToClient (cl, level, string);
|
|
}
|
|
|
|
if (sv.recorders) {
|
|
sizebuf_t *dbuf = SVR_WriteBegin (dem_all, cl - svs.clients,
|
|
strlen (string) + 3);
|
|
MSG_WriteByte (dbuf, svc_print);
|
|
MSG_WriteByte (dbuf, level);
|
|
MSG_WriteString (dbuf, string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_BroadcastCommand
|
|
|
|
Sends text to all active clients
|
|
*/
|
|
void
|
|
SV_BroadcastCommand (const char *fmt, ...)
|
|
{
|
|
char string[1024];
|
|
va_list argptr;
|
|
|
|
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);
|
|
}
|