mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-06 05:01:26 +00:00
1231 lines
29 KiB
C
1231 lines
29 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
|
|
|
|
static __attribute__ ((unused)) const char rcsid[] =
|
|
"$Id$";
|
|
|
|
#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 "bothdefs.h"
|
|
#include "compat.h"
|
|
#include "server.h"
|
|
#include "sv_demo.h"
|
|
#include "sv_progs.h"
|
|
|
|
qboolean sv_allow_cheats;
|
|
|
|
int fp_messages = 4, fp_persecond = 4, fp_secondsdead = 10;
|
|
char fp_msg[255] = { 0 };
|
|
cvar_t *sv_leetnickmatch;
|
|
|
|
static qboolean
|
|
match_char (char a, char b)
|
|
{
|
|
a = tolower (sys_char_map[(byte)a]);
|
|
b = tolower (sys_char_map[(byte)b]);
|
|
|
|
if (a == b || (sv_leetnickmatch->int_val
|
|
&& ( (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 only be entered 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. Only using 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;
|
|
}
|
|
|
|
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 (!QFS_NextFilename (name,
|
|
va ("%s/frag_", qfs_gamedir->dir.def), ".log")) {
|
|
SV_Printf ("Can't open any logfiles.\n");
|
|
sv_fraglogfile = NULL;
|
|
} else {
|
|
SV_Printf ("Logging frags to %s.\n", name->str);
|
|
sv_fraglogfile = QFS_WOpen (name->str, 0);
|
|
}
|
|
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 (float time)
|
|
{
|
|
int t = time + 0.5;
|
|
|
|
#if 0 //FIXME ditch or cvar?
|
|
if (t < 60) {
|
|
return va ("%ds", t);
|
|
} else if (t < 600) {
|
|
return va ("%dm%02ds", t / 60, t % 60);
|
|
} else if (t < 3600) {
|
|
return va ("%dm", t / 60);
|
|
} else if (t < 36000) {
|
|
t /= 60;
|
|
return va ("%dh%02dm", t / 60, t % 60);
|
|
} else if (t < 86400) {
|
|
return va ("%dh", t / 3600);
|
|
} else {
|
|
t /= 3600;
|
|
return va ("%dd%02dh", t / 24, t % 24);
|
|
}
|
|
#endif
|
|
if (t < 60) {
|
|
return va ("%ds", t);
|
|
} else if (t < 3600) {
|
|
return va ("%dm%02ds", t / 60, t % 60);
|
|
} else if (t < 86400) {
|
|
return va ("%dh%02dm%02ds", t / 3600, (t / 60) % 60, t % 60);
|
|
} else {
|
|
return va ("%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)\n", curlevel->str, nice_time (sv.time));
|
|
return;
|
|
}
|
|
level = Cmd_Argv (1);
|
|
|
|
// check to make sure the level exists
|
|
expanded = nva ("maps/%s.bsp", level);
|
|
QFS_FOpenFile (expanded, &f);
|
|
if (!f) {
|
|
SV_Printf ("Can't find %s\n", expanded);
|
|
free (expanded);
|
|
return;
|
|
}
|
|
Qclose (f);
|
|
free (expanded);
|
|
|
|
if (sv.demorecording)
|
|
SV_Stop (0);
|
|
|
|
SV_BroadcastCommand ("changing\n");
|
|
SV_SendMessagesToAll ();
|
|
|
|
dstring_copystr (curlevel, level);
|
|
SV_SpawnServer (level);
|
|
|
|
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 ()));
|
|
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\n");
|
|
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\n");
|
|
continue;
|
|
}
|
|
SV_Printf ("%4i %4i %3.1f %4i",
|
|
(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;
|
|
const char *cmd_undo = 0;
|
|
int field_offs = 0;
|
|
|
|
switch (mode) {
|
|
case 0:
|
|
cmd = "cuff";
|
|
cmd_do = "cuffed";
|
|
cmd_undo = "un-cuffed";
|
|
field_offs = field_offset (client_t, cuff_time);
|
|
break;
|
|
case 1:
|
|
cmd = "mute";
|
|
cmd_do = "muted";
|
|
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 ("for %.1f minutes", mins)
|
|
: "permanently", reason);
|
|
} else {
|
|
SV_BroadcastPrintf (PRINT_HIGH, "Admin Banned user %s %s\n",
|
|
cl->name, mins ? va ("for %.1f minutes", mins)
|
|
: "permanently");
|
|
}
|
|
SV_DropClient (cl);
|
|
Cmd_ExecuteString (va ("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 (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.demorecording) {
|
|
DemoWrite_Begin (dem_all, 0, strlen (text->str) + 3);
|
|
MSG_WriteByte (&demo.dbuf->sz, svc_print);
|
|
MSG_WriteByte (&demo.dbuf->sz, PRINT_HIGH);
|
|
MSG_WriteString (&demo.dbuf->sz, text->str);
|
|
MSG_WriteByte (&demo.dbuf->sz, PRINT_CHAT);
|
|
MSG_WriteString (&demo.dbuf->sz, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (cvar_t *var)
|
|
{
|
|
if (var->flags & CVAR_SERVERINFO) {
|
|
Info_SetValueForKey (svs.info, var->name, var->string,
|
|
(!sv_highchars || !sv_highchars->int_val));
|
|
|
|
SV_SendServerInfoChange (var->name, var->string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
SV_Serverinfo_f
|
|
|
|
Examine or change the serverinfo string
|
|
*/
|
|
static void
|
|
SV_Serverinfo_f (void)
|
|
{
|
|
cvar_t *var;
|
|
|
|
if (Cmd_Argc () == 1) {
|
|
SV_Printf ("Server info settings:\n");
|
|
Info_Print (svs.info);
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc () != 3) {
|
|
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 (Cmd_Argv (1));
|
|
if (var && (var->flags & CVAR_SERVERINFO)) {
|
|
Cvar_Set (var, Cmd_Argv (2));
|
|
} else {
|
|
Info_SetValueForKey (svs.info, Cmd_Argv (1), Cmd_Argv (2),
|
|
!sv_highchars->int_val);
|
|
SV_SendServerInfoChange (Cmd_Argv (1), Cmd_Argv (2));
|
|
}
|
|
}
|
|
|
|
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;
|
|
PR_PushFrame (&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);
|
|
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)
|
|
{
|
|
if (Cmd_Argc () == 1) {
|
|
SV_Printf ("Local info settings:\n");
|
|
Info_Print (localinfo);
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argc () != 3) {
|
|
SV_Printf ("usage: localinfo [ <key> <value> ]\n");
|
|
return;
|
|
}
|
|
|
|
if (Cmd_Argv (1)[0] == '*') {
|
|
SV_Printf ("Star variables cannot be changed.\n");
|
|
return;
|
|
}
|
|
SV_SetLocalinfo (Cmd_Argv (1), Cmd_Argv (2));
|
|
}
|
|
|
|
/*
|
|
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->int_val);
|
|
}
|
|
|
|
/*
|
|
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 only track up to 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 (!QFS_NextFilename (cl->uploadfn,
|
|
va ("%s/snap/%d-", qfs_gamedir->dir.def, uid),
|
|
".pcx")) {
|
|
SV_Printf ("Snap: Couldn't create a file, clean some out.\n");
|
|
dstring_delete (cl->uploadfn);
|
|
cl->uploadfn = 0;
|
|
return;
|
|
}
|
|
|
|
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 ("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
|
|
sv_leetnickmatch = Cvar_Get ("sv_3133735_7h4n_7h0u", "1", CVAR_NONE, NULL,
|
|
"Match '1' as 'i' and such in nicks");
|
|
}
|