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.
25 #include "prvm_cmds.h"
28 // for secure rcon authentication
34 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
35 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
36 cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
37 cvar_t sv_status_show_qcstatus = {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."};
38 cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
39 cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
40 cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
41 cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
42 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
43 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
44 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
45 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
46 cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
47 qboolean allowcheats = false;
49 extern qboolean host_shuttingdown;
50 extern cvar_t developer_entityparsing;
58 void Host_Quit_f (void)
61 Con_Printf("shutting down already!\n");
71 static void Host_Status_f (void)
73 prvm_prog_t *prog = SVVM_prog;
76 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
77 void (*print) (const char *fmt, ...);
78 char ip[48]; // can contain a full length v6 address with [] and a port
82 if (cmd_source == src_command)
84 // if running a client, try to send over network so the client's status report parser will see the report
85 if (cls.state == ca_connected)
87 Cmd_ForwardToServer ();
93 print = SV_ClientPrintf;
101 if (strcmp(Cmd_Argv(1), "1") == 0)
103 else if (strcmp(Cmd_Argv(1), "2") == 0)
107 for (players = 0, i = 0;i < svs.maxclients;i++)
108 if (svs.clients[i].active)
110 print ("host: %s\n", Cvar_VariableString ("hostname"));
111 print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
112 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
113 print ("map: %s\n", sv.name);
114 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
115 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
118 print ("^2IP %%pl ping time frags no name\n");
120 print ("^5IP no name\n");
122 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
129 if (in == 0 || in == 1)
131 seconds = (int)(realtime - client->connecttime);
132 minutes = seconds / 60;
135 seconds -= (minutes * 60);
136 hours = minutes / 60;
138 minutes -= (hours * 60);
144 if (client->netconnection)
145 for (j = 0;j < NETGRAPH_PACKETS;j++)
146 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
148 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
149 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
152 if(sv_status_privacy.integer && cmd_source != src_command)
153 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
155 strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
157 frags = client->frags;
159 if(sv_status_show_qcstatus.integer)
161 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
162 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
168 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
169 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
173 frags = atoi(qcstatus);
177 if (in == 0) // default layout
179 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
181 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
182 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
187 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
188 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
192 else if (in == 1) // extended layout
194 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);
196 else if (in == 2) // reduced layout
198 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
208 Sets client to godmode
211 static void Host_God_f (void)
213 prvm_prog_t *prog = SVVM_prog;
216 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
220 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
221 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
222 SV_ClientPrint("godmode OFF\n");
224 SV_ClientPrint("godmode ON\n");
227 static void Host_Notarget_f (void)
229 prvm_prog_t *prog = SVVM_prog;
232 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
236 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
237 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
238 SV_ClientPrint("notarget OFF\n");
240 SV_ClientPrint("notarget ON\n");
243 qboolean noclip_anglehack;
245 static void Host_Noclip_f (void)
247 prvm_prog_t *prog = SVVM_prog;
250 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
254 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
256 noclip_anglehack = true;
257 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
258 SV_ClientPrint("noclip ON\n");
262 noclip_anglehack = false;
263 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
264 SV_ClientPrint("noclip OFF\n");
272 Sets client to flymode
275 static void Host_Fly_f (void)
277 prvm_prog_t *prog = SVVM_prog;
280 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
284 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
286 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
287 SV_ClientPrint("flymode ON\n");
291 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
292 SV_ClientPrint("flymode OFF\n");
303 void Host_Pings_f (void); // called by Host_Ping_f
304 static void Host_Ping_f (void)
308 void (*print) (const char *fmt, ...);
310 if (cmd_source == src_command)
312 // if running a client, try to send over network so the client's ping report parser will see the report
313 if (cls.state == ca_connected)
315 Cmd_ForwardToServer ();
321 print = SV_ClientPrintf;
326 print("Client ping times:\n");
327 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
331 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
334 // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
335 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
340 ===============================================================================
344 ===============================================================================
348 ======================
353 command from the console. Active clients are kicked off.
354 ======================
356 static void Host_Map_f (void)
358 char level[MAX_QPATH];
362 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
366 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
367 if (gamemode == GAME_DELUXEQUAKE)
368 Cvar_Set("warpmark", "");
370 cls.demonum = -1; // stop demo loop in case this fails
373 Host_ShutdownServer();
375 if(svs.maxclients != svs.maxclients_next)
377 svs.maxclients = svs.maxclients_next;
379 Mem_Free(svs.clients);
380 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
385 if (key_dest == key_menu || key_dest == key_menu_grabbed)
390 svs.serverflags = 0; // haven't completed an episode yet
391 allowcheats = sv_cheats.integer != 0;
392 strlcpy(level, Cmd_Argv(1), sizeof(level));
393 SV_SpawnServer(level);
394 if (sv.active && cls.state == ca_disconnected)
395 CL_EstablishConnection("local:1", -2);
402 Goes to a new map, taking all clients along
405 static void Host_Changelevel_f (void)
407 char level[MAX_QPATH];
411 Con_Print("changelevel <levelname> : continue game on a new level\n");
422 if (key_dest == key_menu || key_dest == key_menu_grabbed)
427 SV_SaveSpawnparms ();
428 allowcheats = sv_cheats.integer != 0;
429 strlcpy(level, Cmd_Argv(1), sizeof(level));
430 SV_SpawnServer(level);
431 if (sv.active && cls.state == ca_disconnected)
432 CL_EstablishConnection("local:1", -2);
439 Restarts the current server for a dead player
442 static void Host_Restart_f (void)
444 char mapname[MAX_QPATH];
448 Con_Print("restart : restart current level\n");
453 Con_Print("Only the server may restart\n");
459 if (key_dest == key_menu || key_dest == key_menu_grabbed)
464 allowcheats = sv_cheats.integer != 0;
465 strlcpy(mapname, sv.name, sizeof(mapname));
466 SV_SpawnServer(mapname);
467 if (sv.active && cls.state == ca_disconnected)
468 CL_EstablishConnection("local:1", -2);
475 This command causes the client to wait for the signon messages again.
476 This is sent just before a server changes levels
479 void Host_Reconnect_f (void)
482 // if not connected, reconnect to the most recent server
485 // if we have connected to a server recently, the userinfo
486 // will still contain its IP address, so get the address...
487 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
489 CL_EstablishConnection(temp, -1);
491 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
494 // if connected, do something based on protocol
495 if (cls.protocol == PROTOCOL_QUAKEWORLD)
497 // quakeworld can just re-login
498 if (cls.qw_downloadmemory) // don't change when downloading
503 if (cls.state == ca_connected && cls.signon < SIGNONS)
505 Con_Printf("reconnecting...\n");
506 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
507 MSG_WriteString(&cls.netcon->message, "new");
512 // netquake uses reconnect on level changes (silly)
515 Con_Print("reconnect : wait for signon messages again\n");
520 Con_Print("reconnect: no signon, ignoring reconnect\n");
523 cls.signon = 0; // need new connection messages
528 =====================
531 User command to connect to server
532 =====================
534 static void Host_Connect_f (void)
538 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
541 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
542 if(rcon_secure.integer <= 0)
543 Cvar_SetQuick(&rcon_password, "");
544 CL_EstablishConnection(Cmd_Argv(1), 2);
549 ===============================================================================
553 ===============================================================================
556 #define SAVEGAME_VERSION 5
558 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
561 int i, k, l, numbuffers, lightstyles = 64;
562 char comment[SAVEGAME_COMMENT_LENGTH+1];
563 char line[MAX_INPUTLINE];
567 // first we have to figure out if this can be saved in 64 lightstyles
568 // (for Quake compatibility)
569 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
570 if (sv.lightstyles[i][0])
573 isserver = prog == SVVM_prog;
575 Con_Printf("Saving game to %s...\n", name);
576 f = FS_OpenRealFile(name, "wb", false);
579 Con_Print("ERROR: couldn't open.\n");
583 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
585 memset(comment, 0, sizeof(comment));
587 dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters));
589 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
590 // convert space to _ to make stdio happy
591 // LordHavoc: convert control characters to _ as well
592 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
593 if (ISWHITESPACEORCONTROL(comment[i]))
595 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
597 FS_Printf(f, "%s\n", comment);
600 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
601 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
602 FS_Printf(f, "%d\n", current_skill);
603 FS_Printf(f, "%s\n", sv.name);
604 FS_Printf(f, "%f\n",sv.time);
608 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
609 FS_Printf(f, "(dummy)\n");
610 FS_Printf(f, "%d\n", 0);
611 FS_Printf(f, "%s\n", "(dummy)");
612 FS_Printf(f, "%f\n", realtime);
615 // write the light styles
616 for (i=0 ; i<lightstyles ; i++)
618 if (isserver && sv.lightstyles[i][0])
619 FS_Printf(f, "%s\n", sv.lightstyles[i]);
624 PRVM_ED_WriteGlobals (prog, f);
625 for (i=0 ; i<prog->num_edicts ; i++)
627 FS_Printf(f,"// edict %d\n", i);
628 //Con_Printf("edict %d...\n", i);
629 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
634 FS_Printf(f,"// DarkPlaces extended savegame\n");
635 // darkplaces extension - extra lightstyles, support for color lightstyles
636 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
637 if (isserver && sv.lightstyles[i][0])
638 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
640 // darkplaces extension - model precaches
641 for (i=1 ; i<MAX_MODELS ; i++)
642 if (sv.model_precache[i][0])
643 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
645 // darkplaces extension - sound precaches
646 for (i=1 ; i<MAX_SOUNDS ; i++)
647 if (sv.sound_precache[i][0])
648 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
650 // darkplaces extension - save buffers
651 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
652 for (i = 0; i < numbuffers; i++)
654 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
655 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
657 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
658 for(k = 0; k < stringbuffer->num_strings; k++)
660 if (!stringbuffer->strings[k])
662 // Parse the string a bit to turn special characters
663 // (like newline, specifically) into escape codes
664 s = stringbuffer->strings[k];
665 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
692 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
700 Con_Print("done.\n");
708 static void Host_Savegame_f (void)
710 prvm_prog_t *prog = SVVM_prog;
711 char name[MAX_QPATH];
712 qboolean deadflag = false;
716 Con_Print("Can't save - no server running.\n");
720 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
724 // singleplayer checks
727 Con_Print("Can't save in intermission.\n");
733 Con_Print("Can't savegame with a dead player\n");
738 Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n");
742 Con_Print("save <savename> : save a game\n");
746 if (strstr(Cmd_Argv(1), ".."))
748 Con_Print("Relative pathnames are not allowed.\n");
752 strlcpy (name, Cmd_Argv(1), sizeof (name));
753 FS_DefaultExtension (name, ".sav", sizeof (name));
755 Host_Savegame_to(prog, name);
765 static void Host_Loadgame_f (void)
767 prvm_prog_t *prog = SVVM_prog;
768 char filename[MAX_QPATH];
769 char mapname[MAX_QPATH];
776 int i, k, numbuffers;
779 float spawn_parms[NUM_SPAWN_PARMS];
780 prvm_stringbuffer_t *stringbuffer;
784 Con_Print("load <savename> : load a game\n");
788 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
789 FS_DefaultExtension (filename, ".sav", sizeof (filename));
791 Con_Printf("Loading game from %s...\n", filename);
793 // stop playing demos
794 if (cls.demoplayback)
799 if (key_dest == key_menu || key_dest == key_menu_grabbed)
804 cls.demonum = -1; // stop demo loop in case this fails
806 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
809 Con_Print("ERROR: couldn't open.\n");
813 if(developer_entityparsing.integer)
814 Con_Printf("Host_Loadgame_f: loading version\n");
817 COM_ParseToken_Simple(&t, false, false, true);
818 version = atoi(com_token);
819 if (version != SAVEGAME_VERSION)
822 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
826 if(developer_entityparsing.integer)
827 Con_Printf("Host_Loadgame_f: loading description\n");
830 COM_ParseToken_Simple(&t, false, false, true);
832 for (i = 0;i < NUM_SPAWN_PARMS;i++)
834 COM_ParseToken_Simple(&t, false, false, true);
835 spawn_parms[i] = atof(com_token);
838 COM_ParseToken_Simple(&t, false, false, true);
839 // this silliness is so we can load 1.06 save files, which have float skill values
840 current_skill = (int)(atof(com_token) + 0.5);
841 Cvar_SetValue ("skill", (float)current_skill);
843 if(developer_entityparsing.integer)
844 Con_Printf("Host_Loadgame_f: loading mapname\n");
847 COM_ParseToken_Simple(&t, false, false, true);
848 strlcpy (mapname, com_token, sizeof(mapname));
850 if(developer_entityparsing.integer)
851 Con_Printf("Host_Loadgame_f: loading time\n");
854 COM_ParseToken_Simple(&t, false, false, true);
855 time = atof(com_token);
857 allowcheats = sv_cheats.integer != 0;
859 if(developer_entityparsing.integer)
860 Con_Printf("Host_Loadgame_f: spawning server\n");
862 SV_SpawnServer (mapname);
866 Con_Print("Couldn't load map\n");
869 sv.paused = true; // pause until all clients connect
872 if(developer_entityparsing.integer)
873 Con_Printf("Host_Loadgame_f: loading light styles\n");
875 // load the light styles
880 for (i = 0;i < MAX_LIGHTSTYLES;i++)
884 COM_ParseToken_Simple(&t, false, false, true);
885 // if this is a 64 lightstyle savegame produced by Quake, stop now
886 // we have to check this because darkplaces may save more than 64
887 if (com_token[0] == '{')
892 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
895 if(developer_entityparsing.integer)
896 Con_Printf("Host_Loadgame_f: skipping until globals\n");
898 // now skip everything before the first opening brace
899 // (this is for forward compatibility, so that older versions (at
900 // least ones with this fix) can load savegames with extra data before the
901 // first brace, as might be produced by a later engine version)
905 if (!COM_ParseToken_Simple(&t, false, false, true))
907 if (com_token[0] == '{')
914 // unlink all entities
915 World_UnlinkAll(&sv.world);
917 // load the edicts out of the savegame file
922 while (COM_ParseToken_Simple(&t, false, false, true))
923 if (!strcmp(com_token, "}"))
925 if (!COM_ParseToken_Simple(&start, false, false, true))
930 if (strcmp(com_token,"{"))
933 Host_Error ("First token isn't a brace");
938 if(developer_entityparsing.integer)
939 Con_Printf("Host_Loadgame_f: loading globals\n");
941 // parse the global vars
942 PRVM_ED_ParseGlobals (prog, start);
944 // restore the autocvar globals
945 Cvar_UpdateAllAutoCvars();
950 if (entnum >= MAX_EDICTS)
953 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
955 while (entnum >= prog->max_edicts)
956 PRVM_MEM_IncreaseEdicts(prog);
957 ent = PRVM_EDICT_NUM(entnum);
958 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
959 ent->priv.server->free = false;
961 if(developer_entityparsing.integer)
962 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
964 PRVM_ED_ParseEdict (prog, start, ent);
966 // link it into the bsp tree
967 if (!ent->priv.server->free)
975 prog->num_edicts = entnum;
978 for (i = 0;i < NUM_SPAWN_PARMS;i++)
979 svs.clients[0].spawn_parms[i] = spawn_parms[i];
981 if(developer_entityparsing.integer)
982 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
984 // read extended data if present
985 // the extended data is stored inside a /* */ comment block, which the
986 // parser intentionally skips, so we have to check for it manually here
989 while (*end == '\r' || *end == '\n')
991 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
993 if(developer_entityparsing.integer)
994 Con_Printf("Host_Loadgame_f: loading extended data\n");
996 Con_Printf("Loading extended DarkPlaces savegame\n");
998 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
999 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
1000 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
1003 while (COM_ParseToken_Simple(&t, false, false, true))
1005 if (!strcmp(com_token, "sv.lightstyles"))
1007 COM_ParseToken_Simple(&t, false, false, true);
1008 i = atoi(com_token);
1009 COM_ParseToken_Simple(&t, false, false, true);
1010 if (i >= 0 && i < MAX_LIGHTSTYLES)
1011 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1013 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1015 else if (!strcmp(com_token, "sv.model_precache"))
1017 COM_ParseToken_Simple(&t, false, false, true);
1018 i = atoi(com_token);
1019 COM_ParseToken_Simple(&t, false, false, true);
1020 if (i >= 0 && i < MAX_MODELS)
1022 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1023 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1026 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1028 else if (!strcmp(com_token, "sv.sound_precache"))
1030 COM_ParseToken_Simple(&t, false, false, true);
1031 i = atoi(com_token);
1032 COM_ParseToken_Simple(&t, false, false, true);
1033 if (i >= 0 && i < MAX_SOUNDS)
1034 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1036 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1038 else if (!strcmp(com_token, "sv.buffer"))
1040 if (COM_ParseToken_Simple(&t, false, false, true))
1042 i = atoi(com_token);
1045 k = STRINGBUFFER_SAVED;
1046 if (COM_ParseToken_Simple(&t, false, false, true))
1047 k |= atoi(com_token);
1048 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1049 Con_Printf("failed to create stringbuffer %i\n", i);
1052 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1055 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1057 else if (!strcmp(com_token, "sv.bufstr"))
1059 if (!COM_ParseToken_Simple(&t, false, false, true))
1060 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1063 i = atoi(com_token);
1064 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1067 if (COM_ParseToken_Simple(&t, false, false, true))
1069 k = atoi(com_token);
1070 if (COM_ParseToken_Simple(&t, false, false, true))
1071 BufStr_Set(prog, stringbuffer, k, com_token);
1073 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1076 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1079 Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1082 // skip any trailing text or unrecognized commands
1083 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1090 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1091 numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1092 for (i = 0; i < numbuffers; i++)
1094 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1095 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1096 BufStr_Del(prog, stringbuffer);
1099 if(developer_entityparsing.integer)
1100 Con_Printf("Host_Loadgame_f: finished\n");
1102 // make sure we're connected to loopback
1103 if (sv.active && cls.state == ca_disconnected)
1104 CL_EstablishConnection("local:1", -2);
1107 //============================================================================
1110 ======================
1112 ======================
1114 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1115 static void Host_Name_f (void)
1117 prvm_prog_t *prog = SVVM_prog;
1119 qboolean valid_colors;
1120 const char *newNameSource;
1121 char newName[sizeof(host_client->name)];
1123 if (Cmd_Argc () == 1)
1125 if (cmd_source == src_command)
1127 Con_Printf("name: %s\n", cl_name.string);
1132 if (Cmd_Argc () == 2)
1133 newNameSource = Cmd_Argv(1);
1135 newNameSource = Cmd_Args();
1137 strlcpy(newName, newNameSource, sizeof(newName));
1139 if (cmd_source == src_command)
1141 Cvar_Set ("_cl_name", newName);
1142 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1144 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1145 Con_Printf("name: %s\n", cl_name.string);
1150 if (realtime < host_client->nametime)
1152 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1156 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1158 // point the string back at updateclient->name to keep it safe
1159 strlcpy (host_client->name, newName, sizeof (host_client->name));
1161 for (i = 0, j = 0;host_client->name[i];i++)
1162 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1163 host_client->name[j++] = host_client->name[i];
1164 host_client->name[j] = 0;
1166 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1167 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1169 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1170 host_client->name[sizeof(host_client->name) - 1] = 0;
1171 host_client->name[0] = STRING_COLOR_TAG;
1172 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1175 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1176 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1179 l = strlen(host_client->name);
1180 if(l < sizeof(host_client->name) - 1)
1182 // duplicate the color tag to escape it
1183 host_client->name[i] = STRING_COLOR_TAG;
1184 host_client->name[i+1] = 0;
1185 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1189 // remove the last character to fix the color code
1190 host_client->name[l-1] = 0;
1191 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1195 // find the last color tag offset and decide if we need to add a reset tag
1196 for (i = 0, j = -1;host_client->name[i];i++)
1198 if (host_client->name[i] == STRING_COLOR_TAG)
1200 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1203 // if this happens to be a reset tag then we don't need one
1204 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1209 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]))
1215 if (host_client->name[i+1] == STRING_COLOR_TAG)
1222 // does not end in the default color string, so add it
1223 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1224 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1226 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1227 if (strcmp(host_client->old_name, host_client->name))
1229 if (host_client->begun)
1230 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1231 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1232 // send notification to all clients
1233 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1234 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1235 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1236 SV_WriteNetnameIntoDemo(host_client);
1241 ======================
1243 ======================
1245 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1246 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1247 static void Host_Playermodel_f (void)
1249 prvm_prog_t *prog = SVVM_prog;
1251 char newPath[sizeof(host_client->playermodel)];
1253 if (Cmd_Argc () == 1)
1255 if (cmd_source == src_command)
1257 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1262 if (Cmd_Argc () == 2)
1263 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1265 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1267 for (i = 0, j = 0;newPath[i];i++)
1268 if (newPath[i] != '\r' && newPath[i] != '\n')
1269 newPath[j++] = newPath[i];
1272 if (cmd_source == src_command)
1274 Cvar_Set ("_cl_playermodel", newPath);
1279 if (realtime < host_client->nametime)
1281 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1285 host_client->nametime = realtime + 5;
1288 // point the string back at updateclient->name to keep it safe
1289 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1290 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1291 if (strcmp(host_client->old_model, host_client->playermodel))
1293 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1294 /*// send notification to all clients
1295 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1296 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1297 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1302 ======================
1304 ======================
1306 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1307 static void Host_Playerskin_f (void)
1309 prvm_prog_t *prog = SVVM_prog;
1311 char newPath[sizeof(host_client->playerskin)];
1313 if (Cmd_Argc () == 1)
1315 if (cmd_source == src_command)
1317 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1322 if (Cmd_Argc () == 2)
1323 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1325 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1327 for (i = 0, j = 0;newPath[i];i++)
1328 if (newPath[i] != '\r' && newPath[i] != '\n')
1329 newPath[j++] = newPath[i];
1332 if (cmd_source == src_command)
1334 Cvar_Set ("_cl_playerskin", newPath);
1339 if (realtime < host_client->nametime)
1341 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1345 host_client->nametime = realtime + 5;
1348 // point the string back at updateclient->name to keep it safe
1349 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1350 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1351 if (strcmp(host_client->old_skin, host_client->playerskin))
1353 //if (host_client->begun)
1354 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1355 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1356 /*// send notification to all clients
1357 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1358 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1359 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1363 static void Host_Version_f (void)
1365 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1368 static void Host_Say(qboolean teamonly)
1370 prvm_prog_t *prog = SVVM_prog;
1375 // LordHavoc: long say messages
1377 qboolean fromServer = false;
1379 if (cmd_source == src_command)
1381 if (cls.state == ca_dedicated)
1388 Cmd_ForwardToServer ();
1393 if (Cmd_Argc () < 2)
1396 if (!teamplay.integer)
1406 // note this uses the chat prefix \001
1407 if (!fromServer && !teamonly)
1408 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1409 else if (!fromServer && teamonly)
1410 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1411 else if(*(sv_adminnick.string))
1412 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1414 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1415 p2 = text + strlen(text);
1416 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1418 if (p2[-1] == '\"' && quoted)
1423 strlcat(text, "\n", sizeof(text));
1425 // note: save is not a valid edict if fromServer is true
1427 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1428 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1429 SV_ClientPrint(text);
1432 if (cls.state == ca_dedicated)
1433 Con_Print(&text[1]);
1437 static void Host_Say_f(void)
1443 static void Host_Say_Team_f(void)
1449 static void Host_Tell_f(void)
1451 const char *playername_start = NULL;
1452 size_t playername_length = 0;
1453 int playernumber = 0;
1456 const char *p1, *p2;
1457 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1458 qboolean fromServer = false;
1460 if (cmd_source == src_command)
1462 if (cls.state == ca_dedicated)
1466 Cmd_ForwardToServer ();
1471 if (Cmd_Argc () < 2)
1474 // note this uses the chat prefix \001
1476 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1477 else if(*(sv_adminnick.string))
1478 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1480 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1483 p2 = p1 + strlen(p1);
1484 // remove the target name
1485 while (p1 < p2 && *p1 == ' ')
1490 while (p1 < p2 && *p1 == ' ')
1492 while (p1 < p2 && isdigit(*p1))
1494 playernumber = playernumber * 10 + (*p1 - '0');
1502 playername_start = p1;
1503 while (p1 < p2 && *p1 != '"')
1505 playername_length = p1 - playername_start;
1511 playername_start = p1;
1512 while (p1 < p2 && *p1 != ' ')
1514 playername_length = p1 - playername_start;
1516 while (p1 < p2 && *p1 == ' ')
1518 if(playername_start)
1520 // set playernumber to the right client
1522 if(playername_length >= sizeof(namebuf))
1525 Con_Print("Host_Tell: too long player name/ID\n");
1527 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1530 memcpy(namebuf, playername_start, playername_length);
1531 namebuf[playername_length] = 0;
1532 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1534 if (!svs.clients[playernumber].active)
1536 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1540 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1543 Con_Print("Host_Tell: invalid player name/ID\n");
1545 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1548 // remove trailing newlines
1549 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1551 // remove quotes if present
1557 else if (fromServer)
1558 Con_Print("Host_Tell: missing end quote\n");
1560 SV_ClientPrint("Host_Tell: missing end quote\n");
1562 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1565 return; // empty say
1566 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1572 host_client = svs.clients + playernumber;
1573 SV_ClientPrint(text);
1583 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1584 static void Host_Color(int changetop, int changebottom)
1586 prvm_prog_t *prog = SVVM_prog;
1587 int top, bottom, playercolor;
1589 // get top and bottom either from the provided values or the current values
1590 // (allows changing only top or bottom, or both at once)
1591 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1592 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1596 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1602 playercolor = top*16 + bottom;
1604 if (cmd_source == src_command)
1606 Cvar_SetValueQuick(&cl_color, playercolor);
1610 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1613 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1615 Con_DPrint("Calling SV_ChangeTeam\n");
1616 prog->globals.fp[OFS_PARM0] = playercolor;
1617 PRVM_serverglobalfloat(time) = sv.time;
1618 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1619 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1623 if (host_client->edict)
1625 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1626 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1628 host_client->colors = playercolor;
1629 if (host_client->old_colors != host_client->colors)
1631 host_client->old_colors = host_client->colors;
1632 // send notification to all clients
1633 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1634 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1635 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1640 static void Host_Color_f(void)
1644 if (Cmd_Argc() == 1)
1646 if (cmd_source == src_command)
1648 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1649 Con_Print("color <0-15> [0-15]\n");
1654 if (Cmd_Argc() == 2)
1655 top = bottom = atoi(Cmd_Argv(1));
1658 top = atoi(Cmd_Argv(1));
1659 bottom = atoi(Cmd_Argv(2));
1661 Host_Color(top, bottom);
1664 static void Host_TopColor_f(void)
1666 if (Cmd_Argc() == 1)
1668 if (cmd_source == src_command)
1670 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1671 Con_Print("topcolor <0-15>\n");
1676 Host_Color(atoi(Cmd_Argv(1)), -1);
1679 static void Host_BottomColor_f(void)
1681 if (Cmd_Argc() == 1)
1683 if (cmd_source == src_command)
1685 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1686 Con_Print("bottomcolor <0-15>\n");
1691 Host_Color(-1, atoi(Cmd_Argv(1)));
1694 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1695 cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
1696 static void Host_Rate_f(void)
1700 if (Cmd_Argc() != 2)
1702 if (cmd_source == src_command)
1704 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1705 Con_Print("rate <bytespersecond>\n");
1710 rate = atoi(Cmd_Argv(1));
1712 if (cmd_source == src_command)
1714 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1718 host_client->rate = rate;
1720 static void Host_Rate_BurstSize_f(void)
1724 if (Cmd_Argc() != 2)
1726 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1727 Con_Print("rate_burstsize <bytes>\n");
1731 rate_burstsize = atoi(Cmd_Argv(1));
1733 if (cmd_source == src_command)
1735 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1739 host_client->rate_burstsize = rate_burstsize;
1747 static void Host_Kill_f (void)
1749 prvm_prog_t *prog = SVVM_prog;
1750 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1752 SV_ClientPrint("Can't suicide -- already dead!\n");
1756 PRVM_serverglobalfloat(time) = sv.time;
1757 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1758 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1767 static void Host_Pause_f (void)
1769 void (*print) (const char *fmt, ...);
1770 if (cmd_source == src_command)
1772 // if running a client, try to send over network so the pause is handled by the server
1773 if (cls.state == ca_connected)
1775 Cmd_ForwardToServer ();
1781 print = SV_ClientPrintf;
1783 if (!pausable.integer)
1785 if (cmd_source == src_client)
1787 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1789 print("Pause not allowed.\n");
1796 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1797 // send notification to all clients
1798 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1799 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1803 ======================
1805 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1806 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1807 ======================
1809 cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"};
1810 static void Host_PModel_f (void)
1812 prvm_prog_t *prog = SVVM_prog;
1815 if (Cmd_Argc () == 1)
1817 if (cmd_source == src_command)
1819 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1823 i = atoi(Cmd_Argv(1));
1825 if (cmd_source == src_command)
1827 if (cl_pmodel.integer == i)
1829 Cvar_SetValue ("_cl_pmodel", i);
1830 if (cls.state == ca_connected)
1831 Cmd_ForwardToServer ();
1835 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1838 //===========================================================================
1846 static void Host_PreSpawn_f (void)
1848 if (host_client->prespawned)
1850 Con_Print("prespawn not valid -- already prespawned\n");
1853 host_client->prespawned = true;
1855 if (host_client->netconnection)
1857 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1858 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1859 MSG_WriteByte (&host_client->netconnection->message, 2);
1860 host_client->sendsignon = 0; // enable unlimited sends again
1863 // reset the name change timer because the client will send name soon
1864 host_client->nametime = 0;
1872 static void Host_Spawn_f (void)
1874 prvm_prog_t *prog = SVVM_prog;
1877 int stats[MAX_CL_STATS];
1879 if (!host_client->prespawned)
1881 Con_Print("Spawn not valid -- not yet prespawned\n");
1884 if (host_client->spawned)
1886 Con_Print("Spawn not valid -- already spawned\n");
1889 host_client->spawned = true;
1891 // reset name change timer again because they might want to change name
1892 // again in the first 5 seconds after connecting
1893 host_client->nametime = 0;
1895 // LordHavoc: moved this above the QC calls at FrikaC's request
1896 // LordHavoc: commented this out
1897 //if (host_client->netconnection)
1898 // SZ_Clear (&host_client->netconnection->message);
1900 // run the entrance script
1903 // loaded games are fully initialized already
1904 if (PRVM_serverfunction(RestoreGame))
1906 Con_DPrint("Calling RestoreGame\n");
1907 PRVM_serverglobalfloat(time) = sv.time;
1908 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1909 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1914 //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name);
1916 // copy spawn parms out of the client_t
1917 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1918 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1920 // call the spawn function
1921 host_client->clientconnectcalled = true;
1922 PRVM_serverglobalfloat(time) = sv.time;
1923 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1924 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1926 if (cls.state == ca_dedicated)
1927 Con_Printf("%s connected\n", host_client->name);
1929 PRVM_serverglobalfloat(time) = sv.time;
1930 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1933 if (!host_client->netconnection)
1936 // send time of update
1937 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1938 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1940 // send all current names, colors, and frag counts
1941 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1943 if (!client->active)
1945 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1946 MSG_WriteByte (&host_client->netconnection->message, i);
1947 MSG_WriteString (&host_client->netconnection->message, client->name);
1948 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1949 MSG_WriteByte (&host_client->netconnection->message, i);
1950 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1951 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1952 MSG_WriteByte (&host_client->netconnection->message, i);
1953 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1956 // send all current light styles
1957 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1959 if (sv.lightstyles[i][0])
1961 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1962 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1963 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1968 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1969 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1970 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1972 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1973 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1974 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1976 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1977 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1978 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1980 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1981 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1982 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1985 // Never send a roll angle, because savegames can catch the server
1986 // in a state where it is expecting the client to correct the angle
1987 // and it won't happen if the game was just loaded, so you wind up
1988 // with a permanent head tilt
1991 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1992 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1993 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1994 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1998 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1999 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
2000 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
2001 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
2004 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
2006 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
2007 MSG_WriteByte (&host_client->netconnection->message, 3);
2015 static void Host_Begin_f (void)
2017 if (!host_client->spawned)
2019 Con_Print("Begin not valid -- not yet spawned\n");
2022 if (host_client->begun)
2024 Con_Print("Begin not valid -- already begun\n");
2027 host_client->begun = true;
2029 // LordHavoc: note: this code also exists in SV_DropClient
2033 for (i = 0;i < svs.maxclients;i++)
2034 if (svs.clients[i].active && !svs.clients[i].spawned)
2036 if (i == svs.maxclients)
2038 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2039 sv.paused = sv.loadgame = false; // we're basically done with loading now
2044 //===========================================================================
2051 Kicks a user off of the server
2054 static void Host_Kick_f (void)
2057 const char *message = NULL;
2060 qboolean byNumber = false;
2067 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2069 i = (int)(atof(Cmd_Argv(2)) - 1);
2070 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2076 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2078 if (!host_client->active)
2080 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2085 if (i < svs.maxclients)
2087 if (cmd_source == src_command)
2089 if (cls.state == ca_dedicated)
2092 who = cl_name.string;
2097 // can't kick yourself!
2098 if (host_client == save)
2103 message = Cmd_Args();
2104 COM_ParseToken_Simple(&message, false, false, true);
2107 message++; // skip the #
2108 while (*message == ' ') // skip white space
2110 message += strlen(Cmd_Argv(2)); // skip the number
2112 while (*message && *message == ' ')
2116 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2118 SV_ClientPrintf("Kicked by %s\n", who);
2119 SV_DropClient (false); // kicked
2126 ===============================================================================
2130 ===============================================================================
2138 static void Host_Give_f (void)
2140 prvm_prog_t *prog = SVVM_prog;
2146 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2151 v = atoi (Cmd_Argv(2));
2165 // MED 01/04/97 added hipnotic give stuff
2166 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2171 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2173 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2175 else if (t[0] == '9')
2176 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2177 else if (t[0] == '0')
2178 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2179 else if (t[0] >= '2')
2180 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2185 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2190 if (gamemode == GAME_ROGUE)
2191 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2193 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2196 if (gamemode == GAME_ROGUE)
2198 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2199 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2200 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2204 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2208 if (gamemode == GAME_ROGUE)
2210 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2211 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2212 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2216 if (gamemode == GAME_ROGUE)
2218 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2219 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2220 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2224 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2228 if (gamemode == GAME_ROGUE)
2230 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2231 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2232 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2236 PRVM_serveredictfloat(host_client->edict, health) = v;
2239 if (gamemode == GAME_ROGUE)
2241 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2242 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2243 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2247 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2251 if (gamemode == GAME_ROGUE)
2253 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2254 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2255 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2261 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2266 for (i=0 ; i<prog->num_edicts ; i++)
2268 e = PRVM_EDICT_NUM(i);
2269 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2272 Con_Print("No viewthing on map\n");
2281 static void Host_Viewmodel_f (void)
2283 prvm_prog_t *prog = SVVM_prog;
2290 e = FindViewthing(prog);
2293 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2294 if (m && m->loaded && m->Draw)
2296 PRVM_serveredictfloat(e, frame) = 0;
2297 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2300 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2309 static void Host_Viewframe_f (void)
2311 prvm_prog_t *prog = SVVM_prog;
2319 e = FindViewthing(prog);
2322 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2324 f = atoi(Cmd_Argv(1));
2325 if (f >= m->numframes)
2328 PRVM_serveredictfloat(e, frame) = f;
2333 static void PrintFrameName (dp_model_t *m, int frame)
2336 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2338 Con_Printf("frame %i\n", frame);
2346 static void Host_Viewnext_f (void)
2348 prvm_prog_t *prog = SVVM_prog;
2355 e = FindViewthing(prog);
2358 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2360 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2361 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2362 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2364 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2373 static void Host_Viewprev_f (void)
2375 prvm_prog_t *prog = SVVM_prog;
2382 e = FindViewthing(prog);
2385 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2387 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2388 if (PRVM_serveredictfloat(e, frame) < 0)
2389 PRVM_serveredictfloat(e, frame) = 0;
2391 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2396 ===============================================================================
2400 ===============================================================================
2409 static void Host_Startdemos_f (void)
2413 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2419 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2422 Con_DPrintf("%i demo(s) in loop\n", c);
2424 for (i=1 ; i<c+1 ; i++)
2425 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2427 // LordHavoc: clear the remaining slots
2428 for (;i <= MAX_DEMOS;i++)
2429 cls.demos[i-1][0] = 0;
2431 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2445 Return to looping demos
2448 static void Host_Demos_f (void)
2450 if (cls.state == ca_dedicated)
2452 if (cls.demonum == -1)
2462 Return to looping demos
2465 static void Host_Stopdemo_f (void)
2467 if (!cls.demoplayback)
2470 Host_ShutdownServer ();
2473 static void Host_SendCvar_f (void)
2477 const char *cvarname;
2483 cvarname = Cmd_Argv(1);
2484 if (cls.state == ca_connected)
2486 c = Cvar_FindVar(cvarname);
2487 // LordHavoc: if there is no such cvar or if it is private, send a
2488 // reply indicating that it has no value
2489 if(!c || (c->flags & CVAR_PRIVATE))
2490 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2492 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2495 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2499 if (cls.state != ca_dedicated)
2503 for(;i<svs.maxclients;i++)
2504 if(svs.clients[i].active && svs.clients[i].netconnection)
2506 host_client = &svs.clients[i];
2507 Host_ClientCommands("sendcvar %s\n", cvarname);
2512 static void MaxPlayers_f(void)
2516 if (Cmd_Argc() != 2)
2518 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2524 Con_Print("maxplayers can not be changed while a server is running.\n");
2525 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2528 n = atoi(Cmd_Argv(1));
2529 n = bound(1, n, MAX_SCOREBOARD);
2530 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2532 svs.maxclients_next = n;
2534 Cvar_Set ("deathmatch", "0");
2536 Cvar_Set ("deathmatch", "1");
2540 =====================
2543 ProQuake rcon support
2544 =====================
2546 static void Host_PQRcon_f (void)
2551 lhnetsocket_t *mysocket;
2552 char peer_address[64];
2554 if (Cmd_Argc() == 1)
2556 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2560 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2562 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2566 e = strchr(rcon_password.string, ' ');
2567 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2571 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2575 if (!rcon_address.string[0])
2577 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2580 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2582 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2583 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2587 unsigned char bufdata[64];
2590 MSG_WriteLong(&buf, 0);
2591 MSG_WriteByte(&buf, CCREQ_RCON);
2592 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2593 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2594 MSG_WriteString(&buf, Cmd_Args());
2595 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2596 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2601 //=============================================================================
2603 // QuakeWorld commands
2606 =====================
2609 Send the rest of the command line over as
2610 an unconnected command.
2611 =====================
2613 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2618 lhnetsocket_t *mysocket;
2621 if (Cmd_Argc() == 1)
2623 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2627 if (!rcon_password.string || !rcon_password.string[0])
2629 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2633 e = strchr(rcon_password.string, ' ');
2634 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2637 to = cls.netcon->peeraddress;
2640 if (!rcon_address.string[0])
2642 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2645 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2647 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2648 if (mysocket && Cmd_Args()[0])
2650 // simply put together the rcon packet and send it
2651 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2653 if(cls.rcon_commands[cls.rcon_ringpos][0])
2656 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2657 Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]);
2658 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2661 for (i = 0;i < MAX_RCONS;i++)
2662 if(cls.rcon_commands[i][0])
2663 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2667 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2668 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2669 cls.rcon_addresses[cls.rcon_ringpos] = to;
2670 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2671 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2673 else if(rcon_secure.integer > 0)
2677 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2678 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2679 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
2682 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2683 NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &to);
2688 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2694 ====================
2697 user <name or userid>
2699 Dump userdata / masterdata for a user
2700 ====================
2702 static void Host_User_f (void) // credit: taken from QuakeWorld
2707 if (Cmd_Argc() != 2)
2709 Con_Printf ("Usage: user <username / userid>\n");
2713 uid = atoi(Cmd_Argv(1));
2715 for (i = 0;i < cl.maxclients;i++)
2717 if (!cl.scores[i].name[0])
2719 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2721 InfoString_Print(cl.scores[i].qw_userinfo);
2725 Con_Printf ("User not in server.\n");
2729 ====================
2732 Dump userids for all current players
2733 ====================
2735 static void Host_Users_f (void) // credit: taken from QuakeWorld
2741 Con_Printf ("userid frags name\n");
2742 Con_Printf ("------ ----- ----\n");
2743 for (i = 0;i < cl.maxclients;i++)
2745 if (cl.scores[i].name[0])
2747 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2752 Con_Printf ("%i total users\n", c);
2757 Host_FullServerinfo_f
2759 Sent by server when serverinfo changes
2762 // TODO: shouldn't this be a cvar instead?
2763 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2766 if (Cmd_Argc() != 2)
2768 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2772 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2773 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2774 cl.qw_teamplay = atoi(temp);
2781 Allow clients to change userinfo
2785 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2792 if (Cmd_Argc() != 2)
2794 Con_Printf ("fullinfo <complete info string>\n");
2804 while (*s && *s != '\\')
2810 Con_Printf ("MISSING VALUE\n");
2816 while (*s && *s != '\\')
2823 CL_SetInfo(key, value, false, false, false, false);
2831 Allow clients to change userinfo
2834 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2836 if (Cmd_Argc() == 1)
2838 InfoString_Print(cls.userinfo);
2841 if (Cmd_Argc() != 3)
2843 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2846 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2850 ====================
2853 packet <destination> <contents>
2855 Contents allows \n escape character
2856 ====================
2858 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2864 lhnetaddress_t address;
2865 lhnetsocket_t *mysocket;
2867 if (Cmd_Argc() != 3)
2869 Con_Printf ("packet <destination> <contents>\n");
2873 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2875 Con_Printf ("Bad address\n");
2881 send[0] = send[1] = send[2] = send[3] = -1;
2883 l = (int)strlen (in);
2884 for (i=0 ; i<l ; i++)
2886 if (out >= send + sizeof(send) - 1)
2888 if (in[i] == '\\' && in[i+1] == 'n')
2893 else if (in[i] == '\\' && in[i+1] == '0')
2898 else if (in[i] == '\\' && in[i+1] == 't')
2903 else if (in[i] == '\\' && in[i+1] == 'r')
2908 else if (in[i] == '\\' && in[i+1] == '"')
2917 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2919 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2921 NetConn_Write(mysocket, send, out - send, &address);
2925 ====================
2928 Send back ping and packet loss update for all current players to this player
2929 ====================
2931 void Host_Pings_f (void)
2933 int i, j, ping, packetloss, movementloss;
2936 if (!host_client->netconnection)
2939 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2941 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2942 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2944 for (i = 0;i < svs.maxclients;i++)
2948 if (svs.clients[i].netconnection)
2950 for (j = 0;j < NETGRAPH_PACKETS;j++)
2951 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2953 for (j = 0;j < NETGRAPH_PACKETS;j++)
2954 if (svs.clients[i].movement_count[j] < 0)
2957 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2958 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2959 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2960 ping = bound(0, ping, 9999);
2961 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2963 // send qw_svc_updateping and qw_svc_updatepl messages
2964 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2965 MSG_WriteShort(&host_client->netconnection->message, ping);
2966 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2967 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2971 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2973 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2975 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2976 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2979 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2980 MSG_WriteString(&host_client->netconnection->message, "\n");
2983 static void Host_PingPLReport_f(void)
2988 if (l > cl.maxclients)
2990 for (i = 0;i < l;i++)
2992 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2993 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2994 if(errbyte && *errbyte == ',')
2995 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2997 cl.scores[i].qw_movementloss = 0;
3001 //=============================================================================
3008 void Host_InitCommands (void)
3010 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
3012 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
3013 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
3014 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
3015 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
3016 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
3017 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
3018 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
3019 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
3020 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
3021 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3022 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
3023 Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)");
3024 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
3025 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
3026 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
3027 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3028 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3029 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3030 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3031 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3032 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3033 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3035 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3036 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3037 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3039 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3040 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3041 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3042 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3044 Cvar_RegisterVariable (&cl_name);
3045 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3046 Cvar_RegisterVariable (&cl_color);
3047 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3048 Cvar_RegisterVariable (&cl_rate);
3049 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3050 Cvar_RegisterVariable (&cl_rate_burstsize);
3051 Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3052 Cvar_RegisterVariable (&cl_pmodel);
3053 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3055 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3056 Cvar_RegisterVariable (&cl_playermodel);
3057 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3058 Cvar_RegisterVariable (&cl_playerskin);
3059 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3061 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3062 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3063 Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)");
3064 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3066 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3068 Cvar_RegisterVariable (&rcon_password);
3069 Cvar_RegisterVariable (&rcon_address);
3070 Cvar_RegisterVariable (&rcon_secure);
3071 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3072 Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP");
3073 Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP");
3074 Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)");
3075 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3076 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3077 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3078 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3079 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3080 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3081 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3082 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3084 Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)");
3085 Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)");
3087 Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)");
3088 Cvar_RegisterVariable (&r_fixtrans_auto);
3090 Cvar_RegisterVariable (&team);
3091 Cvar_RegisterVariable (&skin);
3092 Cvar_RegisterVariable (&noaim);
3094 Cvar_RegisterVariable(&sv_cheats);
3095 Cvar_RegisterVariable(&sv_adminnick);
3096 Cvar_RegisterVariable(&sv_status_privacy);
3097 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3098 Cvar_RegisterVariable(&sv_namechangetimer);
3101 void Host_NoOperation_f(void)