2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 cvar_t sv_cheats = {CVAR_SERVER | CVAR_NOTIFY, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
28 cvar_t sv_adminnick = {CVAR_SERVER | CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
29 cvar_t sv_status_privacy = {CVAR_SERVER | CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
30 cvar_t sv_status_show_qcstatus = {CVAR_SERVER | CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
31 cvar_t sv_namechangetimer = {CVAR_SERVER | CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
34 ===============================================================================
38 ===============================================================================
42 ======================
47 command from the console. Active clients are kicked off.
48 ======================
50 static void SV_Map_f(cmd_state_t *cmd)
52 char level[MAX_QPATH];
54 if (Cmd_Argc(cmd) != 2)
56 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
60 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
61 if (gamemode == GAME_DELUXEQUAKE)
62 Cvar_Set(&cvars_all, "warpmark", "");
64 cls.demonum = -1; // stop demo loop in case this fails
69 if(svs.maxclients != svs.maxclients_next)
71 svs.maxclients = svs.maxclients_next;
73 Mem_Free(svs.clients);
74 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
79 if (key_dest == key_menu || key_dest == key_menu_grabbed)
84 svs.serverflags = 0; // haven't completed an episode yet
85 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
86 SV_SpawnServer(level);
87 if (sv.active && cls.state == ca_disconnected)
88 CL_EstablishConnection("local:1", -2);
95 Goes to a new map, taking all clients along
98 static void SV_Changelevel_f(cmd_state_t *cmd)
100 char level[MAX_QPATH];
102 if (Cmd_Argc(cmd) != 2)
104 Con_Print("changelevel <levelname> : continue game on a new level\n");
115 if (key_dest == key_menu || key_dest == key_menu_grabbed)
120 SV_SaveSpawnparms ();
121 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
122 SV_SpawnServer(level);
123 if (sv.active && cls.state == ca_disconnected)
124 CL_EstablishConnection("local:1", -2);
131 Restarts the current server for a dead player
134 static void SV_Restart_f(cmd_state_t *cmd)
136 char mapname[MAX_QPATH];
138 if (Cmd_Argc(cmd) != 1)
140 Con_Print("restart : restart current level\n");
145 Con_Print("Only the server may restart\n");
151 if (key_dest == key_menu || key_dest == key_menu_grabbed)
156 strlcpy(mapname, sv.name, sizeof(mapname));
157 SV_SpawnServer(mapname);
158 if (sv.active && cls.state == ca_disconnected)
159 CL_EstablishConnection("local:1", -2);
162 //===========================================================================
164 // Disable cheats if sv_cheats is turned off
165 static void SV_DisableCheats_c(char *value)
167 prvm_prog_t *prog = SVVM_prog;
170 if (value[0] == '0' && !value[1])
172 while (svs.clients[i].edict)
174 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
175 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
176 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
177 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
178 if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
179 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
181 noclip_anglehack = false;
182 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
193 Sets client to godmode
196 static void SV_God_f(cmd_state_t *cmd)
198 prvm_prog_t *prog = SVVM_prog;
200 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
201 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
202 SV_ClientPrint("godmode OFF\n");
204 SV_ClientPrint("godmode ON\n");
207 qboolean noclip_anglehack;
209 static void SV_Noclip_f(cmd_state_t *cmd)
211 prvm_prog_t *prog = SVVM_prog;
213 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
215 noclip_anglehack = true;
216 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
217 SV_ClientPrint("noclip ON\n");
221 noclip_anglehack = false;
222 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
223 SV_ClientPrint("noclip OFF\n");
232 static void SV_Give_f(cmd_state_t *cmd)
234 prvm_prog_t *prog = SVVM_prog;
238 t = Cmd_Argv(cmd, 1);
239 v = atoi (Cmd_Argv(cmd, 2));
253 // MED 01/04/97 added hipnotic give stuff
254 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
259 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
261 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
263 else if (t[0] == '9')
264 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
265 else if (t[0] == '0')
266 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
267 else if (t[0] >= '2')
268 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
273 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
278 if (gamemode == GAME_ROGUE)
279 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
281 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
284 if (gamemode == GAME_ROGUE)
286 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
287 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
288 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
292 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
296 if (gamemode == GAME_ROGUE)
298 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
299 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
300 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
304 if (gamemode == GAME_ROGUE)
306 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
307 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
308 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
312 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
316 if (gamemode == GAME_ROGUE)
318 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
319 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
320 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
324 PRVM_serveredictfloat(host_client->edict, health) = v;
327 if (gamemode == GAME_ROGUE)
329 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
330 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
331 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
335 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
339 if (gamemode == GAME_ROGUE)
341 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
342 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
343 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
353 Sets client to flymode
356 static void SV_Fly_f(cmd_state_t *cmd)
358 prvm_prog_t *prog = SVVM_prog;
360 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
362 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
363 SV_ClientPrint("flymode ON\n");
367 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
368 SV_ClientPrint("flymode OFF\n");
372 static void SV_Notarget_f(cmd_state_t *cmd)
374 prvm_prog_t *prog = SVVM_prog;
376 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
377 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
378 SV_ClientPrint("notarget OFF\n");
380 SV_ClientPrint("notarget ON\n");
388 static void SV_Kill_f(cmd_state_t *cmd)
390 prvm_prog_t *prog = SVVM_prog;
391 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
393 SV_ClientPrint("Can't suicide -- already dead!\n");
397 PRVM_serverglobalfloat(time) = sv.time;
398 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
399 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
407 static void SV_Pause_f(cmd_state_t *cmd)
409 void (*print) (const char *fmt, ...);
410 if (cmd->source == src_command)
412 // if running a client, try to send over network so the pause is handled by the server
413 if (cls.state == ca_connected)
415 Cmd_ForwardToServer_f(cmd);
421 print = SV_ClientPrintf;
423 if (!pausable.integer)
425 if (cmd->source == src_client)
427 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
429 print("Pause not allowed.\n");
436 if (cmd->source != src_command)
437 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
438 else if(*(sv_adminnick.string))
439 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
441 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
442 // send notification to all clients
443 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
444 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
447 static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
449 prvm_prog_t *prog = SVVM_prog;
454 // LadyHavoc: long say messages
456 qboolean fromServer = false;
458 if (cmd->source == src_command)
460 if (cls.state == ca_dedicated)
467 Cmd_ForwardToServer_f(cmd);
472 if (Cmd_Argc (cmd) < 2)
475 if (!teamplay.integer)
485 // note this uses the chat prefix \001
486 if (!fromServer && !teamonly)
487 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
488 else if (!fromServer && teamonly)
489 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
490 else if(*(sv_adminnick.string))
491 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
493 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
494 p2 = text + strlen(text);
495 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
497 if (p2[-1] == '\"' && quoted)
502 strlcat(text, "\n", sizeof(text));
504 // note: save is not a valid edict if fromServer is true
506 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
507 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
508 SV_ClientPrint(text);
511 if (cls.state == ca_dedicated)
515 static void SV_Say_f(cmd_state_t *cmd)
520 static void SV_Say_Team_f(cmd_state_t *cmd)
525 static void SV_Tell_f(cmd_state_t *cmd)
527 const char *playername_start = NULL;
528 size_t playername_length = 0;
529 int playernumber = 0;
533 char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
534 qboolean fromServer = false;
536 if (cmd->source == src_command)
538 if (cls.state == ca_dedicated)
542 Cmd_ForwardToServer_f(cmd);
547 if (Cmd_Argc (cmd) < 2)
550 // note this uses the chat prefix \001
552 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
553 else if(*(sv_adminnick.string))
554 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
556 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
559 p2 = p1 + strlen(p1);
560 // remove the target name
561 while (p1 < p2 && *p1 == ' ')
566 while (p1 < p2 && *p1 == ' ')
568 while (p1 < p2 && isdigit(*p1))
570 playernumber = playernumber * 10 + (*p1 - '0');
578 playername_start = p1;
579 while (p1 < p2 && *p1 != '"')
581 playername_length = p1 - playername_start;
587 playername_start = p1;
588 while (p1 < p2 && *p1 != ' ')
590 playername_length = p1 - playername_start;
592 while (p1 < p2 && *p1 == ' ')
596 // set playernumber to the right client
598 if(playername_length >= sizeof(namebuf))
601 Con_Print("Host_Tell: too long player name/ID\n");
603 SV_ClientPrint("Host_Tell: too long player name/ID\n");
606 memcpy(namebuf, playername_start, playername_length);
607 namebuf[playername_length] = 0;
608 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
610 if (!svs.clients[playernumber].active)
612 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
616 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
619 Con_Print("Host_Tell: invalid player name/ID\n");
621 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
624 // remove trailing newlines
625 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
627 // remove quotes if present
634 Con_Print("Host_Tell: missing end quote\n");
636 SV_ClientPrint("Host_Tell: missing end quote\n");
638 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
642 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
648 host_client = svs.clients + playernumber;
649 SV_ClientPrint(text);
659 static void SV_Ping_f(cmd_state_t *cmd)
663 void (*print) (const char *fmt, ...);
665 if (cmd->source == src_command)
667 // if running a client, try to send over network so the client's ping report parser will see the report
668 if (cls.state == ca_connected)
670 Cmd_ForwardToServer_f(cmd);
676 print = SV_ClientPrintf;
681 print("Client ping times:\n");
682 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
686 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
694 Send back ping and packet loss update for all current players to this player
697 static void SV_Pings_f(cmd_state_t *cmd)
699 int i, j, ping, packetloss, movementloss;
702 if (!host_client->netconnection)
705 if (sv.protocol != PROTOCOL_QUAKEWORLD)
707 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
708 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
710 for (i = 0;i < svs.maxclients;i++)
714 if (svs.clients[i].netconnection)
716 for (j = 0;j < NETGRAPH_PACKETS;j++)
717 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
719 for (j = 0;j < NETGRAPH_PACKETS;j++)
720 if (svs.clients[i].movement_count[j] < 0)
723 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
724 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
725 ping = (int)floor(svs.clients[i].ping*1000+0.5);
726 ping = bound(0, ping, 9999);
727 if (sv.protocol == PROTOCOL_QUAKEWORLD)
729 // send qw_svc_updateping and qw_svc_updatepl messages
730 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
731 MSG_WriteShort(&host_client->netconnection->message, ping);
732 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
733 MSG_WriteByte(&host_client->netconnection->message, packetloss);
737 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
739 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
741 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
742 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
745 if (sv.protocol != PROTOCOL_QUAKEWORLD)
746 MSG_WriteString(&host_client->netconnection->message, "\n");
753 user <name or userid>
755 Dump userdata / masterdata for a user
758 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
763 if (Cmd_Argc(cmd) != 2)
765 Con_Printf ("Usage: user <username / userid>\n");
769 uid = atoi(Cmd_Argv(cmd, 1));
771 for (i = 0;i < cl.maxclients;i++)
773 if (!cl.scores[i].name[0])
775 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
777 InfoString_Print(cl.scores[i].qw_userinfo);
781 Con_Printf ("User not in server.\n");
788 Dump userids for all current players
791 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
797 Con_Printf ("userid frags name\n");
798 Con_Printf ("------ ----- ----\n");
799 for (i = 0;i < cl.maxclients;i++)
801 if (cl.scores[i].name[0])
803 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
808 Con_Printf ("%i total users\n", c);
816 static void SV_Status_f(cmd_state_t *cmd)
818 prvm_prog_t *prog = SVVM_prog;
821 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
822 void (*print) (const char *fmt, ...);
823 char ip[48]; // can contain a full length v6 address with [] and a port
827 if (cmd->source == src_command)
829 // if running a client, try to send over network so the client's status report parser will see the report
830 if (cls.state == ca_connected)
832 Cmd_ForwardToServer_f(cmd);
838 print = SV_ClientPrintf;
844 if (Cmd_Argc(cmd) == 2)
846 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
848 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
852 for (players = 0, i = 0;i < svs.maxclients;i++)
853 if (svs.clients[i].active)
855 print ("host: %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
856 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
857 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
858 print ("map: %s\n", sv.name);
859 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
860 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
863 print ("^2IP %%pl ping time frags no name\n");
865 print ("^5IP no name\n");
867 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
874 if (in == 0 || in == 1)
876 seconds = (int)(host.realtime - client->connecttime);
877 minutes = seconds / 60;
880 seconds -= (minutes * 60);
881 hours = minutes / 60;
883 minutes -= (hours * 60);
889 if (client->netconnection)
890 for (j = 0;j < NETGRAPH_PACKETS;j++)
891 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
893 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
894 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
897 if(sv_status_privacy.integer && cmd->source != src_command)
898 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
900 strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
902 frags = client->frags;
904 if(sv_status_show_qcstatus.integer)
906 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
907 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
913 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
914 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
918 frags = atoi(qcstatus);
922 if (in == 0) // default layout
924 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
926 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
927 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
932 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
933 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
937 else if (in == 1) // extended layout
939 print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
941 else if (in == 2) // reduced layout
943 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
949 ======================
951 ======================
953 static void SV_Name_f(cmd_state_t *cmd)
955 prvm_prog_t *prog = SVVM_prog;
957 qboolean valid_colors;
958 const char *newNameSource;
959 char newName[sizeof(host_client->name)];
961 if (Cmd_Argc (cmd) == 1)
964 if (Cmd_Argc (cmd) == 2)
965 newNameSource = Cmd_Argv(cmd, 1);
967 newNameSource = Cmd_Args(cmd);
969 strlcpy(newName, newNameSource, sizeof(newName));
971 if (cmd->source == src_command)
974 if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
976 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
980 host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
982 // point the string back at updateclient->name to keep it safe
983 strlcpy (host_client->name, newName, sizeof (host_client->name));
985 for (i = 0, j = 0;host_client->name[i];i++)
986 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
987 host_client->name[j++] = host_client->name[i];
988 host_client->name[j] = 0;
990 if(host_client->name[0] == 1 || host_client->name[0] == 2)
991 // may interfere with chat area, and will needlessly beep; so let's add a ^7
993 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
994 host_client->name[sizeof(host_client->name) - 1] = 0;
995 host_client->name[0] = STRING_COLOR_TAG;
996 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
999 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1000 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1003 l = strlen(host_client->name);
1004 if(l < sizeof(host_client->name) - 1)
1006 // duplicate the color tag to escape it
1007 host_client->name[i] = STRING_COLOR_TAG;
1008 host_client->name[i+1] = 0;
1009 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1013 // remove the last character to fix the color code
1014 host_client->name[l-1] = 0;
1015 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1019 // find the last color tag offset and decide if we need to add a reset tag
1020 for (i = 0, j = -1;host_client->name[i];i++)
1022 if (host_client->name[i] == STRING_COLOR_TAG)
1024 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1027 // if this happens to be a reset tag then we don't need one
1028 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1033 if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4]))
1039 if (host_client->name[i+1] == STRING_COLOR_TAG)
1046 // does not end in the default color string, so add it
1047 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1048 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1050 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1051 if (strcmp(host_client->old_name, host_client->name))
1053 if (host_client->begun)
1054 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1055 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1056 // send notification to all clients
1057 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1058 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1059 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1060 SV_WriteNetnameIntoDemo(host_client);
1064 static void SV_Rate_f(cmd_state_t *cmd)
1068 rate = atoi(Cmd_Argv(cmd, 1));
1070 if (cmd->source == src_command)
1073 host_client->rate = rate;
1076 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1080 if (Cmd_Argc(cmd) != 2)
1083 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1085 host_client->rate_burstsize = rate_burstsize;
1088 static void SV_Color_f(cmd_state_t *cmd)
1090 prvm_prog_t *prog = SVVM_prog;
1092 int top, bottom, playercolor;
1094 top = atoi(Cmd_Argv(cmd, 1));
1095 bottom = atoi(Cmd_Argv(cmd, 2));
1097 playercolor = top*16 + bottom;
1099 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1101 Con_DPrint("Calling SV_ChangeTeam\n");
1102 prog->globals.fp[OFS_PARM0] = playercolor;
1103 PRVM_serverglobalfloat(time) = sv.time;
1104 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1105 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1109 if (host_client->edict)
1111 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1112 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1114 host_client->colors = playercolor;
1115 if (host_client->old_colors != host_client->colors)
1117 host_client->old_colors = host_client->colors;
1118 // send notification to all clients
1119 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1120 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1121 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1130 Kicks a user off of the server
1133 static void SV_Kick_f(cmd_state_t *cmd)
1136 const char *message = NULL;
1139 qboolean byNumber = false;
1146 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1148 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1149 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1155 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1157 if (!host_client->active)
1159 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1164 if (i < svs.maxclients)
1166 if (cmd->source == src_command)
1168 if (cls.state == ca_dedicated)
1176 // can't kick yourself!
1177 if (host_client == save)
1180 if (Cmd_Argc(cmd) > 2)
1182 message = Cmd_Args(cmd);
1183 COM_ParseToken_Simple(&message, false, false, true);
1186 message++; // skip the #
1187 while (*message == ' ') // skip white space
1189 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
1191 while (*message && *message == ' ')
1195 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1197 SV_ClientPrintf("Kicked by %s\n", who);
1198 SV_DropClient (false); // kicked
1204 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1208 if (Cmd_Argc(cmd) != 2)
1210 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1216 Con_Print("maxplayers can not be changed while a server is running.\n");
1217 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1220 n = atoi(Cmd_Argv(cmd, 1));
1221 n = bound(1, n, MAX_SCOREBOARD);
1222 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1224 svs.maxclients_next = n;
1226 Cvar_Set (&cvars_all, "deathmatch", "0");
1228 Cvar_Set (&cvars_all, "deathmatch", "1");
1232 ===============================================================================
1236 ===============================================================================
1239 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
1244 for (i=0 ; i<prog->num_edicts ; i++)
1246 e = PRVM_EDICT_NUM(i);
1247 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1250 Con_Print("No viewthing on map\n");
1259 static void SV_Viewmodel_f(cmd_state_t *cmd)
1261 prvm_prog_t *prog = SVVM_prog;
1268 e = FindViewthing(prog);
1271 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1272 if (m && m->loaded && m->Draw)
1274 PRVM_serveredictfloat(e, frame) = 0;
1275 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1278 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1287 static void SV_Viewframe_f(cmd_state_t *cmd)
1289 prvm_prog_t *prog = SVVM_prog;
1297 e = FindViewthing(prog);
1300 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1302 f = atoi(Cmd_Argv(cmd, 1));
1303 if (f >= m->numframes)
1306 PRVM_serveredictfloat(e, frame) = f;
1310 static void PrintFrameName (dp_model_t *m, int frame)
1313 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1315 Con_Printf("frame %i\n", frame);
1323 static void SV_Viewnext_f(cmd_state_t *cmd)
1325 prvm_prog_t *prog = SVVM_prog;
1332 e = FindViewthing(prog);
1335 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1337 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1338 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1339 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1341 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1350 static void SV_Viewprev_f(cmd_state_t *cmd)
1352 prvm_prog_t *prog = SVVM_prog;
1359 e = FindViewthing(prog);
1362 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1364 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1365 if (PRVM_serveredictfloat(e, frame) < 0)
1366 PRVM_serveredictfloat(e, frame) = 0;
1368 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1372 void SV_InitOperatorCommands(void)
1374 Cvar_RegisterVariable(&sv_cheats);
1375 Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1376 Cvar_RegisterVariable(&sv_adminnick);
1377 Cvar_RegisterVariable(&sv_status_privacy);
1378 Cvar_RegisterVariable(&sv_status_show_qcstatus);
1379 Cvar_RegisterVariable(&sv_namechangetimer);
1381 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1382 Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1383 Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1384 Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1385 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1386 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1387 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1388 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1389 Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1390 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1391 Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1392 Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1393 Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1394 Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1395 Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1396 Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1397 Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1398 Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1399 Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1401 // commands that do not have automatic forwarding from cmd_client, these are internal details of the network protocol and not of interest to users (if they know what they are doing they can still use a generic "cmd prespawn" or similar)
1402 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1403 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "spawn", SV_Spawn_f, "internal use - signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
1404 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "begin", SV_Begin_f, "internal use - signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
1405 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "pings", SV_Pings_f, "internal use - command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
1407 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1408 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1409 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1410 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1411 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1412 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1414 Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1415 Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
1416 Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1417 Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");