/* 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 #endif #ifdef HAVE_STRINGS_H # include #endif #include #include #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" bool 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 bool 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 bool 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 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 : 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 \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; bool 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 [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 [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 \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 \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 [ ]\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 [ ]\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 \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 \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> \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 \"\"\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 \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); }