559 lines
14 KiB
C
559 lines
14 KiB
C
/*
|
|
Copyright (C) 1997-2001 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 the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
// sv_main.c -- server main program
|
|
|
|
#include "engine.h"
|
|
#include "server.h"
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
Msg redirection
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
|
|
|
|
void SV_FlushRedirect (int sv_redirected, char *outputbuf)
|
|
{
|
|
if (sv_redirected == RD_PACKET)
|
|
{
|
|
Netchan_OutOfBandPrint (NS_SERVER, net_from, "print\n%s", outputbuf);
|
|
}
|
|
else if (sv_redirected == RD_CLIENT)
|
|
{
|
|
MSG_WriteByte (&sv_client->netchan.message, svc_print);
|
|
MSG_WriteByte (&sv_client->netchan.message, PRINT_HIGH);
|
|
MSG_WriteString (&sv_client->netchan.message, outputbuf);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
EVENT MESSAGES
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
=================
|
|
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);
|
|
vsprintf (string, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
MSG_WriteByte (&cl->netchan.message, svc_print);
|
|
MSG_WriteByte (&cl->netchan.message, level);
|
|
MSG_WriteString (&cl->netchan.message, string);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SV_BroadcastPrintf
|
|
|
|
Sends text to all active clients
|
|
=================
|
|
*/
|
|
void SV_BroadcastPrintf (int level, char *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
char string[2048];
|
|
client_t *cl;
|
|
int i;
|
|
|
|
va_start (argptr,fmt);
|
|
vsprintf (string, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
// echo to console
|
|
if (dedicated->value)
|
|
{
|
|
char echo[1024];
|
|
int i;
|
|
|
|
// mask off high bits
|
|
for (i = 0; i < 1023 && string[i]; i++)
|
|
echo[i] = string[i] & 127;
|
|
echo[i] = 0;
|
|
Msg ("%s", echo );
|
|
}
|
|
|
|
for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++)
|
|
{
|
|
if (level < cl->messagelevel) continue;
|
|
if (cl->state != cs_spawned) continue;
|
|
|
|
MSG_WriteByte (&cl->netchan.message, svc_print);
|
|
MSG_WriteByte (&cl->netchan.message, level);
|
|
MSG_WriteString (&cl->netchan.message, 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);
|
|
vsprintf (string, fmt,argptr);
|
|
va_end (argptr);
|
|
|
|
MSG_Begin(svc_stufftext);
|
|
MSG_WriteString (&sv.multicast, string);
|
|
MSG_Send(MSG_ALL_R, NULL, NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
SV_Send
|
|
|
|
Sends the contents of sv.multicast to a subset of the clients,
|
|
then clears sv.multicast.
|
|
|
|
MULTICAST_ONE send to one client (ent can't be NULL)
|
|
MULTICAST_ALL same as broadcast (origin can be NULL)
|
|
MULTICAST_PVS send to clients potentially visible from org
|
|
MULTICAST_PHS send to clients potentially hearable from org
|
|
=================
|
|
*/
|
|
void _MSG_Send (msgtype_t to, vec3_t origin, edict_t *ent, const char *filename, int fileline)
|
|
{
|
|
byte *mask = NULL;
|
|
int leafnum = 0, cluster = 0;
|
|
int area1 = 0, area2 = 0;
|
|
int j, numclients = maxclients->value;
|
|
client_t *client, *current = svs.clients;
|
|
bool reliable = false;
|
|
|
|
/*if(origin == NULL || ent == NULL)
|
|
{
|
|
if(to == MSG_ONE || to == MSG_ONE_R)
|
|
Msg("MSG_Send: ent == NULL (called at %s:%i)\n", filename, fileline);
|
|
else Msg("MSG_Send: origin == NULL (called at %s:%i)\n", filename, fileline);
|
|
return;
|
|
}*/
|
|
|
|
// if doing a serverrecord, store everything
|
|
if (svs.demofile) SZ_Write (&svs.demo_multicast, sv.multicast.data, sv.multicast.cursize);
|
|
|
|
switch (to)
|
|
{
|
|
case MSG_ALL_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MSG_ALL:
|
|
// nothing to sort
|
|
break;
|
|
case MSG_PHS_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MSG_PHS:
|
|
if(origin == NULL) return;
|
|
leafnum = CM_PointLeafnum (origin);
|
|
cluster = CM_LeafCluster (leafnum);
|
|
mask = CM_ClusterPHS (cluster);
|
|
area1 = CM_LeafArea (leafnum);
|
|
break;
|
|
case MSG_PVS_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MSG_PVS:
|
|
if(origin == NULL) return;
|
|
leafnum = CM_PointLeafnum (origin);
|
|
cluster = CM_LeafCluster (leafnum);
|
|
mask = CM_ClusterPVS (cluster);
|
|
area1 = CM_LeafArea (leafnum);
|
|
break;
|
|
case MSG_ONE_R:
|
|
reliable = true; // intentional fallthrough
|
|
case MSG_ONE:
|
|
if(ent == NULL) return;
|
|
j = PRVM_NUM_FOR_EDICT(ent);
|
|
if (j < 1 || j > numclients) return;
|
|
current = svs.clients + (j - 1);
|
|
numclients = 1; // send to one
|
|
break;
|
|
default:
|
|
MsgWarn("MSG_Send: bad destination: %i (called at %s:%i)\n", to, filename, fileline);
|
|
return;
|
|
}
|
|
|
|
// send the data to all relevent clients (or once only)
|
|
for (j = 0, client = current; j < numclients; j++, client++)
|
|
{
|
|
if (client->state == cs_free || client->state == cs_zombie) continue;
|
|
if (client->state != cs_spawned && !reliable) continue;
|
|
|
|
if (mask)
|
|
{
|
|
area2 = CM_LeafArea (leafnum);
|
|
cluster = CM_LeafCluster (leafnum);
|
|
leafnum = CM_PointLeafnum (client->edict->progs.sv->origin);
|
|
if (!CM_AreasConnected (area1, area2)) continue;
|
|
if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7))))) continue;
|
|
}
|
|
|
|
if (reliable) _SZ_Write (&client->netchan.message, sv.multicast.data, sv.multicast.cursize, filename, fileline);
|
|
else _SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize, filename, fileline);
|
|
}
|
|
SZ_Clear (&sv.multicast);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
MSG_Begin
|
|
|
|
Misc helper function
|
|
=================
|
|
*/
|
|
|
|
void _MSG_Begin( int dest, const char *filename, int fileline )
|
|
{
|
|
_MSG_WriteChar (&sv.multicast, dest, filename, fileline );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SV_StartSound
|
|
|
|
Each entity can have eight independant sound sources, like voice,
|
|
weapon, feet, etc.
|
|
|
|
If cahnnel & 8, the sound will be sent to everyone, not just
|
|
things in the PHS.
|
|
|
|
FIXME: if entity isn't in PHS, they must be forced to be sent or
|
|
have the origin explicitly sent.
|
|
|
|
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)
|
|
|
|
Timeofs can range from 0.0 to 0.1 to cause sounds to be started
|
|
later in the frame than they normally would.
|
|
|
|
If origin is NULL, the origin is determined from the entity origin
|
|
or the midpoint of the entity box for bmodels.
|
|
==================
|
|
*/
|
|
void SV_StartSound (vec3_t origin, edict_t *entity, int channel, int soundindex, float volume, float attenuation, float timeofs)
|
|
{
|
|
int i, ent, flags, sendchan;
|
|
vec3_t origin_v;
|
|
bool use_phs;
|
|
|
|
if (volume < 0 || volume > 1.0)
|
|
{
|
|
MsgWarn("SV_StartSound: volume = %f\n", volume);
|
|
volume = bound(0, volume, 1.0);
|
|
}
|
|
if (attenuation < 0 || attenuation > 4)
|
|
{
|
|
MsgWarn("SV_StartSound: attenuation = %f\n", attenuation);
|
|
attenuation = bound(0, volume, 4);
|
|
}
|
|
if (timeofs < 0 || timeofs > 0.255)
|
|
{
|
|
MsgWarn("SV_StartSound: timeofs = %f\n", timeofs);
|
|
timeofs = bound(0, timeofs, 0.255 );
|
|
}
|
|
ent = PRVM_NUM_FOR_EDICT(entity);
|
|
|
|
if (channel & 8) // no PHS flag
|
|
{
|
|
use_phs = false;
|
|
channel &= 7;
|
|
}
|
|
else use_phs = true;
|
|
sendchan = (ent<<3) | (channel&7);
|
|
|
|
flags = 0;
|
|
if (volume != DEFAULT_SOUND_PACKET_VOLUME) flags |= SND_VOLUME;
|
|
if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) flags |= SND_ATTENUATION;
|
|
|
|
// the client doesn't know that bmodels have weird origins
|
|
// the origin can also be explicitly set
|
|
if (entity->progs.sv->solid == SOLID_BSP || !VectorIsNull(origin))
|
|
flags |= SND_POS;
|
|
|
|
// always send the entity number for channel overrides
|
|
flags |= SND_ENT;
|
|
|
|
if (timeofs) flags |= SND_OFFSET;
|
|
|
|
// use the entity origin unless it is a bmodel or explicitly specified
|
|
if (!origin)
|
|
{
|
|
origin = origin_v;
|
|
if (entity->progs.sv->solid == SOLID_BSP)
|
|
{
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
origin_v[i] = entity->progs.sv->origin[i]+0.5*(entity->progs.sv->mins[i]+entity->progs.sv->maxs[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
VectorCopy (entity->progs.sv->origin, origin_v);
|
|
}
|
|
}
|
|
|
|
MSG_Begin(svc_sound);
|
|
MSG_WriteByte (&sv.multicast, flags);
|
|
MSG_WriteByte (&sv.multicast, soundindex);
|
|
|
|
if (flags & SND_VOLUME) MSG_WriteByte(&sv.multicast, volume*255);
|
|
if (flags & SND_ATTENUATION) MSG_WriteByte(&sv.multicast, attenuation*64);
|
|
if (flags & SND_OFFSET) MSG_WriteByte(&sv.multicast, timeofs*1000);
|
|
if (flags & SND_ENT) MSG_WriteShort(&sv.multicast, sendchan);
|
|
if (flags & SND_POS) MSG_WritePos32(&sv.multicast, origin);
|
|
|
|
// if the sound doesn't attenuate,send it to everyone
|
|
// (global radio chatter, voiceovers, etc)
|
|
if (attenuation == ATTN_NONE) use_phs = false;
|
|
|
|
if (channel & CHAN_RELIABLE)
|
|
{
|
|
if(use_phs)
|
|
{
|
|
MSG_Send (MSG_PHS_R, origin, NULL);
|
|
}
|
|
else
|
|
{
|
|
MSG_Send(MSG_ALL_R, origin, NULL );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(use_phs)
|
|
{
|
|
MSG_Send (MSG_PHS, origin, NULL);
|
|
}
|
|
else
|
|
{
|
|
MSG_Send (MSG_ALL, origin, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
FRAME UPDATES
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
=======================
|
|
SV_SendClientDatagram
|
|
=======================
|
|
*/
|
|
bool SV_SendClientDatagram (client_t *client)
|
|
{
|
|
byte msg_buf[MAX_MSGLEN];
|
|
sizebuf_t msg;
|
|
|
|
SV_BuildClientFrame (client);
|
|
|
|
SZ_Init (&msg, msg_buf, sizeof(msg_buf));
|
|
|
|
// send over all the relevant entity_state_t
|
|
// and the player_state_t
|
|
SV_WriteFrameToClient (client, &msg);
|
|
|
|
// copy the accumulated multicast datagram
|
|
// for this client out to the message
|
|
// it is necessary for this to be after the WriteEntities
|
|
// so that entity references will be current
|
|
if (client->datagram.overflowed)
|
|
Msg ("WARNING: datagram overflowed for %s\n", client->name);
|
|
else
|
|
SZ_Write (&msg, client->datagram.data, client->datagram.cursize);
|
|
SZ_Clear (&client->datagram);
|
|
|
|
if (msg.overflowed)
|
|
{ // must have room left for the packet header
|
|
Msg ("WARNING: msg overflowed for %s\n", client->name);
|
|
SZ_Clear (&msg);
|
|
}
|
|
|
|
// send the datagram
|
|
Netchan_Transmit (&client->netchan, msg.cursize, msg.data);
|
|
|
|
// record the size for rate estimation
|
|
client->message_size[sv.framenum % RATE_MESSAGES] = msg.cursize;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
SV_DemoCompleted
|
|
==================
|
|
*/
|
|
void SV_DemoCompleted (void)
|
|
{
|
|
if (sv.demofile)
|
|
{
|
|
FS_Close (sv.demofile);
|
|
sv.demofile = NULL;
|
|
}
|
|
SV_Nextserver ();
|
|
}
|
|
|
|
|
|
/*
|
|
=======================
|
|
SV_RateDrop
|
|
|
|
Returns true if the client is over its current
|
|
bandwidth estimation and should not be sent another packet
|
|
=======================
|
|
*/
|
|
bool SV_RateDrop (client_t *c)
|
|
{
|
|
int total;
|
|
int i;
|
|
|
|
// never drop over the loopback
|
|
if (c->netchan.remote_address.type == NA_LOOPBACK)
|
|
return false;
|
|
|
|
total = 0;
|
|
|
|
for (i = 0 ; i < RATE_MESSAGES ; i++)
|
|
{
|
|
total += c->message_size[i];
|
|
}
|
|
|
|
if (total > c->rate)
|
|
{
|
|
c->surpressCount++;
|
|
c->message_size[sv.framenum % RATE_MESSAGES] = 0;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=======================
|
|
SV_SendClientMessages
|
|
=======================
|
|
*/
|
|
void SV_SendClientMessages (void)
|
|
{
|
|
int i;
|
|
client_t *c;
|
|
int msglen;
|
|
byte msgbuf[MAX_MSGLEN];
|
|
|
|
msglen = 0;
|
|
|
|
// read the next demo message if needed
|
|
if (sv.state == ss_demo && sv.demofile)
|
|
{
|
|
if (sv_paused->value) msglen = 0;
|
|
else
|
|
{
|
|
// get the next message
|
|
if(!FS_Read (sv.demofile, &msglen, 4))
|
|
{
|
|
SV_DemoCompleted ();
|
|
return;
|
|
}
|
|
msglen = LittleLong (msglen);
|
|
if (msglen == -1)
|
|
{
|
|
SV_DemoCompleted ();
|
|
return;
|
|
}
|
|
if (msglen > MAX_MSGLEN) Host_Error("SV_SendClientMessages: msglen > MAX_MSGLEN\n");
|
|
if(!FS_Read (sv.demofile, msgbuf, msglen))
|
|
{
|
|
SV_DemoCompleted ();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// send a message to each connected client
|
|
for (i = 0, c = svs.clients; i < maxclients->value; i++, c++)
|
|
{
|
|
if (!c->state) continue;
|
|
// 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_DropClient (c);
|
|
}
|
|
|
|
if (sv.state == ss_cinematic || sv.state == ss_demo || sv.state == ss_pic)
|
|
{
|
|
Netchan_Transmit (&c->netchan, msglen, msgbuf);
|
|
}
|
|
else if (c->state == cs_spawned)
|
|
{
|
|
// don't overrun bandwidth
|
|
if (SV_RateDrop (c)) continue;
|
|
SV_SendClientDatagram (c);
|
|
}
|
|
else
|
|
{
|
|
// just update reliable if needed
|
|
if (c->netchan.message.cursize || host.realtime - c->netchan.last_sent > 1.0f )
|
|
Netchan_Transmit (&c->netchan, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
|