mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2025-03-13 06:02:33 +00:00
- add MAX_NET_EDICTS and MAX_NET_EDICTS_MASK defines, which are 512 and 511 respectively - change baselines to access the array directly, rather than through the entity's "data" field - cleanup SV_ReliableSVC_Emit - add entity remapping. the entity number used internally in the server no longer matches the number sent to the client, and it releases the mapping after 10 seconds of inuse, so there's no "512 entity limit" anymore. Still the MAX_EDICTS limit though, which is currently 768, but it can probably be defined to something much higher without any trouble.
871 lines
22 KiB
C
871 lines
22 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
|
|
|
|
*/
|
|
static const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#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/console.h"
|
|
#include "QF/cvar.h"
|
|
#include "QF/msg.h"
|
|
#include "QF/net_svc_qw.h"
|
|
#include "QF/sound.h" // FIXME: DEFAULT_SOUND_PACKET_*
|
|
#include "QF/sys.h"
|
|
|
|
#include "bothdefs.h"
|
|
#include "compat.h"
|
|
#include "server.h"
|
|
#include "sv_progs.h"
|
|
|
|
#define CHAN_AUTO 0
|
|
#define CHAN_WEAPON 1
|
|
#define CHAN_VOICE 2
|
|
#define CHAN_ITEM 3
|
|
#define CHAN_BODY 4
|
|
|
|
/* SV_Printf redirection */
|
|
|
|
char outputbuf[8000];
|
|
int con_printf_no_log;
|
|
redirect_t sv_redirected;
|
|
|
|
|
|
void
|
|
SV_FlushRedirect (void)
|
|
{
|
|
char send[8000 + 6];
|
|
net_svc_qwprint_t block;
|
|
|
|
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) {
|
|
block.level = PRINT_HIGH;
|
|
block.message = outputbuf;
|
|
SV_ReliableSVC_Emit (host_client, svc_qwprint, &block);
|
|
}
|
|
// clear it
|
|
outputbuf[0] = 0;
|
|
}
|
|
|
|
/*
|
|
SV_BeginRedirect
|
|
|
|
Send SV_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;
|
|
}
|
|
|
|
#define MAXPRINTMSG 4096
|
|
|
|
/*
|
|
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 int pending = 0; // partial line being printed
|
|
char premsg[MAXPRINTMSG];
|
|
unsigned char msg[MAXPRINTMSG];
|
|
char msg2[MAXPRINTMSG];
|
|
char msg3[MAXPRINTMSG];
|
|
|
|
time_t mytime = 0;
|
|
struct tm *local = NULL;
|
|
qboolean timestamps = false;
|
|
|
|
unsigned char *in, *out;
|
|
|
|
vsnprintf (premsg, sizeof (premsg), fmt, args);
|
|
in = premsg;
|
|
out = msg;
|
|
|
|
// expand FFnickFF to nick <userid>
|
|
do {
|
|
switch (*in) {
|
|
case 0xFF: {
|
|
char *end = strchr (in + 1, 0xFF);
|
|
int userid = 0;
|
|
int len;
|
|
int i;
|
|
|
|
if (!end)
|
|
end = in + strlen (in);
|
|
*end = '\0';
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
if (!svs.clients[i].state)
|
|
continue;
|
|
if (!strcmp (svs.clients[i].name, in + 1)) {
|
|
userid = svs.clients[i].userid;
|
|
break;
|
|
}
|
|
}
|
|
len = snprintf (out, sizeof (msg) - (out - msg),
|
|
"%s <%d>", in + 1, userid);
|
|
out += len;
|
|
in = end + 1;
|
|
break;
|
|
}
|
|
default:
|
|
*out++ = *in++;
|
|
}
|
|
} while (sizeof (msg) - (out - msg) > 0 && *in);
|
|
*out = '\0';
|
|
|
|
if (sv_redirected) { // Add to redirected message
|
|
if (strlen (msg) + strlen (outputbuf) > sizeof (outputbuf) - 1)
|
|
SV_FlushRedirect ();
|
|
strncat (outputbuf, msg, sizeof (outputbuf) - strlen (outputbuf));
|
|
}
|
|
if (!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);
|
|
strftime (msg3, sizeof (msg3), sv_timefmt->string, local);
|
|
|
|
snprintf (msg2, sizeof (msg2), "%s%s", msg3, msg);
|
|
} else {
|
|
snprintf (msg2, sizeof (msg2), "%s", msg);
|
|
}
|
|
if (msg2[strlen (msg2) - 1] != '\n') {
|
|
pending = 1;
|
|
} else {
|
|
pending = 0;
|
|
}
|
|
|
|
Con_Printf ("%s", msg2); // also echo to debugging console
|
|
if (sv_logfile)
|
|
Qprintf (sv_logfile, "%s", msg2);
|
|
}
|
|
}
|
|
|
|
void
|
|
SV_Printf (const char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
|
|
va_start (argptr, fmt);
|
|
SV_Print (fmt, argptr);
|
|
va_end (argptr);
|
|
}
|
|
|
|
/* EVENT MESSAGES */
|
|
|
|
static void
|
|
SV_PrintToClient (client_t *cl, int level, const char *string)
|
|
{
|
|
static unsigned char *buffer;
|
|
const unsigned char *a;
|
|
unsigned char *b;
|
|
int size;
|
|
static int buffer_size;
|
|
net_svc_qwprint_t block;
|
|
|
|
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\n",
|
|
buffer_size);
|
|
}
|
|
|
|
a = string;
|
|
b = buffer;
|
|
// strip 0xFFs
|
|
while ((*b = *a++))
|
|
if (*b != 0xFF)
|
|
b++;
|
|
|
|
block.level = level;
|
|
block.message = buffer;
|
|
SV_ReliableSVC_Emit (cl, svc_qwprint, &block);
|
|
}
|
|
|
|
/*
|
|
SV_ClientPrintf
|
|
|
|
Sends text across to be displayed if the level passes
|
|
*/
|
|
void
|
|
SV_ClientPrintf (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);
|
|
|
|
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)
|
|
continue;
|
|
|
|
SV_PrintToClient (cl, level, string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_BroadcastCommand
|
|
|
|
Sends text to all active clients
|
|
*/
|
|
void
|
|
SV_BroadcastCommand (const char *fmt, ...)
|
|
{
|
|
char string[1024];
|
|
va_list argptr;
|
|
net_svc_stufftext_t block;
|
|
|
|
if (!sv.state)
|
|
return;
|
|
va_start (argptr, fmt);
|
|
vsnprintf (string, sizeof (string), fmt, argptr);
|
|
va_end (argptr);
|
|
|
|
block.commands = string;
|
|
NET_SVC_Emit (svc_stufftext, &block, &sv.reliable_datagram);
|
|
}
|
|
|
|
/*
|
|
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)
|
|
{
|
|
byte *mask;
|
|
client_t *client;
|
|
int leafnum, j;
|
|
mleaf_t *leaf;
|
|
qboolean reliable;
|
|
int entnum; // XXX: evil hack for entmap
|
|
int netnum;
|
|
|
|
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);
|
|
}
|
|
|
|
// check if it's a svc_qwsound block. yes, this is an evil hack
|
|
if (sv.multicast.cursize >= 3 && *sv.multicast.data == svc_qwsound)
|
|
entnum = ((sv.multicast.data[1] >> 3) +
|
|
(sv.multicast.data[2] << 5)) & MAX_NET_EDICTS_MASK;
|
|
else
|
|
entnum = ENTMAP_INVALID;
|
|
|
|
// 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 (Length (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 - sv.worldmodel->leafs - 1;
|
|
if (!(mask[leafnum >> 3] & (1 << (leafnum & 7)))) {
|
|
// SV_Printf ("supressed multicast\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
inrange:
|
|
if (entnum != ENTMAP_INVALID) { // mmm, hackiness
|
|
netnum = SV_EntMap_Get (&client->entmap, entnum);
|
|
if (netnum == ENTMAP_INVALID)
|
|
continue;
|
|
sv.multicast.data[1] = ((netnum << 3) |
|
|
(sv.multicast.data[1] & 7)) & 255;
|
|
sv.multicast.data[2] = (netnum >> 5) |
|
|
(sv.multicast.data[2] & 240);
|
|
}
|
|
|
|
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
|
|
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,
|
|
float volume, float attenuation)
|
|
{
|
|
int i, sound_num;
|
|
qboolean use_phs;
|
|
qboolean reliable = false;
|
|
net_svc_qwsound_t block;
|
|
|
|
if (volume < 0 || volume > 1)
|
|
SV_Error ("SV_StartSound: volume = %f", 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]) {
|
|
SV_Printf ("SV_StartSound: %s not precacheed\n", sample);
|
|
return;
|
|
}
|
|
|
|
block.sound_num = sound_num;
|
|
|
|
block.entity = 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;
|
|
|
|
block.channel = channel;
|
|
|
|
block.volume = volume;
|
|
// 4 * 64 == 256, which overflows a byte. 4 is the stated max for
|
|
// it, and I don't want to break any progs, so I just nudge it
|
|
// down instead
|
|
if (attenuation == 4)
|
|
attenuation = 3.999;
|
|
block.attenuation = attenuation;
|
|
|
|
// use the entity origin unless it is a bmodel
|
|
if (SVfloat (entity, solid) == SOLID_BSP) {
|
|
for (i = 0; i < 3; i++)
|
|
block.position[i] = SVvector (entity, origin)[i] + 0.5 *
|
|
(SVvector (entity, mins)[i] + SVvector (entity, maxs)[i]);
|
|
} else {
|
|
VectorCopy (SVvector (entity, origin), block.position);
|
|
}
|
|
|
|
NET_SVC_Emit (svc_qwsound, &block, &sv.multicast);
|
|
|
|
if (use_phs)
|
|
SV_Multicast (block.position,
|
|
reliable ? MULTICAST_PHS_R : MULTICAST_PHS);
|
|
else
|
|
SV_Multicast (block.position,
|
|
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;
|
|
net_svc_chokecount_t chokecount;
|
|
net_svc_damage_t damage;
|
|
net_svc_setangle_t setangle;
|
|
|
|
ent = client->edict;
|
|
|
|
// send the chokecount for r_netgraph
|
|
if (client->chokecount) {
|
|
chokecount.count = client->chokecount;
|
|
NET_SVC_Emit (svc_chokecount, &chokecount, msg);
|
|
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));
|
|
damage.armor = SVfloat (ent, dmg_save);
|
|
damage.blood = SVfloat (ent, dmg_take);
|
|
for (i = 0; i < 3; i++)
|
|
damage.from[i] = SVvector (other, origin)[i] + 0.5 *
|
|
(SVvector (other, mins)[i] +
|
|
SVvector (other, maxs)[i]);
|
|
NET_SVC_Emit (svc_damage, &damage, msg);
|
|
|
|
SVfloat (ent, dmg_take) = 0;
|
|
SVfloat (ent, dmg_save) = 0;
|
|
}
|
|
// a fixangle might get lost in a dropped packet. Oh well.
|
|
if (SVfloat (ent, fixangle)) {
|
|
VectorCopy (SVvector (ent, angles), setangle.angles);
|
|
NET_SVC_Emit (svc_setangle, &setangle, msg);
|
|
SVfloat (ent, 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 i;
|
|
int stats[MAX_CL_STATS];
|
|
net_svc_qwupdatestat_t updatestat;
|
|
net_svc_updatestatlong_t updatestatlong;
|
|
|
|
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] = 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 (!client->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);
|
|
|
|
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) {
|
|
updatestat.stat = i;
|
|
updatestat.value = stats[i];
|
|
SV_ReliableSVC_Emit (client, svc_qwupdatestat, &updatestat);
|
|
} else {
|
|
updatestatlong.stat = i;
|
|
updatestatlong.value = stats[i];
|
|
SV_ReliableSVC_Emit (client, svc_updatestatlong, &updatestatlong);
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean
|
|
SV_SendClientDatagram (client_t *client)
|
|
{
|
|
byte buf[MAX_DATAGRAM];
|
|
sizebuf_t msg;
|
|
|
|
msg.maxsize = sizeof (buf);
|
|
msg.data = buf;
|
|
msg.allowoverflow = true;
|
|
SZ_Clear (&msg);
|
|
|
|
// 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);
|
|
|
|
// remove any stale mappings
|
|
SV_EntMap_Clean (&client->entmap, 10);
|
|
|
|
// 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, buf);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
SV_UpdateToReliableMessages (void)
|
|
{
|
|
client_t *client;
|
|
edict_t *ent;
|
|
int i, j;
|
|
pr_type_t *val;
|
|
net_svc_updatefrags_t updatefrags;
|
|
net_svc_entgravity_t entgravity;
|
|
net_svc_maxspeed_t maxspeed;
|
|
|
|
// 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 != (int) SVfloat (host_client->edict, frags)) {
|
|
updatefrags.player = i;
|
|
updatefrags.frags = SVfloat (host_client->edict, frags);
|
|
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
|
|
if (client->state < cs_connected)
|
|
continue;
|
|
SV_ReliableSVC_Emit (client, svc_updatefrags, &updatefrags);
|
|
}
|
|
|
|
host_client->old_frags = SVfloat (host_client->edict, frags);
|
|
}
|
|
// maxspeed/entgravity changes
|
|
ent = host_client->edict;
|
|
|
|
val = GetEdictFieldValue (&sv_pr_state, ent, "gravity");
|
|
if (val && host_client->entgravity != val->float_var) {
|
|
host_client->entgravity = val->float_var;
|
|
entgravity.gravity = host_client->entgravity;
|
|
SV_ReliableSVC_Emit (host_client, svc_entgravity, &entgravity);
|
|
}
|
|
val = GetEdictFieldValue (&sv_pr_state, ent, "maxspeed");
|
|
if (val && host_client->maxspeed != val->float_var) {
|
|
host_client->maxspeed = val->float_var;
|
|
maxspeed.maxspeed = host_client->maxspeed;
|
|
SV_ReliableSVC_Emit (host_client, svc_maxspeed, &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
|
|
|
|
void
|
|
SV_SendClientMessages (void)
|
|
{
|
|
client_t *c;
|
|
int i, j;
|
|
|
|
// 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) {
|
|
int i;
|
|
byte *data = Hunk_TempAlloc (MAX_MSGLEN + 8);
|
|
|
|
memset (data, 0, 8);
|
|
|
|
memcpy (data + 8, c->netchan.message.data,
|
|
c->netchan.message.cursize);
|
|
Analyze_Server_Packet (data, c->netchan.message.cursize + 8);
|
|
|
|
for (i = 0; i < c->num_backbuf; i++) {
|
|
memcpy (data + 8, c->backbuf_data[i], c->backbuf_size[i]);
|
|
Analyze_Server_Packet (data, c->backbuf_size[i] + 8);
|
|
}
|
|
|
|
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
|
|
}
|
|
// 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)
|
|
{
|
|
client_t *c;
|
|
int i;
|
|
|
|
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 ();
|
|
}
|