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 = 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 = 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 Con_Printf("name: %s\n", cl_name.string);
1129 if (Cmd_Argc () == 2)
1130 newNameSource = Cmd_Argv(1);
1132 newNameSource = Cmd_Args();
1134 strlcpy(newName, newNameSource, sizeof(newName));
1136 if (cmd_source == src_command)
1138 Cvar_Set ("_cl_name", newName);
1139 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1141 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1142 Con_Printf("name: %s\n", cl_name.string);
1147 if (realtime < host_client->nametime)
1149 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1153 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1155 // point the string back at updateclient->name to keep it safe
1156 strlcpy (host_client->name, newName, sizeof (host_client->name));
1158 for (i = 0, j = 0;host_client->name[i];i++)
1159 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1160 host_client->name[j++] = host_client->name[i];
1161 host_client->name[j] = 0;
1163 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1164 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1166 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1167 host_client->name[sizeof(host_client->name) - 1] = 0;
1168 host_client->name[0] = STRING_COLOR_TAG;
1169 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1172 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1173 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1176 l = strlen(host_client->name);
1177 if(l < sizeof(host_client->name) - 1)
1179 // duplicate the color tag to escape it
1180 host_client->name[i] = STRING_COLOR_TAG;
1181 host_client->name[i+1] = 0;
1182 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1186 // remove the last character to fix the color code
1187 host_client->name[l-1] = 0;
1188 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1192 // find the last color tag offset and decide if we need to add a reset tag
1193 for (i = 0, j = -1;host_client->name[i];i++)
1195 if (host_client->name[i] == STRING_COLOR_TAG)
1197 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1200 // if this happens to be a reset tag then we don't need one
1201 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1206 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]))
1212 if (host_client->name[i+1] == STRING_COLOR_TAG)
1219 // does not end in the default color string, so add it
1220 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1221 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1223 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1224 if (strcmp(host_client->old_name, host_client->name))
1226 if (host_client->begun)
1227 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1228 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1229 // send notification to all clients
1230 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1231 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1232 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1233 SV_WriteNetnameIntoDemo(host_client);
1238 ======================
1240 ======================
1242 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1243 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1244 static void Host_Playermodel_f (void)
1246 prvm_prog_t *prog = SVVM_prog;
1248 char newPath[sizeof(host_client->playermodel)];
1250 if (Cmd_Argc () == 1)
1252 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1256 if (Cmd_Argc () == 2)
1257 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1259 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1261 for (i = 0, j = 0;newPath[i];i++)
1262 if (newPath[i] != '\r' && newPath[i] != '\n')
1263 newPath[j++] = newPath[i];
1266 if (cmd_source == src_command)
1268 Cvar_Set ("_cl_playermodel", newPath);
1273 if (realtime < host_client->nametime)
1275 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1279 host_client->nametime = realtime + 5;
1282 // point the string back at updateclient->name to keep it safe
1283 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1284 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1285 if (strcmp(host_client->old_model, host_client->playermodel))
1287 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1288 /*// send notification to all clients
1289 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1290 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1291 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1296 ======================
1298 ======================
1300 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1301 static void Host_Playerskin_f (void)
1303 prvm_prog_t *prog = SVVM_prog;
1305 char newPath[sizeof(host_client->playerskin)];
1307 if (Cmd_Argc () == 1)
1309 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1313 if (Cmd_Argc () == 2)
1314 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1316 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1318 for (i = 0, j = 0;newPath[i];i++)
1319 if (newPath[i] != '\r' && newPath[i] != '\n')
1320 newPath[j++] = newPath[i];
1323 if (cmd_source == src_command)
1325 Cvar_Set ("_cl_playerskin", newPath);
1330 if (realtime < host_client->nametime)
1332 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1336 host_client->nametime = realtime + 5;
1339 // point the string back at updateclient->name to keep it safe
1340 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1341 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1342 if (strcmp(host_client->old_skin, host_client->playerskin))
1344 //if (host_client->begun)
1345 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1346 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1347 /*// send notification to all clients
1348 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1349 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1350 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1354 static void Host_Version_f (void)
1356 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1359 static void Host_Say(qboolean teamonly)
1361 prvm_prog_t *prog = SVVM_prog;
1366 // LordHavoc: long say messages
1368 qboolean fromServer = false;
1370 if (cmd_source == src_command)
1372 if (cls.state == ca_dedicated)
1379 Cmd_ForwardToServer ();
1384 if (Cmd_Argc () < 2)
1387 if (!teamplay.integer)
1397 // note this uses the chat prefix \001
1398 if (!fromServer && !teamonly)
1399 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1400 else if (!fromServer && teamonly)
1401 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1402 else if(*(sv_adminnick.string))
1403 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1405 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1406 p2 = text + strlen(text);
1407 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1409 if (p2[-1] == '\"' && quoted)
1414 strlcat(text, "\n", sizeof(text));
1416 // note: save is not a valid edict if fromServer is true
1418 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1419 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1420 SV_ClientPrint(text);
1423 if (cls.state == ca_dedicated)
1424 Con_Print(&text[1]);
1428 static void Host_Say_f(void)
1434 static void Host_Say_Team_f(void)
1440 static void Host_Tell_f(void)
1442 const char *playername_start = NULL;
1443 size_t playername_length = 0;
1444 int playernumber = 0;
1447 const char *p1, *p2;
1448 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1449 qboolean fromServer = false;
1451 if (cmd_source == src_command)
1453 if (cls.state == ca_dedicated)
1457 Cmd_ForwardToServer ();
1462 if (Cmd_Argc () < 2)
1465 // note this uses the chat prefix \001
1467 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1468 else if(*(sv_adminnick.string))
1469 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1471 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1474 p2 = p1 + strlen(p1);
1475 // remove the target name
1476 while (p1 < p2 && *p1 == ' ')
1481 while (p1 < p2 && *p1 == ' ')
1483 while (p1 < p2 && isdigit(*p1))
1485 playernumber = playernumber * 10 + (*p1 - '0');
1493 playername_start = p1;
1494 while (p1 < p2 && *p1 != '"')
1496 playername_length = p1 - playername_start;
1502 playername_start = p1;
1503 while (p1 < p2 && *p1 != ' ')
1505 playername_length = p1 - playername_start;
1507 while (p1 < p2 && *p1 == ' ')
1509 if(playername_start)
1511 // set playernumber to the right client
1513 if(playername_length >= sizeof(namebuf))
1516 Con_Print("Host_Tell: too long player name/ID\n");
1518 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1521 memcpy(namebuf, playername_start, playername_length);
1522 namebuf[playername_length] = 0;
1523 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1525 if (!svs.clients[playernumber].active)
1527 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1531 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1534 Con_Print("Host_Tell: invalid player name/ID\n");
1536 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1539 // remove trailing newlines
1540 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1542 // remove quotes if present
1548 else if (fromServer)
1549 Con_Print("Host_Tell: missing end quote\n");
1551 SV_ClientPrint("Host_Tell: missing end quote\n");
1553 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1556 return; // empty say
1557 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1563 host_client = svs.clients + playernumber;
1564 SV_ClientPrint(text);
1574 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1575 static void Host_Color(int changetop, int changebottom)
1577 prvm_prog_t *prog = SVVM_prog;
1578 int top, bottom, playercolor;
1580 // get top and bottom either from the provided values or the current values
1581 // (allows changing only top or bottom, or both at once)
1582 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1583 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1587 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1593 playercolor = top*16 + bottom;
1595 if (cmd_source == src_command)
1597 Cvar_SetValueQuick(&cl_color, playercolor);
1601 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1604 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1606 Con_DPrint("Calling SV_ChangeTeam\n");
1607 prog->globals.fp[OFS_PARM0] = playercolor;
1608 PRVM_serverglobalfloat(time) = sv.time;
1609 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1610 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1614 if (host_client->edict)
1616 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1617 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1619 host_client->colors = playercolor;
1620 if (host_client->old_colors != host_client->colors)
1622 host_client->old_colors = host_client->colors;
1623 // send notification to all clients
1624 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1625 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1626 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1631 static void Host_Color_f(void)
1635 if (Cmd_Argc() == 1)
1637 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1638 Con_Print("color <0-15> [0-15]\n");
1642 if (Cmd_Argc() == 2)
1643 top = bottom = atoi(Cmd_Argv(1));
1646 top = atoi(Cmd_Argv(1));
1647 bottom = atoi(Cmd_Argv(2));
1649 Host_Color(top, bottom);
1652 static void Host_TopColor_f(void)
1654 if (Cmd_Argc() == 1)
1656 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1657 Con_Print("topcolor <0-15>\n");
1661 Host_Color(atoi(Cmd_Argv(1)), -1);
1664 static void Host_BottomColor_f(void)
1666 if (Cmd_Argc() == 1)
1668 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1669 Con_Print("bottomcolor <0-15>\n");
1673 Host_Color(-1, atoi(Cmd_Argv(1)));
1676 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1677 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)"};
1678 static void Host_Rate_f(void)
1682 if (Cmd_Argc() != 2)
1684 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1685 Con_Print("rate <bytespersecond>\n");
1689 rate = atoi(Cmd_Argv(1));
1691 if (cmd_source == src_command)
1693 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1697 host_client->rate = rate;
1699 static void Host_Rate_BurstSize_f(void)
1703 if (Cmd_Argc() != 2)
1705 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1706 Con_Print("rate_burstsize <bytes>\n");
1710 rate_burstsize = atoi(Cmd_Argv(1));
1712 if (cmd_source == src_command)
1714 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1718 host_client->rate_burstsize = rate_burstsize;
1726 static void Host_Kill_f (void)
1728 prvm_prog_t *prog = SVVM_prog;
1729 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1731 SV_ClientPrint("Can't suicide -- already dead!\n");
1735 PRVM_serverglobalfloat(time) = sv.time;
1736 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1737 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1746 static void Host_Pause_f (void)
1748 void (*print) (const char *fmt, ...);
1749 if (cmd_source == src_command)
1751 // if running a client, try to send over network so the pause is handled by the server
1752 if (cls.state == ca_connected)
1754 Cmd_ForwardToServer ();
1760 print = SV_ClientPrintf;
1762 if (!pausable.integer)
1764 if (cmd_source == src_client)
1766 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1768 print("Pause not allowed.\n");
1775 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1776 // send notification to all clients
1777 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1778 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1782 ======================
1784 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1785 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1786 ======================
1788 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)"};
1789 static void Host_PModel_f (void)
1791 prvm_prog_t *prog = SVVM_prog;
1794 if (Cmd_Argc () == 1)
1796 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1799 i = atoi(Cmd_Argv(1));
1801 if (cmd_source == src_command)
1803 if (cl_pmodel.integer == i)
1805 Cvar_SetValue ("_cl_pmodel", i);
1806 if (cls.state == ca_connected)
1807 Cmd_ForwardToServer ();
1811 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1814 //===========================================================================
1822 static void Host_PreSpawn_f (void)
1824 if (host_client->prespawned)
1826 Con_Print("prespawn not valid -- already prespawned\n");
1829 host_client->prespawned = true;
1831 if (host_client->netconnection)
1833 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1834 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1835 MSG_WriteByte (&host_client->netconnection->message, 2);
1836 host_client->sendsignon = 0; // enable unlimited sends again
1839 // reset the name change timer because the client will send name soon
1840 host_client->nametime = 0;
1848 static void Host_Spawn_f (void)
1850 prvm_prog_t *prog = SVVM_prog;
1853 int stats[MAX_CL_STATS];
1855 if (!host_client->prespawned)
1857 Con_Print("Spawn not valid -- not yet prespawned\n");
1860 if (host_client->spawned)
1862 Con_Print("Spawn not valid -- already spawned\n");
1865 host_client->spawned = true;
1867 // reset name change timer again because they might want to change name
1868 // again in the first 5 seconds after connecting
1869 host_client->nametime = 0;
1871 // LordHavoc: moved this above the QC calls at FrikaC's request
1872 // LordHavoc: commented this out
1873 //if (host_client->netconnection)
1874 // SZ_Clear (&host_client->netconnection->message);
1876 // run the entrance script
1879 // loaded games are fully initialized already
1880 if (PRVM_serverfunction(RestoreGame))
1882 Con_DPrint("Calling RestoreGame\n");
1883 PRVM_serverglobalfloat(time) = sv.time;
1884 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1885 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1890 //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);
1892 // copy spawn parms out of the client_t
1893 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1894 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1896 // call the spawn function
1897 host_client->clientconnectcalled = true;
1898 PRVM_serverglobalfloat(time) = sv.time;
1899 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1900 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1902 if (cls.state == ca_dedicated)
1903 Con_Printf("%s connected\n", host_client->name);
1905 PRVM_serverglobalfloat(time) = sv.time;
1906 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1909 if (!host_client->netconnection)
1912 // send time of update
1913 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1914 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1916 // send all current names, colors, and frag counts
1917 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1919 if (!client->active)
1921 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1922 MSG_WriteByte (&host_client->netconnection->message, i);
1923 MSG_WriteString (&host_client->netconnection->message, client->name);
1924 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1925 MSG_WriteByte (&host_client->netconnection->message, i);
1926 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1927 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1928 MSG_WriteByte (&host_client->netconnection->message, i);
1929 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1932 // send all current light styles
1933 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1935 if (sv.lightstyles[i][0])
1937 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1938 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1939 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1944 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1945 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1946 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1948 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1949 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1950 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1952 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1953 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1954 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1956 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1957 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1958 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1961 // Never send a roll angle, because savegames can catch the server
1962 // in a state where it is expecting the client to correct the angle
1963 // and it won't happen if the game was just loaded, so you wind up
1964 // with a permanent head tilt
1967 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1968 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1969 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1970 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1974 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1975 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1976 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1977 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1980 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1982 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1983 MSG_WriteByte (&host_client->netconnection->message, 3);
1991 static void Host_Begin_f (void)
1993 if (!host_client->spawned)
1995 Con_Print("Begin not valid -- not yet spawned\n");
1998 if (host_client->begun)
2000 Con_Print("Begin not valid -- already begun\n");
2003 host_client->begun = true;
2005 // LordHavoc: note: this code also exists in SV_DropClient
2009 for (i = 0;i < svs.maxclients;i++)
2010 if (svs.clients[i].active && !svs.clients[i].spawned)
2012 if (i == svs.maxclients)
2014 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2015 sv.paused = sv.loadgame = false; // we're basically done with loading now
2020 //===========================================================================
2027 Kicks a user off of the server
2030 static void Host_Kick_f (void)
2033 const char *message = NULL;
2036 qboolean byNumber = false;
2043 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2045 i = (int)(atof(Cmd_Argv(2)) - 1);
2046 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2052 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2054 if (!host_client->active)
2056 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2061 if (i < svs.maxclients)
2063 if (cmd_source == src_command)
2065 if (cls.state == ca_dedicated)
2068 who = cl_name.string;
2073 // can't kick yourself!
2074 if (host_client == save)
2079 message = Cmd_Args();
2080 COM_ParseToken_Simple(&message, false, false, true);
2083 message++; // skip the #
2084 while (*message == ' ') // skip white space
2086 message += strlen(Cmd_Argv(2)); // skip the number
2088 while (*message && *message == ' ')
2092 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2094 SV_ClientPrintf("Kicked by %s\n", who);
2095 SV_DropClient (false); // kicked
2102 ===============================================================================
2106 ===============================================================================
2114 static void Host_Give_f (void)
2116 prvm_prog_t *prog = SVVM_prog;
2122 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2127 v = atoi (Cmd_Argv(2));
2141 // MED 01/04/97 added hipnotic give stuff
2142 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2147 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2149 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2151 else if (t[0] == '9')
2152 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2153 else if (t[0] == '0')
2154 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2155 else if (t[0] >= '2')
2156 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2161 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2166 if (gamemode == GAME_ROGUE)
2167 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2169 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2172 if (gamemode == GAME_ROGUE)
2174 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2175 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2176 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2180 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2184 if (gamemode == GAME_ROGUE)
2186 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2187 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2188 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2192 if (gamemode == GAME_ROGUE)
2194 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2195 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2196 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2200 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2204 if (gamemode == GAME_ROGUE)
2206 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2207 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2208 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2212 PRVM_serveredictfloat(host_client->edict, health) = v;
2215 if (gamemode == GAME_ROGUE)
2217 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2218 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2219 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2223 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2227 if (gamemode == GAME_ROGUE)
2229 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2230 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2231 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2237 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2242 for (i=0 ; i<prog->num_edicts ; i++)
2244 e = PRVM_EDICT_NUM(i);
2245 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2248 Con_Print("No viewthing on map\n");
2257 static void Host_Viewmodel_f (void)
2259 prvm_prog_t *prog = SVVM_prog;
2266 e = FindViewthing(prog);
2269 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2270 if (m && m->loaded && m->Draw)
2272 PRVM_serveredictfloat(e, frame) = 0;
2273 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2276 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2285 static void Host_Viewframe_f (void)
2287 prvm_prog_t *prog = SVVM_prog;
2295 e = FindViewthing(prog);
2298 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2300 f = atoi(Cmd_Argv(1));
2301 if (f >= m->numframes)
2304 PRVM_serveredictfloat(e, frame) = f;
2309 static void PrintFrameName (dp_model_t *m, int frame)
2312 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2314 Con_Printf("frame %i\n", frame);
2322 static void Host_Viewnext_f (void)
2324 prvm_prog_t *prog = SVVM_prog;
2331 e = FindViewthing(prog);
2334 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2336 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2337 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2338 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2340 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2349 static void Host_Viewprev_f (void)
2351 prvm_prog_t *prog = SVVM_prog;
2358 e = FindViewthing(prog);
2361 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2363 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2364 if (PRVM_serveredictfloat(e, frame) < 0)
2365 PRVM_serveredictfloat(e, frame) = 0;
2367 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2372 ===============================================================================
2376 ===============================================================================
2385 static void Host_Startdemos_f (void)
2389 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2395 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2398 Con_DPrintf("%i demo(s) in loop\n", c);
2400 for (i=1 ; i<c+1 ; i++)
2401 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2403 // LordHavoc: clear the remaining slots
2404 for (;i <= MAX_DEMOS;i++)
2405 cls.demos[i-1][0] = 0;
2407 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2421 Return to looping demos
2424 static void Host_Demos_f (void)
2426 if (cls.state == ca_dedicated)
2428 if (cls.demonum == -1)
2438 Return to looping demos
2441 static void Host_Stopdemo_f (void)
2443 if (!cls.demoplayback)
2446 Host_ShutdownServer ();
2449 static void Host_SendCvar_f (void)
2453 const char *cvarname;
2459 cvarname = Cmd_Argv(1);
2460 if (cls.state == ca_connected)
2462 c = Cvar_FindVar(cvarname);
2463 // LordHavoc: if there is no such cvar or if it is private, send a
2464 // reply indicating that it has no value
2465 if(!c || (c->flags & CVAR_PRIVATE))
2466 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2468 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2471 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2475 if (cls.state != ca_dedicated)
2479 for(;i<svs.maxclients;i++)
2480 if(svs.clients[i].active && svs.clients[i].netconnection)
2482 host_client = &svs.clients[i];
2483 Host_ClientCommands("sendcvar %s\n", cvarname);
2488 static void MaxPlayers_f(void)
2492 if (Cmd_Argc() != 2)
2494 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2500 Con_Print("maxplayers can not be changed while a server is running.\n");
2501 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2504 n = atoi(Cmd_Argv(1));
2505 n = bound(1, n, MAX_SCOREBOARD);
2506 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2508 svs.maxclients_next = n;
2510 Cvar_Set ("deathmatch", "0");
2512 Cvar_Set ("deathmatch", "1");
2516 =====================
2519 ProQuake rcon support
2520 =====================
2522 static void Host_PQRcon_f (void)
2527 lhnetsocket_t *mysocket;
2528 char peer_address[64];
2530 if (Cmd_Argc() == 1)
2532 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2536 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2538 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2542 e = strchr(rcon_password.string, ' ');
2543 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2547 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2551 if (!rcon_address.string[0])
2553 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2556 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2558 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2559 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2563 unsigned char bufdata[64];
2566 MSG_WriteLong(&buf, 0);
2567 MSG_WriteByte(&buf, CCREQ_RCON);
2568 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2569 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2570 MSG_WriteString(&buf, Cmd_Args());
2571 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2572 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2577 //=============================================================================
2579 // QuakeWorld commands
2582 =====================
2585 Send the rest of the command line over as
2586 an unconnected command.
2587 =====================
2589 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2594 lhnetsocket_t *mysocket;
2597 if (Cmd_Argc() == 1)
2599 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2603 if (!rcon_password.string || !rcon_password.string[0])
2605 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2609 e = strchr(rcon_password.string, ' ');
2610 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2613 to = cls.netcon->peeraddress;
2616 if (!rcon_address.string[0])
2618 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2621 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2623 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2624 if (mysocket && Cmd_Args()[0])
2626 // simply put together the rcon packet and send it
2627 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2629 if(cls.rcon_commands[cls.rcon_ringpos][0])
2632 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2633 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]);
2634 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2637 for (i = 0;i < MAX_RCONS;i++)
2638 if(cls.rcon_commands[i][0])
2639 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2643 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2644 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2645 cls.rcon_addresses[cls.rcon_ringpos] = to;
2646 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2647 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2649 else if(rcon_secure.integer > 0)
2653 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2654 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2655 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2658 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2659 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2664 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2670 ====================
2673 user <name or userid>
2675 Dump userdata / masterdata for a user
2676 ====================
2678 static void Host_User_f (void) // credit: taken from QuakeWorld
2683 if (Cmd_Argc() != 2)
2685 Con_Printf ("Usage: user <username / userid>\n");
2689 uid = atoi(Cmd_Argv(1));
2691 for (i = 0;i < cl.maxclients;i++)
2693 if (!cl.scores[i].name[0])
2695 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2697 InfoString_Print(cl.scores[i].qw_userinfo);
2701 Con_Printf ("User not in server.\n");
2705 ====================
2708 Dump userids for all current players
2709 ====================
2711 static void Host_Users_f (void) // credit: taken from QuakeWorld
2717 Con_Printf ("userid frags name\n");
2718 Con_Printf ("------ ----- ----\n");
2719 for (i = 0;i < cl.maxclients;i++)
2721 if (cl.scores[i].name[0])
2723 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2728 Con_Printf ("%i total users\n", c);
2733 Host_FullServerinfo_f
2735 Sent by server when serverinfo changes
2738 // TODO: shouldn't this be a cvar instead?
2739 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2742 if (Cmd_Argc() != 2)
2744 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2748 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2749 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2750 cl.qw_teamplay = atoi(temp);
2757 Allow clients to change userinfo
2761 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2768 if (Cmd_Argc() != 2)
2770 Con_Printf ("fullinfo <complete info string>\n");
2780 while (*s && *s != '\\')
2786 Con_Printf ("MISSING VALUE\n");
2792 while (*s && *s != '\\')
2799 CL_SetInfo(key, value, false, false, false, false);
2807 Allow clients to change userinfo
2810 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2812 if (Cmd_Argc() == 1)
2814 InfoString_Print(cls.userinfo);
2817 if (Cmd_Argc() != 3)
2819 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2822 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2826 ====================
2829 packet <destination> <contents>
2831 Contents allows \n escape character
2832 ====================
2834 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2840 lhnetaddress_t address;
2841 lhnetsocket_t *mysocket;
2843 if (Cmd_Argc() != 3)
2845 Con_Printf ("packet <destination> <contents>\n");
2849 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2851 Con_Printf ("Bad address\n");
2857 send[0] = send[1] = send[2] = send[3] = -1;
2859 l = (int)strlen (in);
2860 for (i=0 ; i<l ; i++)
2862 if (out >= send + sizeof(send) - 1)
2864 if (in[i] == '\\' && in[i+1] == 'n')
2869 else if (in[i] == '\\' && in[i+1] == '0')
2874 else if (in[i] == '\\' && in[i+1] == 't')
2879 else if (in[i] == '\\' && in[i+1] == 'r')
2884 else if (in[i] == '\\' && in[i+1] == '"')
2893 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2895 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2897 NetConn_Write(mysocket, send, out - send, &address);
2901 ====================
2904 Send back ping and packet loss update for all current players to this player
2905 ====================
2907 void Host_Pings_f (void)
2909 int i, j, ping, packetloss, movementloss;
2912 if (!host_client->netconnection)
2915 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2917 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2918 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2920 for (i = 0;i < svs.maxclients;i++)
2924 if (svs.clients[i].netconnection)
2926 for (j = 0;j < NETGRAPH_PACKETS;j++)
2927 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2929 for (j = 0;j < NETGRAPH_PACKETS;j++)
2930 if (svs.clients[i].movement_count[j] < 0)
2933 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2934 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2935 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2936 ping = bound(0, ping, 9999);
2937 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2939 // send qw_svc_updateping and qw_svc_updatepl messages
2940 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2941 MSG_WriteShort(&host_client->netconnection->message, ping);
2942 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2943 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2947 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2949 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2951 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2952 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2955 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2956 MSG_WriteString(&host_client->netconnection->message, "\n");
2959 static void Host_PingPLReport_f(void)
2964 if (l > cl.maxclients)
2966 for (i = 0;i < l;i++)
2968 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2969 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2970 if(errbyte && *errbyte == ',')
2971 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2973 cl.scores[i].qw_movementloss = 0;
2977 //=============================================================================
2984 void Host_InitCommands (void)
2986 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2988 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2989 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2990 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2991 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2992 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2993 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2994 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2995 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2996 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2997 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2998 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2999 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)");
3000 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
3001 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
3002 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
3003 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3004 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3005 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3006 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3007 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3008 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3009 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3011 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3012 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3013 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3015 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3016 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3017 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3018 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3020 Cvar_RegisterVariable (&cl_name);
3021 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3022 Cvar_RegisterVariable (&cl_color);
3023 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3024 Cvar_RegisterVariable (&cl_rate);
3025 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3026 Cvar_RegisterVariable (&cl_rate_burstsize);
3027 Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3028 Cvar_RegisterVariable (&cl_pmodel);
3029 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3031 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3032 Cvar_RegisterVariable (&cl_playermodel);
3033 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3034 Cvar_RegisterVariable (&cl_playerskin);
3035 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3037 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3038 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3039 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)");
3040 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3042 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3044 Cvar_RegisterVariable (&rcon_password);
3045 Cvar_RegisterVariable (&rcon_address);
3046 Cvar_RegisterVariable (&rcon_secure);
3047 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3048 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");
3049 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");
3050 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)");
3051 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3052 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3053 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3054 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3055 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3056 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3057 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3058 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3060 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)");
3061 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)");
3063 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)");
3064 Cvar_RegisterVariable (&r_fixtrans_auto);
3066 Cvar_RegisterVariable (&team);
3067 Cvar_RegisterVariable (&skin);
3068 Cvar_RegisterVariable (&noaim);
3070 Cvar_RegisterVariable(&sv_cheats);
3071 Cvar_RegisterVariable(&sv_adminnick);
3072 Cvar_RegisterVariable(&sv_status_privacy);
3073 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3074 Cvar_RegisterVariable(&sv_namechangetimer);
3077 void Host_NoOperation_f(void)