mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-06-10 11:59:58 -05:00
Quack 3: Arena
This commit is contained in:
committed by
GitHub
parent
0bf99969fd
commit
40035fa235
341
server/server.h
Normal file
341
server/server.h
Normal 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
1050
server/sv_ccmds.c
Normal file
File diff suppressed because it is too large
Load Diff
727
server/sv_ents.c
Normal file
727
server/sv_ents.c
Normal 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
396
server/sv_game.c
Normal 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
465
server/sv_init.c
Normal 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
1055
server/sv_main.c
Normal file
File diff suppressed because it is too large
Load Diff
15
server/sv_null.c
Normal file
15
server/sv_null.c
Normal 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
567
server/sv_send.c
Normal 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
664
server/sv_user.c
Normal 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
659
server/sv_world.c
Normal 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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user