Quack 3: Arena

This commit is contained in:
Literally A Penguin
2024-09-25 15:40:36 -05:00
committed by GitHub
parent 0bf99969fd
commit 40035fa235
74 changed files with 36999 additions and 0 deletions

341
server/server.h Normal file
View File

@ -0,0 +1,341 @@
/*
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.
*/
// server.h
//define PARANOID // speed sapping error checking
#include "../qcommon/qcommon.h"
#include "../game/game.h"
//=============================================================================
#define MAX_MASTERS 8 // max recipients for heartbeat packets
typedef enum {
ss_dead, // no map loaded
ss_loading, // spawning level edicts
ss_game, // actively running
ss_cinematic,
ss_demo,
ss_pic
} server_state_t;
// some qc commands are only valid before the server has finished
// initializing (precache commands, static sounds / objects, etc)
typedef struct
{
server_state_t state; // precache commands are only valid during load
qboolean attractloop; // running cinematics and demos for the local system only
qboolean loadgame; // client begins should reuse existing entity
unsigned time; // always sv.framenum * 100 msec
int framenum;
char name[MAX_QPATH]; // map name, or cinematic name
struct cmodel_s *models[MAX_MODELS];
char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH];
entity_state_t baselines[MAX_EDICTS];
// the multicast buffer is used to send a message to a set of clients
// it is only used to marshall data until SV_Multicast is called
sizebuf_t multicast;
byte multicast_buf[MAX_MSGLEN];
// demo server information
FILE *demofile;
qboolean timedemo; // don't time sync
} server_t;
#define EDICT_NUM(n) ((edict_t *)((byte *)ge->edicts + ge->edict_size*(n)))
#define NUM_FOR_EDICT(e) ( ((byte *)(e)-(byte *)ge->edicts ) / ge->edict_size)
typedef enum
{
cs_free, // can be reused for a new connection
cs_zombie, // client has been disconnected, but don't reuse
// connection for a couple seconds
cs_connected, // has been assigned to a client_t, but not in game yet
cs_spawned // client is fully in game
} client_state_t;
typedef struct
{
int areabytes;
byte areabits[MAX_MAP_AREAS/8]; // portalarea visibility bits
player_state_t ps;
int num_entities;
int first_entity; // into the circular sv_packet_entities[]
int senttime; // for ping calculations
} client_frame_t;
#define LATENCY_COUNTS 16
#define RATE_MESSAGES 10
typedef struct client_s
{
client_state_t state;
char userinfo[MAX_INFO_STRING]; // name, etc
int lastframe; // for delta compression
usercmd_t lastcmd; // for filling in big drops
int commandMsec; // every seconds this is reset, if user
// commands exhaust it, assume time cheating
int frame_latency[LATENCY_COUNTS];
int ping;
int message_size[RATE_MESSAGES]; // used to rate drop packets
int rate;
int surpressCount; // number of messages rate supressed
edict_t *edict; // EDICT_NUM(clientnum+1)
char name[32]; // extracted from userinfo, high bits masked
int messagelevel; // for filtering printed messages
// The datagram is written to by sound calls, prints, temp ents, etc.
// It can be harmlessly overflowed.
sizebuf_t datagram;
byte datagram_buf[MAX_MSGLEN];
client_frame_t frames[UPDATE_BACKUP]; // updates can be delta'd from here
byte *download; // file being downloaded
int downloadsize; // total bytes (can't use EOF because of paks)
int downloadcount; // bytes sent
int lastmessage; // sv.framenum when packet was last received
int lastconnect;
int challenge; // challenge of this user, randomly generated
netchan_t netchan;
} client_t;
// a client can leave the server in one of four ways:
// dropping properly by quiting or disconnecting
// timing out if no valid messages are received for timeout.value seconds
// getting kicked off by the server operator
// a program error, like an overflowed reliable buffer
//=============================================================================
// MAX_CHALLENGES is made large to prevent a denial
// of service attack that could cycle all of them
// out before legitimate users connected
#define MAX_CHALLENGES 1024
typedef struct
{
netadr_t adr;
int challenge;
int time;
} challenge_t;
typedef struct
{
qboolean initialized; // sv_init has completed
int realtime; // always increasing, no clamping, etc
char mapcmd[MAX_TOKEN_CHARS]; // ie: *intro.cin+base
int spawncount; // incremented each server start
// used to check late spawns
client_t *clients; // [maxclients->value];
int num_client_entities; // maxclients->value*UPDATE_BACKUP*MAX_PACKET_ENTITIES
int next_client_entities; // next client_entity to use
entity_state_t *client_entities; // [num_client_entities]
int last_heartbeat;
challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting
// serverrecord values
FILE *demofile;
sizebuf_t demo_multicast;
byte demo_multicast_buf[MAX_MSGLEN];
} server_static_t;
//=============================================================================
extern netadr_t net_from;
extern sizebuf_t net_message;
extern netadr_t master_adr[MAX_MASTERS]; // address of the master server
extern server_static_t svs; // persistant server info
extern server_t sv; // local server
extern cvar_t *sv_paused;
extern cvar_t *maxclients;
extern cvar_t *sv_noreload; // don't reload level state when reentering
extern cvar_t *sv_airaccelerate; // don't reload level state when reentering
// development tool
extern cvar_t *sv_enforcetime;
extern client_t *sv_client;
extern edict_t *sv_player;
//===========================================================
//
// sv_main.c
//
void SV_FinalMessage (char *message, qboolean reconnect);
void SV_DropClient (client_t *drop);
int SV_ModelIndex (char *name);
int SV_SoundIndex (char *name);
int SV_ImageIndex (char *name);
void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg);
void SV_ExecuteUserCommand (char *s);
void SV_InitOperatorCommands (void);
void SV_SendServerinfo (client_t *client);
void SV_UserinfoChanged (client_t *cl);
void Master_Heartbeat (void);
void Master_Packet (void);
//
// sv_init.c
//
void SV_InitGame (void);
void SV_Map (qboolean attractloop, char *levelstring, qboolean loadgame);
//
// sv_phys.c
//
void SV_PrepWorldFrame (void);
//
// sv_send.c
//
typedef enum {RD_NONE, RD_CLIENT, RD_PACKET} redirect_t;
#define SV_OUTPUTBUF_LENGTH (MAX_MSGLEN - 16)
extern char sv_outputbuf[SV_OUTPUTBUF_LENGTH];
void SV_FlushRedirect (int sv_redirected, char *outputbuf);
void SV_DemoCompleted (void);
void SV_SendClientMessages (void);
void SV_Multicast (vec3_t origin, multicast_t to);
void SV_StartSound (vec3_t origin, edict_t *entity, int channel,
int soundindex, float volume,
float attenuation, float timeofs);
void SV_ClientPrintf (client_t *cl, int level, char *fmt, ...);
void SV_BroadcastPrintf (int level, char *fmt, ...);
void SV_BroadcastCommand (char *fmt, ...);
//
// sv_user.c
//
void SV_Nextserver (void);
void SV_ExecuteClientMessage (client_t *cl);
//
// sv_ccmds.c
//
void SV_ReadLevelFile (void);
void SV_Status_f (void);
//
// sv_ents.c
//
void SV_WriteFrameToClient (client_t *client, sizebuf_t *msg);
void SV_RecordDemoMessage (void);
void SV_BuildClientFrame (client_t *client);
void SV_Error (char *error, ...);
//
// sv_game.c
//
extern game_export_t *ge;
void SV_InitGameProgs (void);
void SV_ShutdownGameProgs (void);
void SV_InitEdict (edict_t *e);
//============================================================
//
// high level object sorting to reduce interaction tests
//
void SV_ClearWorld (void);
// called after the world model has been loaded, before linking any entities
void SV_UnlinkEdict (edict_t *ent);
// call before removing an entity, and before trying to move one,
// so it doesn't clip against itself
void SV_LinkEdict (edict_t *ent);
// Needs to be called any time an entity changes origin, mins, maxs,
// or solid. Automatically unlinks if needed.
// sets ent->v.absmin and ent->v.absmax
// sets ent->leafnums[] for pvs determination even if the entity
// is not solid
int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **list, int maxcount, int areatype);
// fills in a table of edict pointers with edicts that have bounding boxes
// that intersect the given area. It is possible for a non-axial bmodel
// to be returned that doesn't actually intersect the area on an exact
// test.
// returns the number of pointers filled in
// ??? does this always return the world?
//===================================================================
//
// functions that interact with everything apropriate
//
int SV_PointContents (vec3_t p);
// returns the CONTENTS_* value from the world at the given point.
// Quake 2 extends this to also check entities, to allow moving liquids
trace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passedict, int contentmask);
// mins and maxs are relative
// if the entire move stays in a solid volume, trace.allsolid will be set,
// trace.startsolid will be set, and trace.fraction will be 0
// if the starting point is in a solid, it will be allowed to move out
// to an open area
// passedict is explicitly excluded from clipping checks (normally NULL)

1050
server/sv_ccmds.c Normal file

File diff suppressed because it is too large Load Diff

727
server/sv_ents.c Normal file
View File

@ -0,0 +1,727 @@
/*
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.
*/
#include "server.h"
/*
=============================================================================
Encode a client frame onto the network channel
=============================================================================
*/
#if 0
// because there can be a lot of projectiles, there is a special
// network protocol for them
#define MAX_PROJECTILES 64
edict_t *projectiles[MAX_PROJECTILES];
int numprojs;
cvar_t *sv_projectiles;
qboolean SV_AddProjectileUpdate (edict_t *ent)
{
if (!sv_projectiles)
sv_projectiles = Cvar_Get("sv_projectiles", "1", 0);
if (!sv_projectiles->value)
return false;
if (!(ent->svflags & SVF_PROJECTILE))
return false;
if (numprojs == MAX_PROJECTILES)
return true;
projectiles[numprojs++] = ent;
return true;
}
void SV_EmitProjectileUpdate (sizebuf_t *msg)
{
byte bits[16]; // [modelindex] [48 bits] xyz p y 12 12 12 8 8 [entitynum] [e2]
int n, i;
edict_t *ent;
int x, y, z, p, yaw;
int len;
if (!numprojs)
return;
MSG_WriteByte (msg, numprojs);
for (n=0 ; n<numprojs ; n++)
{
ent = projectiles[n];
x = (int)(ent->s.origin[0]+4096)>>1;
y = (int)(ent->s.origin[1]+4096)>>1;
z = (int)(ent->s.origin[2]+4096)>>1;
p = (int)(256*ent->s.angles[0]/360)&255;
yaw = (int)(256*ent->s.angles[1]/360)&255;
len = 0;
bits[len++] = x;
bits[len++] = (x>>8) | (y<<4);
bits[len++] = (y>>4);
bits[len++] = z;
bits[len++] = (z>>8);
if (ent->s.effects & EF_BLASTER)
bits[len-1] |= 64;
if (ent->s.old_origin[0] != ent->s.origin[0] ||
ent->s.old_origin[1] != ent->s.origin[1] ||
ent->s.old_origin[2] != ent->s.origin[2]) {
bits[len-1] |= 128;
x = (int)(ent->s.old_origin[0]+4096)>>1;
y = (int)(ent->s.old_origin[1]+4096)>>1;
z = (int)(ent->s.old_origin[2]+4096)>>1;
bits[len++] = x;
bits[len++] = (x>>8) | (y<<4);
bits[len++] = (y>>4);
bits[len++] = z;
bits[len++] = (z>>8);
}
bits[len++] = p;
bits[len++] = yaw;
bits[len++] = ent->s.modelindex;
bits[len++] = (ent->s.number & 0x7f);
if (ent->s.number > 255) {
bits[len-1] |= 128;
bits[len++] = (ent->s.number >> 7);
}
for (i=0 ; i<len ; i++)
MSG_WriteByte (msg, bits[i]);
}
}
#endif
/*
=============
SV_EmitPacketEntities
Writes a delta update of an entity_state_t list to the message.
=============
*/
void SV_EmitPacketEntities (client_frame_t *from, client_frame_t *to, sizebuf_t *msg)
{
entity_state_t *oldent, *newent;
int oldindex, newindex;
int oldnum, newnum;
int from_num_entities;
int bits;
#if 0
if (numprojs)
MSG_WriteByte (msg, svc_packetentities2);
else
#endif
MSG_WriteByte (msg, svc_packetentities);
if (!from)
from_num_entities = 0;
else
from_num_entities = from->num_entities;
newindex = 0;
oldindex = 0;
while (newindex < to->num_entities || oldindex < from_num_entities)
{
if (newindex >= to->num_entities)
newnum = 9999;
else
{
newent = &svs.client_entities[(to->first_entity+newindex)%svs.num_client_entities];
newnum = newent->number;
}
if (oldindex >= from_num_entities)
oldnum = 9999;
else
{
oldent = &svs.client_entities[(from->first_entity+oldindex)%svs.num_client_entities];
oldnum = oldent->number;
}
if (newnum == oldnum)
{ // delta update from old position
// because the force parm is false, this will not result
// in any bytes being emited if the entity has not changed at all
// note that players are always 'newentities', this updates their oldorigin always
// and prevents warping
MSG_WriteDeltaEntity (oldent, newent, msg, false, newent->number <= maxclients->value);
oldindex++;
newindex++;
continue;
}
if (newnum < oldnum)
{ // this is a new entity, send it from the baseline
MSG_WriteDeltaEntity (&sv.baselines[newnum], newent, msg, true, true);
newindex++;
continue;
}
if (newnum > oldnum)
{ // the old entity isn't present in the new message
bits = U_REMOVE;
if (oldnum >= 256)
bits |= U_NUMBER16 | U_MOREBITS1;
MSG_WriteByte (msg, bits&255 );
if (bits & 0x0000ff00)
MSG_WriteByte (msg, (bits>>8)&255 );
if (bits & U_NUMBER16)
MSG_WriteShort (msg, oldnum);
else
MSG_WriteByte (msg, oldnum);
oldindex++;
continue;
}
}
MSG_WriteShort (msg, 0); // end of packetentities
#if 0
if (numprojs)
SV_EmitProjectileUpdate(msg);
#endif
}
/*
=============
SV_WritePlayerstateToClient
=============
*/
void SV_WritePlayerstateToClient (client_frame_t *from, client_frame_t *to, sizebuf_t *msg)
{
int i;
int pflags;
player_state_t *ps, *ops;
player_state_t dummy;
int statbits;
ps = &to->ps;
if (!from)
{
memset (&dummy, 0, sizeof(dummy));
ops = &dummy;
}
else
ops = &from->ps;
//
// determine what needs to be sent
//
pflags = 0;
if (ps->pmove.pm_type != ops->pmove.pm_type)
pflags |= PS_M_TYPE;
if (ps->pmove.origin[0] != ops->pmove.origin[0]
|| ps->pmove.origin[1] != ops->pmove.origin[1]
|| ps->pmove.origin[2] != ops->pmove.origin[2] )
pflags |= PS_M_ORIGIN;
if (ps->pmove.velocity[0] != ops->pmove.velocity[0]
|| ps->pmove.velocity[1] != ops->pmove.velocity[1]
|| ps->pmove.velocity[2] != ops->pmove.velocity[2] )
pflags |= PS_M_VELOCITY;
if (ps->pmove.pm_time != ops->pmove.pm_time)
pflags |= PS_M_TIME;
if (ps->pmove.pm_flags != ops->pmove.pm_flags)
pflags |= PS_M_FLAGS;
if (ps->pmove.gravity != ops->pmove.gravity)
pflags |= PS_M_GRAVITY;
if (ps->pmove.delta_angles[0] != ops->pmove.delta_angles[0]
|| ps->pmove.delta_angles[1] != ops->pmove.delta_angles[1]
|| ps->pmove.delta_angles[2] != ops->pmove.delta_angles[2] )
pflags |= PS_M_DELTA_ANGLES;
if (ps->viewoffset[0] != ops->viewoffset[0]
|| ps->viewoffset[1] != ops->viewoffset[1]
|| ps->viewoffset[2] != ops->viewoffset[2] )
pflags |= PS_VIEWOFFSET;
if (ps->viewangles[0] != ops->viewangles[0]
|| ps->viewangles[1] != ops->viewangles[1]
|| ps->viewangles[2] != ops->viewangles[2] )
pflags |= PS_VIEWANGLES;
if (ps->kick_angles[0] != ops->kick_angles[0]
|| ps->kick_angles[1] != ops->kick_angles[1]
|| ps->kick_angles[2] != ops->kick_angles[2] )
pflags |= PS_KICKANGLES;
if (ps->blend[0] != ops->blend[0]
|| ps->blend[1] != ops->blend[1]
|| ps->blend[2] != ops->blend[2]
|| ps->blend[3] != ops->blend[3] )
pflags |= PS_BLEND;
if (ps->fov != ops->fov)
pflags |= PS_FOV;
if (ps->rdflags != ops->rdflags)
pflags |= PS_RDFLAGS;
if (ps->gunframe != ops->gunframe)
pflags |= PS_WEAPONFRAME;
pflags |= PS_WEAPONINDEX;
//
// write it
//
MSG_WriteByte (msg, svc_playerinfo);
MSG_WriteShort (msg, pflags);
//
// write the pmove_state_t
//
if (pflags & PS_M_TYPE)
MSG_WriteByte (msg, ps->pmove.pm_type);
if (pflags & PS_M_ORIGIN)
{
MSG_WriteShort (msg, ps->pmove.origin[0]);
MSG_WriteShort (msg, ps->pmove.origin[1]);
MSG_WriteShort (msg, ps->pmove.origin[2]);
}
if (pflags & PS_M_VELOCITY)
{
MSG_WriteShort (msg, ps->pmove.velocity[0]);
MSG_WriteShort (msg, ps->pmove.velocity[1]);
MSG_WriteShort (msg, ps->pmove.velocity[2]);
}
if (pflags & PS_M_TIME)
MSG_WriteByte (msg, ps->pmove.pm_time);
if (pflags & PS_M_FLAGS)
MSG_WriteByte (msg, ps->pmove.pm_flags);
if (pflags & PS_M_GRAVITY)
MSG_WriteShort (msg, ps->pmove.gravity);
if (pflags & PS_M_DELTA_ANGLES)
{
MSG_WriteShort (msg, ps->pmove.delta_angles[0]);
MSG_WriteShort (msg, ps->pmove.delta_angles[1]);
MSG_WriteShort (msg, ps->pmove.delta_angles[2]);
}
//
// write the rest of the player_state_t
//
if (pflags & PS_VIEWOFFSET)
{
MSG_WriteChar (msg, ps->viewoffset[0]*4);
MSG_WriteChar (msg, ps->viewoffset[1]*4);
MSG_WriteChar (msg, ps->viewoffset[2]*4);
}
if (pflags & PS_VIEWANGLES)
{
MSG_WriteAngle16 (msg, ps->viewangles[0]);
MSG_WriteAngle16 (msg, ps->viewangles[1]);
MSG_WriteAngle16 (msg, ps->viewangles[2]);
}
if (pflags & PS_KICKANGLES)
{
MSG_WriteChar (msg, ps->kick_angles[0]*4);
MSG_WriteChar (msg, ps->kick_angles[1]*4);
MSG_WriteChar (msg, ps->kick_angles[2]*4);
}
if (pflags & PS_WEAPONINDEX)
{
MSG_WriteByte (msg, ps->gunindex);
}
if (pflags & PS_WEAPONFRAME)
{
MSG_WriteByte (msg, ps->gunframe);
MSG_WriteChar (msg, ps->gunoffset[0]*4);
MSG_WriteChar (msg, ps->gunoffset[1]*4);
MSG_WriteChar (msg, ps->gunoffset[2]*4);
MSG_WriteChar (msg, ps->gunangles[0]*4);
MSG_WriteChar (msg, ps->gunangles[1]*4);
MSG_WriteChar (msg, ps->gunangles[2]*4);
}
if (pflags & PS_BLEND)
{
MSG_WriteByte (msg, ps->blend[0]*255);
MSG_WriteByte (msg, ps->blend[1]*255);
MSG_WriteByte (msg, ps->blend[2]*255);
MSG_WriteByte (msg, ps->blend[3]*255);
}
if (pflags & PS_FOV)
MSG_WriteByte (msg, ps->fov);
if (pflags & PS_RDFLAGS)
MSG_WriteByte (msg, ps->rdflags);
// send stats
statbits = 0;
for (i=0 ; i<MAX_STATS ; i++)
if (ps->stats[i] != ops->stats[i])
statbits |= 1<<i;
MSG_WriteLong (msg, statbits);
for (i=0 ; i<MAX_STATS ; i++)
if (statbits & (1<<i) )
MSG_WriteShort (msg, ps->stats[i]);
}
/*
==================
SV_WriteFrameToClient
==================
*/
void SV_WriteFrameToClient (client_t *client, sizebuf_t *msg)
{
client_frame_t *frame, *oldframe;
int lastframe;
//Com_Printf ("%i -> %i\n", client->lastframe, sv.framenum);
// this is the frame we are creating
frame = &client->frames[sv.framenum & UPDATE_MASK];
if (client->lastframe <= 0)
{ // client is asking for a retransmit
oldframe = NULL;
lastframe = -1;
}
else if (sv.framenum - client->lastframe >= (UPDATE_BACKUP - 3) )
{ // client hasn't gotten a good message through in a long time
// Com_Printf ("%s: Delta request from out-of-date packet.\n", client->name);
oldframe = NULL;
lastframe = -1;
}
else
{ // we have a valid message to delta from
oldframe = &client->frames[client->lastframe & UPDATE_MASK];
lastframe = client->lastframe;
}
MSG_WriteByte (msg, svc_frame);
MSG_WriteLong (msg, sv.framenum);
MSG_WriteLong (msg, lastframe); // what we are delta'ing from
MSG_WriteByte (msg, client->surpressCount); // rate dropped packets
client->surpressCount = 0;
// send over the areabits
MSG_WriteByte (msg, frame->areabytes);
SZ_Write (msg, frame->areabits, frame->areabytes);
// delta encode the playerstate
SV_WritePlayerstateToClient (oldframe, frame, msg);
// delta encode the entities
SV_EmitPacketEntities (oldframe, frame, msg);
}
/*
=============================================================================
Build a client frame structure
=============================================================================
*/
byte fatpvs[65536/8]; // 32767 is MAX_MAP_LEAFS
/*
============
SV_FatPVS
The client will interpolate the view position,
so we can't use a single PVS point
===========
*/
void SV_FatPVS (vec3_t org)
{
int leafs[64];
int i, j, count;
int longs;
byte *src;
vec3_t mins, maxs;
for (i=0 ; i<3 ; i++)
{
mins[i] = org[i] - 8;
maxs[i] = org[i] + 8;
}
count = CM_BoxLeafnums (mins, maxs, leafs, 64, NULL);
if (count < 1)
Com_Error (ERR_FATAL, "SV_FatPVS: count < 1");
longs = (CM_NumClusters()+31)>>5;
// convert leafs to clusters
for (i=0 ; i<count ; i++)
leafs[i] = CM_LeafCluster(leafs[i]);
memcpy (fatpvs, CM_ClusterPVS(leafs[0]), longs<<2);
// or in all the other leaf bits
for (i=1 ; i<count ; i++)
{
for (j=0 ; j<i ; j++)
if (leafs[i] == leafs[j])
break;
if (j != i)
continue; // already have the cluster we want
src = CM_ClusterPVS(leafs[i]);
for (j=0 ; j<longs ; j++)
((long *)fatpvs)[j] |= ((long *)src)[j];
}
}
/*
=============
SV_BuildClientFrame
Decides which entities are going to be visible to the client, and
copies off the playerstat and areabits.
=============
*/
void SV_BuildClientFrame (client_t *client)
{
int e, i;
vec3_t org;
edict_t *ent;
edict_t *clent;
client_frame_t *frame;
entity_state_t *state;
int l;
int clientarea, clientcluster;
int leafnum;
int c_fullsend;
byte *clientphs;
byte *bitvector;
clent = client->edict;
if (!clent->client)
return; // not in game yet
#if 0
numprojs = 0; // no projectiles yet
#endif
// this is the frame we are creating
frame = &client->frames[sv.framenum & UPDATE_MASK];
frame->senttime = svs.realtime; // save it for ping calc later
// find the client's PVS
for (i=0 ; i<3 ; i++)
org[i] = clent->client->ps.pmove.origin[i]*0.125 + clent->client->ps.viewoffset[i];
leafnum = CM_PointLeafnum (org);
clientarea = CM_LeafArea (leafnum);
clientcluster = CM_LeafCluster (leafnum);
// calculate the visible areas
frame->areabytes = CM_WriteAreaBits (frame->areabits, clientarea);
// grab the current player_state_t
frame->ps = clent->client->ps;
SV_FatPVS (org);
clientphs = CM_ClusterPHS (clientcluster);
// build up the list of visible entities
frame->num_entities = 0;
frame->first_entity = svs.next_client_entities;
c_fullsend = 0;
for (e=1 ; e<ge->num_edicts ; e++)
{
ent = EDICT_NUM(e);
// ignore ents without visible models
if (ent->svflags & SVF_NOCLIENT)
continue;
// ignore ents without visible models unless they have an effect
if (!ent->s.modelindex && !ent->s.effects && !ent->s.sound
&& !ent->s.event)
continue;
// ignore if not touching a PV leaf
if (ent != clent)
{
// check area
if (!CM_AreasConnected (clientarea, ent->areanum))
{ // doors can legally straddle two areas, so
// we may need to check another one
if (!ent->areanum2
|| !CM_AreasConnected (clientarea, ent->areanum2))
continue; // blocked by a door
}
// beams just check one point for PHS
if (ent->s.renderfx & RF_BEAM)
{
l = ent->clusternums[0];
if ( !(clientphs[l >> 3] & (1 << (l&7) )) )
continue;
}
else
{
// FIXME: if an ent has a model and a sound, but isn't
// in the PVS, only the PHS, clear the model
if (ent->s.sound)
{
bitvector = fatpvs; //clientphs;
}
else
bitvector = fatpvs;
if (ent->num_clusters == -1)
{ // too many leafs for individual check, go by headnode
if (!CM_HeadnodeVisible (ent->headnode, bitvector))
continue;
c_fullsend++;
}
else
{ // check individual leafs
for (i=0 ; i < ent->num_clusters ; i++)
{
l = ent->clusternums[i];
if (bitvector[l >> 3] & (1 << (l&7) ))
break;
}
if (i == ent->num_clusters)
continue; // not visible
}
if (!ent->s.modelindex)
{ // don't send sounds if they will be attenuated away
vec3_t delta;
float len;
VectorSubtract (org, ent->s.origin, delta);
len = VectorLength (delta);
if (len > 400)
continue;
}
}
}
#if 0
if (SV_AddProjectileUpdate(ent))
continue; // added as a special projectile
#endif
// add it to the circular client_entities array
state = &svs.client_entities[svs.next_client_entities%svs.num_client_entities];
if (ent->s.number != e)
{
Com_DPrintf ("FIXING ENT->S.NUMBER!!!\n");
ent->s.number = e;
}
*state = ent->s;
// don't mark players missiles as solid
if (ent->owner == client->edict)
state->solid = 0;
svs.next_client_entities++;
frame->num_entities++;
}
}
/*
==================
SV_RecordDemoMessage
Save everything in the world out without deltas.
Used for recording footage for merged or assembled demos
==================
*/
void SV_RecordDemoMessage (void)
{
int e;
edict_t *ent;
entity_state_t nostate;
sizebuf_t buf;
byte buf_data[32768];
int len;
if (!svs.demofile)
return;
memset (&nostate, 0, sizeof(nostate));
SZ_Init (&buf, buf_data, sizeof(buf_data));
// write a frame message that doesn't contain a player_state_t
MSG_WriteByte (&buf, svc_frame);
MSG_WriteLong (&buf, sv.framenum);
MSG_WriteByte (&buf, svc_packetentities);
e = 1;
ent = EDICT_NUM(e);
while (e < ge->num_edicts)
{
// ignore ents without visible models unless they have an effect
if (ent->inuse &&
ent->s.number &&
(ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event) &&
!(ent->svflags & SVF_NOCLIENT))
MSG_WriteDeltaEntity (&nostate, &ent->s, &buf, false, true);
e++;
ent = EDICT_NUM(e);
}
MSG_WriteShort (&buf, 0); // end of packetentities
// now add the accumulated multicast information
SZ_Write (&buf, svs.demo_multicast.data, svs.demo_multicast.cursize);
SZ_Clear (&svs.demo_multicast);
// now write the entire message to the file, prefixed by the length
len = LittleLong (buf.cursize);
fwrite (&len, 4, 1, svs.demofile);
fwrite (buf.data, buf.cursize, 1, svs.demofile);
}

396
server/sv_game.c Normal file
View File

@ -0,0 +1,396 @@
/*
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_game.c -- interface to the game dll
#include "server.h"
game_export_t *ge;
/*
===============
PF_Unicast
Sends the contents of the mutlicast buffer to a single client
===============
*/
void PF_Unicast (edict_t *ent, qboolean reliable)
{
int p;
client_t *client;
if (!ent)
return;
p = NUM_FOR_EDICT(ent);
if (p < 1 || p > maxclients->value)
return;
client = svs.clients + (p-1);
if (reliable)
SZ_Write (&client->netchan.message, sv.multicast.data, sv.multicast.cursize);
else
SZ_Write (&client->datagram, sv.multicast.data, sv.multicast.cursize);
SZ_Clear (&sv.multicast);
}
/*
===============
PF_dprintf
Debug print to server console
===============
*/
void PF_dprintf (char *fmt, ...)
{
char msg[1024];
va_list argptr;
va_start (argptr,fmt);
vsprintf (msg, fmt, argptr);
va_end (argptr);
Com_Printf ("%s", msg);
}
/*
===============
PF_cprintf
Print to a single client
===============
*/
void PF_cprintf (edict_t *ent, int level, char *fmt, ...)
{
char msg[1024];
va_list argptr;
int n;
if (ent)
{
n = NUM_FOR_EDICT(ent);
if (n < 1 || n > maxclients->value)
Com_Error (ERR_DROP, "cprintf to a non-client");
}
va_start (argptr,fmt);
vsprintf (msg, fmt, argptr);
va_end (argptr);
if (ent)
SV_ClientPrintf (svs.clients+(n-1), level, "%s", msg);
else
Com_Printf ("%s", msg);
}
/*
===============
PF_centerprintf
centerprint to a single client
===============
*/
void PF_centerprintf (edict_t *ent, char *fmt, ...)
{
char msg[1024];
va_list argptr;
int n;
n = NUM_FOR_EDICT(ent);
if (n < 1 || n > maxclients->value)
return; // Com_Error (ERR_DROP, "centerprintf to a non-client");
va_start (argptr,fmt);
vsprintf (msg, fmt, argptr);
va_end (argptr);
MSG_WriteByte (&sv.multicast,svc_centerprint);
MSG_WriteString (&sv.multicast,msg);
PF_Unicast (ent, true);
}
/*
===============
PF_error
Abort the server with a game error
===============
*/
void PF_error (char *fmt, ...)
{
char msg[1024];
va_list argptr;
va_start (argptr,fmt);
vsprintf (msg, fmt, argptr);
va_end (argptr);
Com_Error (ERR_DROP, "Game Error: %s", msg);
}
/*
=================
PF_setmodel
Also sets mins and maxs for inline bmodels
=================
*/
void PF_setmodel (edict_t *ent, char *name)
{
int i;
cmodel_t *mod;
if (!name)
Com_Error (ERR_DROP, "PF_setmodel: NULL");
i = SV_ModelIndex (name);
// ent->model = name;
ent->s.modelindex = i;
// if it is an inline model, get the size information for it
if (name[0] == '*')
{
mod = CM_InlineModel (name);
VectorCopy (mod->mins, ent->mins);
VectorCopy (mod->maxs, ent->maxs);
SV_LinkEdict (ent);
}
}
/*
===============
PF_Configstring
===============
*/
void PF_Configstring (int index, char *val)
{
if (index < 0 || index >= MAX_CONFIGSTRINGS)
Com_Error (ERR_DROP, "configstring: bad index %i\n", index);
if (!val)
val = "";
// change the string in sv
strcpy (sv.configstrings[index], val);
if (sv.state != ss_loading)
{ // send the update to everyone
SZ_Clear (&sv.multicast);
MSG_WriteChar (&sv.multicast, svc_configstring);
MSG_WriteShort (&sv.multicast, index);
MSG_WriteString (&sv.multicast, val);
SV_Multicast (vec3_origin, MULTICAST_ALL_R);
}
}
void PF_WriteChar (int c) {MSG_WriteChar (&sv.multicast, c);}
void PF_WriteByte (int c) {MSG_WriteByte (&sv.multicast, c);}
void PF_WriteShort (int c) {MSG_WriteShort (&sv.multicast, c);}
void PF_WriteLong (int c) {MSG_WriteLong (&sv.multicast, c);}
void PF_WriteFloat (float f) {MSG_WriteFloat (&sv.multicast, f);}
void PF_WriteString (char *s) {MSG_WriteString (&sv.multicast, s);}
void PF_WritePos (vec3_t pos) {MSG_WritePos (&sv.multicast, pos);}
void PF_WriteDir (vec3_t dir) {MSG_WriteDir (&sv.multicast, dir);}
void PF_WriteAngle (float f) {MSG_WriteAngle (&sv.multicast, f);}
/*
=================
PF_inPVS
Also checks portalareas so that doors block sight
=================
*/
qboolean PF_inPVS (vec3_t p1, vec3_t p2)
{
int leafnum;
int cluster;
int area1, area2;
byte *mask;
leafnum = CM_PointLeafnum (p1);
cluster = CM_LeafCluster (leafnum);
area1 = CM_LeafArea (leafnum);
mask = CM_ClusterPVS (cluster);
leafnum = CM_PointLeafnum (p2);
cluster = CM_LeafCluster (leafnum);
area2 = CM_LeafArea (leafnum);
if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
return false;
if (!CM_AreasConnected (area1, area2))
return false; // a door blocks sight
return true;
}
/*
=================
PF_inPHS
Also checks portalareas so that doors block sound
=================
*/
qboolean PF_inPHS (vec3_t p1, vec3_t p2)
{
int leafnum;
int cluster;
int area1, area2;
byte *mask;
leafnum = CM_PointLeafnum (p1);
cluster = CM_LeafCluster (leafnum);
area1 = CM_LeafArea (leafnum);
mask = CM_ClusterPHS (cluster);
leafnum = CM_PointLeafnum (p2);
cluster = CM_LeafCluster (leafnum);
area2 = CM_LeafArea (leafnum);
if ( mask && (!(mask[cluster>>3] & (1<<(cluster&7)) ) ) )
return false; // more than one bounce away
if (!CM_AreasConnected (area1, area2))
return false; // a door blocks hearing
return true;
}
void PF_StartSound (edict_t *entity, int channel, int sound_num, float volume,
float attenuation, float timeofs)
{
if (!entity)
return;
SV_StartSound (NULL, entity, channel, sound_num, volume, attenuation, timeofs);
}
//==============================================
/*
===============
SV_ShutdownGameProgs
Called when either the entire server is being killed, or
it is changing to a different game directory.
===============
*/
void SV_ShutdownGameProgs (void)
{
if (!ge)
return;
ge->Shutdown ();
Sys_UnloadGame ();
ge = NULL;
}
/*
===============
SV_InitGameProgs
Init the game subsystem for a new map
===============
*/
void SCR_DebugGraph (float value, int color);
void SV_InitGameProgs (void)
{
game_import_t import;
// unload anything we have now
if (ge)
SV_ShutdownGameProgs ();
// load a new game dll
import.multicast = SV_Multicast;
import.unicast = PF_Unicast;
import.bprintf = SV_BroadcastPrintf;
import.dprintf = PF_dprintf;
import.cprintf = PF_cprintf;
import.centerprintf = PF_centerprintf;
import.error = PF_error;
import.linkentity = SV_LinkEdict;
import.unlinkentity = SV_UnlinkEdict;
import.BoxEdicts = SV_AreaEdicts;
import.trace = SV_Trace;
import.pointcontents = SV_PointContents;
import.setmodel = PF_setmodel;
import.inPVS = PF_inPVS;
import.inPHS = PF_inPHS;
import.Pmove = Pmove;
import.modelindex = SV_ModelIndex;
import.soundindex = SV_SoundIndex;
import.imageindex = SV_ImageIndex;
import.configstring = PF_Configstring;
import.sound = PF_StartSound;
import.positioned_sound = SV_StartSound;
import.WriteChar = PF_WriteChar;
import.WriteByte = PF_WriteByte;
import.WriteShort = PF_WriteShort;
import.WriteLong = PF_WriteLong;
import.WriteFloat = PF_WriteFloat;
import.WriteString = PF_WriteString;
import.WritePosition = PF_WritePos;
import.WriteDir = PF_WriteDir;
import.WriteAngle = PF_WriteAngle;
import.TagMalloc = Z_TagMalloc;
import.TagFree = Z_Free;
import.FreeTags = Z_FreeTags;
import.cvar = Cvar_Get;
import.cvar_set = Cvar_Set;
import.cvar_forceset = Cvar_ForceSet;
import.argc = Cmd_Argc;
import.argv = Cmd_Argv;
import.args = Cmd_Args;
import.AddCommandString = Cbuf_AddText;
import.DebugGraph = SCR_DebugGraph;
import.SetAreaPortalState = CM_SetAreaPortalState;
import.AreasConnected = CM_AreasConnected;
ge = (game_export_t *)Sys_GetGameAPI (&import);
if (!ge)
Com_Error (ERR_DROP, "failed to load game DLL");
if (ge->apiversion != GAME_API_VERSION)
Com_Error (ERR_DROP, "game is version %i, not %i", ge->apiversion,
GAME_API_VERSION);
ge->Init ();
}

465
server/sv_init.c Normal file
View File

@ -0,0 +1,465 @@
/*
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.
*/
#include "server.h"
server_static_t svs; // persistant server info
server_t sv; // local server
/*
================
SV_FindIndex
================
*/
int SV_FindIndex (char *name, int start, int max, qboolean create)
{
int i;
if (!name || !name[0])
return 0;
for (i=1 ; i<max && sv.configstrings[start+i][0] ; i++)
if (!strcmp(sv.configstrings[start+i], name))
return i;
if (!create)
return 0;
if (i == max)
Com_Error (ERR_DROP, "*Index: overflow");
strncpy (sv.configstrings[start+i], name, sizeof(sv.configstrings[i]));
if (sv.state != ss_loading)
{ // send the update to everyone
SZ_Clear (&sv.multicast);
MSG_WriteChar (&sv.multicast, svc_configstring);
MSG_WriteShort (&sv.multicast, start+i);
MSG_WriteString (&sv.multicast, name);
SV_Multicast (vec3_origin, MULTICAST_ALL_R);
}
return i;
}
int SV_ModelIndex (char *name)
{
return SV_FindIndex (name, CS_MODELS, MAX_MODELS, true);
}
int SV_SoundIndex (char *name)
{
return SV_FindIndex (name, CS_SOUNDS, MAX_SOUNDS, true);
}
int SV_ImageIndex (char *name)
{
return SV_FindIndex (name, CS_IMAGES, MAX_IMAGES, true);
}
/*
================
SV_CreateBaseline
Entity baselines are used to compress the update messages
to the clients -- only the fields that differ from the
baseline will be transmitted
================
*/
void SV_CreateBaseline (void)
{
edict_t *svent;
int entnum;
for (entnum = 1; entnum < ge->num_edicts ; entnum++)
{
svent = EDICT_NUM(entnum);
if (!svent->inuse)
continue;
if (!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
continue;
svent->s.number = entnum;
//
// take current state as baseline
//
VectorCopy (svent->s.origin, svent->s.old_origin);
sv.baselines[entnum] = svent->s;
}
}
/*
=================
SV_CheckForSavegame
=================
*/
void SV_CheckForSavegame (void)
{
char name[MAX_OSPATH];
FILE *f;
int i;
if (sv_noreload->value)
return;
if (Cvar_VariableValue ("deathmatch"))
return;
Com_sprintf (name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
f = fopen (name, "rb");
if (!f)
return; // no savegame
fclose (f);
SV_ClearWorld ();
// get configstrings and areaportals
SV_ReadLevelFile ();
if (!sv.loadgame)
{ // coming back to a level after being in a different
// level, so run it for ten seconds
// rlava2 was sending too many lightstyles, and overflowing the
// reliable data. temporarily changing the server state to loading
// prevents these from being passed down.
server_state_t previousState; // PGM
previousState = sv.state; // PGM
sv.state = ss_loading; // PGM
for (i=0 ; i<100 ; i++)
ge->RunFrame ();
sv.state = previousState; // PGM
}
}
/*
================
SV_SpawnServer
Change the server to a new map, taking all connected
clients along with it.
================
*/
void SV_SpawnServer (char *server, char *spawnpoint, server_state_t serverstate, qboolean attractloop, qboolean loadgame)
{
int i;
unsigned checksum;
if (attractloop)
Cvar_Set ("paused", "0");
Com_Printf ("------- Server Initialization -------\n");
Com_DPrintf ("SpawnServer: %s\n",server);
if (sv.demofile)
fclose (sv.demofile);
svs.spawncount++; // any partially connected client will be
// restarted
sv.state = ss_dead;
Com_SetServerState (sv.state);
// wipe the entire per-level structure
memset (&sv, 0, sizeof(sv));
svs.realtime = 0;
sv.loadgame = loadgame;
sv.attractloop = attractloop;
// save name for levels that don't set message
strcpy (sv.configstrings[CS_NAME], server);
if (Cvar_VariableValue ("deathmatch"))
{
sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value);
pm_airaccelerate = sv_airaccelerate->value;
}
else
{
strcpy(sv.configstrings[CS_AIRACCEL], "0");
pm_airaccelerate = 0;
}
SZ_Init (&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));
strcpy (sv.name, server);
// leave slots at start for clients only
for (i=0 ; i<maxclients->value ; i++)
{
// needs to reconnect
if (svs.clients[i].state > cs_connected)
svs.clients[i].state = cs_connected;
svs.clients[i].lastframe = -1;
}
sv.time = 1000;
strcpy (sv.name, server);
strcpy (sv.configstrings[CS_NAME], server);
if (serverstate != ss_game)
{
sv.models[1] = CM_LoadMap ("", false, &checksum); // no real map
}
else
{
Com_sprintf (sv.configstrings[CS_MODELS+1],sizeof(sv.configstrings[CS_MODELS+1]),
"maps/%s.bsp", server);
sv.models[1] = CM_LoadMap (sv.configstrings[CS_MODELS+1], false, &checksum);
}
Com_sprintf (sv.configstrings[CS_MAPCHECKSUM],sizeof(sv.configstrings[CS_MAPCHECKSUM]),
"%i", checksum);
//
// clear physics interaction links
//
SV_ClearWorld ();
for (i=1 ; i< CM_NumInlineModels() ; i++)
{
Com_sprintf (sv.configstrings[CS_MODELS+1+i], sizeof(sv.configstrings[CS_MODELS+1+i]),
"*%i", i);
sv.models[i+1] = CM_InlineModel (sv.configstrings[CS_MODELS+1+i]);
}
//
// spawn the rest of the entities on the map
//
// precache and static commands can be issued during
// map initialization
sv.state = ss_loading;
Com_SetServerState (sv.state);
// load and spawn all other entities
ge->SpawnEntities ( sv.name, CM_EntityString(), spawnpoint );
// run two frames to allow everything to settle
ge->RunFrame ();
ge->RunFrame ();
// all precaches are complete
sv.state = serverstate;
Com_SetServerState (sv.state);
// create a baseline for more efficient communications
SV_CreateBaseline ();
// check for a savegame
SV_CheckForSavegame ();
// set serverinfo variable
Cvar_FullSet ("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);
Com_Printf ("-------------------------------------\n");
}
/*
==============
SV_InitGame
A brand new game has been started
==============
*/
void SV_InitGame (void)
{
int i;
edict_t *ent;
char idmaster[32];
if (svs.initialized)
{
// cause any connected clients to reconnect
SV_Shutdown ("Server restarted\n", true);
}
else
{
// make sure the client is down
CL_Drop ();
SCR_BeginLoadingPlaque ();
}
// get any latched variable changes (maxclients, etc)
Cvar_GetLatchedVars ();
svs.initialized = true;
if (Cvar_VariableValue ("coop") && Cvar_VariableValue ("deathmatch"))
{
Com_Printf("Deathmatch and Coop both set, disabling Coop\n");
Cvar_FullSet ("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
}
// dedicated servers are can't be single player and are usually DM
// so unless they explicity set coop, force it to deathmatch
if (dedicated->value)
{
if (!Cvar_VariableValue ("coop"))
Cvar_FullSet ("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
}
// init clients
if (Cvar_VariableValue ("deathmatch"))
{
if (maxclients->value <= 1)
Cvar_FullSet ("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
else if (maxclients->value > MAX_CLIENTS)
Cvar_FullSet ("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
}
else if (Cvar_VariableValue ("coop"))
{
if (maxclients->value <= 1 || maxclients->value > 4)
Cvar_FullSet ("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
#ifdef COPYPROTECT
if (!sv.attractloop && !dedicated->value)
Sys_CopyProtect ();
#endif
}
else // non-deathmatch, non-coop is one player
{
Cvar_FullSet ("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
#ifdef COPYPROTECT
if (!sv.attractloop)
Sys_CopyProtect ();
#endif
}
svs.spawncount = rand();
svs.clients = Z_Malloc (sizeof(client_t)*maxclients->value);
svs.num_client_entities = maxclients->value*UPDATE_BACKUP*64;
svs.client_entities = Z_Malloc (sizeof(entity_state_t)*svs.num_client_entities);
// init network stuff
NET_Config ( (maxclients->value > 1) );
// heartbeats will always be sent to the id master
svs.last_heartbeat = -99999; // send immediately
Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER);
NET_StringToAdr (idmaster, &master_adr[0]);
// init game
SV_InitGameProgs ();
for (i=0 ; i<maxclients->value ; i++)
{
ent = EDICT_NUM(i+1);
ent->s.number = i+1;
svs.clients[i].edict = ent;
memset (&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
}
}
/*
======================
SV_Map
the full syntax is:
map [*]<map>$<startspot>+<nextserver>
command from the console or progs.
Map can also be a.cin, .pcx, or .dm2 file
Nextserver is used to allow a cinematic to play, then proceed to
another level:
map tram.cin+jail_e3
======================
*/
void SV_Map (qboolean attractloop, char *levelstring, qboolean loadgame)
{
char level[MAX_QPATH];
char *ch;
int l;
char spawnpoint[MAX_QPATH];
sv.loadgame = loadgame;
sv.attractloop = attractloop;
if (sv.state == ss_dead && !sv.loadgame)
SV_InitGame (); // the game is just starting
strcpy (level, levelstring);
// if there is a + in the map, set nextserver to the remainder
ch = strstr(level, "+");
if (ch)
{
*ch = 0;
Cvar_Set ("nextserver", va("gamemap \"%s\"", ch+1));
}
else
Cvar_Set ("nextserver", "");
//ZOID special hack for end game screen in coop mode
if (Cvar_VariableValue ("coop") && !Q_stricmp(level, "victory.pcx"))
Cvar_Set ("nextserver", "gamemap \"*base1\"");
// if there is a $, use the remainder as a spawnpoint
ch = strstr(level, "$");
if (ch)
{
*ch = 0;
strcpy (spawnpoint, ch+1);
}
else
spawnpoint[0] = 0;
// skip the end-of-unit flag if necessary
if (level[0] == '*')
strcpy (level, level+1);
l = strlen(level);
if (l > 4 && !strcmp (level+l-4, ".cin") )
{
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_cinematic, attractloop, loadgame);
}
else if (l > 4 && !strcmp (level+l-4, ".dm2") )
{
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_demo, attractloop, loadgame);
}
else if (l > 4 && !strcmp (level+l-4, ".pcx") )
{
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SpawnServer (level, spawnpoint, ss_pic, attractloop, loadgame);
}
else
{
SCR_BeginLoadingPlaque (); // for local system
SV_BroadcastCommand ("changing\n");
SV_SendClientMessages ();
SV_SpawnServer (level, spawnpoint, ss_game, attractloop, loadgame);
Cbuf_CopyToDefer ();
}
SV_BroadcastCommand ("reconnect\n");
}

1055
server/sv_main.c Normal file

File diff suppressed because it is too large Load Diff

15
server/sv_null.c Normal file
View File

@ -0,0 +1,15 @@
// sv_null.c -- this file can stub out the entire server system
// for pure net-only clients
void SV_Init (void)
{
}
void SV_Shutdown (char *finalmsg, qboolean reconnect)
{
}
void SV_Frame (float time)
{
}

567
server/sv_send.c Normal file
View File

@ -0,0 +1,567 @@
/*
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 "server.h"
/*
=============================================================================
Com_Printf 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 copy[1024];
int i;
// mask off high bits
for (i=0 ; i<1023 && string[i] ; i++)
copy[i] = string[i]&127;
copy[i] = 0;
Com_Printf ("%s", copy);
}
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_WriteByte (&sv.multicast, svc_stufftext);
MSG_WriteString (&sv.multicast, string);
SV_Multicast (NULL, MULTICAST_ALL_R);
}
/*
=================
SV_Multicast
Sends the contents of sv.multicast to a subset of the clients,
then clears sv.multicast.
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 SV_Multicast (vec3_t origin, multicast_t to)
{
client_t *client;
byte *mask;
int leafnum, cluster;
int j;
qboolean reliable;
int area1, area2;
reliable = false;
if (to != MULTICAST_ALL_R && to != MULTICAST_ALL)
{
leafnum = CM_PointLeafnum (origin);
area1 = CM_LeafArea (leafnum);
}
else
{
leafnum = 0; // just to avoid compiler warnings
area1 = 0;
}
// if doing a serverrecord, store everything
if (svs.demofile)
SZ_Write (&svs.demo_multicast, sv.multicast.data, sv.multicast.cursize);
switch (to)
{
case MULTICAST_ALL_R:
reliable = true; // intentional fallthrough
case MULTICAST_ALL:
leafnum = 0;
mask = NULL;
break;
case MULTICAST_PHS_R:
reliable = true; // intentional fallthrough
case MULTICAST_PHS:
leafnum = CM_PointLeafnum (origin);
cluster = CM_LeafCluster (leafnum);
mask = CM_ClusterPHS (cluster);
break;
case MULTICAST_PVS_R:
reliable = true; // intentional fallthrough
case MULTICAST_PVS:
leafnum = CM_PointLeafnum (origin);
cluster = CM_LeafCluster (leafnum);
mask = CM_ClusterPVS (cluster);
break;
default:
mask = NULL;
Com_Error (ERR_FATAL, "SV_Multicast: bad to:%i", to);
}
// send the data to all relevent clients
for (j = 0, client = svs.clients; j < maxclients->value; j++, client++)
{
if (client->state == cs_free || client->state == cs_zombie)
continue;
if (client->state != cs_spawned && !reliable)
continue;
if (mask)
{
leafnum = CM_PointLeafnum (client->edict->s.origin);
cluster = CM_LeafCluster (leafnum);
area2 = CM_LeafArea (leafnum);
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);
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.
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 sendchan;
int flags;
int i;
int ent;
vec3_t origin_v;
qboolean use_phs;
if (volume < 0 || volume > 1.0)
Com_Error (ERR_FATAL, "SV_StartSound: volume = %f", volume);
if (attenuation < 0 || attenuation > 4)
Com_Error (ERR_FATAL, "SV_StartSound: attenuation = %f", attenuation);
// if (channel < 0 || channel > 15)
// Com_Error (ERR_FATAL, "SV_StartSound: channel = %i", channel);
if (timeofs < 0 || timeofs > 0.255)
Com_Error (ERR_FATAL, "SV_StartSound: timeofs = %f", timeofs);
ent = 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->svflags & SVF_NOCLIENT)
|| (entity->solid == SOLID_BSP)
|| 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->solid == SOLID_BSP)
{
for (i=0 ; i<3 ; i++)
origin_v[i] = entity->s.origin[i]+0.5*(entity->mins[i]+entity->maxs[i]);
}
else
{
VectorCopy (entity->s.origin, origin_v);
}
}
MSG_WriteByte (&sv.multicast, 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_WritePos (&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)
SV_Multicast (origin, MULTICAST_PHS_R);
else
SV_Multicast (origin, MULTICAST_ALL_R);
}
else
{
if (use_phs)
SV_Multicast (origin, MULTICAST_PHS);
else
SV_Multicast (origin, MULTICAST_ALL);
}
}
/*
===============================================================================
FRAME UPDATES
===============================================================================
*/
/*
=======================
SV_SendClientDatagram
=======================
*/
qboolean SV_SendClientDatagram (client_t *client)
{
byte msg_buf[MAX_MSGLEN];
sizebuf_t msg;
SV_BuildClientFrame (client);
SZ_Init (&msg, msg_buf, sizeof(msg_buf));
msg.allowoverflow = true;
// 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)
Com_Printf ("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
Com_Printf ("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)
{
fclose (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
=======================
*/
qboolean 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];
int r;
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
r = fread (&msglen, 4, 1, sv.demofile);
if (r != 1)
{
SV_DemoCompleted ();
return;
}
msglen = LittleLong (msglen);
if (msglen == -1)
{
SV_DemoCompleted ();
return;
}
if (msglen > MAX_MSGLEN)
Com_Error (ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN");
r = fread (msgbuf, msglen, 1, sv.demofile);
if (r != 1)
{
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 || curtime - c->netchan.last_sent > 1000 )
Netchan_Transmit (&c->netchan, 0, NULL);
}
}
}

664
server/sv_user.c Normal file
View File

@ -0,0 +1,664 @@
/*
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_user.c -- server code for moving users
#include "server.h"
edict_t *sv_player;
/*
============================================================
USER STRINGCMD EXECUTION
sv_client and sv_player will be valid.
============================================================
*/
/*
==================
SV_BeginDemoServer
==================
*/
void SV_BeginDemoserver (void)
{
char name[MAX_OSPATH];
Com_sprintf (name, sizeof(name), "demos/%s", sv.name);
FS_FOpenFile (name, &sv.demofile);
if (!sv.demofile)
Com_Error (ERR_DROP, "Couldn't open %s\n", name);
}
/*
================
SV_New_f
Sends the first message from the server to a connected client.
This will be sent on the initial connection and upon each server load.
================
*/
void SV_New_f (void)
{
char *gamedir;
int playernum;
edict_t *ent;
Com_DPrintf ("New() from %s\n", sv_client->name);
if (sv_client->state != cs_connected)
{
Com_Printf ("New not valid -- already spawned\n");
return;
}
// demo servers just dump the file message
if (sv.state == ss_demo)
{
SV_BeginDemoserver ();
return;
}
//
// serverdata needs to go over for all types of servers
// to make sure the protocol is right, and to set the gamedir
//
gamedir = Cvar_VariableString ("gamedir");
// send the serverdata
MSG_WriteByte (&sv_client->netchan.message, svc_serverdata);
MSG_WriteLong (&sv_client->netchan.message, PROTOCOL_VERSION);
MSG_WriteLong (&sv_client->netchan.message, svs.spawncount);
MSG_WriteByte (&sv_client->netchan.message, sv.attractloop);
MSG_WriteString (&sv_client->netchan.message, gamedir);
if (sv.state == ss_cinematic || sv.state == ss_pic)
playernum = -1;
else
playernum = sv_client - svs.clients;
MSG_WriteShort (&sv_client->netchan.message, playernum);
// send full levelname
MSG_WriteString (&sv_client->netchan.message, sv.configstrings[CS_NAME]);
//
// game server
//
if (sv.state == ss_game)
{
// set up the entity for the client
ent = EDICT_NUM(playernum+1);
ent->s.number = playernum+1;
sv_client->edict = ent;
memset (&sv_client->lastcmd, 0, sizeof(sv_client->lastcmd));
// begin fetching configstrings
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i 0\n",svs.spawncount) );
}
}
/*
==================
SV_Configstrings_f
==================
*/
void SV_Configstrings_f (void)
{
int start;
Com_DPrintf ("Configstrings() from %s\n", sv_client->name);
if (sv_client->state != cs_connected)
{
Com_Printf ("configstrings not valid -- already spawned\n");
return;
}
// handle the case of a level changing while a client was connecting
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
{
Com_Printf ("SV_Configstrings_f from different level\n");
SV_New_f ();
return;
}
start = atoi(Cmd_Argv(2));
// write a packet full of data
while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
&& start < MAX_CONFIGSTRINGS)
{
if (sv.configstrings[start][0])
{
MSG_WriteByte (&sv_client->netchan.message, svc_configstring);
MSG_WriteShort (&sv_client->netchan.message, start);
MSG_WriteString (&sv_client->netchan.message, sv.configstrings[start]);
}
start++;
}
// send next command
if (start == MAX_CONFIGSTRINGS)
{
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i 0\n",svs.spawncount) );
}
else
{
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
MSG_WriteString (&sv_client->netchan.message, va("cmd configstrings %i %i\n",svs.spawncount, start) );
}
}
/*
==================
SV_Baselines_f
==================
*/
void SV_Baselines_f (void)
{
int start;
entity_state_t nullstate;
entity_state_t *base;
Com_DPrintf ("Baselines() from %s\n", sv_client->name);
if (sv_client->state != cs_connected)
{
Com_Printf ("baselines not valid -- already spawned\n");
return;
}
// handle the case of a level changing while a client was connecting
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
{
Com_Printf ("SV_Baselines_f from different level\n");
SV_New_f ();
return;
}
start = atoi(Cmd_Argv(2));
memset (&nullstate, 0, sizeof(nullstate));
// write a packet full of data
while ( sv_client->netchan.message.cursize < MAX_MSGLEN/2
&& start < MAX_EDICTS)
{
base = &sv.baselines[start];
if (base->modelindex || base->sound || base->effects)
{
MSG_WriteByte (&sv_client->netchan.message, svc_spawnbaseline);
MSG_WriteDeltaEntity (&nullstate, base, &sv_client->netchan.message, true, true);
}
start++;
}
// send next command
if (start == MAX_EDICTS)
{
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
MSG_WriteString (&sv_client->netchan.message, va("precache %i\n", svs.spawncount) );
}
else
{
MSG_WriteByte (&sv_client->netchan.message, svc_stufftext);
MSG_WriteString (&sv_client->netchan.message, va("cmd baselines %i %i\n",svs.spawncount, start) );
}
}
/*
==================
SV_Begin_f
==================
*/
void SV_Begin_f (void)
{
Com_DPrintf ("Begin() from %s\n", sv_client->name);
// handle the case of a level changing while a client was connecting
if ( atoi(Cmd_Argv(1)) != svs.spawncount )
{
Com_Printf ("SV_Begin_f from different level\n");
SV_New_f ();
return;
}
sv_client->state = cs_spawned;
// call the game begin function
ge->ClientBegin (sv_player);
Cbuf_InsertFromDefer ();
}
//=============================================================================
/*
==================
SV_NextDownload_f
==================
*/
void SV_NextDownload_f (void)
{
int r;
int percent;
int size;
if (!sv_client->download)
return;
r = sv_client->downloadsize - sv_client->downloadcount;
if (r > 1024)
r = 1024;
MSG_WriteByte (&sv_client->netchan.message, svc_download);
MSG_WriteShort (&sv_client->netchan.message, r);
sv_client->downloadcount += r;
size = sv_client->downloadsize;
if (!size)
size = 1;
percent = sv_client->downloadcount*100/size;
MSG_WriteByte (&sv_client->netchan.message, percent);
SZ_Write (&sv_client->netchan.message,
sv_client->download + sv_client->downloadcount - r, r);
if (sv_client->downloadcount != sv_client->downloadsize)
return;
FS_FreeFile (sv_client->download);
sv_client->download = NULL;
}
/*
==================
SV_BeginDownload_f
==================
*/
void SV_BeginDownload_f(void)
{
char *name;
extern cvar_t *allow_download;
extern cvar_t *allow_download_players;
extern cvar_t *allow_download_models;
extern cvar_t *allow_download_sounds;
extern cvar_t *allow_download_maps;
extern int file_from_pak; // ZOID did file come from pak?
int offset = 0;
name = Cmd_Argv(1);
if (Cmd_Argc() > 2)
offset = atoi(Cmd_Argv(2)); // downloaded offset
// hacked by zoid to allow more conrol over download
// first off, no .. or global allow check
if (strstr (name, "..") || !allow_download->value
// leading dot is no good
|| *name == '.'
// leading slash bad as well, must be in subdir
|| *name == '/'
// next up, skin check
|| (strncmp(name, "players/", 6) == 0 && !allow_download_players->value)
// now models
|| (strncmp(name, "models/", 6) == 0 && !allow_download_models->value)
// now sounds
|| (strncmp(name, "sound/", 6) == 0 && !allow_download_sounds->value)
// now maps (note special case for maps, must not be in pak)
|| (strncmp(name, "maps/", 6) == 0 && !allow_download_maps->value)
// MUST be in a subdirectory
|| !strstr (name, "/") )
{ // don't allow anything with .. path
MSG_WriteByte (&sv_client->netchan.message, svc_download);
MSG_WriteShort (&sv_client->netchan.message, -1);
MSG_WriteByte (&sv_client->netchan.message, 0);
return;
}
if (sv_client->download)
FS_FreeFile (sv_client->download);
sv_client->downloadsize = FS_LoadFile (name, (void **)&sv_client->download);
sv_client->downloadcount = offset;
if (offset > sv_client->downloadsize)
sv_client->downloadcount = sv_client->downloadsize;
if (!sv_client->download
// special check for maps, if it came from a pak file, don't allow
// download ZOID
|| (strncmp(name, "maps/", 5) == 0 && file_from_pak))
{
Com_DPrintf ("Couldn't download %s to %s\n", name, sv_client->name);
if (sv_client->download) {
FS_FreeFile (sv_client->download);
sv_client->download = NULL;
}
MSG_WriteByte (&sv_client->netchan.message, svc_download);
MSG_WriteShort (&sv_client->netchan.message, -1);
MSG_WriteByte (&sv_client->netchan.message, 0);
return;
}
SV_NextDownload_f ();
Com_DPrintf ("Downloading %s to %s\n", name, sv_client->name);
}
//============================================================================
/*
=================
SV_Disconnect_f
The client is going to disconnect, so remove the connection immediately
=================
*/
void SV_Disconnect_f (void)
{
// SV_EndRedirect ();
SV_DropClient (sv_client);
}
/*
==================
SV_ShowServerinfo_f
Dumps the serverinfo info string
==================
*/
void SV_ShowServerinfo_f (void)
{
Info_Print (Cvar_Serverinfo());
}
void SV_Nextserver (void)
{
char *v;
//ZOID, ss_pic can be nextserver'd in coop mode
if (sv.state == ss_game || (sv.state == ss_pic && !Cvar_VariableValue("coop")))
return; // can't nextserver while playing a normal game
svs.spawncount++; // make sure another doesn't sneak in
v = Cvar_VariableString ("nextserver");
if (!v[0])
Cbuf_AddText ("killserver\n");
else
{
Cbuf_AddText (v);
Cbuf_AddText ("\n");
}
Cvar_Set ("nextserver","");
}
/*
==================
SV_Nextserver_f
A cinematic has completed or been aborted by a client, so move
to the next server,
==================
*/
void SV_Nextserver_f (void)
{
if ( atoi(Cmd_Argv(1)) != svs.spawncount ) {
Com_DPrintf ("Nextserver() from wrong level, from %s\n", sv_client->name);
return; // leftover from last server
}
Com_DPrintf ("Nextserver() from %s\n", sv_client->name);
SV_Nextserver ();
}
typedef struct
{
char *name;
void (*func) (void);
} ucmd_t;
ucmd_t ucmds[] =
{
// auto issued
{"new", SV_New_f},
{"configstrings", SV_Configstrings_f},
{"baselines", SV_Baselines_f},
{"begin", SV_Begin_f},
{"nextserver", SV_Nextserver_f},
{"disconnect", SV_Disconnect_f},
// issued by hand at client consoles
{"info", SV_ShowServerinfo_f},
{"download", SV_BeginDownload_f},
{"nextdl", SV_NextDownload_f},
{NULL, NULL}
};
/*
==================
SV_ExecuteUserCommand
==================
*/
void SV_ExecuteUserCommand (char *s)
{
ucmd_t *u;
Cmd_TokenizeString (s, true);
sv_player = sv_client->edict;
// SV_BeginRedirect (RD_CLIENT);
for (u=ucmds ; u->name ; u++)
if (!strcmp (Cmd_Argv(0), u->name) )
{
u->func ();
break;
}
if (!u->name && sv.state == ss_game)
ge->ClientCommand (sv_player);
// SV_EndRedirect ();
}
/*
===========================================================================
USER CMD EXECUTION
===========================================================================
*/
void SV_ClientThink (client_t *cl, usercmd_t *cmd)
{
cl->commandMsec -= cmd->msec;
if (cl->commandMsec < 0 && sv_enforcetime->value )
{
Com_DPrintf ("commandMsec underflow from %s\n", cl->name);
return;
}
ge->ClientThink (cl->edict, cmd);
}
#define MAX_STRINGCMDS 8
/*
===================
SV_ExecuteClientMessage
The current net_message is parsed for the given client
===================
*/
void SV_ExecuteClientMessage (client_t *cl)
{
int c;
char *s;
usercmd_t nullcmd;
usercmd_t oldest, oldcmd, newcmd;
int net_drop;
int stringCmdCount;
int checksum, calculatedChecksum;
int checksumIndex;
qboolean move_issued;
int lastframe;
sv_client = cl;
sv_player = sv_client->edict;
// only allow one move command
move_issued = false;
stringCmdCount = 0;
while (1)
{
if (net_message.readcount > net_message.cursize)
{
Com_Printf ("SV_ReadClientMessage: badread\n");
SV_DropClient (cl);
return;
}
c = MSG_ReadByte (&net_message);
if (c == -1)
break;
switch (c)
{
default:
Com_Printf ("SV_ReadClientMessage: unknown command char\n");
SV_DropClient (cl);
return;
case clc_nop:
break;
case clc_userinfo:
strncpy (cl->userinfo, MSG_ReadString (&net_message), sizeof(cl->userinfo)-1);
SV_UserinfoChanged (cl);
break;
case clc_move:
if (move_issued)
return; // someone is trying to cheat...
move_issued = true;
checksumIndex = net_message.readcount;
checksum = MSG_ReadByte (&net_message);
lastframe = MSG_ReadLong (&net_message);
if (lastframe != cl->lastframe) {
cl->lastframe = lastframe;
if (cl->lastframe > 0) {
cl->frame_latency[cl->lastframe&(LATENCY_COUNTS-1)] =
svs.realtime - cl->frames[cl->lastframe & UPDATE_MASK].senttime;
}
}
memset (&nullcmd, 0, sizeof(nullcmd));
MSG_ReadDeltaUsercmd (&net_message, &nullcmd, &oldest);
MSG_ReadDeltaUsercmd (&net_message, &oldest, &oldcmd);
MSG_ReadDeltaUsercmd (&net_message, &oldcmd, &newcmd);
if ( cl->state != cs_spawned )
{
cl->lastframe = -1;
break;
}
// if the checksum fails, ignore the rest of the packet
calculatedChecksum = COM_BlockSequenceCRCByte (
net_message.data + checksumIndex + 1,
net_message.readcount - checksumIndex - 1,
cl->netchan.incoming_sequence);
if (calculatedChecksum != checksum)
{
Com_DPrintf ("Failed command checksum for %s (%d != %d)/%d\n",
cl->name, calculatedChecksum, checksum,
cl->netchan.incoming_sequence);
return;
}
if (!sv_paused->value)
{
net_drop = cl->netchan.dropped;
if (net_drop < 20)
{
//if (net_drop > 2)
// Com_Printf ("drop %i\n", net_drop);
while (net_drop > 2)
{
SV_ClientThink (cl, &cl->lastcmd);
net_drop--;
}
if (net_drop > 1)
SV_ClientThink (cl, &oldest);
if (net_drop > 0)
SV_ClientThink (cl, &oldcmd);
}
SV_ClientThink (cl, &newcmd);
}
cl->lastcmd = newcmd;
break;
case clc_stringcmd:
s = MSG_ReadString (&net_message);
// malicious users may try using too many string commands
if (++stringCmdCount < MAX_STRINGCMDS)
SV_ExecuteUserCommand (s);
if (cl->state == cs_zombie)
return; // disconnect command
break;
}
}
}

659
server/sv_world.c Normal file
View File

@ -0,0 +1,659 @@
/*
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.
*/
// world.c -- world query functions
#include "server.h"
/*
===============================================================================
ENTITY AREA CHECKING
FIXME: this use of "area" is different from the bsp file use
===============================================================================
*/
// (type *)STRUCT_FROM_LINK(link_t *link, type, member)
// ent = STRUCT_FROM_LINK(link,entity_t,order)
// FIXME: remove this mess!
#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (int)&(((t *)0)->m)))
#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area)
typedef struct areanode_s
{
int axis; // -1 = leaf node
float dist;
struct areanode_s *children[2];
link_t trigger_edicts;
link_t solid_edicts;
} areanode_t;
#define AREA_DEPTH 4
#define AREA_NODES 32
areanode_t sv_areanodes[AREA_NODES];
int sv_numareanodes;
float *area_mins, *area_maxs;
edict_t **area_list;
int area_count, area_maxcount;
int area_type;
int SV_HullForEntity (edict_t *ent);
// ClearLink is used for new headnodes
void ClearLink (link_t *l)
{
l->prev = l->next = l;
}
void RemoveLink (link_t *l)
{
l->next->prev = l->prev;
l->prev->next = l->next;
}
void InsertLinkBefore (link_t *l, link_t *before)
{
l->next = before;
l->prev = before->prev;
l->prev->next = l;
l->next->prev = l;
}
/*
===============
SV_CreateAreaNode
Builds a uniformly subdivided tree for the given world size
===============
*/
areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs)
{
areanode_t *anode;
vec3_t size;
vec3_t mins1, maxs1, mins2, maxs2;
anode = &sv_areanodes[sv_numareanodes];
sv_numareanodes++;
ClearLink (&anode->trigger_edicts);
ClearLink (&anode->solid_edicts);
if (depth == AREA_DEPTH)
{
anode->axis = -1;
anode->children[0] = anode->children[1] = NULL;
return anode;
}
VectorSubtract (maxs, mins, size);
if (size[0] > size[1])
anode->axis = 0;
else
anode->axis = 1;
anode->dist = 0.5 * (maxs[anode->axis] + mins[anode->axis]);
VectorCopy (mins, mins1);
VectorCopy (mins, mins2);
VectorCopy (maxs, maxs1);
VectorCopy (maxs, maxs2);
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
anode->children[0] = SV_CreateAreaNode (depth+1, mins2, maxs2);
anode->children[1] = SV_CreateAreaNode (depth+1, mins1, maxs1);
return anode;
}
/*
===============
SV_ClearWorld
===============
*/
void SV_ClearWorld (void)
{
memset (sv_areanodes, 0, sizeof(sv_areanodes));
sv_numareanodes = 0;
SV_CreateAreaNode (0, sv.models[1]->mins, sv.models[1]->maxs);
}
/*
===============
SV_UnlinkEdict
===============
*/
void SV_UnlinkEdict (edict_t *ent)
{
if (!ent->area.prev)
return; // not linked in anywhere
RemoveLink (&ent->area);
ent->area.prev = ent->area.next = NULL;
}
/*
===============
SV_LinkEdict
===============
*/
#define MAX_TOTAL_ENT_LEAFS 128
void SV_LinkEdict (edict_t *ent)
{
areanode_t *node;
int leafs[MAX_TOTAL_ENT_LEAFS];
int clusters[MAX_TOTAL_ENT_LEAFS];
int num_leafs;
int i, j, k;
int area;
int topnode;
if (ent->area.prev)
SV_UnlinkEdict (ent); // unlink from old position
if (ent == ge->edicts)
return; // don't add the world
if (!ent->inuse)
return;
// set the size
VectorSubtract (ent->maxs, ent->mins, ent->size);
// encode the size into the entity_state for client prediction
if (ent->solid == SOLID_BBOX && !(ent->svflags & SVF_DEADMONSTER))
{ // assume that x/y are equal and symetric
i = ent->maxs[0]/8;
if (i<1)
i = 1;
if (i>31)
i = 31;
// z is not symetric
j = (-ent->mins[2])/8;
if (j<1)
j = 1;
if (j>31)
j = 31;
// and z maxs can be negative...
k = (ent->maxs[2]+32)/8;
if (k<1)
k = 1;
if (k>63)
k = 63;
ent->s.solid = (k<<10) | (j<<5) | i;
}
else if (ent->solid == SOLID_BSP)
{
ent->s.solid = 31; // a solid_bbox will never create this value
}
else
ent->s.solid = 0;
// set the abs box
if (ent->solid == SOLID_BSP &&
(ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2]) )
{ // expand for rotation
float max, v;
int i;
max = 0;
for (i=0 ; i<3 ; i++)
{
v =fabs( ent->mins[i]);
if (v > max)
max = v;
v =fabs( ent->maxs[i]);
if (v > max)
max = v;
}
for (i=0 ; i<3 ; i++)
{
ent->absmin[i] = ent->s.origin[i] - max;
ent->absmax[i] = ent->s.origin[i] + max;
}
}
else
{ // normal
VectorAdd (ent->s.origin, ent->mins, ent->absmin);
VectorAdd (ent->s.origin, ent->maxs, ent->absmax);
}
// because movement is clipped an epsilon away from an actual edge,
// we must fully check even when bounding boxes don't quite touch
ent->absmin[0] -= 1;
ent->absmin[1] -= 1;
ent->absmin[2] -= 1;
ent->absmax[0] += 1;
ent->absmax[1] += 1;
ent->absmax[2] += 1;
// link to PVS leafs
ent->num_clusters = 0;
ent->areanum = 0;
ent->areanum2 = 0;
//get all leafs, including solids
num_leafs = CM_BoxLeafnums (ent->absmin, ent->absmax,
leafs, MAX_TOTAL_ENT_LEAFS, &topnode);
// set areas
for (i=0 ; i<num_leafs ; i++)
{
clusters[i] = CM_LeafCluster (leafs[i]);
area = CM_LeafArea (leafs[i]);
if (area)
{ // doors may legally straggle two areas,
// but nothing should evern need more than that
if (ent->areanum && ent->areanum != area)
{
if (ent->areanum2 && ent->areanum2 != area && sv.state == ss_loading)
Com_DPrintf ("Object touching 3 areas at %f %f %f\n",
ent->absmin[0], ent->absmin[1], ent->absmin[2]);
ent->areanum2 = area;
}
else
ent->areanum = area;
}
}
if (num_leafs >= MAX_TOTAL_ENT_LEAFS)
{ // assume we missed some leafs, and mark by headnode
ent->num_clusters = -1;
ent->headnode = topnode;
}
else
{
ent->num_clusters = 0;
for (i=0 ; i<num_leafs ; i++)
{
if (clusters[i] == -1)
continue; // not a visible leaf
for (j=0 ; j<i ; j++)
if (clusters[j] == clusters[i])
break;
if (j == i)
{
if (ent->num_clusters == MAX_ENT_CLUSTERS)
{ // assume we missed some leafs, and mark by headnode
ent->num_clusters = -1;
ent->headnode = topnode;
break;
}
ent->clusternums[ent->num_clusters++] = clusters[i];
}
}
}
// if first time, make sure old_origin is valid
if (!ent->linkcount)
{
VectorCopy (ent->s.origin, ent->s.old_origin);
}
ent->linkcount++;
if (ent->solid == SOLID_NOT)
return;
// find the first node that the ent's box crosses
node = sv_areanodes;
while (1)
{
if (node->axis == -1)
break;
if (ent->absmin[node->axis] > node->dist)
node = node->children[0];
else if (ent->absmax[node->axis] < node->dist)
node = node->children[1];
else
break; // crosses the node
}
// link it in
if (ent->solid == SOLID_TRIGGER)
InsertLinkBefore (&ent->area, &node->trigger_edicts);
else
InsertLinkBefore (&ent->area, &node->solid_edicts);
}
/*
====================
SV_AreaEdicts_r
====================
*/
void SV_AreaEdicts_r (areanode_t *node)
{
link_t *l, *next, *start;
edict_t *check;
int count;
count = 0;
// touch linked edicts
if (area_type == AREA_SOLID)
start = &node->solid_edicts;
else
start = &node->trigger_edicts;
for (l=start->next ; l != start ; l = next)
{
next = l->next;
check = EDICT_FROM_AREA(l);
if (check->solid == SOLID_NOT)
continue; // deactivated
if (check->absmin[0] > area_maxs[0]
|| check->absmin[1] > area_maxs[1]
|| check->absmin[2] > area_maxs[2]
|| check->absmax[0] < area_mins[0]
|| check->absmax[1] < area_mins[1]
|| check->absmax[2] < area_mins[2])
continue; // not touching
if (area_count == area_maxcount)
{
Com_Printf ("SV_AreaEdicts: MAXCOUNT\n");
return;
}
area_list[area_count] = check;
area_count++;
}
if (node->axis == -1)
return; // terminal node
// recurse down both sides
if ( area_maxs[node->axis] > node->dist )
SV_AreaEdicts_r ( node->children[0] );
if ( area_mins[node->axis] < node->dist )
SV_AreaEdicts_r ( node->children[1] );
}
/*
================
SV_AreaEdicts
================
*/
int SV_AreaEdicts (vec3_t mins, vec3_t maxs, edict_t **list,
int maxcount, int areatype)
{
area_mins = mins;
area_maxs = maxs;
area_list = list;
area_count = 0;
area_maxcount = maxcount;
area_type = areatype;
SV_AreaEdicts_r (sv_areanodes);
return area_count;
}
//===========================================================================
/*
=============
SV_PointContents
=============
*/
int SV_PointContents (vec3_t p)
{
edict_t *touch[MAX_EDICTS], *hit;
int i, num;
int contents, c2;
int headnode;
float *angles;
// get base contents from world
contents = CM_PointContents (p, sv.models[1]->headnode);
// or in contents from all the other entities
num = SV_AreaEdicts (p, p, touch, MAX_EDICTS, AREA_SOLID);
for (i=0 ; i<num ; i++)
{
hit = touch[i];
// might intersect, so do an exact clip
headnode = SV_HullForEntity (hit);
angles = hit->s.angles;
if (hit->solid != SOLID_BSP)
angles = vec3_origin; // boxes don't rotate
c2 = CM_TransformedPointContents (p, headnode, hit->s.origin, hit->s.angles);
contents |= c2;
}
return contents;
}
typedef struct
{
vec3_t boxmins, boxmaxs;// enclose the test object along entire move
float *mins, *maxs; // size of the moving object
vec3_t mins2, maxs2; // size when clipping against mosnters
float *start, *end;
trace_t trace;
edict_t *passedict;
int contentmask;
} moveclip_t;
/*
================
SV_HullForEntity
Returns a headnode that can be used for testing or clipping an
object of mins/maxs size.
Offset is filled in to contain the adjustment that must be added to the
testing object's origin to get a point to use with the returned hull.
================
*/
int SV_HullForEntity (edict_t *ent)
{
cmodel_t *model;
// decide which clipping hull to use, based on the size
if (ent->solid == SOLID_BSP)
{ // explicit hulls in the BSP model
model = sv.models[ ent->s.modelindex ];
if (!model)
Com_Error (ERR_FATAL, "MOVETYPE_PUSH with a non bsp model");
return model->headnode;
}
// create a temp hull from bounding box sizes
return CM_HeadnodeForBox (ent->mins, ent->maxs);
}
//===========================================================================
/*
====================
SV_ClipMoveToEntities
====================
*/
void SV_ClipMoveToEntities ( moveclip_t *clip )
{
int i, num;
edict_t *touchlist[MAX_EDICTS], *touch;
trace_t trace;
int headnode;
float *angles;
num = SV_AreaEdicts (clip->boxmins, clip->boxmaxs, touchlist
, MAX_EDICTS, AREA_SOLID);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i=0 ; i<num ; i++)
{
touch = touchlist[i];
if (touch->solid == SOLID_NOT)
continue;
if (touch == clip->passedict)
continue;
if (clip->trace.allsolid)
return;
if (clip->passedict)
{
if (touch->owner == clip->passedict)
continue; // don't clip against own missiles
if (clip->passedict->owner == touch)
continue; // don't clip against owner
}
if ( !(clip->contentmask & CONTENTS_DEADMONSTER)
&& (touch->svflags & SVF_DEADMONSTER) )
continue;
// might intersect, so do an exact clip
headnode = SV_HullForEntity (touch);
angles = touch->s.angles;
if (touch->solid != SOLID_BSP)
angles = vec3_origin; // boxes don't rotate
if (touch->svflags & SVF_MONSTER)
trace = CM_TransformedBoxTrace (clip->start, clip->end,
clip->mins2, clip->maxs2, headnode, clip->contentmask,
touch->s.origin, angles);
else
trace = CM_TransformedBoxTrace (clip->start, clip->end,
clip->mins, clip->maxs, headnode, clip->contentmask,
touch->s.origin, angles);
if (trace.allsolid || trace.startsolid ||
trace.fraction < clip->trace.fraction)
{
trace.ent = touch;
if (clip->trace.startsolid)
{
clip->trace = trace;
clip->trace.startsolid = true;
}
else
clip->trace = trace;
}
else if (trace.startsolid)
clip->trace.startsolid = true;
}
}
/*
==================
SV_TraceBounds
==================
*/
void SV_TraceBounds (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, vec3_t boxmins, vec3_t boxmaxs)
{
#if 0
// debug to test against everything
boxmins[0] = boxmins[1] = boxmins[2] = -9999;
boxmaxs[0] = boxmaxs[1] = boxmaxs[2] = 9999;
#else
int i;
for (i=0 ; i<3 ; i++)
{
if (end[i] > start[i])
{
boxmins[i] = start[i] + mins[i] - 1;
boxmaxs[i] = end[i] + maxs[i] + 1;
}
else
{
boxmins[i] = end[i] + mins[i] - 1;
boxmaxs[i] = start[i] + maxs[i] + 1;
}
}
#endif
}
/*
==================
SV_Trace
Moves the given mins/maxs volume through the world from start to end.
Passedict and edicts owned by passedict are explicitly not checked.
==================
*/
trace_t SV_Trace (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, edict_t *passedict, int contentmask)
{
moveclip_t clip;
if (!mins)
mins = vec3_origin;
if (!maxs)
maxs = vec3_origin;
memset ( &clip, 0, sizeof ( moveclip_t ) );
// clip to world
clip.trace = CM_BoxTrace (start, end, mins, maxs, 0, contentmask);
clip.trace.ent = ge->edicts;
if (clip.trace.fraction == 0)
return clip.trace; // blocked by the world
clip.contentmask = contentmask;
clip.start = start;
clip.end = end;
clip.mins = mins;
clip.maxs = maxs;
clip.passedict = passedict;
VectorCopy (mins, clip.mins2);
VectorCopy (maxs, clip.maxs2);
// create the bounding box of the entire move
SV_TraceBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs );
// clip to other solid entities
SV_ClipMoveToEntities ( &clip );
return clip.trace;
}