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 char ip[48]; // can contain a full length v6 address with [] and a port
831 if (Cmd_Argc(cmd) == 2)
833 if (strcmp(Cmd_Argv(cmd, 1), "1") == 0)
835 else if (strcmp(Cmd_Argv(cmd, 1), "2") == 0)
839 for (players = 0, i = 0;i < svs.maxclients;i++)
840 if (svs.clients[i].active)
842 SV_ClientPrintf ("host: %s\n", Cvar_VariableString (&cvars_all, "hostname", CVAR_SERVER));
843 SV_ClientPrintf ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
844 SV_ClientPrintf ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
845 SV_ClientPrintf ("map: %s\n", sv.name);
846 SV_ClientPrintf ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
847 SV_ClientPrintf ("players: %i active (%i max)\n\n", players, svs.maxclients);
850 SV_ClientPrintf ("^2IP %%pl ping time frags no name\n");
852 SV_ClientPrintf ("^5IP no name\n");
854 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
861 if (in == 0 || in == 1)
863 seconds = (int)(host.realtime - client->connecttime);
864 minutes = seconds / 60;
867 seconds -= (minutes * 60);
868 hours = minutes / 60;
870 minutes -= (hours * 60);
876 if (client->netconnection)
877 for (j = 0;j < NETGRAPH_PACKETS;j++)
878 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
880 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
881 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
884 if(sv_status_privacy.integer && cmd->source != src_command && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP)
885 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
887 strlcpy(ip, (client->netconnection && *client->netconnection->address) ? client->netconnection->address : "botclient", 48);
889 frags = client->frags;
891 if(sv_status_show_qcstatus.integer)
893 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
894 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
900 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
901 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
905 frags = atoi(qcstatus);
909 if (in == 0) // default layout
911 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
913 // LadyHavoc: this is very touchy because we must maintain ProQuake compatible status output
914 SV_ClientPrintf ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
915 SV_ClientPrintf (" %s\n", ip);
919 // LadyHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
920 SV_ClientPrintf ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
921 SV_ClientPrintf (" %s\n", ip);
924 else if (in == 1) // extended layout
926 SV_ClientPrintf ("%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);
928 else if (in == 2) // reduced layout
930 SV_ClientPrintf ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
936 ======================
938 ======================
940 static void SV_Name_f(cmd_state_t *cmd)
942 prvm_prog_t *prog = SVVM_prog;
944 qboolean valid_colors;
945 const char *newNameSource;
946 char newName[sizeof(host_client->name)];
948 if (Cmd_Argc (cmd) == 1)
951 if (Cmd_Argc (cmd) == 2)
952 newNameSource = Cmd_Argv(cmd, 1);
954 newNameSource = Cmd_Args(cmd);
956 strlcpy(newName, newNameSource, sizeof(newName));
958 if (cmd->source == src_command)
961 if (host.realtime < host_client->nametime && strcmp(newName, host_client->name))
963 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
967 host_client->nametime = host.realtime + max(0.0f, sv_namechangetimer.value);
969 // point the string back at updateclient->name to keep it safe
970 strlcpy (host_client->name, newName, sizeof (host_client->name));
972 for (i = 0, j = 0;host_client->name[i];i++)
973 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
974 host_client->name[j++] = host_client->name[i];
975 host_client->name[j] = 0;
977 if(host_client->name[0] == 1 || host_client->name[0] == 2)
978 // may interfere with chat area, and will needlessly beep; so let's add a ^7
980 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
981 host_client->name[sizeof(host_client->name) - 1] = 0;
982 host_client->name[0] = STRING_COLOR_TAG;
983 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
986 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
987 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
990 l = strlen(host_client->name);
991 if(l < sizeof(host_client->name) - 1)
993 // duplicate the color tag to escape it
994 host_client->name[i] = STRING_COLOR_TAG;
995 host_client->name[i+1] = 0;
996 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1000 // remove the last character to fix the color code
1001 host_client->name[l-1] = 0;
1002 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1006 // find the last color tag offset and decide if we need to add a reset tag
1007 for (i = 0, j = -1;host_client->name[i];i++)
1009 if (host_client->name[i] == STRING_COLOR_TAG)
1011 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1014 // if this happens to be a reset tag then we don't need one
1015 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1020 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]))
1026 if (host_client->name[i+1] == STRING_COLOR_TAG)
1033 // does not end in the default color string, so add it
1034 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1035 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1037 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1038 if (strcmp(host_client->old_name, host_client->name))
1040 if (host_client->begun)
1041 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1042 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1043 // send notification to all clients
1044 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1045 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1046 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1047 SV_WriteNetnameIntoDemo(host_client);
1051 static void SV_Rate_f(cmd_state_t *cmd)
1055 rate = atoi(Cmd_Argv(cmd, 1));
1057 if (cmd->source == src_command)
1060 host_client->rate = rate;
1063 static void SV_Rate_BurstSize_f(cmd_state_t *cmd)
1067 if (Cmd_Argc(cmd) != 2)
1070 rate_burstsize = atoi(Cmd_Argv(cmd, 1));
1072 host_client->rate_burstsize = rate_burstsize;
1075 static void SV_Color_f(cmd_state_t *cmd)
1077 prvm_prog_t *prog = SVVM_prog;
1079 int top, bottom, playercolor;
1081 top = atoi(Cmd_Argv(cmd, 1));
1082 bottom = atoi(Cmd_Argv(cmd, 2));
1087 playercolor = top*16 + bottom;
1089 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1091 Con_DPrint("Calling SV_ChangeTeam\n");
1092 prog->globals.fp[OFS_PARM0] = playercolor;
1093 PRVM_serverglobalfloat(time) = sv.time;
1094 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1095 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1099 if (host_client->edict)
1101 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1102 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1104 host_client->colors = playercolor;
1105 if (host_client->old_colors != host_client->colors)
1107 host_client->old_colors = host_client->colors;
1108 // send notification to all clients
1109 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1110 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1111 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1120 Kicks a user off of the server
1123 static void SV_Kick_f(cmd_state_t *cmd)
1126 const char *message = NULL;
1129 qboolean byNumber = false;
1136 if (Cmd_Argc(cmd) > 2 && strcmp(Cmd_Argv(cmd, 1), "#") == 0)
1138 i = (int)(atof(Cmd_Argv(cmd, 2)) - 1);
1139 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1145 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1147 if (!host_client->active)
1149 if (strcasecmp(host_client->name, Cmd_Argv(cmd, 1)) == 0)
1154 if (i < svs.maxclients)
1156 if (cmd->source == src_command)
1158 if (cls.state == ca_dedicated)
1161 who = cl_name.string;
1166 // can't kick yourself!
1167 if (host_client == save)
1170 if (Cmd_Argc(cmd) > 2)
1172 message = Cmd_Args(cmd);
1173 COM_ParseToken_Simple(&message, false, false, true);
1176 message++; // skip the #
1177 while (*message == ' ') // skip white space
1179 message += strlen(Cmd_Argv(cmd, 2)); // skip the number
1181 while (*message && *message == ' ')
1185 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
1187 SV_ClientPrintf("Kicked by %s\n", who);
1188 SV_DropClient (false); // kicked
1194 static void SV_MaxPlayers_f(cmd_state_t *cmd)
1198 if (Cmd_Argc(cmd) != 2)
1200 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
1206 Con_Print("maxplayers can not be changed while a server is running.\n");
1207 Con_Print("It will be changed on next server startup (\"map\" command).\n");
1210 n = atoi(Cmd_Argv(cmd, 1));
1211 n = bound(1, n, MAX_SCOREBOARD);
1212 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
1214 svs.maxclients_next = n;
1216 Cvar_Set (&cvars_all, "deathmatch", "0");
1218 Cvar_Set (&cvars_all, "deathmatch", "1");
1222 ======================
1224 ======================
1226 // the old playermodel in cl_main has been renamed to __cl_playermodel
1227 static void SV_Playermodel_f(cmd_state_t *cmd)
1229 prvm_prog_t *prog = SVVM_prog;
1231 char newPath[sizeof(host_client->playermodel)];
1233 if (Cmd_Argc (cmd) == 1)
1236 if (Cmd_Argc (cmd) == 2)
1237 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1239 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1241 for (i = 0, j = 0;newPath[i];i++)
1242 if (newPath[i] != '\r' && newPath[i] != '\n')
1243 newPath[j++] = newPath[i];
1247 if (host.realtime < host_client->nametime)
1249 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1253 host_client->nametime = host.realtime + 5;
1256 // point the string back at updateclient->name to keep it safe
1257 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1258 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1259 if (strcmp(host_client->old_model, host_client->playermodel))
1261 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1262 /*// send notification to all clients
1263 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1264 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1265 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1270 ======================
1272 ======================
1274 static void SV_Playerskin_f(cmd_state_t *cmd)
1276 prvm_prog_t *prog = SVVM_prog;
1278 char newPath[sizeof(host_client->playerskin)];
1280 if (Cmd_Argc (cmd) == 1)
1283 if (Cmd_Argc (cmd) == 2)
1284 strlcpy (newPath, Cmd_Argv(cmd, 1), sizeof (newPath));
1286 strlcpy (newPath, Cmd_Args(cmd), sizeof (newPath));
1288 for (i = 0, j = 0;newPath[i];i++)
1289 if (newPath[i] != '\r' && newPath[i] != '\n')
1290 newPath[j++] = newPath[i];
1294 if (host.realtime < host_client->nametime)
1296 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1300 host_client->nametime = host.realtime + 5;
1303 // point the string back at updateclient->name to keep it safe
1304 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1305 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1306 if (strcmp(host_client->old_skin, host_client->playerskin))
1308 //if (host_client->begun)
1309 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1310 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1311 /*// send notification to all clients
1312 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1313 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1314 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1319 ======================
1321 LadyHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1322 LadyHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1323 ======================
1325 static void SV_PModel_f(cmd_state_t *cmd)
1327 prvm_prog_t *prog = SVVM_prog;
1329 if (Cmd_Argc (cmd) == 1)
1332 PRVM_serveredictfloat(host_client->edict, pmodel) = atoi(Cmd_Argv(cmd, 1));
1336 ===============================================================================
1340 ===============================================================================
1343 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
1348 for (i=0 ; i<prog->num_edicts ; i++)
1350 e = PRVM_EDICT_NUM(i);
1351 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
1354 Con_Print("No viewthing on map\n");
1363 static void SV_Viewmodel_f(cmd_state_t *cmd)
1365 prvm_prog_t *prog = SVVM_prog;
1372 e = FindViewthing(prog);
1375 m = Mod_ForName (Cmd_Argv(cmd, 1), false, true, NULL);
1376 if (m && m->loaded && m->Draw)
1378 PRVM_serveredictfloat(e, frame) = 0;
1379 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
1382 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(cmd, 1));
1391 static void SV_Viewframe_f(cmd_state_t *cmd)
1393 prvm_prog_t *prog = SVVM_prog;
1401 e = FindViewthing(prog);
1404 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1406 f = atoi(Cmd_Argv(cmd, 1));
1407 if (f >= m->numframes)
1410 PRVM_serveredictfloat(e, frame) = f;
1414 static void PrintFrameName (dp_model_t *m, int frame)
1417 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
1419 Con_Printf("frame %i\n", frame);
1427 static void SV_Viewnext_f(cmd_state_t *cmd)
1429 prvm_prog_t *prog = SVVM_prog;
1436 e = FindViewthing(prog);
1439 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1441 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
1442 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
1443 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
1445 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1454 static void SV_Viewprev_f(cmd_state_t *cmd)
1456 prvm_prog_t *prog = SVVM_prog;
1463 e = FindViewthing(prog);
1466 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
1468 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
1469 if (PRVM_serveredictfloat(e, frame) < 0)
1470 PRVM_serveredictfloat(e, frame) = 0;
1472 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
1476 static void SV_SendCvar_f(cmd_state_t *cmd)
1479 const char *cvarname;
1482 if(Cmd_Argc(cmd) != 2)
1485 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
1488 cvarname = Cmd_Argv(cmd, 1);
1491 if (cls.state != ca_dedicated)
1495 for(;i<svs.maxclients;i++)
1496 if(svs.clients[i].active && svs.clients[i].netconnection)
1498 host_client = &svs.clients[i];
1499 SV_ClientCommands("sendcvar %s\n", cvarname);
1504 void SV_InitOperatorCommands(void)
1506 Cvar_RegisterVariable(&sv_cheats);
1507 Cvar_RegisterCallback(&sv_cheats, SV_DisableCheats_c);
1508 Cvar_RegisterVariable(&sv_adminnick);
1509 Cvar_RegisterVariable(&sv_status_privacy);
1510 Cvar_RegisterVariable(&sv_status_show_qcstatus);
1511 Cvar_RegisterVariable(&sv_namechangetimer);
1513 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "status", SV_Status_f, "print server status information");
1514 Cmd_AddCommand(CMD_SHARED, "map", SV_Map_f, "kick everyone off the server and start a new level");
1515 Cmd_AddCommand(CMD_SHARED, "restart", SV_Restart_f, "restart current level");
1516 Cmd_AddCommand(CMD_SHARED, "changelevel", SV_Changelevel_f, "change to another level, bringing along all connected clients");
1517 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "say", SV_Say_f, "send a chat message to everyone on the server");
1518 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "say_team", SV_Say_Team_f, "send a chat message to your team on the server");
1519 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "tell", SV_Tell_f, "send a chat message to only one person on the server");
1520 Cmd_AddCommand(CMD_SERVER | CMD_SERVER_FROM_CLIENT, "pause", SV_Pause_f, "pause the game (if the server allows pausing)");
1521 Cmd_AddCommand(CMD_SHARED, "kick", SV_Kick_f, "kick a player off the server by number or name");
1522 Cmd_AddCommand(CMD_SHARED | CMD_SERVER_FROM_CLIENT, "ping", SV_Ping_f, "print ping times of all players on the server");
1523 Cmd_AddCommand(CMD_SHARED, "load", SV_Loadgame_f, "load a saved game file");
1524 Cmd_AddCommand(CMD_SHARED, "save", SV_Savegame_f, "save the game to a file");
1525 Cmd_AddCommand(CMD_SHARED, "viewmodel", SV_Viewmodel_f, "change model of viewthing entity in current level");
1526 Cmd_AddCommand(CMD_SHARED, "viewframe", SV_Viewframe_f, "change animation frame of viewthing entity in current level");
1527 Cmd_AddCommand(CMD_SHARED, "viewnext", SV_Viewnext_f, "change to next animation frame of viewthing entity in current level");
1528 Cmd_AddCommand(CMD_SHARED, "viewprev", SV_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
1529 Cmd_AddCommand(CMD_SHARED, "maxplayers", SV_MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
1530 Cmd_AddCommand(CMD_SHARED, "user", SV_User_f, "prints additional information about a player number or name on the scoreboard");
1531 Cmd_AddCommand(CMD_SHARED, "users", SV_Users_f, "prints additional information about all players on the scoreboard");
1532 Cmd_AddCommand(CMD_SERVER, "sendcvar", SV_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
1534 // 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)
1535 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "prespawn", SV_PreSpawn_f, "internal use - signon 1 (client acknowledges that server information has been received)");
1536 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)");
1537 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)");
1538 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)");
1540 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "god", SV_God_f, "god mode (invulnerability)");
1541 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "notarget", SV_Notarget_f, "notarget mode (monsters do not see you)");
1542 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "fly", SV_Fly_f, "fly mode (flight)");
1543 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "noclip", SV_Noclip_f, "noclip mode (flight without collisions, move through walls)");
1544 Cmd_AddCommand(CMD_CHEAT | CMD_SERVER_FROM_CLIENT, "give", SV_Give_f, "alter inventory");
1545 Cmd_AddCommand(CMD_SERVER_FROM_CLIENT, "kill", SV_Kill_f, "die instantly");
1547 Cmd_AddCommand(CMD_USERINFO, "color", SV_Color_f, "change your player shirt and pants colors");
1548 Cmd_AddCommand(CMD_USERINFO, "name", SV_Name_f, "change your player name");
1549 Cmd_AddCommand(CMD_USERINFO, "rate", SV_Rate_f, "change your network connection speed");
1550 Cmd_AddCommand(CMD_USERINFO, "rate_burstsize", SV_Rate_BurstSize_f, "change your network connection speed");
1551 Cmd_AddCommand(CMD_USERINFO, "pmodel", SV_PModel_f, "(Nehahra-only) change your player model choice");
1552 Cmd_AddCommand(CMD_USERINFO, "playermodel", SV_Playermodel_f, "change your player model");
1553 Cmd_AddCommand(CMD_USERINFO, "playerskin", SV_Playerskin_f, "change your player skin number");