mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-14 17:01:22 +00:00
12c84046f3
This is an extremely extensive patch as it hits every cvar, and every usage of the cvars. Cvars no longer store the value they control, instead, they use a cexpr value object to reference the value and specify the value's type (currently, a null type is used for strings). Non-string cvars are passed through cexpr, allowing expressions in the cvars' settings. Also, cvars have returned to an enhanced version of the original (id quake) registration scheme. As a minor benefit, relevant code having direct access to the cvar-controlled variables is probably a slight optimization as it removed a pointer dereference, and the variables can be located for data locality. The static cvar descriptors are made private as an additional safety layer, though there's nothing stopping external modification via Cvar_FindVar (which is needed for adding listeners). While not used yet (partly due to working out the design), cvars can have a validation function. Registering a cvar allows a primary listener (and its data) to be specified: it will always be called first when the cvar is modified. The combination of proper listeners and direct access to the controlled variable greatly simplifies the more complex cvar interactions as much less null checking is required, and there's no need for one cvar's callback to call another's. nq-x11 is known to work at least well enough for the demos. More testing will come.
1234 lines
32 KiB
C
1234 lines
32 KiB
C
/*
|
|
sv_main.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
|
|
|
|
#include "QF/cmd.h"
|
|
#include "QF/cvar.h"
|
|
#include "QF/msg.h"
|
|
#include "QF/mathlib.h"
|
|
#include "QF/set.h"
|
|
#include "QF/sys.h"
|
|
#include "QF/va.h"
|
|
|
|
#include "compat.h"
|
|
#include "world.h"
|
|
|
|
#include "nq/include/host.h"
|
|
#include "nq/include/server.h"
|
|
#include "nq/include/sv_progs.h"
|
|
|
|
server_t sv;
|
|
server_static_t svs;
|
|
double sv_frametime;
|
|
|
|
char localmodels[MAX_MODELS][6]; // inline model names for precache
|
|
|
|
int sv_protocol = PROTOCOL_FITZQUAKE;
|
|
|
|
static void
|
|
SV_Protocol_f (void)
|
|
{
|
|
int i;
|
|
|
|
switch (Cmd_Argc ()) {
|
|
case 1:
|
|
Sys_Printf ("\"sv_protocol\" is \"%i\"\n", sv_protocol);
|
|
break;
|
|
case 2:
|
|
i = atoi (Cmd_Argv (1));
|
|
if (i != PROTOCOL_NETQUAKE && i != PROTOCOL_FITZQUAKE) {
|
|
Sys_Printf ("sv_protocol must be %i or %i\n",
|
|
PROTOCOL_NETQUAKE, PROTOCOL_FITZQUAKE);
|
|
} else {
|
|
sv_protocol = i;
|
|
if (sv.active)
|
|
Sys_Printf ("changes will not take effect until the next "
|
|
"level load.\n");
|
|
}
|
|
break;
|
|
default:
|
|
Sys_Printf ("usage: sv_protocol <protocol>\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
SV_Init (void)
|
|
{
|
|
int i;
|
|
|
|
SV_Progs_Init ();
|
|
|
|
SV_Physics_Init_Cvars ();
|
|
SV_User_Init_Cvars ();
|
|
|
|
Cmd_AddCommand ("sv_protocol", SV_Protocol_f, "set the protocol to be "
|
|
"used after the next map load");
|
|
|
|
for (i = 0; i < MAX_MODELS; i++)
|
|
snprintf (localmodels[i], sizeof (localmodels[i]), "*%i", i);
|
|
}
|
|
|
|
// EVENT MESSAGES =============================================================
|
|
|
|
/*
|
|
SV_StartParticle
|
|
|
|
Make sure the event gets sent to all clients
|
|
*/
|
|
void
|
|
SV_StartParticle (const vec3_t org, const vec3_t dir, int color, int count)
|
|
{
|
|
int i, v;
|
|
|
|
if (sv.datagram.cursize > MAX_DATAGRAM - 16)
|
|
return;
|
|
MSG_WriteByte (&sv.datagram, svc_particle);
|
|
MSG_WriteCoordV (&sv.datagram, org);
|
|
for (i = 0; i < 3; i++) {
|
|
v = dir[i] * 16;
|
|
if (v > 127)
|
|
v = 127;
|
|
else if (v < -128)
|
|
v = -128;
|
|
MSG_WriteByte (&sv.datagram, v);
|
|
}
|
|
MSG_WriteByte (&sv.datagram, count);
|
|
MSG_WriteByte (&sv.datagram, color);
|
|
}
|
|
|
|
/*
|
|
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, field_mask, sound_num;
|
|
vec3_t v;
|
|
|
|
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 > 7)
|
|
Sys_Error ("SV_StartSound: channel = %i", channel);
|
|
|
|
if (sv.datagram.cursize > MAX_DATAGRAM - 16)
|
|
return;
|
|
|
|
// 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]) {
|
|
Sys_Printf ("SV_StartSound: %s not precacheed\n", sample);
|
|
return;
|
|
}
|
|
|
|
ent = NUM_FOR_EDICT (&sv_pr_state, entity);
|
|
|
|
field_mask = 0;
|
|
if (volume != DEFAULT_SOUND_PACKET_VOLUME)
|
|
field_mask |= SND_VOLUME;
|
|
if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
|
|
field_mask |= SND_ATTENUATION;
|
|
|
|
if (ent >= 8192) {
|
|
if (sv.protocol == PROTOCOL_NETQUAKE)
|
|
return; //don't send any info protocol can't support
|
|
else
|
|
field_mask |= SND_LARGEENTITY;
|
|
}
|
|
if (sound_num >= 256 || channel >= 8) {
|
|
if (sv.protocol == PROTOCOL_NETQUAKE)
|
|
return; //don't send any info protocol can't support
|
|
else
|
|
field_mask |= SND_LARGESOUND;
|
|
}
|
|
|
|
// directed messages go to only the entity on which they are targeted
|
|
MSG_WriteByte (&sv.datagram, svc_sound);
|
|
MSG_WriteByte (&sv.datagram, field_mask);
|
|
if (field_mask & SND_VOLUME)
|
|
MSG_WriteByte (&sv.datagram, volume);
|
|
if (field_mask & SND_ATTENUATION)
|
|
MSG_WriteByte (&sv.datagram, attenuation * 64);
|
|
|
|
if (field_mask & SND_LARGEENTITY) {
|
|
MSG_WriteShort (&sv.datagram, ent);
|
|
MSG_WriteByte (&sv.datagram, channel);
|
|
} else {
|
|
MSG_WriteShort (&sv.datagram, (ent << 3 | channel));
|
|
}
|
|
if (field_mask & SND_LARGESOUND)
|
|
MSG_WriteShort (&sv.datagram, sound_num);
|
|
else
|
|
MSG_WriteByte (&sv.datagram, sound_num);
|
|
|
|
VectorBlend (SVvector (entity, mins), SVvector (entity, maxs), 0.5, v);
|
|
VectorAdd (v, SVvector (entity, origin), v);
|
|
MSG_WriteCoordV (&sv.datagram, v);
|
|
}
|
|
|
|
// CLIENT SPAWNING ============================================================
|
|
|
|
/*
|
|
SV_SendServerinfo
|
|
|
|
Sends the first message from the server to a connected client.
|
|
This will be sent on the initial connection and upon each server load.
|
|
*/
|
|
static void
|
|
SV_SendServerinfo (client_t *client)
|
|
{
|
|
const char **s;
|
|
char message[2048];
|
|
int i;
|
|
|
|
MSG_WriteByte (&client->message, svc_print);
|
|
snprintf (message, sizeof (message), "%c\nVersion %s server (%i CRC)\n", 2,
|
|
NQ_VERSION, sv_pr_state.crc);
|
|
MSG_WriteString (&client->message, message);
|
|
|
|
MSG_WriteByte (&client->message, svc_serverinfo);
|
|
MSG_WriteLong (&client->message, sv.protocol);
|
|
MSG_WriteByte (&client->message, svs.maxclients);
|
|
|
|
if (!coop && deathmatch)
|
|
MSG_WriteByte (&client->message, GAME_DEATHMATCH);
|
|
else
|
|
MSG_WriteByte (&client->message, GAME_COOP);
|
|
|
|
snprintf (message, sizeof (message), "%s",
|
|
PR_GetString (&sv_pr_state, SVstring (sv.edicts, message)));
|
|
message[sizeof (message) - 1] = 0;
|
|
|
|
MSG_WriteString (&client->message, message);
|
|
|
|
// send only the first 256 model and sound precaches if protocol 15
|
|
for (i = 0, s = sv.model_precache + 1; *s; s++, i++)
|
|
if (sv.protocol != PROTOCOL_NETQUAKE || i < 256)
|
|
MSG_WriteString (&client->message, *s);
|
|
MSG_WriteByte (&client->message, 0);
|
|
|
|
for (i = 0, s = sv.sound_precache + 1; *s; s++, i++)
|
|
if (sv.protocol != PROTOCOL_NETQUAKE || i < 256)
|
|
MSG_WriteString (&client->message, *s);
|
|
MSG_WriteByte (&client->message, 0);
|
|
|
|
// send music
|
|
MSG_WriteByte (&client->message, svc_cdtrack);
|
|
MSG_WriteByte (&client->message, SVfloat (sv.edicts, sounds));
|
|
MSG_WriteByte (&client->message, SVfloat (sv.edicts, sounds));
|
|
|
|
// set view
|
|
MSG_WriteByte (&client->message, svc_setview);
|
|
MSG_WriteShort (&client->message,
|
|
NUM_FOR_EDICT (&sv_pr_state, client->edict));
|
|
|
|
MSG_WriteByte (&client->message, svc_signonnum);
|
|
MSG_WriteByte (&client->message, 1);
|
|
|
|
client->sendsignon = true;
|
|
client->spawned = false; // need prespawn, spawn, etc
|
|
}
|
|
|
|
/*
|
|
SV_ConnectClient
|
|
|
|
Initializes a client_t for a new net connection. This will be called only
|
|
once for a player each game, not once for each level change.
|
|
*/
|
|
static void
|
|
SV_ConnectClient (int clientnum)
|
|
{
|
|
edict_t *ent;
|
|
client_t *client;
|
|
int edictnum;
|
|
struct qsocket_s *netconnection;
|
|
int i;
|
|
float spawn_parms[NUM_SPAWN_PARMS];
|
|
|
|
client = svs.clients + clientnum;
|
|
|
|
Sys_MaskPrintf (SYS_dev, "Client %s connected\n",
|
|
client->netconnection->address);
|
|
|
|
edictnum = clientnum + 1;
|
|
|
|
ent = EDICT_NUM (&sv_pr_state, edictnum);
|
|
|
|
// set up the client_t
|
|
netconnection = client->netconnection;
|
|
|
|
if (sv.loadgame)
|
|
memcpy (spawn_parms, client->spawn_parms, sizeof (spawn_parms));
|
|
memset (client, 0, sizeof (*client));
|
|
client->netconnection = netconnection;
|
|
|
|
strcpy (client->name, "unconnected");
|
|
client->active = true;
|
|
client->spawned = false;
|
|
client->edict = ent;
|
|
client->message.data = client->msgbuf;
|
|
client->message.maxsize = sizeof (client->msgbuf);
|
|
client->message.allowoverflow = true; // we can catch it
|
|
|
|
client->privileged = false;
|
|
|
|
if (sv.loadgame)
|
|
memcpy (client->spawn_parms, spawn_parms, sizeof (spawn_parms));
|
|
else {
|
|
// call the progs to get default spawn parms for the new client
|
|
PR_ExecuteProgram (&sv_pr_state, sv_funcs.SetNewParms);
|
|
for (i = 0; i < NUM_SPAWN_PARMS; i++)
|
|
client->spawn_parms[i] = sv_globals.parms[i];
|
|
}
|
|
|
|
SV_SendServerinfo (client);
|
|
}
|
|
|
|
void
|
|
SV_CheckForNewClients (void)
|
|
{
|
|
unsigned i;
|
|
struct qsocket_s *ret;
|
|
|
|
// check for new connections
|
|
while (1) {
|
|
ret = NET_CheckNewConnections ();
|
|
if (!ret)
|
|
break;
|
|
|
|
// init a new client structure
|
|
for (i = 0; i < svs.maxclients; i++)
|
|
if (!svs.clients[i].active)
|
|
break;
|
|
if (i == svs.maxclients)
|
|
Sys_Error ("Host_CheckForNewClients: no free clients");
|
|
|
|
svs.clients[i].netconnection = ret;
|
|
SV_ConnectClient (i);
|
|
|
|
net_activeconnections++;
|
|
}
|
|
}
|
|
|
|
// FRAME UPDATES ==============================================================
|
|
|
|
void
|
|
SV_ClearDatagram (void)
|
|
{
|
|
SZ_Clear (&sv.datagram);
|
|
}
|
|
|
|
/*
|
|
The PVS must include a small area around the client to allow head bobbing
|
|
or other small motion on the client side. Otherwise, a bob might cause an
|
|
entity that should be visible to not show up, especially when the bob
|
|
crosses a waterline.
|
|
*/
|
|
|
|
static set_t *fatpvs;
|
|
|
|
static void
|
|
SV_AddToFatPVS (vec3_t org, mnode_t *node)
|
|
{
|
|
float d;
|
|
plane_t *plane;
|
|
|
|
while (1) {
|
|
// if this is a leaf, accumulate the pvs bits
|
|
if (node->contents < 0) {
|
|
if (node->contents != CONTENTS_SOLID) {
|
|
set_union (fatpvs, Mod_LeafPVS ((mleaf_t *) node,
|
|
sv.worldmodel));
|
|
}
|
|
return;
|
|
}
|
|
|
|
plane = node->plane;
|
|
d = DotProduct (org, plane->normal) - plane->dist;
|
|
if (d > 8)
|
|
node = node->children[0];
|
|
else if (d < -8)
|
|
node = node->children[1];
|
|
else { // go down both
|
|
SV_AddToFatPVS (org, node->children[0]);
|
|
node = node->children[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_FatPVS
|
|
|
|
Calculates a PVS that is the inclusive or of all leafs within 8 pixels of the
|
|
given point.
|
|
*/
|
|
static set_t *
|
|
SV_FatPVS (vec3_t org)
|
|
{
|
|
if (!fatpvs) {
|
|
fatpvs = set_new_size (sv.worldmodel->brush.visleafs);
|
|
}
|
|
set_expand (fatpvs, sv.worldmodel->brush.visleafs);
|
|
set_empty (fatpvs);
|
|
SV_AddToFatPVS (org, sv.worldmodel->brush.nodes);
|
|
return fatpvs;
|
|
}
|
|
|
|
//=============================================================================
|
|
|
|
static void
|
|
SV_WriteEntitiesToClient (edict_t *clent, sizebuf_t *msg)
|
|
{
|
|
pr_uint_t bits, e;
|
|
set_t *pvs;
|
|
float miss;
|
|
vec3_t org;
|
|
edict_t *ent;
|
|
entity_state_t *baseline;
|
|
edict_leaf_t *el;
|
|
|
|
// find the client's PVS
|
|
VectorAdd (SVvector (clent, origin), SVvector (clent, view_ofs), org);
|
|
pvs = SV_FatPVS (org);
|
|
|
|
// send over all entities (excpet the client) that touch the pvs
|
|
ent = NEXT_EDICT (&sv_pr_state, sv.edicts);
|
|
for (e = 1; e < sv.num_edicts; e++, ent = NEXT_EDICT (&sv_pr_state, ent)) {
|
|
baseline = &SVdata (ent)->state;
|
|
|
|
// ignore if not touching a PV leaf
|
|
if (ent != clent) { // clent is ALWAYS sent
|
|
// ignore ents without visible models
|
|
if (!SVfloat (ent, modelindex) ||
|
|
!*PR_GetString (&sv_pr_state, SVstring (ent, model)))
|
|
continue;
|
|
|
|
// don't send model > 255 for protocol 15
|
|
if (sv.protocol == PROTOCOL_NETQUAKE
|
|
&& (int) SVfloat (ent, modelindex) & 0xFF00)
|
|
continue;
|
|
|
|
for (el = SVdata (ent)->leafs; el; el = el->next) {
|
|
if (set_is_member (pvs, el->leafnum))
|
|
break;
|
|
}
|
|
|
|
if (!el)
|
|
continue; // not visible
|
|
}
|
|
|
|
if (msg->cursize + 24 > msg->maxsize) {
|
|
Sys_Printf ("packet overflow\n");
|
|
return;
|
|
}
|
|
// send an update
|
|
bits = 0;
|
|
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
miss = SVvector (ent, origin)[i] - baseline->origin[i];
|
|
if (miss < -0.1 || miss > 0.1)
|
|
bits |= U_ORIGIN1 << i;
|
|
}
|
|
|
|
if (SVvector (ent, angles)[0] != baseline->angles[0])
|
|
bits |= U_ANGLE1;
|
|
|
|
if (SVvector (ent, angles)[1] != baseline->angles[1])
|
|
bits |= U_ANGLE2;
|
|
|
|
if (SVvector (ent, angles)[2] != baseline->angles[2])
|
|
bits |= U_ANGLE3;
|
|
|
|
if (SVfloat (ent, movetype) == MOVETYPE_STEP)
|
|
bits |= U_STEP; // don't mess up the step animation
|
|
|
|
if (baseline->colormap != SVfloat (ent, colormap))
|
|
bits |= U_COLORMAP;
|
|
|
|
if (baseline->skinnum != SVfloat (ent, skin))
|
|
bits |= U_SKIN;
|
|
|
|
if (baseline->frame != SVfloat (ent, frame))
|
|
bits |= U_FRAME;
|
|
|
|
if (baseline->effects != SVfloat (ent, effects))
|
|
bits |= U_EFFECTS;
|
|
|
|
if (baseline->modelindex != SVfloat (ent, modelindex))
|
|
bits |= U_MODEL;
|
|
|
|
if (sv_fields.alpha != -1)
|
|
SVdata (ent)->alpha = ENTALPHA_ENCODE(SVfloat (ent, alpha));
|
|
|
|
//don't send invisible entities unless they have effects
|
|
if (SVdata (ent)->alpha == ENTALPHA_ZERO && !SVfloat (ent, effects))
|
|
continue;
|
|
|
|
if (sv.protocol != PROTOCOL_NETQUAKE) {
|
|
if (SVdata (ent)->state.alpha != SVdata (ent)->alpha)
|
|
bits |= U_ALPHA;
|
|
if (bits & U_FRAME && (int) SVfloat (ent, frame) & 0xFF00)
|
|
bits |= U_FRAME2;
|
|
if (bits & U_MODEL && (int) SVfloat (ent, modelindex) & 0xFF00)
|
|
bits |= U_MODEL2;
|
|
if (SVdata (ent)->sendinterval)
|
|
bits |= U_LERPFINISH;
|
|
if (bits >= 65536)
|
|
bits |= U_EXTEND1;
|
|
if (bits >= 16777216)
|
|
bits |= U_EXTEND2;
|
|
}
|
|
|
|
|
|
if (e >= 256)
|
|
bits |= U_LONGENTITY;
|
|
|
|
if (bits >= 256)
|
|
bits |= U_MOREBITS;
|
|
|
|
// write the message
|
|
MSG_WriteByte (msg, bits | U_SIGNAL);
|
|
|
|
if (bits & U_MOREBITS)
|
|
MSG_WriteByte (msg, bits >> 8);
|
|
if (bits & U_EXTEND1)
|
|
MSG_WriteByte (msg, bits >> 16);
|
|
if (bits & U_EXTEND2)
|
|
MSG_WriteByte (msg, bits >> 24);
|
|
|
|
if (bits & U_LONGENTITY)
|
|
MSG_WriteShort (msg, e);
|
|
else
|
|
MSG_WriteByte (msg, e);
|
|
|
|
if (bits & U_MODEL)
|
|
MSG_WriteByte (msg, SVfloat (ent, modelindex));
|
|
if (bits & U_FRAME)
|
|
MSG_WriteByte (msg, SVfloat (ent, frame));
|
|
if (bits & U_COLORMAP)
|
|
MSG_WriteByte (msg, SVfloat (ent, colormap));
|
|
if (bits & U_SKIN)
|
|
MSG_WriteByte (msg, SVfloat (ent, skin));
|
|
if (bits & U_EFFECTS)
|
|
MSG_WriteByte (msg, SVfloat (ent, effects));
|
|
if (bits & U_ORIGIN1)
|
|
MSG_WriteCoord (msg, SVvector (ent, origin)[0]);
|
|
if (bits & U_ANGLE1)
|
|
MSG_WriteAngle (msg, SVvector (ent, angles)[0]);
|
|
if (bits & U_ORIGIN2)
|
|
MSG_WriteCoord (msg, SVvector (ent, origin)[1]);
|
|
if (bits & U_ANGLE2)
|
|
MSG_WriteAngle (msg, SVvector (ent, angles)[1]);
|
|
if (bits & U_ORIGIN3)
|
|
MSG_WriteCoord (msg, SVvector (ent, origin)[2]);
|
|
if (bits & U_ANGLE3)
|
|
MSG_WriteAngle (msg, SVvector (ent, angles)[2]);
|
|
|
|
if (bits & U_ALPHA)
|
|
MSG_WriteByte(msg, SVdata (ent)->alpha);
|
|
if (bits & U_FRAME2)
|
|
MSG_WriteByte(msg, (int) SVfloat (ent, frame) >> 8);
|
|
if (bits & U_MODEL2)
|
|
MSG_WriteByte(msg, (int) SVfloat (ent, modelindex) >> 8);
|
|
if (bits & U_LERPFINISH)
|
|
MSG_WriteByte(msg, rint ((SVfloat (ent, nextthink) - sv.time) * 255));
|
|
}
|
|
}
|
|
|
|
static void
|
|
SV_CleanupEnts (void)
|
|
{
|
|
pr_uint_t e;
|
|
edict_t *ent;
|
|
|
|
ent = NEXT_EDICT (&sv_pr_state, sv.edicts);
|
|
for (e = 1; e < sv.num_edicts; e++, ent = NEXT_EDICT (&sv_pr_state, ent))
|
|
SVfloat (ent, effects) = (int) SVfloat (ent, effects)
|
|
& ~EF_MUZZLEFLASH;
|
|
}
|
|
|
|
void
|
|
SV_WriteClientdataToMessage (edict_t *ent, sizebuf_t *msg)
|
|
{
|
|
int bits, items, i;
|
|
vec3_t v;
|
|
edict_t *other;
|
|
const char *weaponmodel;
|
|
|
|
weaponmodel = PR_GetString (&sv_pr_state, SVstring (ent, weaponmodel));
|
|
|
|
// send a damage message
|
|
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));
|
|
VectorBlend (SVvector (other, mins), SVvector (other, maxs), 0.5, v);
|
|
VectorAdd (v, SVvector (other, origin), v);
|
|
MSG_WriteCoordV (msg, v);
|
|
SVfloat (ent, dmg_take) = 0;
|
|
SVfloat (ent, dmg_save) = 0;
|
|
}
|
|
|
|
// send the current viewpos offset from the view entity
|
|
SV_SetIdealPitch (); // how much to look up / down ideally
|
|
|
|
// a fixangle might get lost in a dropped packet. Oh well.
|
|
if (SVfloat (ent, fixangle)) {
|
|
MSG_WriteByte (msg, svc_setangle);
|
|
MSG_WriteAngleV (msg, SVvector (ent, angles));
|
|
SVfloat (ent, fixangle) = 0;
|
|
}
|
|
|
|
bits = 0;
|
|
|
|
if (SVvector (ent, view_ofs)[2] != DEFAULT_VIEWHEIGHT)
|
|
bits |= SU_VIEWHEIGHT;
|
|
|
|
if (SVfloat (ent, idealpitch))
|
|
bits |= SU_IDEALPITCH;
|
|
|
|
// stuff the sigil bits into the high bits of items for sbar, or else
|
|
// mix in items2
|
|
if (sv_fields.items2 != -1)
|
|
items = (int) SVfloat (ent, items) | ((int) SVfloat (ent, items2)
|
|
<< 23);
|
|
else
|
|
items = (int) SVfloat (ent, items) | ((int) *sv_globals.serverflags
|
|
<< 28);
|
|
|
|
bits |= SU_ITEMS;
|
|
|
|
if ((int) SVfloat (ent, flags) & FL_ONGROUND)
|
|
bits |= SU_ONGROUND;
|
|
|
|
if (SVfloat (ent, waterlevel) >= 2)
|
|
bits |= SU_INWATER;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (SVvector (ent, punchangle)[i])
|
|
bits |= (SU_PUNCH1 << i);
|
|
if (SVvector (ent, velocity)[i])
|
|
bits |= (SU_VELOCITY1 << i);
|
|
}
|
|
|
|
if (SVfloat (ent, weaponframe))
|
|
bits |= SU_WEAPONFRAME;
|
|
|
|
if (SVfloat (ent, armorvalue))
|
|
bits |= SU_ARMOR;
|
|
|
|
// if (SVfloat (ent, weapon))
|
|
bits |= SU_WEAPON;
|
|
|
|
if (sv.protocol != PROTOCOL_NETQUAKE) {
|
|
if (bits & SU_WEAPON && SV_ModelIndex(weaponmodel) & 0xFF00)
|
|
bits |= SU_WEAPON2;
|
|
if ((int) SVfloat (ent, armorvalue) & 0xFF00)
|
|
bits |= SU_ARMOR2;
|
|
if ((int) SVfloat (ent, currentammo) & 0xFF00)
|
|
bits |= SU_AMMO2;
|
|
if ((int) SVfloat (ent, ammo_shells) & 0xFF00)
|
|
bits |= SU_SHELLS2;
|
|
if ((int) SVfloat (ent, ammo_nails) & 0xFF00)
|
|
bits |= SU_NAILS2;
|
|
if ((int) SVfloat (ent, ammo_rockets) & 0xFF00)
|
|
bits |= SU_ROCKETS2;
|
|
if ((int) SVfloat (ent, ammo_cells) & 0xFF00)
|
|
bits |= SU_CELLS2;
|
|
if (bits & SU_WEAPONFRAME && (int) SVfloat (ent, weaponframe) & 0xFF00)
|
|
bits |= SU_WEAPONFRAME2;
|
|
if (bits & SU_WEAPON && SVdata (ent)->alpha != ENTALPHA_DEFAULT)
|
|
bits |= SU_WEAPONALPHA; //for now, weaponalpha = client entity alpha
|
|
if (bits >= 65536)
|
|
bits |= SU_EXTEND1;
|
|
if (bits >= 16777216)
|
|
bits |= SU_EXTEND2;
|
|
}
|
|
|
|
// send the data
|
|
|
|
MSG_WriteByte (msg, svc_clientdata);
|
|
MSG_WriteShort (msg, bits);
|
|
|
|
if (bits & SU_EXTEND1)
|
|
MSG_WriteByte(msg, bits>>16);
|
|
if (bits & SU_EXTEND2)
|
|
MSG_WriteByte(msg, bits>>24);
|
|
|
|
if (bits & SU_VIEWHEIGHT)
|
|
MSG_WriteByte (msg, SVvector (ent, view_ofs)[2]);
|
|
|
|
if (bits & SU_IDEALPITCH)
|
|
MSG_WriteByte (msg, SVfloat (ent, idealpitch));
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if (bits & (SU_PUNCH1 << i))
|
|
MSG_WriteByte (msg, SVvector (ent, punchangle)[i]);
|
|
if (bits & (SU_VELOCITY1 << i))
|
|
MSG_WriteByte (msg, SVvector (ent, velocity)[i] / 16);
|
|
}
|
|
|
|
// if (bits & SU_ITEMS) // [always sent]
|
|
MSG_WriteLong (msg, items);
|
|
|
|
if (bits & SU_WEAPONFRAME)
|
|
MSG_WriteByte (msg, SVfloat (ent, weaponframe));
|
|
if (bits & SU_ARMOR)
|
|
MSG_WriteByte (msg, SVfloat (ent, armorvalue));
|
|
if (bits & SU_WEAPON)
|
|
MSG_WriteByte (msg, SV_ModelIndex (weaponmodel));
|
|
|
|
MSG_WriteShort (msg, SVfloat (ent, health));
|
|
MSG_WriteByte (msg, SVfloat (ent, currentammo));
|
|
MSG_WriteByte (msg, SVfloat (ent, ammo_shells));
|
|
MSG_WriteByte (msg, SVfloat (ent, ammo_nails));
|
|
MSG_WriteByte (msg, SVfloat (ent, ammo_rockets));
|
|
MSG_WriteByte (msg, SVfloat (ent, ammo_cells));
|
|
|
|
if (standard_quake) {
|
|
MSG_WriteByte (msg, SVfloat (ent, weapon));
|
|
} else {
|
|
// NOTE: this is abysmally stupid. weapon is being treated as a
|
|
// radio button style bit mask, limiting the available weapons to
|
|
// 32. Sure, that's a lot of weapons, but still...
|
|
//
|
|
// Send the index of the lowest order set bit.
|
|
unsigned weapon;
|
|
weapon = (unsigned) SVfloat (ent, weapon);
|
|
for (i = 0; i < 32; i++) {
|
|
if (weapon & (1 << i)) {
|
|
MSG_WriteByte (msg, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bits & SU_WEAPON2)
|
|
MSG_WriteByte (msg, SV_ModelIndex(weaponmodel) >> 8);
|
|
if (bits & SU_ARMOR2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, armorvalue) >> 8);
|
|
if (bits & SU_AMMO2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, currentammo) >> 8);
|
|
if (bits & SU_SHELLS2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, ammo_shells) >> 8);
|
|
if (bits & SU_NAILS2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, ammo_nails) >> 8);
|
|
if (bits & SU_ROCKETS2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, ammo_rockets) >> 8);
|
|
if (bits & SU_CELLS2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, ammo_cells) >> 8);
|
|
if (bits & SU_WEAPONFRAME2)
|
|
MSG_WriteByte (msg, (int) SVfloat (ent, weaponframe) >> 8);
|
|
if (bits & SU_WEAPONALPHA)
|
|
MSG_WriteByte (msg, SVdata (ent)->alpha); //for now, weaponalpha = client entity alpha
|
|
}
|
|
|
|
static qboolean
|
|
SV_SendClientDatagram (client_t *client)
|
|
{
|
|
byte buf[MAX_DATAGRAM];
|
|
sizebuf_t msg;
|
|
|
|
msg.data = buf;
|
|
msg.maxsize = sizeof (buf);
|
|
msg.cursize = 0;
|
|
|
|
if (strcmp (client->netconnection->address, "LOCAL") != 0)
|
|
msg.maxsize = DATAGRAM_MTU;
|
|
|
|
MSG_WriteByte (&msg, svc_time);
|
|
MSG_WriteFloat (&msg, sv.time);
|
|
|
|
// add the client specific data to the datagram
|
|
SV_WriteClientdataToMessage (client->edict, &msg);
|
|
|
|
SV_WriteEntitiesToClient (client->edict, &msg);
|
|
|
|
// copy the server datagram if there is space
|
|
if (msg.cursize + sv.datagram.cursize < msg.maxsize)
|
|
SZ_Write (&msg, sv.datagram.data, sv.datagram.cursize);
|
|
|
|
// send the datagram
|
|
if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1) {
|
|
SV_DropClient (true); // if the message couldn't send, kick off
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
SV_UpdateToReliableMessages (void)
|
|
{
|
|
unsigned i, j;
|
|
client_t *client;
|
|
|
|
// check for changes to be sent over the reliable streams
|
|
for (i = 0, host_client = svs.clients; i < svs.maxclients;
|
|
i++, host_client++) {
|
|
if (host_client->old_frags != (int) SVfloat (host_client->edict,
|
|
frags)) {
|
|
for (j = 0, client = svs.clients; j < svs.maxclients; j++,
|
|
client++) {
|
|
if (!client->active)
|
|
continue;
|
|
MSG_WriteByte (&client->message, svc_updatefrags);
|
|
MSG_WriteByte (&client->message, i);
|
|
MSG_WriteShort (&client->message,
|
|
SVfloat (host_client->edict, frags));
|
|
}
|
|
|
|
host_client->old_frags = SVfloat (host_client->edict, frags);
|
|
}
|
|
}
|
|
|
|
for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) {
|
|
if (!client->active)
|
|
continue;
|
|
SZ_Write (&client->message, sv.reliable_datagram.data,
|
|
sv.reliable_datagram.cursize);
|
|
}
|
|
|
|
SZ_Clear (&sv.reliable_datagram);
|
|
}
|
|
|
|
/*
|
|
SV_SendNop
|
|
|
|
Send a nop message without trashing or sending the accumulated client
|
|
message buffer
|
|
*/
|
|
static void
|
|
SV_SendNop (client_t *client)
|
|
{
|
|
sizebuf_t msg;
|
|
byte buf[4];
|
|
|
|
msg.data = buf;
|
|
msg.maxsize = sizeof (buf);
|
|
msg.cursize = 0;
|
|
|
|
MSG_WriteByte (&msg, svc_nop);
|
|
|
|
if (NET_SendUnreliableMessage (client->netconnection, &msg) == -1)
|
|
SV_DropClient (true); // if the message couldn't send, kick off
|
|
client->last_message = sv.time;
|
|
}
|
|
|
|
void
|
|
SV_SendClientMessages (void)
|
|
{
|
|
unsigned i;
|
|
|
|
// update frags, names, etc
|
|
SV_UpdateToReliableMessages ();
|
|
|
|
// build individual updates
|
|
for (i = 0, host_client = svs.clients; i < svs.maxclients;
|
|
i++, host_client++) {
|
|
if (!host_client->active)
|
|
continue;
|
|
|
|
if (host_client->spawned) {
|
|
if (!SV_SendClientDatagram (host_client))
|
|
continue;
|
|
} else {
|
|
// The player isn't totally in the game yet
|
|
// Send small keepalive messages if too much time has passed
|
|
// Send a full message when the next signon stage has been
|
|
// requested; some other message data (name changes, etc) may
|
|
// accumulate between signon stages
|
|
if (!host_client->sendsignon) {
|
|
if (sv.time - host_client->last_message > 5)
|
|
SV_SendNop (host_client);
|
|
continue; // don't send out non-signon messages
|
|
}
|
|
}
|
|
|
|
// check for an overflowed message. Should happen only on a very
|
|
// bad up connection that backs up a lot, then changes level
|
|
if (host_client->message.overflowed) {
|
|
SV_DropClient (true);
|
|
host_client->message.overflowed = false;
|
|
continue;
|
|
}
|
|
|
|
if (host_client->message.cursize || host_client->dropasap) {
|
|
if (!NET_CanSendMessage (host_client->netconnection)) {
|
|
// I_Printf ("can't write\n");
|
|
continue;
|
|
}
|
|
|
|
if (host_client->dropasap)
|
|
SV_DropClient (false); // went to another level
|
|
else {
|
|
if (NET_SendMessage
|
|
(host_client->netconnection, &host_client->message) == -1)
|
|
SV_DropClient (true); // if the message couldn't send,
|
|
// kick off
|
|
SZ_Clear (&host_client->message);
|
|
host_client->last_message = sv.time;
|
|
host_client->sendsignon = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clear muzzle flashes
|
|
SV_CleanupEnts ();
|
|
}
|
|
|
|
// SERVER SPAWNING ============================================================
|
|
|
|
int
|
|
SV_ModelIndex (const char *name)
|
|
{
|
|
int i;
|
|
|
|
if (!name || !name[0])
|
|
return 0;
|
|
|
|
for (i = 0; i < MAX_MODELS && sv.model_precache[i]; i++)
|
|
if (!strcmp (sv.model_precache[i], name))
|
|
return i;
|
|
if (i == MAX_MODELS || !sv.model_precache[i])
|
|
Sys_Error ("SV_ModelIndex: model %s not precached", name);
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
SV_CreateBaseline (void)
|
|
{
|
|
pr_uint_t entnum;
|
|
edict_t *svent;
|
|
entity_state_t *baseline;
|
|
int bits;
|
|
|
|
for (entnum = 0; entnum < sv.num_edicts; entnum++) {
|
|
// get the current server version
|
|
svent = EDICT_NUM (&sv_pr_state, entnum);
|
|
baseline = &SVdata (svent)->state;
|
|
|
|
if (svent->free)
|
|
continue;
|
|
if (entnum > svs.maxclients && !SVfloat (svent, modelindex))
|
|
continue;
|
|
|
|
// create entity baseline
|
|
VectorCopy (SVvector (svent, origin), baseline->origin);
|
|
VectorCopy (SVvector (svent, angles), baseline->angles);
|
|
baseline->frame = SVfloat (svent, frame);
|
|
baseline->skinnum = SVfloat (svent, skin);
|
|
if (entnum > 0 && entnum <= svs.maxclients) {
|
|
baseline->colormap = entnum;
|
|
baseline->modelindex = SV_ModelIndex ("progs/player.mdl");
|
|
baseline->alpha = ENTALPHA_DEFAULT;
|
|
} else {
|
|
const char *model;
|
|
model = PR_GetString (&sv_pr_state, SVstring (svent, model));
|
|
baseline->colormap = 0;
|
|
baseline->modelindex = SV_ModelIndex (model);
|
|
baseline->alpha = ENTALPHA_DEFAULT;
|
|
}
|
|
|
|
bits = 0;
|
|
if (sv.protocol == PROTOCOL_NETQUAKE) {
|
|
//still want to send baseline in PROTOCOL_NETQUAKE, so reset
|
|
//these values
|
|
if (baseline->modelindex & 0xFF00)
|
|
baseline->modelindex = 0;
|
|
if (baseline->frame & 0xFF00)
|
|
baseline->frame = 0;
|
|
baseline->alpha = ENTALPHA_DEFAULT;
|
|
} else {
|
|
if (baseline->modelindex & 0xFF00)
|
|
bits |= B_LARGEMODEL;
|
|
if (baseline->frame & 0xFF00)
|
|
bits |= B_LARGEFRAME;
|
|
if (baseline->alpha != ENTALPHA_DEFAULT)
|
|
bits |= B_ALPHA;
|
|
}
|
|
|
|
// add to the message
|
|
if (bits)
|
|
MSG_WriteByte (&sv.signon, svc_spawnbaseline2);
|
|
else
|
|
MSG_WriteByte (&sv.signon, svc_spawnbaseline);
|
|
|
|
MSG_WriteShort (&sv.signon, entnum);
|
|
|
|
if (bits)
|
|
MSG_WriteByte (&sv.signon, bits);
|
|
|
|
if (bits & B_LARGEMODEL)
|
|
MSG_WriteShort (&sv.signon, baseline->modelindex);
|
|
else
|
|
MSG_WriteByte (&sv.signon, baseline->modelindex);
|
|
|
|
if (bits & B_LARGEFRAME)
|
|
MSG_WriteShort (&sv.signon, baseline->frame);
|
|
else
|
|
MSG_WriteByte (&sv.signon, baseline->frame);
|
|
MSG_WriteByte (&sv.signon, baseline->colormap);
|
|
MSG_WriteByte (&sv.signon, baseline->skinnum);
|
|
|
|
MSG_WriteCoordAngleV (&sv.signon, (vec_t*)&baseline->origin,//FIXME
|
|
baseline->angles);
|
|
|
|
if (bits & B_ALPHA)
|
|
MSG_WriteByte (&sv.signon, baseline->alpha);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_SendReconnect
|
|
|
|
Tell all the clients that the server is changing levels
|
|
*/
|
|
static void
|
|
SV_SendReconnect (void)
|
|
{
|
|
byte data[128];
|
|
sizebuf_t msg;
|
|
|
|
msg.data = data;
|
|
msg.cursize = 0;
|
|
msg.maxsize = sizeof (data);
|
|
|
|
MSG_WriteByte (&msg, svc_stufftext);
|
|
MSG_WriteString (&msg, "reconnect\n");
|
|
NET_SendToAll (&msg, 5.0);
|
|
|
|
if (!net_is_dedicated)
|
|
Cmd_ExecuteString ("reconnect\n", src_command);
|
|
}
|
|
|
|
/*
|
|
SV_SaveSpawnparms
|
|
|
|
Grabs the current state of each client for saving across the
|
|
transition to another level
|
|
*/
|
|
void
|
|
SV_SaveSpawnparms (void)
|
|
{
|
|
unsigned i, j;
|
|
|
|
svs.serverflags = *sv_globals.serverflags;
|
|
|
|
for (i = 0, host_client = svs.clients; i < svs.maxclients;
|
|
i++, host_client++) {
|
|
if (!host_client->active)
|
|
continue;
|
|
|
|
// call the progs to get default spawn parms for the new client
|
|
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, host_client->edict);
|
|
PR_ExecuteProgram (&sv_pr_state, sv_funcs.SetChangeParms);
|
|
for (j = 0; j < NUM_SPAWN_PARMS; j++)
|
|
host_client->spawn_parms[j] = sv_globals.parms[j];
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_SpawnServer
|
|
|
|
This is called at the start of each level
|
|
*/
|
|
void
|
|
SV_SpawnServer (const char *server)
|
|
{
|
|
byte *buf;
|
|
QFile *ent_file;
|
|
edict_t *ent;
|
|
|
|
|
|
S_BlockSound ();
|
|
// let's not have any servers with no name
|
|
if (hostname[0] == 0)
|
|
Cvar_Set ("hostname", "UNNAMED");
|
|
|
|
Sys_MaskPrintf (SYS_dev, "SpawnServer: %s\n", server);
|
|
svs.changelevel_issued = false; // now safe to issue another
|
|
svs.phys_client = SV_Physics_Client;
|
|
|
|
// tell all connected clients that we are going to a new level
|
|
if (sv.active) {
|
|
SV_SendReconnect ();
|
|
}
|
|
|
|
// make cvars consistant
|
|
if (coop)
|
|
deathmatch = 0;
|
|
current_skill = skill;
|
|
if (current_skill < 0)
|
|
current_skill = 0;
|
|
if (current_skill > 3)
|
|
current_skill = 3;
|
|
|
|
skill = current_skill;
|
|
|
|
// set up the new server
|
|
Host_ClearMemory ();
|
|
|
|
memset (&sv, 0, sizeof (sv));
|
|
|
|
strcpy (sv.name, server);
|
|
|
|
sv.protocol = sv_protocol;
|
|
|
|
// load progs to get entity field count
|
|
sv.max_edicts = bound (MIN_EDICTS, max_edicts, MAX_EDICTS);
|
|
SV_LoadProgs ();
|
|
SV_FreeAllEdictLeafs ();
|
|
|
|
sv.datagram.maxsize = sizeof (sv.datagram_buf);
|
|
sv.datagram.cursize = 0;
|
|
sv.datagram.data = sv.datagram_buf;
|
|
|
|
sv.reliable_datagram.maxsize = sizeof (sv.reliable_datagram_buf);
|
|
sv.reliable_datagram.cursize = 0;
|
|
sv.reliable_datagram.data = sv.reliable_datagram_buf;
|
|
|
|
sv.signon.maxsize = sizeof (sv.signon_buf);
|
|
sv.signon.cursize = 0;
|
|
sv.signon.data = sv.signon_buf;
|
|
|
|
// leave slots at start for only clients
|
|
sv.num_edicts = svs.maxclients + 1;
|
|
for (unsigned i = 0; i < svs.maxclients; i++) {
|
|
ent = EDICT_NUM (&sv_pr_state, i + 1);
|
|
svs.clients[i].edict = ent;
|
|
}
|
|
|
|
sv.state = ss_loading;
|
|
sv.paused = false;
|
|
|
|
sv.time = 1.0;
|
|
|
|
strcpy (sv.name, server);
|
|
snprintf (sv.modelname, sizeof (sv.modelname), "maps/%s.bsp", server);
|
|
sv.worldmodel = Mod_ForName (sv.modelname, false);
|
|
if (!sv.worldmodel) {
|
|
Sys_Printf ("Couldn't spawn server %s\n", sv.modelname);
|
|
sv.active = false;
|
|
S_UnblockSound ();
|
|
return;
|
|
}
|
|
sv.models[1] = sv.worldmodel;
|
|
|
|
// clear world interaction links
|
|
SV_ClearWorld ();
|
|
|
|
sv.sound_precache[0] = sv_pr_state.pr_strings;
|
|
|
|
sv.model_precache[0] = sv_pr_state.pr_strings;
|
|
sv.model_precache[1] = sv.modelname;
|
|
for (unsigned i = 1; i < sv.worldmodel->brush.numsubmodels; i++) {
|
|
sv.model_precache[1 + i] = localmodels[i];
|
|
sv.models[i + 1] = Mod_ForName (localmodels[i], false);
|
|
}
|
|
|
|
// load the rest of the entities
|
|
ent = EDICT_NUM (&sv_pr_state, 0);
|
|
memset (&E_fld (ent, 0), 0, sv_pr_state.progs->entityfields * 4);
|
|
ent->free = false;
|
|
SVstring (ent, model) = PR_SetString (&sv_pr_state, sv.worldmodel->path);
|
|
SVfloat (ent, modelindex) = 1; // world model
|
|
SVfloat (ent, solid) = SOLID_BSP;
|
|
SVfloat (ent, movetype) = MOVETYPE_PUSH;
|
|
|
|
if (coop)
|
|
*sv_globals.coop = coop;
|
|
else
|
|
*sv_globals.deathmatch = deathmatch;
|
|
|
|
*sv_globals.mapname = PR_SetString (&sv_pr_state, sv.name);
|
|
|
|
// serverflags are for cross level information (sigils)
|
|
*sv_globals.serverflags = svs.serverflags;
|
|
|
|
*sv_globals.time = sv.time;
|
|
ent_file = QFS_VOpenFile (va (0, "maps/%s.ent", server), 0,
|
|
sv.worldmodel->vpath);
|
|
if ((buf = QFS_LoadFile (ent_file, 0))) {
|
|
ED_LoadFromFile (&sv_pr_state, (char *) buf);
|
|
free (buf);
|
|
} else {
|
|
ED_LoadFromFile (&sv_pr_state, sv.worldmodel->brush.entities);
|
|
}
|
|
|
|
sv.active = true;
|
|
|
|
// all setup is completed, any further precache statements are errors
|
|
sv.state = ss_active;
|
|
|
|
// run two frames to allow everything to settle
|
|
sv_frametime = host_frametime = 0.1;
|
|
SV_Physics ();
|
|
sv.time += host_frametime;
|
|
SV_Physics ();
|
|
sv.time += host_frametime;
|
|
|
|
// create a baseline for more efficient communications
|
|
SV_CreateBaseline ();
|
|
|
|
if (sv.signon.cursize > 8000-2)
|
|
Sys_Printf ("%i byte signon buffer exceeds standard limit of 7998.\n",
|
|
sv.signon.cursize);
|
|
|
|
// send serverinfo to all connected clients
|
|
for (unsigned i = 0; i < svs.maxclients; i++) {
|
|
host_client = svs.clients + i;
|
|
if (host_client->active) {
|
|
SV_SendServerinfo (host_client);
|
|
}
|
|
}
|
|
|
|
Sys_MaskPrintf (SYS_dev, "Server spawned.\n");
|
|
S_UnblockSound ();
|
|
}
|