better command output redirection handling: maplist over rcon should show

all (or most, there are limits still) of the maps on a server with many
maps.

move the optional progs funcs into sv_funcs_t and add UserInfoChanged,
ChatMessage and LocalinfoChanged callback support.

clean up PF_setinfo (and SV_SetInfo_f and SV_Localinfo_f) using shared code
where possible und to use the UserInfoChanged and LocalinfoChanged
callbacks.

add chat message callback to SV_Say. if it returns zero, normal chat
handling is done, otherwise it's assumed to have been handled by the progs.

provide a hook for unkown user commands. non-zero return means it's been
handled.
This commit is contained in:
Bill Currie 2003-11-21 06:09:21 +00:00
parent 886b766295
commit ce745c8078
11 changed files with 218 additions and 127 deletions

View file

@ -427,8 +427,6 @@ extern QFile *sv_fraglogfile;
extern double sv_frametime;
extern double realtime;
extern struct progs_s sv_pr_state;
extern const char *client_info_filters[];
extern struct cbuf_s *sv_cbuf;
@ -548,11 +546,19 @@ void *SV_AddUserCommand (const char *name, void (*func) (void *userdata),
void (*on_free) (void *userdata));
int SV_RemoveUserCommand (void *cmd);
void SV_Spawn (client_t *client);
void SV_SetUserinfo (client_t *client, const char *key, const char *value);
extern int (*ucmd_unknown)(void);
//
// svonly.c
//
typedef enum {RD_NONE, RD_CLIENT, RD_PACKET} redirect_t;
typedef enum {
RD_NONE,
RD_CLIENT,
RD_PACKET,
RD_MOD,
} redirect_t;
void SV_BeginRedirect (redirect_t rd);
void SV_EndRedirect (void);
extern redirect_t sv_redirected;
@ -562,6 +568,7 @@ extern redirect_t sv_redirected;
//
void SV_Status_f (void);
const char *SV_Current_Map (void);
void SV_SetLocalinfo (const char *key, const char *value);
//
@ -573,6 +580,7 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg,
//
// sv_nchan.c
//
int ClientReliableCheckSize (client_t *cl, int maxsize, int minsize);
void ClientReliableCheckBlock(client_t *cl, int maxsize);
void ClientReliable_FinishWrite(client_t *cl);
void ClientReliableWrite_Begin(client_t *cl, int c, int maxsize);
@ -585,7 +593,7 @@ void ClientReliableWrite_Coord(client_t *cl, float f);
void ClientReliableWrite_Long(client_t *cl, int c);
void ClientReliableWrite_Short(client_t *cl, int c);
void ClientReliableWrite_String(client_t *cl, const char *s);
void ClientReliableWrite_SZ(client_t *cl, void *data, int len);
void ClientReliableWrite_SZ(client_t *cl, const void *data, int len);
void ClientReliableWrite_AngleV(client_t *cl, const vec3_t v);
void ClientReliableWrite_CoordV(client_t *cl, const vec3_t v);

View file

@ -102,6 +102,7 @@ typedef struct {
byte *mfile;
byte buffer[20 * MAX_MSGLEN];
int bufsize;
int forceFrame;
} demo_t;
extern demo_t demo;

View file

@ -79,6 +79,15 @@ typedef struct {
func_t ClientDisconnect;
func_t SetNewParms;
func_t SetChangeParms;
func_t EndFrame;
func_t SpectatorConnect;
func_t SpectatorThink;
func_t SpectatorDisconnect;
func_t UserInfoCallback;
func_t UserInfoChanged;
func_t ChatMessage;
func_t LocalinfoChanged;
} sv_funcs_t;
extern sv_funcs_t sv_funcs;
@ -159,10 +168,14 @@ typedef struct
int gravity;
int maxspeed;
int team_str; //string
} sv_fields_t;
extern sv_fields_t sv_fields;
extern struct progs_s sv_pr_state;
#if TYPECHECK_PROGS
#define SVFIELD(e,f,t) E_var (e, PR_AccessField (&sv_pr_state, #f, ev_##t, __FILE__, __LINE__), t)
#else
@ -178,12 +191,6 @@ extern sv_fields_t sv_fields;
#define PROGHEADER_CRC 54730
extern func_t EndFrame;
extern func_t SpectatorConnect;
extern func_t SpectatorThink;
extern func_t SpectatorDisconnect;
extern func_t UserInfoCallback;
static inline void
sv_pr_touch (edict_t *self, edict_t *other)
{

View file

@ -481,7 +481,7 @@ SV_Status_f (void)
SV_Printf ("packets/frame : %5.2f\n", pak);
// min fps lat drp
if (sv_redirected != RD_NONE) {
if (sv_redirected != RD_NONE && sv_redirected != RD_MOD) {
// most remote clients are 40 columns
// 0123456789012345678901234567890123456789
SV_Printf ("name userid frags\n");
@ -843,6 +843,32 @@ SV_Serverinfo_f (void)
}
}
void
SV_SetLocalinfo (const char *key, const char *value)
{
char *oldvalue = 0;
if (sv_funcs.LocalinfoChanged)
oldvalue = strdup (Info_ValueForKey (localinfo, key));
if (*value)
Info_SetValueForKey (localinfo, key, value, !sv_highchars->int_val);
else
Info_RemoveKey (localinfo, key);
if (sv_funcs.LocalinfoChanged) {
*sv_globals.time = sv.time;
*sv_globals.self = 0;
P_STRING (&sv_pr_state, 0) = PR_SetString (&sv_pr_state, key);
P_STRING (&sv_pr_state, 1) = PR_SetString (&sv_pr_state, oldvalue);
P_STRING (&sv_pr_state, 2) = PR_SetString (&sv_pr_state, value);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.LocalinfoChanged);
}
if (oldvalue)
free (oldvalue);
}
/*
SV_Serverinfo_f
@ -866,11 +892,7 @@ SV_Localinfo_f (void)
SV_Printf ("Star variables cannot be changed.\n");
return;
}
if (*Cmd_Argv (2))
Info_SetValueForKey (localinfo, Cmd_Argv (1), Cmd_Argv (2),
!sv_highchars->int_val);
else
Info_RemoveKey (localinfo, Cmd_Argv (1));
SV_SetLocalinfo (Cmd_Argv (1), Cmd_Argv (2));
}
/*

View file

@ -344,11 +344,11 @@ SV_DropClient (client_t *drop)
// this will set the body to a dead frame, among other things
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, drop->edict);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.ClientDisconnect);
} else if (SpectatorDisconnect) {
} else if (sv_funcs.SpectatorDisconnect) {
// call the prog function for removing a client
// this will set the body to a dead frame, among other things
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, drop->edict);
PR_ExecuteProgram (&sv_pr_state, SpectatorDisconnect);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.SpectatorDisconnect);
}
}
if (drop->spectator)

View file

@ -54,14 +54,33 @@ PushBackbuf (client_t *cl)
cl->num_backbuf++;
}
int
ClientReliableCheckSize (client_t *cl, int maxsize, int minsize)
{
sizebuf_t *msg = &cl->netchan.message;
if (cl->num_backbuf)
msg = &cl->backbuf;
if (maxsize <= msg->maxsize - msg->cursize - 1)
return maxsize;
if (minsize <= msg->maxsize - msg->cursize - 1)
return msg->maxsize - msg->cursize - 1;
if (cl->num_backbuf == MAX_BACK_BUFFERS)
return 0;
return cl->backbuf.maxsize;
}
// check to see if client block will fit, if not, rotate buffers
void
ClientReliableCheckBlock (client_t *cl, int maxsize)
{
if (cl->num_backbuf ||
cl->netchan.message.cursize > cl->netchan.message.maxsize - maxsize -
1) {
sizebuf_t *msg = &cl->netchan.message;
if (cl->num_backbuf || msg->cursize > msg->maxsize - maxsize - 1) {
// we would probably overflow the buffer, save it for next
if (!cl->num_backbuf) {
PushBackbuf (cl);
@ -72,8 +91,7 @@ ClientReliableCheckBlock (client_t *cl, int maxsize)
SV_Printf ("WARNING: MAX_BACK_BUFFERS for %s\n", cl->name);
cl->backbuf.cursize = 0; // don't overflow without
// allowoverflow set
cl->netchan.message.overflowed = true; // this will drop the
// client
msg->overflowed = true; // this will drop the client
return;
}
PushBackbuf (cl);
@ -195,7 +213,7 @@ ClientReliableWrite_String (client_t *cl, const char *s)
}
void
ClientReliableWrite_SZ (client_t *cl, void *data, int len)
ClientReliableWrite_SZ (client_t *cl, const void *data, int len)
{
if (cl->num_backbuf) {
SZ_Write (&cl->backbuf, data, len);

View file

@ -813,11 +813,11 @@ SV_Physics (void)
if (*sv_globals.force_retouch)
(*sv_globals.force_retouch)--;
if (EndFrame) {
if (sv_funcs.EndFrame) {
// let the progs know that the frame has ended
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv.edicts);
*sv_globals.other = EDICT_TO_PROG (&sv_pr_state, sv.edicts);
*sv_globals.time = sv.time;
PR_ExecuteProgram (&sv_pr_state, EndFrame);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.EndFrame);
}
}

View file

@ -1468,8 +1468,7 @@ PF_multicast (progs_t *pr)
static void
PF_cfopen (progs_t *pr)
{
R_FLOAT (pr) = CF_Open (P_GSTRING (pr, 0),
P_GSTRING (pr, 1));
R_FLOAT (pr) = CF_Open (P_GSTRING (pr, 0), P_GSTRING (pr, 1));
}
/*
@ -1534,41 +1533,11 @@ PF_setinfokey (progs_t *pr)
int e1 = NUM_FOR_EDICT (pr, edict);
const char *key = P_GSTRING (pr, 1);
const char *value = P_GSTRING (pr, 2);
char *oldval = 0;
if (e1 == 0) {
if (*value)
Info_SetValueForKey (localinfo, key, value,
!sv_highchars->int_val);
else
Info_RemoveKey (localinfo, key);
SV_SetLocalinfo (key, value);
} else if (e1 <= MAX_CLIENTS) {
if (sv_setinfo_e->func)
oldval = strdup (Info_ValueForKey (svs.clients[e1 - 1].userinfo,
key));
Info_SetValueForKey (svs.clients[e1 - 1].userinfo, key, value,
!sv_highchars->int_val);
SV_ExtractFromUserinfo (&svs.clients[e1 - 1]);
// trigger a GIB event
if (sv_setinfo_e->func)
GIB_Event_Callback (sv_setinfo_e, 4,
va("%d", svs.clients[e1 - 1].userid),
key, oldval,
Info_ValueForKey (svs.clients[e1 - 1].userinfo,
key));
if (oldval)
free (oldval);
if (Info_FilterForKey (key, client_info_filters)) {
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, e1 - 1);
MSG_WriteString (&sv.reliable_datagram, key);
MSG_WriteString (&sv.reliable_datagram,
Info_ValueForKey (svs.clients[e1 - 1].userinfo,
key));
}
SV_SetUserinfo (&svs.clients[e1 - 1], key, value);
}
}

View file

@ -57,12 +57,6 @@ cvar_t *pr_checkextensions;
cvar_t *sv_old_entity_free;
cvar_t *sv_hide_version_info;
func_t EndFrame;
func_t SpectatorConnect;
func_t SpectatorDisconnect;
func_t SpectatorThink;
func_t UserInfoCallback;
static int reserved_edicts = MAX_CLIENTS;
static void
@ -169,6 +163,8 @@ SV_LoadProgs (void)
dfunction_t *f;
const char *progs_name = "qwprogs.dat";
memset (&sv_funcs, 0, sizeof (sv_funcs));
if (qfs_gamedir->gamecode && *qfs_gamedir->gamecode)
progs_name = qfs_gamedir->gamecode;
if (*sv_progs->string)
@ -313,21 +309,18 @@ SV_LoadProgs (void)
sv_fields.rotated_bbox = ED_GetFieldIndex (&sv_pr_state, "rotated_bbox");
// Zoid, find the spectator functions
SpectatorConnect = SpectatorThink = SpectatorDisconnect = 0;
EndFrame = UserInfoCallback = 0;
if ((f = ED_FindFunction (&sv_pr_state, "SpectatorConnect")) != NULL)
SpectatorConnect = (func_t) (f - sv_pr_state.pr_functions);
sv_funcs.SpectatorConnect = (func_t) (f - sv_pr_state.pr_functions);
if ((f = ED_FindFunction (&sv_pr_state, "SpectatorThink")) != NULL)
SpectatorThink = (func_t) (f - sv_pr_state.pr_functions);
sv_funcs.SpectatorThink = (func_t) (f - sv_pr_state.pr_functions);
if ((f = ED_FindFunction (&sv_pr_state, "SpectatorDisconnect")) != NULL)
SpectatorDisconnect = (func_t) (f - sv_pr_state.pr_functions);
sv_funcs.SpectatorDisconnect = (func_t) (f - sv_pr_state.pr_functions);
if ((f = ED_FindFunction (&sv_pr_state, "UserInfoCallback")) != NULL)
UserInfoCallback = (func_t) (f - sv_pr_state.pr_functions);
sv_funcs.UserInfoCallback = (func_t) (f - sv_pr_state.pr_functions);
// 2000-01-02 EndFrame function by Maddes/FrikaC
if ((f = ED_FindFunction (&sv_pr_state, "EndFrame")) != NULL)
EndFrame = (func_t) (f - sv_pr_state.pr_functions);
sv_funcs.EndFrame = (func_t) (f - sv_pr_state.pr_functions);
sv_fields.alpha = ED_GetFieldIndex (&sv_pr_state, "alpha");
sv_fields.scale = ED_GetFieldIndex (&sv_pr_state, "scale");

View file

@ -44,6 +44,7 @@ static __attribute__ ((unused)) const char rcsid[] =
#include "QF/console.h"
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/msg.h"
#include "QF/sound.h" // FIXME: DEFAULT_SOUND_PACKET_*
#include "QF/sys.h"
@ -62,7 +63,7 @@ static __attribute__ ((unused)) const char rcsid[] =
/* SV_Printf redirection */
char outputbuf[8000];
dstring_t outputbuf;
int con_printf_no_log;
redirect_t sv_redirected;
@ -71,24 +72,62 @@ static void
SV_FlushRedirect (void)
{
char send[8000 + 6];
int count;
int bytes;
const char *p;
if (!outputbuf.size)
return;
count = strlen (outputbuf.str);
if (sv_redirected == RD_PACKET) {
send[0] = 0xff;
send[1] = 0xff;
send[2] = 0xff;
send[3] = 0xff;
send[4] = A2C_PRINT;
memcpy (send + 5, outputbuf, strlen (outputbuf) + 1);
Netchan_SendPacket (strlen (send) + 1, send, net_from);
} else if (sv_redirected == RD_CLIENT) {
ClientReliableWrite_Begin (host_client, svc_print,
strlen (outputbuf) + 3);
ClientReliableWrite_Byte (host_client, PRINT_HIGH);
ClientReliableWrite_String (host_client, outputbuf);
p = outputbuf.str;
while (count) {
bytes = min (count, sizeof (send) - 5);
memcpy (send + 5, p, bytes);
send[5 + bytes] = 0;
Netchan_SendPacket (bytes + 1, send, net_from);
p += bytes;
count -= bytes;
}
} else if (sv_redirected == RD_CLIENT || sv_redirected > RD_MOD) {
client_t *cl;
if (sv_redirected > RD_MOD) {
cl = svs.clients + sv_redirected - RD_MOD - 1;
if (cl->state != cs_spawned)
count = 0;
} else {
cl = host_client;
}
p = outputbuf.str;
while (count) {
// +/- 3 for svc_print, PRINT_HIGH and nul byte
// min of 4 because we don't want to send an effectively empty
// message
bytes = ClientReliableCheckSize (cl, count + 3, 4) - 3;
// if writing another packet would overflow the client, just drop
// the rest of the data. getting rudely disconnected would be much
// more annoying than losing the tail end of the output
if (bytes <= 0)
break;
ClientReliableWrite_Begin (cl, svc_print, bytes + 3);
ClientReliableWrite_Byte (cl, PRINT_HIGH);
ClientReliableWrite_SZ (cl, p, bytes);
p += bytes;
count -= bytes;
}
}
// RD_MOD doesn't do anything :)
// clear it
outputbuf[0] = 0;
dstring_clear (&outputbuf);
}
/*
@ -101,7 +140,7 @@ void
SV_BeginRedirect (redirect_t rd)
{
sv_redirected = rd;
outputbuf[0] = 0;
dstring_clear (&outputbuf);
}
void
@ -171,9 +210,7 @@ SV_Print (const char *fmt, va_list args)
*out = '\0';
if (sv_redirected) { // Add to redirected message
if (strlen (msg) + strlen (outputbuf) > sizeof (outputbuf) - 1)
SV_FlushRedirect ();
strncat (outputbuf, msg, sizeof (outputbuf) - strlen (outputbuf));
dstring_appendstr (&outputbuf, msg);
}
if (!con_printf_no_log) {
// We want to output to console and maybe logfile
@ -820,8 +857,9 @@ SV_SendDemoMessage (void)
min_fps = sv_demofps->value;
min_fps = max (4, min_fps);
if (sv.time - demo.time < 1.0 / min_fps)
if (!demo.forceFrame && sv.time - demo.time < 1.0 / min_fps)
return;
demo.forceFrame = 0;
for (i = 0, c = svs.clients ; i<MAX_CLIENTS ; i++, c++) {
if (c->state != cs_spawned)

View file

@ -491,7 +491,7 @@ SV_Begin_f (void *unused)
if (host_client->spectator) {
SV_SpawnSpectator ();
if (SpectatorConnect) {
if (sv_funcs.SpectatorConnect) {
// copy spawn parms out of the client_t
for (i = 0; i < NUM_SPAWN_PARMS; i++)
sv_globals.parms[i] = host_client->spawn_parms[i];
@ -499,7 +499,7 @@ SV_Begin_f (void *unused)
// call the spawn function
*sv_globals.time = sv.time;
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
PR_ExecuteProgram (&sv_pr_state, SpectatorConnect);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.SpectatorConnect);
}
} else {
// copy spawn parms out of the client_t
@ -787,6 +787,17 @@ SV_Say (qboolean team)
host_client->whensaid[host_client->whensaidhead] = realtime;
}
if (sv_funcs.ChatMessage) {
P_STRING (&sv_pr_state, 0) = PR_SetString (&sv_pr_state, p);
G_FLOAT (&sv_pr_state, 1) = (float) team;
*sv_globals.time = sv.time;
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.ChatMessage);
if (R_FLOAT (&sv_pr_state))
return;
}
text = dstring_new ();
if (host_client->spectator && (!sv_spectalk->int_val || team)) {
@ -1088,6 +1099,49 @@ SV_Msg_f (void *unused)
host_client->messagelevel);
}
void
SV_SetUserinfo (client_t *client, const char *key, const char *value)
{
char *oldvalue = 0;
if (sv_setinfo_e->func || sv_funcs.UserInfoChanged)
oldvalue = strdup (Info_ValueForKey (client->userinfo, key));
if (!Info_SetValueForKey (client->userinfo, key, value,
!sv_highchars->int_val)) {
// key hasn't changed
if (oldvalue)
free (oldvalue);
return;
}
// process any changed values
SV_ExtractFromUserinfo (client);
// trigger a GIB event
if (sv_setinfo_e->func)
GIB_Event_Callback (sv_setinfo_e, 4, va("%d", client->userid),
key, oldvalue, value);
if (sv_funcs.UserInfoChanged) {
*sv_globals.time = sv.time;
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, client->edict);
P_STRING (&sv_pr_state, 0) = PR_SetString (&sv_pr_state, key);
P_STRING (&sv_pr_state, 1) = PR_SetString (&sv_pr_state, oldvalue);
P_STRING (&sv_pr_state, 2) = PR_SetString (&sv_pr_state, value);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.UserInfoChanged);
}
if (oldvalue)
free (oldvalue);
if (Info_FilterForKey (key, client_info_filters)) {
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, key);
MSG_WriteString (&sv.reliable_datagram, value);
}
}
/*
SV_SetInfo_f
@ -1096,7 +1150,8 @@ SV_Msg_f (void *unused)
static void
SV_SetInfo_f (void *unused)
{
char *oldval;
const char *key;
const char *value;
if (Cmd_Argc () == 1) {
SV_Printf ("User info settings:\n");
@ -1112,41 +1167,18 @@ SV_SetInfo_f (void *unused)
if (Cmd_Argv (1)[0] == '*')
return; // don't set priveledged values
if (UserInfoCallback) {
key = Cmd_Argv (1);
value = Cmd_Argv (2);
if (sv_funcs.UserInfoCallback) {
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
P_STRING (&sv_pr_state, 0) = PR_SetString (&sv_pr_state, Cmd_Argv (1));
P_STRING (&sv_pr_state, 1) = PR_SetString (&sv_pr_state, Cmd_Argv (2));
PR_ExecuteProgram (&sv_pr_state, UserInfoCallback);
P_STRING (&sv_pr_state, 0) = PR_SetString (&sv_pr_state, key);
P_STRING (&sv_pr_state, 1) = PR_SetString (&sv_pr_state, value);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.UserInfoCallback);
return;
}
oldval = strdup (Info_ValueForKey (host_client->userinfo, Cmd_Argv (1)));
if (!Info_SetValueForKey (host_client->userinfo, Cmd_Argv (1),
Cmd_Argv (2), !sv_highchars->int_val)) {
// key hasn't changed
free (oldval);
return;
}
// process any changed values
SV_ExtractFromUserinfo (host_client);
// trigger a GIB event
if (sv_setinfo_e->func)
GIB_Event_Callback (sv_setinfo_e, 4, va("%d", host_client->userid),
Cmd_Argv (1), oldval,
Info_ValueForKey (host_client->userinfo,
Cmd_Argv (1)));
free (oldval);
if (Info_FilterForKey (Cmd_Argv (1), client_info_filters)) {
MSG_WriteByte (&sv.reliable_datagram, svc_setinfo);
MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
MSG_WriteString (&sv.reliable_datagram, Cmd_Argv (1));
MSG_WriteString (&sv.reliable_datagram,
Info_ValueForKey (host_client->userinfo,
Cmd_Argv (1)));
}
SV_SetUserinfo (host_client, key, value);
}
/*
@ -1205,6 +1237,7 @@ ucmd_t ucmds[] = {
};
static hashtab_t *ucmd_table;
int (*ucmd_unknown)(void);
static void
call_qc_hook (void *qc_hook)
@ -1340,9 +1373,11 @@ SV_ExecuteUserCommand (const char *s)
u = (ucmd_t*) Hash_Find (ucmd_table, sv_args->argv[0]->str);
if (!u) {
SV_BeginRedirect (RD_CLIENT);
SV_Printf ("Bad user command: %s\n", sv_args->argv[0]->str);
SV_EndRedirect ();
if (ucmd_unknown && !ucmd_unknown ()) {
SV_BeginRedirect (RD_CLIENT);
SV_Printf ("Bad user command: %s\n", sv_args->argv[0]->str);
SV_EndRedirect ();
}
} else {
if (!u->no_redirect)
SV_BeginRedirect (RD_CLIENT);
@ -1705,10 +1740,10 @@ SV_PostRunCmd (void)
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.PlayerPostThink);
SV_RunNewmis ();
} else if (SpectatorThink) {
} else if (sv_funcs.SpectatorThink) {
*sv_globals.time = sv.time;
*sv_globals.self = EDICT_TO_PROG (&sv_pr_state, sv_player);
PR_ExecuteProgram (&sv_pr_state, SpectatorThink);
PR_ExecuteProgram (&sv_pr_state, sv_funcs.SpectatorThink);
}
}