quakeforge/qw/source/sv_ccmds.c
Bill Currie 12c84046f3 [cvar] Make cvars properly typed
This is an extremely extensive patch as it hits every cvar, and every
usage of the cvars. Cvars no longer store the value they control,
instead, they use a cexpr value object to reference the value and
specify the value's type (currently, a null type is used for strings).
Non-string cvars are passed through cexpr, allowing expressions in the
cvars' settings. Also, cvars have returned to an enhanced version of the
original (id quake) registration scheme.

As a minor benefit, relevant code having direct access to the
cvar-controlled variables is probably a slight optimization as it
removed a pointer dereference, and the variables can be located for data
locality.

The static cvar descriptors are made private as an additional safety
layer, though there's nothing stopping external modification via
Cvar_FindVar (which is needed for adding listeners).

While not used yet (partly due to working out the design), cvars can
have a validation function.

Registering a cvar allows a primary listener (and its data) to be
specified: it will always be called first when the cvar is modified. The
combination of proper listeners and direct access to the controlled
variable greatly simplifies the more complex cvar interactions as much
less null checking is required, and there's no need for one cvar's
callback to call another's.

nq-x11 is known to work at least well enough for the demos. More testing
will come.
2022-04-24 19:15:22 +09:00

1315 lines
32 KiB
C

/*
sv_ccmds.c
(description)
Copyright (C) 1996-1997 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include "QF/cmd.h"
#include "QF/console.h"
#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/msg.h"
#include "QF/qargs.h"
#include "QF/qendian.h"
#include "QF/quakefs.h"
#include "QF/sys.h"
#include "QF/va.h"
#include "compat.h"
#include "qw/bothdefs.h"
#include "qw/include/server.h"
#include "qw/include/sv_demo.h"
#include "qw/include/sv_progs.h"
#include "qw/include/sv_qtv.h"
#include "qw/include/sv_recorder.h"
qboolean sv_allow_cheats;
int fp_messages = 4, fp_persecond = 4, fp_secondsdead = 10;
char fp_msg[255] = { 0 };
int sv_leetnickmatch;
static cvar_t sv_leetnickmatch_cvar = {
.name = "sv_3133735_7h4n_7h0u",
.description =
"Match '1' as 'i' and such in nicks",
.default_value = "1",
.flags = CVAR_NONE,
.value = { .type = &cexpr_int, .value = &sv_leetnickmatch },
};
static qboolean
match_char (char a, char b)
{
a = tolower ((byte) sys_char_map[(byte) a]);
b = tolower ((byte) sys_char_map[(byte) b]);
if (a == b || (sv_leetnickmatch
&& ( (a == '1' && b == 'i') || (a == 'i' && b == '1')
|| (a == '1' && b == 'l') || (a == 'l' && b == '1')
|| (a == '3' && b == 'e') || (a == 'e' && b == '3')
|| (a == '4' && b == 'a') || (a == 'a' && b == '4')
|| (a == '5' && b == 'g') || (a == 'g' && b == '5')
|| (a == '5' && b == 'r') || (a == 'r' && b == '5')
|| (a == '7' && b == 't') || (a == 't' && b == '7')
|| (a == '9' && b == 'p') || (a == 'p' && b == '9')
|| (a == '0' && b == 'o') || (a == 'o' && b == '0')
|| (a == '$' && b == 's') || (a == 's' && b == '$'))))
return true;
return false;
}
/*
FIXME: this function and it's callers are getting progressively
uglier as more features are added :)
*/
static client_t *
SV_Match_User (const char *substr)
{
int i, j, uid;
int count = 0;
char *str;
client_t *cl;
client_t *match = 0;
if (!substr || !*substr)
return 0;
uid = strtol (substr, &str, 10);
if (!*str) {
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_zombie)
continue;
if (cl->userid == uid) {
SV_Printf ("User %04d matches with name: %s\n",
cl->userid, cl->name);
return cl;
}
}
SV_Printf ("No such uid: %d\n", uid);
return 0;
}
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_zombie)
continue;
for (str = cl->name; *str && !match_char (*str, substr[0]); str++)
;
while (*str) {
for (j = 0; substr[j] && str[j]; j++)
if (!match_char (substr[j], str[j]))
break;
if (!substr[j]) { // found a match;
match = cl;
count++;
SV_Printf ("User %04d matches with name: %s\n",
cl->userid, cl->name);
break;
}
str++;
}
}
if (count != 1) {
SV_Printf ("No unique matches, ignoring command!\n");
return 0;
}
return match;
}
/*
OPERATOR CONSOLE ONLY COMMANDS
These commands can be entered only from stdin or by a remote operator
datagram
*/
/*
SV_SetMaster_f
Make a master server current
*/
static void
SV_SetMaster_f (void)
{
char data[2];
int i;
memset (&master_adr, 0, sizeof (master_adr));
for (i = 1; i < Cmd_Argc (); i++) {
if (i > MAX_MASTERS) {
SV_Printf ("Too many masters specified. Using only the first %d\n",
MAX_MASTERS);
break;
}
if (!strcmp (Cmd_Argv (i), "none")
|| !NET_StringToAdr (Cmd_Argv (i), &master_adr[i - 1])) {
SV_Printf ("Setting nomaster mode.\n");
return;
}
if (master_adr[i - 1].port == 0)
master_adr[i - 1].port = BigShort (27000);
SV_Printf ("Master server at %s\n",
NET_AdrToString (master_adr[i - 1]));
SV_Printf ("Sending a ping.\n");
data[0] = A2A_PING;
data[1] = 0;
Netchan_SendPacket (2, data, master_adr[i - 1]);
}
svs.last_heartbeat = -99999;
}
#define RESTART_CLSTUFF \
"wait;wait;wait;wait;wait;"\
"wait;wait;wait;wait;wait;"\
"wait;wait;wait;reconnect\n"
static void
SV_Restart_f (void)
{
client_t *client;
int j;
SZ_Clear (net_message->message);
MSG_WriteByte (net_message->message, svc_print);
MSG_WriteByte (net_message->message, PRINT_HIGH);
MSG_WriteString (net_message->message,
"\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9f\n"
" Server \xf2\xe5\xf3\xf4\xe1\xf2\xf4 engaged\xae\xae\xae\n"
"\x9d\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9e\x9e\x9e\x9e\x9e\x9e"
"\x9e\x9e\x9f\n\n");
MSG_WriteByte (net_message->message, svc_stufftext);
MSG_WriteString (net_message->message, RESTART_CLSTUFF);
MSG_WriteByte (net_message->message, svc_disconnect);
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
if (client->state >= cs_spawned)
Netchan_Transmit (&client->netchan, net_message->message->cursize,
net_message->message->data);
}
Sys_Printf ("Shutting down: server restart, shell must relaunch server\n");
SV_Shutdown (0);
// Error code 2 on exit, indication shell must restart the server
exit (2);
}
static void
SV_Quit_f (void)
{
SV_FinalMessage ("server shutdown\n");
SV_Printf ("Shutting down.\n");
Sys_Quit ();
}
static void
SV_Fraglogfile_f (void)
{
dstring_t *name;
if (sv_fraglogfile) {
SV_Printf ("Frag file logging off.\n");
Qclose (sv_fraglogfile);
sv_fraglogfile = NULL;
return;
}
name = dstring_new ();
if (!(sv_fraglogfile = QFS_NextFile (name,
va (0, "%s/frag_",
qfs_gamedir->dir.def), ".log"))) {
SV_Printf ("Can't open any logfiles.\n%s\n", name->str);
} else {
SV_Printf ("Logging frags to %s.\n", name->str);
}
dstring_delete (name);
}
/*
SV_SetPlayer
Sets host_client and sv_player to the player with idnum Cmd_Argv (1)
*/
static qboolean
SV_SetPlayer (void)
{
client_t *cl;
int i, idnum;
idnum = atoi (Cmd_Argv (1));
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_zombie)
continue;
if (cl->userid == idnum) {
host_client = cl;
sv_player = host_client->edict;
return true;
}
}
SV_Printf ("Userid %i is not on the server\n", idnum);
return false;
}
/*
SV_God_f
Sets client to godmode
*/
static void
SV_God_f (void)
{
if (!sv_allow_cheats) {
SV_Printf
("You must run the server with -cheats to enable this command.\n");
return;
}
if (!SV_SetPlayer ())
return;
SVfloat (sv_player, flags) = (int) SVfloat (sv_player, flags) ^ FL_GODMODE;
if (!((int) SVfloat (sv_player, flags) & FL_GODMODE))
SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode OFF\n");
else
SV_ClientPrintf (1, host_client, PRINT_HIGH, "godmode ON\n");
}
static void
SV_Noclip_f (void)
{
if (!sv_allow_cheats) {
SV_Printf
("You must run the server with -cheats to enable this command.\n");
return;
}
if (!SV_SetPlayer ())
return;
if (SVfloat (sv_player, movetype) != MOVETYPE_NOCLIP) {
SVfloat (sv_player, movetype) = MOVETYPE_NOCLIP;
SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip ON\n");
} else {
SVfloat (sv_player, movetype) = MOVETYPE_WALK;
SV_ClientPrintf (1, host_client, PRINT_HIGH, "noclip OFF\n");
}
}
static void
SV_Give_f (void)
{
const char *t;
int v;
if (!sv_allow_cheats) {
SV_Printf
("You must run the server with -cheats to enable this command.\n");
return;
}
if (!SV_SetPlayer ())
return;
t = Cmd_Argv (2);
v = atoi (Cmd_Argv (3));
switch (t[0]) {
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
SVfloat (sv_player, items) =
(int) SVfloat (sv_player, items) | IT_SHOTGUN << (t[0] - '2');
break;
case 's':
SVfloat (sv_player, ammo_shells) = v;
break;
case 'n':
SVfloat (sv_player, ammo_nails) = v;
break;
case 'r':
SVfloat (sv_player, ammo_rockets) = v;
break;
case 'h':
SVfloat (sv_player, health) = v;
break;
case 'c':
SVfloat (sv_player, ammo_cells) = v;
break;
}
}
// Use this to keep track of current level --KB
static dstring_t *curlevel;
const char *
SV_Current_Map (void)
{
return curlevel ? curlevel->str : "";
}
static const char *
nice_time (double time)
{
int t = time + 0.5;
#if 0 //FIXME ditch or cvar?
if (t < 60) {
return va (0, "%ds", t);
} else if (t < 600) {
return va (0, "%dm%02ds", t / 60, t % 60);
} else if (t < 3600) {
return va (0, "%dm", t / 60);
} else if (t < 36000) {
t /= 60;
return va (0, "%dh%02dm", t / 60, t % 60);
} else if (t < 86400) {
return va (0, "%dh", t / 3600);
} else {
t /= 3600;
return va (0, "%dd%02dh", t / 24, t % 24);
}
#endif
if (t < 60) {
return va (0, "%ds", t);
} else if (t < 3600) {
return va (0, "%dm%02ds", t / 60, t % 60);
} else if (t < 86400) {
return va (0, "%dh%02dm%02ds", t / 3600, (t / 60) % 60, t % 60);
} else {
return va (0, "%dd%02dh%02dm%02ds",
t / 86400, (t / 3600) % 24, (t / 60) % 60, t % 60);
}
}
/*
SV_Map_f
handle a
map <mapname>
command from the console or progs.
*/
static void
SV_Map_f (void)
{
const char *level;
char *expanded;
QFile *f;
if (!curlevel)
curlevel = dstring_newstr ();
if (Cmd_Argc () > 2) {
SV_Printf ("map <levelname> : continue game on a new level\n");
return;
}
if (Cmd_Argc () == 1) {
SV_Printf ("map is %s \"%s\" (%s)\n", curlevel->str,
PR_GetString (&sv_pr_state, SVstring (sv.edicts, message)),
nice_time (sv.time));
return;
}
level = Cmd_Argv (1);
// check to make sure the level exists
expanded = nva ("maps/%s.bsp", level);
f = QFS_FOpenFile (expanded);
if (!f) {
SV_Printf ("Can't find %s\n", expanded);
free (expanded);
return;
}
Qclose (f);
free (expanded);
if (sv.recording_demo)
SV_Stop (0);
SV_qtvChanging ();
SV_BroadcastCommand ("changing\n");
SV_SendMessagesToAll ();
dstring_copystr (curlevel, level);
SV_SpawnServer (level);
SV_qtvReconnect ();
SV_BroadcastCommand ("reconnect\n");
}
/*
SV_Kick_f
Kick a user off of the server
*/
static void
SV_Kick_f (void)
{
client_t *cl;
int argc = Cmd_Argc ();
const char *reason;
if (argc < 2) {
SV_Printf ("usage: kick <name/userid>\n");
return;
}
if (!(cl = SV_Match_User (Cmd_Argv (1))))
return;
// print directly, because the dropped client won't get the
// SV_BroadcastPrintf message
if (argc > 2) {
reason = Cmd_Args (2);
SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked: %s\n", cl->name,
reason);
SV_ClientPrintf (1, cl, PRINT_HIGH,
"You were kicked from the game: %s\n", reason);
} else {
SV_BroadcastPrintf (PRINT_HIGH, "%s was kicked\n", cl->name);
SV_ClientPrintf (1, cl, PRINT_HIGH, "You were kicked from the game\n");
}
SV_DropClient (cl);
}
void
SV_Status_f (void)
{
int i;
client_t *cl;
float cpu, avg, pak, demo = 0;
const char *s;
cpu = (svs.stats.latched_active + svs.stats.latched_idle);
if (cpu) {
demo = 100 * svs.stats.latched_demo / cpu;
cpu = 100 * svs.stats.latched_active / cpu;
}
avg = 1000 * svs.stats.latched_active / STATFRAMES;
pak = (float) svs.stats.latched_packets / STATFRAMES;
SV_Printf ("net address : %s\n", NET_AdrToString (net_local_adr));
SV_Printf ("uptime : %s\n",
nice_time (Sys_DoubleTime () - Sys_DoubleTimeBase ()));
SV_Printf ("cpu utilization : %3i%% (%3i%%)\n", (int) cpu, (int)demo);
SV_Printf ("avg response time: %i ms\n", (int) avg);
SV_Printf ("packets/frame : %5.2f\n", pak);
// min fps lat drp
if (sv_redirected != RD_NONE && sv_redirected != RD_MOD) {
// most remote clients are 40 columns
// 0123456789012345678901234567890123456789
SV_Printf ("name userid frags\n");
SV_Printf (" address rate ping drop\n");
SV_Printf (" ---------------- ---- ---- -----\n");
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (!cl->state)
continue;
SV_Printf ("%-16.16s ", cl->name);
SV_Printf ("%6i %5i", cl->userid, (int) SVfloat (cl->edict, frags));
if (cl->spectator)
SV_Printf (" (s)\n");
else
SV_Printf ("\n");
s = NET_BaseAdrToString (cl->netchan.remote_address);
SV_Printf (" %-16.16s", s);
if (cl->state == cs_connected) {
SV_Printf ("CONNECTING\n");
continue;
}
if (cl->state == cs_zombie) {
SV_Printf ("ZOMBIE\n");
continue;
}
if (cl->state == cs_server) {
SV_Printf ("SERVER %d\n", cl->ping);
continue;
}
SV_Printf ("%4i %4i %5.2f\n",
(int) (1000 * cl->netchan.frame_rate),
(int) SV_CalcPing (cl),
100.0 * cl->netchan.drop_count /
cl->netchan.incoming_sequence);
}
} else {
SV_Printf ("frags userid address name rate ping "
"drop qport\n");
SV_Printf ("----- ------ --------------- --------------- ---- ---- "
"----- -----\n");
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (!cl->state)
continue;
SV_Printf ("%5i %6i ", (int) SVfloat (cl->edict, frags),
cl->userid);
s = NET_BaseAdrToString (cl->netchan.remote_address);
SV_Printf ("%-15.15s ", s);
SV_Printf ("%-15.15s ", cl->name);
if (cl->state == cs_connected) {
SV_Printf ("CONNECTING\n");
continue;
}
if (cl->state == cs_zombie) {
SV_Printf ("ZOMBIE\n");
continue;
}
if (cl->state == cs_server) {
SV_Printf ("SERVER %d\n", cl->ping);
continue;
}
SV_Printf ("%4i %4i %4.1f%% %5i",
(int) (1000 * cl->netchan.frame_rate),
(int) SV_CalcPing (cl),
100.0 * cl->netchan.drop_count /
cl->netchan.incoming_sequence, cl->netchan.qport);
if (cl->spectator)
SV_Printf (" (s)\n");
else
SV_Printf ("\n");
}
}
SV_Printf ("\n");
}
#define MAXPENALTY 10.0
static void
SV_Punish (int mode)
{
int i;
double mins = 0.5;
qboolean all = false, done = false;
client_t *cl = 0;
dstring_t *text = dstring_new();
const char *cmd = 0;
const char *cmd_do = 0;
//FIXME const char *cmd_undo = 0;
int field_offs = 0;
switch (mode) {
case 0:
cmd = "cuff";
cmd_do = "cuffed";
//FIXME cmd_undo = "un-cuffed";
field_offs = field_offset (client_t, cuff_time);
break;
case 1:
cmd = "mute";
cmd_do = "muted";
//FIXME cmd_undo = "can speak";
field_offs = field_offset (client_t, lockedtill);
break;
}
if (Cmd_Argc () != 2 && Cmd_Argc () != 3) {
SV_Printf ("usage: %s <name/userid/ALL> [minutes]\n"
" (default = 0.5, 0 = cancel cuff).\n", cmd);
return;
}
if (strequal (Cmd_Argv (1), "ALL")) {
all = true;
} else {
cl = SV_Match_User (Cmd_Argv (1));
}
if (!all && !cl)
return;
if (Cmd_Argc () == 3) {
mins = atof (Cmd_Argv (2));
if (mins < 0.0 || mins > MAXPENALTY)
mins = MAXPENALTY;
}
if (cl) {
*(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
if (mins) {
dsprintf (text, "You are %s for %.1f minutes\n\n"
"reconnecting won't help...\n", cmd_do, mins);
MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
2 + strlen (text->str));
MSG_ReliableWrite_String (&cl->backbuf, text->str);
}
}
if (all) {
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_zombie)
continue;
*(double *)((char *)cl + field_offs) = realtime + mins * 60.0;
done = true;
if (mins) {
dsprintf (text, "You are %s for %.1f minutes\n\n"
"reconnecting won't help...\n", cmd_do, mins);
MSG_ReliableWrite_Begin (&cl->backbuf, svc_centerprint,
2 + strlen (text->str));
MSG_ReliableWrite_String (&cl->backbuf, text->str);
}
}
}
if (done) {
if (mins)
SV_BroadcastPrintf (PRINT_HIGH, "%s %s for %.1f minutes.\n",
all? "All Users" : cl->name, cmd_do, mins);
else
SV_BroadcastPrintf (PRINT_HIGH, "%s %s.\n",
all? "All Users" : cl->name, cmd_do);
}
dstring_delete (text);
}
static void
SV_Cuff_f (void)
{
SV_Punish (0);
}
static void
SV_Mute_f (void)
{
SV_Punish (1);
}
static void
SV_Ban_f (void)
{
double mins = 30.0, m;
client_t *cl;
char *e;
const char *a, *reason;
int argc = Cmd_Argc (), argr = 2;
if (argc < 2) {
SV_Printf ("usage: ban <name/userid> [minutes] [reason]\n"
" (default = 30, 0 = permanent).\n");
return;
}
if (!(cl = SV_Match_User (Cmd_Argv (1))))
return;
if (argc >= 3) {
a = Cmd_Argv (2);
m = strtod (a, &e);
if (e != a) {
argr++;
mins = m;
if (mins < 0.0 || mins > 1000000.0) // bout 2 yrs
mins = 0.0;
}
}
if (argc > argr) {
reason = Cmd_Args (argr);
SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s: %s\n",
cl->name, mins ? va (0, "for %.1f minutes", mins)
: "permanently", reason);
} else {
SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s\n",
cl->name, mins ? va (0, "for %.1f minutes", mins)
: "permanently");
}
SV_DropClient (cl);
Cmd_ExecuteString (va (0, "addip %s %f",
NET_BaseAdrToString (cl->netchan.remote_address),
mins), src_command);
}
static void
SV_Match_f (void)
{
if (Cmd_Argc () != 2) {
SV_Printf ("usage: match <name/userid>\n");
return;
}
if (!SV_Match_User (Cmd_Argv (1)))
SV_Printf ("No unique uid/name matched\n");
}
static void
SV_ConSay (const char *prefix, client_t *client)
{
char *p;
dstring_t *text;
int j;
if (Cmd_Argc () < 2)
return;
p = Hunk_TempAlloc (0, strlen (Cmd_Args (1)) + 1);
strcpy (p, Cmd_Args (1));
if (*p == '"') {
p++;
p[strlen (p) - 1] = 0;
}
text = dstring_new ();
dstring_copystr (text, prefix);
dstring_appendstr (text, "\x8d "); // arrow
j = strlen (text->str);
dstring_appendstr (text, p);
SV_Printf ("%s\n", text->str);
while (text->str[j])
text->str[j++] |= 0x80; // non-bold text
if (client) {
if (client->state >= cs_zombie) {
SV_ClientPrintf (1, client, PRINT_CHAT, "\n"); // bell
SV_ClientPrintf (1, client, PRINT_HIGH, "%s\n", text->str);
SV_ClientPrintf (1, client, PRINT_CHAT, "%s", ""); // bell
}
} else {
for (j = 0, client = svs.clients; j < MAX_CLIENTS; j++, client++) {
if (client->state < cs_zombie)
continue;
SV_ClientPrintf (0, client, PRINT_HIGH, "%s\n", text->str);
if (*prefix != 'I') // beep, except for Info says
SV_ClientPrintf (0, client, PRINT_CHAT, "%s", "");
}
if (sv.recorders) {
sizebuf_t *dbuf;
dbuf = SVR_WriteBegin (dem_all, 0, strlen (text->str) + 7);
MSG_WriteByte (dbuf, svc_print);
MSG_WriteByte (dbuf, PRINT_HIGH);
MSG_WriteString (dbuf, va (0, "%s\n", text->str));
MSG_WriteByte (dbuf, svc_print);
MSG_WriteByte (dbuf, PRINT_CHAT);
MSG_WriteString (dbuf, "");
}
}
}
static void
SV_Tell_f (void)
{
client_t *cl;
if (Cmd_Argc () < 3) {
SV_Printf ("usage: tell <name/userid> <text...>\n");
return;
}
if (!(cl = SV_Match_User (Cmd_Argv (1))))
return;
if (rcon_from_user)
SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Admin", cl);
else
SV_ConSay ("[\xd0\xd2\xc9\xd6\xc1\xd4\xc5] Console", cl);
}
static void
SV_ConSay_f (void)
{
if (rcon_from_user)
SV_ConSay ("Admin", 0);
else
SV_ConSay ("Console", 0);
}
static void
SV_ConSay_Info_f (void)
{
SV_ConSay ("Info", 0);
}
static void
SV_Heartbeat_f (void)
{
svs.last_heartbeat = -9999;
}
static void
SV_SendServerInfoChange (const char *key, const char *value)
{
if (!sv.state)
return;
MSG_WriteByte (&sv.reliable_datagram, svc_serverinfo);
MSG_WriteString (&sv.reliable_datagram, key);
MSG_WriteString (&sv.reliable_datagram, value);
}
void
Cvar_Info (void *data, const cvar_t *cvar)
{
if (cvar->flags & CVAR_SERVERINFO) {
const char *cvar_str = Cvar_VarString (cvar);
Info_SetValueForKey (svs.info, cvar->name, cvar_str, !sv_highchars);
SV_SendServerInfoChange (cvar->name, cvar_str);
}
}
/*
SV_Serverinfo_f
Examine or change the serverinfo string
*/
static void
SV_Serverinfo_f (void)
{
cvar_t *var;
const char *key;
const char *value;
switch (Cmd_Argc ()) {
case 1:
SV_Printf ("Server info settings:\n");
Info_Print (svs.info);
return;
case 2:
key = Cmd_Argv (1);
value = Info_ValueForKey (svs.info, key);
if (Info_Key (svs.info, key))
SV_Printf ("Server info %s: \"%s\"\n", key, value);
else
SV_Printf ("No such key %s\n", Cmd_Argv(1));
return;
case 3:
key = Cmd_Argv (1);
value = Cmd_Argv (2);
break;
default:
SV_Printf ("usage: serverinfo [ <key> <value> ]\n");
return;
}
if (Cmd_Argv (1)[0] == '*') {
SV_Printf ("Star variables cannot be changed.\n");
return;
}
// if this is a cvar, change it too
var = Cvar_FindVar (key);
if (var && (var->flags & CVAR_SERVERINFO)) {
Cvar_SetVar (var, value);
} else {
Info_SetValueForKey (svs.info, key, value, !sv_highchars);
SV_SendServerInfoChange (key, value);
}
}
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);
else
Info_RemoveKey (localinfo, key);
if (sv_funcs.LocalinfoChanged) {
*sv_globals.time = sv.time;
*sv_globals.self = 0;
PR_PushFrame (&sv_pr_state);
PR_RESET_PARAMS (&sv_pr_state);
P_STRING (&sv_pr_state, 0) = PR_SetTempString (&sv_pr_state, key);
P_STRING (&sv_pr_state, 1) = PR_SetTempString (&sv_pr_state, oldvalue);
P_STRING (&sv_pr_state, 2) = PR_SetTempString (&sv_pr_state, value);
sv_pr_state.pr_argc = 3;
PR_ExecuteProgram (&sv_pr_state, sv_funcs.LocalinfoChanged);
PR_PopFrame (&sv_pr_state);
}
if (oldvalue)
free (oldvalue);
}
/*
SV_Serverinfo_f
Examine or change the serverinfo string
*/
static void
SV_Localinfo_f (void)
{
const char *key;
const char *value;
switch (Cmd_Argc ()) {
case 1:
SV_Printf ("Local info settings:\n");
Info_Print (localinfo);
return;
case 2:
key = Cmd_Argv (1);
value = Info_ValueForKey (localinfo, key);
if (Info_Key (localinfo, key))
SV_Printf ("Localinfo %s: \"%s\"\n", key, value);
else
SV_Printf ("No such key %s\n", Cmd_Argv(1));
return;
case 3:
key = Cmd_Argv (1);
value = Cmd_Argv (2);
break;
default:
SV_Printf ("usage: localinfo [ <key> <value> ]\n");
return;
}
if (key[0] == '*') {
SV_Printf ("Star variables cannot be changed.\n");
return;
}
SV_SetLocalinfo (key, value);
}
/*
SV_User_f
Examine a users info strings
*/
static void
SV_User_f (void)
{
if (Cmd_Argc () != 2) {
SV_Printf ("Usage: user <userid>\n");
return;
}
if (!SV_SetPlayer ())
return;
Info_Print (host_client->userinfo);
}
/*
SV_Gamedir
Sets the fake *gamedir to a different directory.
*/
static void
SV_Gamedir (void)
{
const char *dir;
if (Cmd_Argc () == 1) {
SV_Printf ("Current *gamedir: %s\n",
Info_ValueForKey (svs.info, "*gamedir"));
return;
}
if (Cmd_Argc () != 2) {
SV_Printf ("Usage: sv_gamedir <newgamedir>\n");
return;
}
dir = Cmd_Argv (1);
if (strstr (dir, "..") || strstr (dir, "/")
|| strstr (dir, "\\") || strstr (dir, ":")) {
SV_Printf ("*Gamedir should be a single filename, not a path\n");
return;
}
Info_SetValueForStarKey (svs.info, "*gamedir", dir,
!sv_highchars);
}
/*
SV_Floodport_f
Sets the gamedir and path to a different directory.
*/
static void
SV_Floodprot_f (void)
{
int arg1, arg2, arg3;
if (Cmd_Argc () == 1) {
if (fp_messages) {
SV_Printf
("Current floodprot settings: \nAfter %d msgs per %d seconds, "
"silence for %d seconds\n",
fp_messages, fp_persecond, fp_secondsdead);
return;
} else
SV_Printf ("No floodprots enabled.\n");
}
if (Cmd_Argc () != 4) {
SV_Printf
("Usage: floodprot <# of messages> <per # of seconds> <seconds to "
"silence>\n");
SV_Printf
("Use floodprotmsg to set a custom message to say to the "
"flooder.\n");
return;
}
arg1 = atoi (Cmd_Argv (1));
arg2 = atoi (Cmd_Argv (2));
arg3 = atoi (Cmd_Argv (3));
if (arg1 <= 0 || arg2 <= 0 || arg3 <= 0) {
SV_Printf ("All values must be positive numbers\n");
return;
}
if (arg1 > 10) {
SV_Printf ("Can track up to only 10 messages.\n");
return;
}
fp_messages = arg1;
fp_persecond = arg2;
fp_secondsdead = arg3;
}
static void
SV_Floodprotmsg_f (void)
{
if (Cmd_Argc () == 1) {
SV_Printf ("Current msg: %s\n", fp_msg);
return;
} else if (Cmd_Argc () != 2) {
SV_Printf ("Usage: floodprotmsg \"<message>\"\n");
return;
}
snprintf (fp_msg, sizeof (fp_msg), "%s", Cmd_Argv (1));
}
static void
SV_Snap (int uid)
{
client_t *cl;
int i;
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_zombie)
continue;
if (cl->userid == uid)
break;
}
if (i >= MAX_CLIENTS) {
SV_Printf ("userid not found\n");
return;
}
if (!cl->uploadfn)
cl->uploadfn = dstring_new ();
if (!(cl->upload = QFS_NextFile (cl->uploadfn,
va (0, "%s/snap/%d-",
qfs_gamedir->dir.def, uid), ".pcx"))) {
SV_Printf ("Snap: Couldn't create a file, clean some out.\n%s\n",
cl->uploadfn->str);
dstring_delete (cl->uploadfn);
cl->uploadfn = 0;
return;
}
cl->upload_started = 0;
memcpy (&cl->snap_from, &net_from, sizeof (net_from));
if (sv_redirected != RD_NONE)
cl->remote_snap = true;
else
cl->remote_snap = false;
MSG_ReliableWrite_Begin (&cl->backbuf, svc_stufftext, 24);
MSG_ReliableWrite_String (&cl->backbuf, "cmd snap\n");
SV_Printf ("Requesting snap from user %d...\n", uid);
}
static void
SV_Snap_f (void)
{
int uid;
if (Cmd_Argc () != 2) {
SV_Printf ("Usage: snap <userid>\n");
return;
}
uid = atoi (Cmd_Argv (1));
SV_Snap (uid);
}
static void
SV_SnapAll_f (void)
{
client_t *cl;
int i;
for (i = 0, cl = svs.clients; i < MAX_CLIENTS; i++, cl++) {
if (cl->state < cs_connected || cl->spectator)
continue;
SV_Snap (cl->userid);
}
}
void
SV_InitOperatorCommands (void)
{
if (COM_CheckParm ("-cheats")) {
sv_allow_cheats = true;
Info_SetValueForStarKey (svs.info, "*cheats", "ON", 0);
}
Cmd_AddCommand ("fraglogfile", SV_Fraglogfile_f, "Enables logging of kills "
"to frag_##.log");
Cmd_AddCommand ("snap", SV_Snap_f, "Take a screenshot of userid");
Cmd_AddCommand ("snapall", SV_SnapAll_f, "Take a screenshot of all users");
Cmd_AddCommand ("kick", SV_Kick_f, "Remove a user from the server (kick "
"userid)");
Cmd_AddCommand ("status", SV_Status_f, "Report information on the current "
"connected clients and the server - displays userids");
Cmd_AddCommand ("map", SV_Map_f, "Change to a new map (map mapname)");
Cmd_AddCommand ("setmaster", SV_SetMaster_f, "Lists the server with up to "
"eight masters.\n"
"When a server is listed with a master, the master is "
"aware of the server's IP address and port and it is added "
"to the\n"
"list of current servers connected to a master. A "
"heartbeat is sent to the master from the server to "
"indicated that the\n"
"server is still running and alive.\n\n"
"Examples:\n"
"setmaster 192.246.40.12:27002\n"
"setmaster 192.246.40.12:27002 192.246.40.12:27004");
Cmd_AddCommand ("heartbeat", SV_Heartbeat_f, "Force a heartbeat to be sent "
"to the master server.\n"
"A heartbeat tells the Master the server's IP address and "
"that it is still alive.");
Cmd_AddCommand ("restart", SV_Restart_f, "Restart the server (with shell "
"support)");
Cmd_AddCommand ("quit", SV_Quit_f, "Shut down the server");
Cmd_AddCommand ("god", SV_God_f, "Toggle god cheat to userid (god userid) "
"Requires cheats are enabled");
Cmd_AddCommand ("give", SV_Give_f, "Give userid items, or health.\n"
"Items: 1 Axe, 2 Shotgun, 3 Double-Barrelled Shotgun, 4 "
"Nailgun, 5 Super Nailgun, 6 Grenade Launcher, 7 Rocket "
"Launcher,\n"
"8 ThunderBolt, C Cells, H Health, N Nails, R Rockets, S "
"Shells. Requires cheats to be enabled. (give userid item "
"amount)");
Cmd_AddCommand ("noclip", SV_Noclip_f, "Toggle no clipping cheat for "
"userid. Requires cheats to be enabled. (noclip userid)");
Cmd_AddCommand ("serverinfo", SV_Serverinfo_f, "Reports or sets "
"information about server.\n"
"The information stored in this space is broadcast on the "
"network to all players.\n"
"Values:\n"
"dq - Drop Quad Damage when a player dies.\n"
"dr - Drop Ring of Shadows when a player dies.\n"
"rj - Sets the multiplier rate for splash damage kick.\n"
"needpass - Displays the passwords enabled on the server.\n"
"watervis - Toggle the use of r_watervis by OpenGL "
"clients.\n"
"Note: Keys with (*) in front cannot be changed. Maximum "
"key size cannot exceed 64-bytes.\n"
"Maximum size for all keys cannot exceed 512-bytes.\n"
"(serverinfo key value)");
Cmd_AddCommand ("localinfo", SV_Localinfo_f, "Shows or sets localinfo "
"variables.\n"
"Useful for mod programmers who need to allow the admin to "
"change settings.\n"
"This is an alternative storage space to the serverinfo "
"space for mod variables.\n"
"The variables stored in this space are not broadcast on "
"the network.\n"
"This space also has a 32-kilobyte limit which is much "
"greater then the 512-byte limit on the serverinfo space.\n"
"Special Keys: (current map) (next map) - Using this "
"combination will allow the creation of a custom map cycle "
"without editing code.\n\n"
"Example:\n"
"localinfo dm2 dm4\n"
"localinfo dm4 dm6\n"
"localinfo dm6 dm2\n"
"(localinfo key value)");
Cmd_AddCommand ("user", SV_User_f, "Report information about the user "
"(user userid)");
Cmd_AddCommand ("sv_gamedir", SV_Gamedir, "Displays or determines the "
"value of the serverinfo *gamedir variable.\n"
"Note: Useful when the physical gamedir directory has a "
"different name than the widely accepted gamedir "
"directory.\n"
"Example:\n"
"gamedir tf2_5; sv_gamedir fortress\n"
"gamedir ctf4_2; sv_gamedir ctf\n"
"(sv_gamedir dirname)");
Cmd_AddCommand ("floodprot", SV_Floodprot_f, "Sets the options for flood "
"protection.\n"
"Default: 4 4 10\n"
"(floodprot (number of messages) (number of seconds) "
"(silence time in seconds))");
Cmd_AddCommand ("floodprotmsg", SV_Floodprotmsg_f, "Sets the message "
"displayed after flood protection is invoked (floodprotmsg "
"message)");
Cmd_AddCommand ("maplist", Con_Maplist_f, "List all maps on the server");
Cmd_AddCommand ("say", SV_ConSay_f, "Say something to everyone on the "
"server. Will show up as the name 'Console' (or 'Admin') "
"in game");
Cmd_AddCommand ("sayinfo", SV_ConSay_Info_f, "Say something to everyone on "
"the server. Will show up as the name 'Info' in game");
Cmd_AddCommand ("tell", SV_Tell_f, "Say something to a specific user on "
"the server. Will show up as the name 'Console' (or "
"'Admin') in game");
Cmd_AddCommand ("ban", SV_Ban_f, "ban a player for a specified time");
Cmd_AddCommand ("cuff", SV_Cuff_f, "\"hand-cuff\" a player for a "
"specified time");
Cmd_AddCommand ("mute", SV_Mute_f, "silience a player for a specified "
"time");
Cmd_AddCommand ("match", SV_Match_f, "matches nicks as ban/cuff/mute "
"commands do, so you can check safely");
// poor description
Cvar_Register (&sv_leetnickmatch_cvar, 0, 0);
}