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");
110 Con_Printf("You must be running a server to changelevel. Use 'map %s' instead\n", Cmd_Argv(cmd, 1));
116 if (key_dest == key_menu || key_dest == key_menu_grabbed)
121 SV_SaveSpawnparms ();
122 strlcpy(level, Cmd_Argv(cmd, 1), sizeof(level));
123 SV_SpawnServer(level);
124 if (sv.active && cls.state == ca_disconnected)
125 CL_EstablishConnection("local:1", -2);
132 Restarts the current server for a dead player
135 static void SV_Restart_f(cmd_state_t *cmd)
137 char mapname[MAX_QPATH];
139 if (Cmd_Argc(cmd) != 1)
141 Con_Print("restart : restart current level\n");
146 Con_Print("Only the server may restart\n");
152 if (key_dest == key_menu || key_dest == key_menu_grabbed)
157 strlcpy(mapname, sv.name, sizeof(mapname));
158 SV_SpawnServer(mapname);
159 if (sv.active && cls.state == ca_disconnected)
160 CL_EstablishConnection("local:1", -2);
163 //===========================================================================
165 // Disable cheats if sv_cheats is turned off
166 static void SV_DisableCheats_c(cvar_t *var)
168 prvm_prog_t *prog = SVVM_prog;
173 while (svs.clients[i].edict)
175 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_GODMODE))
176 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_GODMODE;
177 if (((int)PRVM_serveredictfloat(svs.clients[i].edict, flags) & FL_NOTARGET))
178 PRVM_serveredictfloat(svs.clients[i].edict, flags) = (int)PRVM_serveredictfloat(svs.clients[i].edict, flags) ^ FL_NOTARGET;
179 if (PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_NOCLIP ||
180 PRVM_serveredictfloat(svs.clients[i].edict, movetype) == MOVETYPE_FLY)
182 noclip_anglehack = false;
183 PRVM_serveredictfloat(svs.clients[i].edict, movetype) = MOVETYPE_WALK;
194 Sets client to godmode
197 static void SV_God_f(cmd_state_t *cmd)
199 prvm_prog_t *prog = SVVM_prog;
201 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
202 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
203 SV_ClientPrint("godmode OFF\n");
205 SV_ClientPrint("godmode ON\n");
208 qboolean noclip_anglehack;
210 static void SV_Noclip_f(cmd_state_t *cmd)
212 prvm_prog_t *prog = SVVM_prog;
214 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
216 noclip_anglehack = true;
217 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
218 SV_ClientPrint("noclip ON\n");
222 noclip_anglehack = false;
223 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
224 SV_ClientPrint("noclip OFF\n");
233 static void SV_Give_f(cmd_state_t *cmd)
235 prvm_prog_t *prog = SVVM_prog;
239 t = Cmd_Argv(cmd, 1);
240 v = atoi (Cmd_Argv(cmd, 2));
254 // MED 01/04/97 added hipnotic give stuff
255 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
260 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
262 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
264 else if (t[0] == '9')
265 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
266 else if (t[0] == '0')
267 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
268 else if (t[0] >= '2')
269 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
274 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
279 if (gamemode == GAME_ROGUE)
280 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
282 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
285 if (gamemode == GAME_ROGUE)
287 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
288 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
289 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
293 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
297 if (gamemode == GAME_ROGUE)
299 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
300 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
301 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
305 if (gamemode == GAME_ROGUE)
307 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
308 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
309 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
313 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
317 if (gamemode == GAME_ROGUE)
319 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
320 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
321 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
325 PRVM_serveredictfloat(host_client->edict, health) = v;
328 if (gamemode == GAME_ROGUE)
330 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
331 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
332 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
336 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
340 if (gamemode == GAME_ROGUE)
342 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
343 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
344 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
354 Sets client to flymode
357 static void SV_Fly_f(cmd_state_t *cmd)
359 prvm_prog_t *prog = SVVM_prog;
361 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
363 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
364 SV_ClientPrint("flymode ON\n");
368 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
369 SV_ClientPrint("flymode OFF\n");
373 static void SV_Notarget_f(cmd_state_t *cmd)
375 prvm_prog_t *prog = SVVM_prog;
377 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
378 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
379 SV_ClientPrint("notarget OFF\n");
381 SV_ClientPrint("notarget ON\n");
389 static void SV_Kill_f(cmd_state_t *cmd)
391 prvm_prog_t *prog = SVVM_prog;
392 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
394 SV_ClientPrint("Can't suicide -- already dead!\n");
398 PRVM_serverglobalfloat(time) = sv.time;
399 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
400 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
408 static void SV_Pause_f(cmd_state_t *cmd)
410 void (*print) (const char *fmt, ...);
411 if (cmd->source == src_command)
413 // if running a client, try to send over network so the pause is handled by the server
414 if (cls.state == ca_connected)
416 CL_ForwardToServer_f(cmd);
422 print = SV_ClientPrintf;
424 if (!pausable.integer)
426 if (cmd->source == src_client)
428 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
430 print("Pause not allowed.\n");
437 if (cmd->source != src_command)
438 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
439 else if(*(sv_adminnick.string))
440 SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
442 SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
443 // send notification to all clients
444 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
445 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
448 static void SV_Say(cmd_state_t *cmd, qboolean teamonly)
450 prvm_prog_t *prog = SVVM_prog;
455 // LadyHavoc: long say messages
457 qboolean fromServer = false;
459 if (cmd->source == src_command)
461 if (cls.state == ca_dedicated)
468 CL_ForwardToServer_f(cmd);
473 if (Cmd_Argc (cmd) < 2)
476 if (!teamplay.integer)
486 // note this uses the chat prefix \001
487 if (!fromServer && !teamonly)
488 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
489 else if (!fromServer && teamonly)
490 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
491 else if(*(sv_adminnick.string))
492 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
494 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
495 p2 = text + strlen(text);
496 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
498 if (p2[-1] == '\"' && quoted)
503 strlcat(text, "\n", sizeof(text));
505 // note: save is not a valid edict if fromServer is true
507 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
508 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
509 SV_ClientPrint(text);
512 if (cls.state == ca_dedicated)
516 static void SV_Say_f(cmd_state_t *cmd)
521 static void SV_Say_Team_f(cmd_state_t *cmd)
526 static void SV_Tell_f(cmd_state_t *cmd)
528 const char *playername_start = NULL;
529 size_t playername_length = 0;
530 int playernumber = 0;
534 char text[MAX_INPUTLINE]; // LadyHavoc: FIXME: temporary buffer overflow fix (was 64)
535 qboolean fromServer = false;
537 if (cmd->source == src_command)
539 if (cls.state == ca_dedicated)
543 CL_ForwardToServer_f(cmd);
548 if (Cmd_Argc (cmd) < 2)
551 // note this uses the chat prefix \001
553 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
554 else if(*(sv_adminnick.string))
555 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
557 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
560 p2 = p1 + strlen(p1);
561 // remove the target name
562 while (p1 < p2 && *p1 == ' ')
567 while (p1 < p2 && *p1 == ' ')
569 while (p1 < p2 && isdigit(*p1))
571 playernumber = playernumber * 10 + (*p1 - '0');
579 playername_start = p1;
580 while (p1 < p2 && *p1 != '"')
582 playername_length = p1 - playername_start;
588 playername_start = p1;
589 while (p1 < p2 && *p1 != ' ')
591 playername_length = p1 - playername_start;
593 while (p1 < p2 && *p1 == ' ')
597 // set playernumber to the right client
599 if(playername_length >= sizeof(namebuf))
602 Con_Print("Host_Tell: too long player name/ID\n");
604 SV_ClientPrint("Host_Tell: too long player name/ID\n");
607 memcpy(namebuf, playername_start, playername_length);
608 namebuf[playername_length] = 0;
609 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
611 if (!svs.clients[playernumber].active)
613 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
617 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
620 Con_Print("Host_Tell: invalid player name/ID\n");
622 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
625 // remove trailing newlines
626 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
628 // remove quotes if present
635 Con_Print("Host_Tell: missing end quote\n");
637 SV_ClientPrint("Host_Tell: missing end quote\n");
639 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
643 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
649 host_client = svs.clients + playernumber;
650 SV_ClientPrint(text);
660 static void SV_Ping_f(cmd_state_t *cmd)
664 void (*print) (const char *fmt, ...);
666 if (cmd->source == src_command)
668 // if running a client, try to send over network so the client's ping report parser will see the report
669 if (cls.state == ca_connected)
671 CL_ForwardToServer_f(cmd);
677 print = SV_ClientPrintf;
682 print("Client ping times:\n");
683 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
687 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
695 Send back ping and packet loss update for all current players to this player
698 static void SV_Pings_f(cmd_state_t *cmd)
700 int i, j, ping, packetloss, movementloss;
703 if (!host_client->netconnection)
706 if (sv.protocol != PROTOCOL_QUAKEWORLD)
708 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
709 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
711 for (i = 0;i < svs.maxclients;i++)
715 if (svs.clients[i].netconnection)
717 for (j = 0;j < NETGRAPH_PACKETS;j++)
718 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
720 for (j = 0;j < NETGRAPH_PACKETS;j++)
721 if (svs.clients[i].movement_count[j] < 0)
724 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
725 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
726 ping = (int)floor(svs.clients[i].ping*1000+0.5);
727 ping = bound(0, ping, 9999);
728 if (sv.protocol == PROTOCOL_QUAKEWORLD)
730 // send qw_svc_updateping and qw_svc_updatepl messages
731 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
732 MSG_WriteShort(&host_client->netconnection->message, ping);
733 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
734 MSG_WriteByte(&host_client->netconnection->message, packetloss);
738 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
740 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
742 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
743 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
746 if (sv.protocol != PROTOCOL_QUAKEWORLD)
747 MSG_WriteString(&host_client->netconnection->message, "\n");
754 user <name or userid>
756 Dump userdata / masterdata for a user
759 static void SV_User_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
764 if (Cmd_Argc(cmd) != 2)
766 Con_Printf ("Usage: user <username / userid>\n");
770 uid = atoi(Cmd_Argv(cmd, 1));
772 for (i = 0;i < cl.maxclients;i++)
774 if (!cl.scores[i].name[0])
776 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(cmd, 1)))
778 InfoString_Print(cl.scores[i].qw_userinfo);
782 Con_Printf ("User not in server.\n");
789 Dump userids for all current players
792 static void SV_Users_f(cmd_state_t *cmd) // credit: taken from QuakeWorld
798 Con_Printf ("userid frags name\n");
799 Con_Printf ("------ ----- ----\n");
800 for (i = 0;i < cl.maxclients;i++)
802 if (cl.scores[i].name[0])
804 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
809 Con_Printf ("%i total users\n", c);
817 static void SV_Status_f(cmd_state_t *cmd)
819 prvm_prog_t *prog = SVVM_prog;
822 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
823 void (*print) (const char *fmt, ...);
824 char ip[48]; // can contain a full length v6 address with [] and a port
828 if (cmd->source == src_command)
830 // if running a client, try to send over network so the client's status report parser will see the report
831 if (cls.state == ca_connected)
833 CL_ForwardToServer_f(cmd);
839 print = SV_ClientPrintf;
845 if (Cmd_Argc(cmd) == 2)
847 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
849 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
853 for (players = 0, i = 0;i < svs.maxclients;i++)
854 if (svs.clients[i].active)
856 print ("host: %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
857 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
858 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
859 print ("map: %s\n", sv.name);
860 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
861 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
864 print ("^2IP %%pl ping time frags no name\n");
866 print ("^5IP no name\n");
868 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
875 if (in == 0 || in == 1)
877 seconds = (int)(host.realtime - client->connecttime);
878 minutes = seconds / 60;
881 seconds -= (minutes * 60);
882 hours = minutes / 60;
884 minutes -= (hours * 60);
890 if (client->netconnection)
891 for (j = 0;j < NETGRAPH_PACKETS;j++)
892 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
894 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
895 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
898 if(sv_status_privacy.integer && cmd->source != src_command)
899 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
901 strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
903 frags = client->frags;
905 if(sv_status_show_qcstatus.integer)
907 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
908 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
914 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
915 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
919 frags = atoi(qcstatus);
923 if (in == 0) // default layout
925 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
927 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
928 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
933 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
934 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
938 else if (in == 1) // extended layout
940 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);
942 else if (in == 2) // reduced layout
944 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
950 ======================
952 ======================
954 static void SV_Name_f(cmd_state_t *cmd)
956 prvm_prog_t *prog = SVVM_prog;
958 qboolean valid_colors;
959 const char *newNameSource;
960 char newName[sizeof(host_client->name)];
962 if (Cmd_Argc (cmd) == 1)
965 if (Cmd_Argc (cmd) == 2)
966 newNameSource = Cmd_Argv(cmd, 1);
968 newNameSource = Cmd_Args(cmd);
970 strlcpy(newName, newNameSource, sizeof(newName));
972 if (cmd->source == src_command)
975 if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
977 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
981 host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
983 // point the string back at updateclient->name to keep it safe
984 strlcpy (host_client->name, newName, sizeof (host_client->name));
986 for (i = 0, j = 0;host_client->name[i];i++)
987 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
988 host_client->name[j++] = host_client->name[i];
989 host_client->name[j] = 0;
991 if(host_client->name[0] == 1 || host_client->name[0] == 2)
992 // may interfere with chat area, and will needlessly beep; so let's add a ^7
994 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
995 host_client->name[sizeof(host_client->name) - 1] = 0;
996 host_client->name[0] = STRING_COLOR_TAG;
997 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1000 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1001 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1004 l = strlen(host_client->name);
1005 if(l < sizeof(host_client->name) - 1)
1007 // duplicate the color tag to escape it
1008 host_client->name[i] = STRING_COLOR_TAG;
1009 host_client->name[i+1] = 0;
1010 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1014 // remove the last character to fix the color code
1015 host_client->name[l-1] = 0;
1016 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1020 // find the last color tag offset and decide if we need to add a reset tag
1021 for (i = 0, j = -1;host_client->name[i];i++)
1023 if (host_client->name[i] == STRING_COLOR_TAG)
1025 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1028 // if this happens to be a reset tag then we don't need one
1029 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1034 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]))
1040 if (host_client->name[i+1] == STRING_COLOR_TAG)
1047 // does not end in the default color string, so add it
1048 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1049 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1051 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1052 if (strcmp(host_client->old_name, host_client->name))
1054 if (host_client->begun)
1055 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1056 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1057 // send notification to all clients
1058 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1059 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1060 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1061 SV_WriteNetnameIntoDemo(host_client);
1065 static void SV_Rate_f(cmd_state_t *cmd)
1069 rate = atoi(Cmd_Argv(cmd, 1));
1071 if (cmd->source == src_command)
1074 host_client->rate = rate;
1077 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1081 if (Cmd_Argc(cmd) != 2)
1084 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1086 host_client->rate_burstsize = rate_burstsize;
1089 static void SV_Color_f(cmd_state_t *cmd)
1091 prvm_prog_t *prog = SVVM_prog;
1093 int top, bottom, playercolor;
1095 top = atoi(Cmd_Argv(cmd, 1));
1096 bottom = atoi(Cmd_Argv(cmd, 2));
1101 playercolor = top*16 + bottom;
1103 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1105 Con_DPrint("Calling SV_ChangeTeam\n");
1106 prog->globals.fp[OFS_PARM0] = playercolor;
1107 PRVM_serverglobalfloat(time) = sv.time;
1108 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1109 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1113 if (host_client->edict)
1115 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1116 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1118 host_client->colors = playercolor;
1119 if (host_client->old_colors != host_client->colors)
1121 host_client->old_colors = host_client->colors;
1122 // send notification to all clients
1123 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1124 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1125 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1134 Kicks a user off of the server
1137 static void SV_Kick_f(cmd_state_t *cmd)
1140 const char *message = NULL;
1143 qboolean byNumber = false;
1150 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1152 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1153 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1159 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1161 if (!host_client->active)
1163 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1168 if (i < svs.maxclients)
1170 if (cmd->source == src_command)
1172 if (cls.state == ca_dedicated)
1175 who = cl_name.string;
1180 // can't kick yourself!
1181 if (host_client == save)
1184 if (Cmd_Argc(cmd) > 2)
1186 message = Cmd_Args(cmd);
1187 COM_ParseToken_Simple(&message, false, false, true);
1190 message++; // skip the #
1191 while (*message == ' ') // skip white space
1193 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
1195 while (*message && *message == ' ')
1199 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1201 SV_ClientPrintf("Kicked by %s\n", who);
1202 SV_DropClient (false); // kicked
1208 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1212 if (Cmd_Argc(cmd) != 2)
1214 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1220 Con_Print("maxplayers can not be changed while a server is running.\n");
1221 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1224 n = atoi(Cmd_Argv(cmd, 1));
1225 n = bound(1, n, MAX_SCOREBOARD);
1226 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1228 svs.maxclients_next = n;
1230 Cvar_Set (&cvars_all, "deathmatch", "0");
1232 Cvar_Set (&cvars_all, "deathmatch", "1");
1236 ===============================================================================
1240 ===============================================================================
1243 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
1248 for (i=0 ; i<prog->num_edicts ; i++)
1250 e = PRVM_EDICT_NUM(i);
1251 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1254 Con_Print("No viewthing on map\n");
1263 static void SV_Viewmodel_f(cmd_state_t *cmd)
1265 prvm_prog_t *prog = SVVM_prog;
1272 e = FindViewthing(prog);
1275 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1276 if (m && m->loaded && m->Draw)
1278 PRVM_serveredictfloat(e, frame) = 0;
1279 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1282 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1291 static void SV_Viewframe_f(cmd_state_t *cmd)
1293 prvm_prog_t *prog = SVVM_prog;
1301 e = FindViewthing(prog);
1304 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1306 f = atoi(Cmd_Argv(cmd, 1));
1307 if (f >= m->numframes)
1310 PRVM_serveredictfloat(e, frame) = f;
1314 static void PrintFrameName (dp_model_t *m, int frame)
1317 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1319 Con_Printf("frame %i\n", frame);
1327 static void SV_Viewnext_f(cmd_state_t *cmd)
1329 prvm_prog_t *prog = SVVM_prog;
1336 e = FindViewthing(prog);
1339 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1341 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1342 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1343 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1345 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1354 static void SV_Viewprev_f(cmd_state_t *cmd)
1356 prvm_prog_t *prog = SVVM_prog;
1363 e = FindViewthing(prog);
1366 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1368 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1369 if (PRVM_serveredictfloat(e, frame) < 0)
1370 PRVM_serveredictfloat(e, frame) = 0;
1372 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1376 void SV_InitOperatorCommands(void)
1378 Cvar_RegisterVariable(&sv_cheats);
1379 Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1380 Cvar_RegisterVariable(&sv_adminnick);
1381 Cvar_RegisterVariable(&sv_status_privacy);
1382 Cvar_RegisterVariable(&sv_status_show_qcstatus);
1383 Cvar_RegisterVariable(&sv_namechangetimer);
1385 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1386 Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1387 Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1388 Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1389 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1390 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1391 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1392 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1393 Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1394 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1395 Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1396 Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1397 Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1398 Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1399 Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1400 Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1401 Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1402 Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1403 Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1405 // 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)
1406 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1407 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)");
1408 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)");
1409 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)");
1411 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1412 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1413 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1414 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1415 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1416 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1418 Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1419 Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
1420 Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1421 Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");