2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 // for secure rcon authentication
33 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
34 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
35 cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
36 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."};
37 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"};
38 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"};
39 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"};
40 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"};
41 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 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)"};
46 qboolean allowcheats = false;
48 extern qboolean host_shuttingdown;
49 extern cvar_t developer_entityparsing;
57 void Host_Quit_f (void)
60 Con_Printf("shutting down already!\n");
70 static void Host_Status_f (void)
72 prvm_prog_t *prog = SVVM_prog;
75 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
76 void (*print) (const char *fmt, ...);
77 char ip[48]; // can contain a full length v6 address with [] and a port
81 if (cmd_source == src_command)
83 // if running a client, try to send over network so the client's status report parser will see the report
84 if (cls.state == ca_connected)
86 Cmd_ForwardToServer ();
92 print = SV_ClientPrintf;
100 if (strcmp(Cmd_Argv(1), "1") == 0)
102 else if (strcmp(Cmd_Argv(1), "2") == 0)
106 for (players = 0, i = 0;i < svs.maxclients;i++)
107 if (svs.clients[i].active)
109 print ("host: %s\n", Cvar_VariableString ("hostname"));
110 print ("version: %s build %s\n", gamename, buildstring);
111 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
112 print ("map: %s\n", sv.name);
113 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
114 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
117 print ("^2IP %%pl ping time frags no name\n");
119 print ("^5IP no name\n");
121 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
128 if (in == 0 || in == 1)
130 seconds = (int)(realtime - client->connecttime);
131 minutes = seconds / 60;
134 seconds -= (minutes * 60);
135 hours = minutes / 60;
137 minutes -= (hours * 60);
143 if (client->netconnection)
144 for (j = 0;j < NETGRAPH_PACKETS;j++)
145 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
147 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
148 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
151 if(sv_status_privacy.integer && cmd_source != src_command)
152 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
154 strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
156 frags = client->frags;
158 if(sv_status_show_qcstatus.integer)
160 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
161 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
167 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
168 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
172 frags = atoi(qcstatus);
176 if (in == 0) // default layout
178 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
180 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
181 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
186 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
187 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
191 else if (in == 1) // extended layout
193 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);
195 else if (in == 2) // reduced layout
197 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
207 Sets client to godmode
210 static void Host_God_f (void)
212 prvm_prog_t *prog = SVVM_prog;
215 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
219 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
220 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
221 SV_ClientPrint("godmode OFF\n");
223 SV_ClientPrint("godmode ON\n");
226 static void Host_Notarget_f (void)
228 prvm_prog_t *prog = SVVM_prog;
231 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
235 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
236 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
237 SV_ClientPrint("notarget OFF\n");
239 SV_ClientPrint("notarget ON\n");
242 qboolean noclip_anglehack;
244 static void Host_Noclip_f (void)
246 prvm_prog_t *prog = SVVM_prog;
249 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
253 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
255 noclip_anglehack = true;
256 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
257 SV_ClientPrint("noclip ON\n");
261 noclip_anglehack = false;
262 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
263 SV_ClientPrint("noclip OFF\n");
271 Sets client to flymode
274 static void Host_Fly_f (void)
276 prvm_prog_t *prog = SVVM_prog;
279 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
283 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
285 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
286 SV_ClientPrint("flymode ON\n");
290 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
291 SV_ClientPrint("flymode OFF\n");
302 void Host_Pings_f (void); // called by Host_Ping_f
303 static void Host_Ping_f (void)
307 void (*print) (const char *fmt, ...);
309 if (cmd_source == src_command)
311 // if running a client, try to send over network so the client's ping report parser will see the report
312 if (cls.state == ca_connected)
314 Cmd_ForwardToServer ();
320 print = SV_ClientPrintf;
325 print("Client ping times:\n");
326 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
330 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
333 // 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)
334 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
339 ===============================================================================
343 ===============================================================================
347 ======================
352 command from the console. Active clients are kicked off.
353 ======================
355 static void Host_Map_f (void)
357 char level[MAX_QPATH];
361 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
365 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
366 if (gamemode == GAME_DELUXEQUAKE)
367 Cvar_Set("warpmark", "");
369 cls.demonum = -1; // stop demo loop in case this fails
372 Host_ShutdownServer();
374 if(svs.maxclients != svs.maxclients_next)
376 svs.maxclients = svs.maxclients_next;
378 Mem_Free(svs.clients);
379 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
383 if (key_dest == key_menu || key_dest == key_menu_grabbed)
387 svs.serverflags = 0; // haven't completed an episode yet
388 allowcheats = sv_cheats.integer != 0;
389 strlcpy(level, Cmd_Argv(1), sizeof(level));
390 SV_SpawnServer(level);
391 if (sv.active && cls.state == ca_disconnected)
392 CL_EstablishConnection("local:1", -2);
399 Goes to a new map, taking all clients along
402 static void Host_Changelevel_f (void)
404 char level[MAX_QPATH];
408 Con_Print("changelevel <levelname> : continue game on a new level\n");
418 if (key_dest == key_menu || key_dest == key_menu_grabbed)
422 SV_SaveSpawnparms ();
423 allowcheats = sv_cheats.integer != 0;
424 strlcpy(level, Cmd_Argv(1), sizeof(level));
425 SV_SpawnServer(level);
426 if (sv.active && cls.state == ca_disconnected)
427 CL_EstablishConnection("local:1", -2);
434 Restarts the current server for a dead player
437 static void Host_Restart_f (void)
439 char mapname[MAX_QPATH];
443 Con_Print("restart : restart current level\n");
448 Con_Print("Only the server may restart\n");
453 if (key_dest == key_menu || key_dest == key_menu_grabbed)
457 allowcheats = sv_cheats.integer != 0;
458 strlcpy(mapname, sv.name, sizeof(mapname));
459 SV_SpawnServer(mapname);
460 if (sv.active && cls.state == ca_disconnected)
461 CL_EstablishConnection("local:1", -2);
468 This command causes the client to wait for the signon messages again.
469 This is sent just before a server changes levels
472 void Host_Reconnect_f (void)
475 // if not connected, reconnect to the most recent server
478 // if we have connected to a server recently, the userinfo
479 // will still contain its IP address, so get the address...
480 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
482 CL_EstablishConnection(temp, -1);
484 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
487 // if connected, do something based on protocol
488 if (cls.protocol == PROTOCOL_QUAKEWORLD)
490 // quakeworld can just re-login
491 if (cls.qw_downloadmemory) // don't change when downloading
496 if (cls.state == ca_connected && cls.signon < SIGNONS)
498 Con_Printf("reconnecting...\n");
499 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
500 MSG_WriteString(&cls.netcon->message, "new");
505 // netquake uses reconnect on level changes (silly)
508 Con_Print("reconnect : wait for signon messages again\n");
513 Con_Print("reconnect: no signon, ignoring reconnect\n");
516 cls.signon = 0; // need new connection messages
521 =====================
524 User command to connect to server
525 =====================
527 static void Host_Connect_f (void)
531 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
534 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
535 if(rcon_secure.integer <= 0)
536 Cvar_SetQuick(&rcon_password, "");
537 CL_EstablishConnection(Cmd_Argv(1), 2);
542 ===============================================================================
546 ===============================================================================
549 #define SAVEGAME_VERSION 5
551 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
554 int i, k, l, numbuffers, lightstyles = 64;
555 char comment[SAVEGAME_COMMENT_LENGTH+1];
556 char line[MAX_INPUTLINE];
560 // first we have to figure out if this can be saved in 64 lightstyles
561 // (for Quake compatibility)
562 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
563 if (sv.lightstyles[i][0])
566 isserver = prog == SVVM_prog;
568 Con_Printf("Saving game to %s...\n", name);
569 f = FS_OpenRealFile(name, "wb", false);
572 Con_Print("ERROR: couldn't open.\n");
576 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
578 memset(comment, 0, sizeof(comment));
580 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));
582 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
583 // convert space to _ to make stdio happy
584 // LordHavoc: convert control characters to _ as well
585 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
586 if (ISWHITESPACEORCONTROL(comment[i]))
588 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
590 FS_Printf(f, "%s\n", comment);
593 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
594 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
595 FS_Printf(f, "%d\n", current_skill);
596 FS_Printf(f, "%s\n", sv.name);
597 FS_Printf(f, "%f\n",sv.time);
601 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
602 FS_Printf(f, "(dummy)\n");
603 FS_Printf(f, "%d\n", 0);
604 FS_Printf(f, "%s\n", "(dummy)");
605 FS_Printf(f, "%f\n", realtime);
608 // write the light styles
609 for (i=0 ; i<lightstyles ; i++)
611 if (isserver && sv.lightstyles[i][0])
612 FS_Printf(f, "%s\n", sv.lightstyles[i]);
617 PRVM_ED_WriteGlobals (prog, f);
618 for (i=0 ; i<prog->num_edicts ; i++)
620 FS_Printf(f,"// edict %d\n", i);
621 //Con_Printf("edict %d...\n", i);
622 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
627 FS_Printf(f,"// DarkPlaces extended savegame\n");
628 // darkplaces extension - extra lightstyles, support for color lightstyles
629 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
630 if (isserver && sv.lightstyles[i][0])
631 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
633 // darkplaces extension - model precaches
634 for (i=1 ; i<MAX_MODELS ; i++)
635 if (sv.model_precache[i][0])
636 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
638 // darkplaces extension - sound precaches
639 for (i=1 ; i<MAX_SOUNDS ; i++)
640 if (sv.sound_precache[i][0])
641 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
643 // darkplaces extension - save buffers
644 numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
645 for (i = 0; i < numbuffers; i++)
647 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
648 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
650 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
651 for(k = 0; k < stringbuffer->num_strings; k++)
653 if (!stringbuffer->strings[k])
655 // Parse the string a bit to turn special characters
656 // (like newline, specifically) into escape codes
657 s = stringbuffer->strings[k];
658 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
685 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
693 Con_Print("done.\n");
701 static void Host_Savegame_f (void)
703 prvm_prog_t *prog = SVVM_prog;
704 char name[MAX_QPATH];
705 qboolean deadflag = false;
709 Con_Print("Can't save - no server running.\n");
713 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
717 // singleplayer checks
720 Con_Print("Can't save in intermission.\n");
726 Con_Print("Can't savegame with a dead player\n");
731 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");
735 Con_Print("save <savename> : save a game\n");
739 if (strstr(Cmd_Argv(1), ".."))
741 Con_Print("Relative pathnames are not allowed.\n");
745 strlcpy (name, Cmd_Argv(1), sizeof (name));
746 FS_DefaultExtension (name, ".sav", sizeof (name));
748 Host_Savegame_to(prog, name);
758 prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, char *format);
759 void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str);
760 void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer);
761 void BufStr_Flush(prvm_prog_t *prog);
763 static void Host_Loadgame_f (void)
765 prvm_prog_t *prog = SVVM_prog;
766 char filename[MAX_QPATH];
767 char mapname[MAX_QPATH];
774 int i, k, numbuffers;
777 float spawn_parms[NUM_SPAWN_PARMS];
778 prvm_stringbuffer_t *stringbuffer;
782 Con_Print("load <savename> : load a game\n");
786 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
787 FS_DefaultExtension (filename, ".sav", sizeof (filename));
789 Con_Printf("Loading game from %s...\n", filename);
791 // stop playing demos
792 if (cls.demoplayback)
796 if (key_dest == key_menu || key_dest == key_menu_grabbed)
800 cls.demonum = -1; // stop demo loop in case this fails
802 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
805 Con_Print("ERROR: couldn't open.\n");
809 if(developer_entityparsing.integer)
810 Con_Printf("Host_Loadgame_f: loading version\n");
813 COM_ParseToken_Simple(&t, false, false, true);
814 version = atoi(com_token);
815 if (version != SAVEGAME_VERSION)
818 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
822 if(developer_entityparsing.integer)
823 Con_Printf("Host_Loadgame_f: loading description\n");
826 COM_ParseToken_Simple(&t, false, false, true);
828 for (i = 0;i < NUM_SPAWN_PARMS;i++)
830 COM_ParseToken_Simple(&t, false, false, true);
831 spawn_parms[i] = atof(com_token);
834 COM_ParseToken_Simple(&t, false, false, true);
835 // this silliness is so we can load 1.06 save files, which have float skill values
836 current_skill = (int)(atof(com_token) + 0.5);
837 Cvar_SetValue ("skill", (float)current_skill);
839 if(developer_entityparsing.integer)
840 Con_Printf("Host_Loadgame_f: loading mapname\n");
843 COM_ParseToken_Simple(&t, false, false, true);
844 strlcpy (mapname, com_token, sizeof(mapname));
846 if(developer_entityparsing.integer)
847 Con_Printf("Host_Loadgame_f: loading time\n");
850 COM_ParseToken_Simple(&t, false, false, true);
851 time = atof(com_token);
853 allowcheats = sv_cheats.integer != 0;
855 if(developer_entityparsing.integer)
856 Con_Printf("Host_Loadgame_f: spawning server\n");
858 SV_SpawnServer (mapname);
862 Con_Print("Couldn't load map\n");
865 sv.paused = true; // pause until all clients connect
868 if(developer_entityparsing.integer)
869 Con_Printf("Host_Loadgame_f: loading light styles\n");
871 // load the light styles
876 for (i = 0;i < MAX_LIGHTSTYLES;i++)
880 COM_ParseToken_Simple(&t, false, false, true);
881 // if this is a 64 lightstyle savegame produced by Quake, stop now
882 // we have to check this because darkplaces may save more than 64
883 if (com_token[0] == '{')
888 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
891 if(developer_entityparsing.integer)
892 Con_Printf("Host_Loadgame_f: skipping until globals\n");
894 // now skip everything before the first opening brace
895 // (this is for forward compatibility, so that older versions (at
896 // least ones with this fix) can load savegames with extra data before the
897 // first brace, as might be produced by a later engine version)
901 if (!COM_ParseToken_Simple(&t, false, false, true))
903 if (com_token[0] == '{')
910 // unlink all entities
911 World_UnlinkAll(&sv.world);
913 // load the edicts out of the savegame file
918 while (COM_ParseToken_Simple(&t, false, false, true))
919 if (!strcmp(com_token, "}"))
921 if (!COM_ParseToken_Simple(&start, false, false, true))
926 if (strcmp(com_token,"{"))
929 Host_Error ("First token isn't a brace");
934 if(developer_entityparsing.integer)
935 Con_Printf("Host_Loadgame_f: loading globals\n");
937 // parse the global vars
938 PRVM_ED_ParseGlobals (prog, start);
940 // restore the autocvar globals
941 Cvar_UpdateAllAutoCvars();
946 if (entnum >= MAX_EDICTS)
949 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
951 while (entnum >= prog->max_edicts)
952 PRVM_MEM_IncreaseEdicts(prog);
953 ent = PRVM_EDICT_NUM(entnum);
954 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
955 ent->priv.server->free = false;
957 if(developer_entityparsing.integer)
958 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
960 PRVM_ED_ParseEdict (prog, start, ent);
962 // link it into the bsp tree
963 if (!ent->priv.server->free)
971 prog->num_edicts = entnum;
974 for (i = 0;i < NUM_SPAWN_PARMS;i++)
975 svs.clients[0].spawn_parms[i] = spawn_parms[i];
977 if(developer_entityparsing.integer)
978 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
980 // read extended data if present
981 // the extended data is stored inside a /* */ comment block, which the
982 // parser intentionally skips, so we have to check for it manually here
985 while (*end == '\r' || *end == '\n')
987 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
989 if(developer_entityparsing.integer)
990 Con_Printf("Host_Loadgame_f: loading extended data\n");
992 Con_Printf("Loading extended DarkPlaces savegame\n");
994 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
995 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
996 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
999 while (COM_ParseToken_Simple(&t, false, false, true))
1001 if (!strcmp(com_token, "sv.lightstyles"))
1003 COM_ParseToken_Simple(&t, false, false, true);
1004 i = atoi(com_token);
1005 COM_ParseToken_Simple(&t, false, false, true);
1006 if (i >= 0 && i < MAX_LIGHTSTYLES)
1007 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1009 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1011 else if (!strcmp(com_token, "sv.model_precache"))
1013 COM_ParseToken_Simple(&t, false, false, true);
1014 i = atoi(com_token);
1015 COM_ParseToken_Simple(&t, false, false, true);
1016 if (i >= 0 && i < MAX_MODELS)
1018 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1019 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1022 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1024 else if (!strcmp(com_token, "sv.sound_precache"))
1026 COM_ParseToken_Simple(&t, false, false, true);
1027 i = atoi(com_token);
1028 COM_ParseToken_Simple(&t, false, false, true);
1029 if (i >= 0 && i < MAX_SOUNDS)
1030 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1032 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1034 else if (!strcmp(com_token, "sv.buffer"))
1036 if (COM_ParseToken_Simple(&t, false, false, true))
1038 i = atoi(com_token);
1041 k = STRINGBUFFER_SAVED;
1042 if (COM_ParseToken_Simple(&t, false, false, true))
1043 k |= atoi(com_token);
1044 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1045 Con_Printf("failed to create stringbuffer %i\n", i);
1048 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1051 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1053 else if (!strcmp(com_token, "sv.bufstr"))
1055 if (!COM_ParseToken_Simple(&t, false, false, true))
1056 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1059 i = atoi(com_token);
1060 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1063 if (COM_ParseToken_Simple(&t, false, false, true))
1065 k = atoi(com_token);
1066 if (COM_ParseToken_Simple(&t, false, false, true))
1067 BufStr_Set(prog, stringbuffer, k, com_token);
1069 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1072 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1075 Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1078 // skip any trailing text or unrecognized commands
1079 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1086 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1087 numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1088 for (i = 0; i < numbuffers; i++)
1090 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1091 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1092 BufStr_Del(prog, stringbuffer);
1095 if(developer_entityparsing.integer)
1096 Con_Printf("Host_Loadgame_f: finished\n");
1098 // make sure we're connected to loopback
1099 if (sv.active && cls.state == ca_disconnected)
1100 CL_EstablishConnection("local:1", -2);
1103 //============================================================================
1106 ======================
1108 ======================
1110 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1111 static void Host_Name_f (void)
1113 prvm_prog_t *prog = SVVM_prog;
1115 qboolean valid_colors;
1116 const char *newNameSource;
1117 char newName[sizeof(host_client->name)];
1119 if (Cmd_Argc () == 1)
1121 Con_Printf("name: %s\n", cl_name.string);
1125 if (Cmd_Argc () == 2)
1126 newNameSource = Cmd_Argv(1);
1128 newNameSource = Cmd_Args();
1130 strlcpy(newName, newNameSource, sizeof(newName));
1132 if (cmd_source == src_command)
1134 Cvar_Set ("_cl_name", newName);
1135 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1137 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1138 Con_Printf("name: %s\n", cl_name.string);
1143 if (realtime < host_client->nametime)
1145 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1149 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1151 // point the string back at updateclient->name to keep it safe
1152 strlcpy (host_client->name, newName, sizeof (host_client->name));
1154 for (i = 0, j = 0;host_client->name[i];i++)
1155 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1156 host_client->name[j++] = host_client->name[i];
1157 host_client->name[j] = 0;
1159 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1160 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1162 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1163 host_client->name[sizeof(host_client->name) - 1] = 0;
1164 host_client->name[0] = STRING_COLOR_TAG;
1165 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1168 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1169 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1172 l = strlen(host_client->name);
1173 if(l < sizeof(host_client->name) - 1)
1175 // duplicate the color tag to escape it
1176 host_client->name[i] = STRING_COLOR_TAG;
1177 host_client->name[i+1] = 0;
1178 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1182 // remove the last character to fix the color code
1183 host_client->name[l-1] = 0;
1184 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1188 // find the last color tag offset and decide if we need to add a reset tag
1189 for (i = 0, j = -1;host_client->name[i];i++)
1191 if (host_client->name[i] == STRING_COLOR_TAG)
1193 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1196 // if this happens to be a reset tag then we don't need one
1197 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1202 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]))
1208 if (host_client->name[i+1] == STRING_COLOR_TAG)
1215 // does not end in the default color string, so add it
1216 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1217 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1219 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1220 if (strcmp(host_client->old_name, host_client->name))
1222 if (host_client->begun)
1223 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1224 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1225 // send notification to all clients
1226 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1227 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1228 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1229 SV_WriteNetnameIntoDemo(host_client);
1234 ======================
1236 ======================
1238 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1239 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1240 static void Host_Playermodel_f (void)
1242 prvm_prog_t *prog = SVVM_prog;
1244 char newPath[sizeof(host_client->playermodel)];
1246 if (Cmd_Argc () == 1)
1248 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1252 if (Cmd_Argc () == 2)
1253 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1255 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1257 for (i = 0, j = 0;newPath[i];i++)
1258 if (newPath[i] != '\r' && newPath[i] != '\n')
1259 newPath[j++] = newPath[i];
1262 if (cmd_source == src_command)
1264 Cvar_Set ("_cl_playermodel", newPath);
1269 if (realtime < host_client->nametime)
1271 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1275 host_client->nametime = realtime + 5;
1278 // point the string back at updateclient->name to keep it safe
1279 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1280 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1281 if (strcmp(host_client->old_model, host_client->playermodel))
1283 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1284 /*// send notification to all clients
1285 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1286 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1287 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1292 ======================
1294 ======================
1296 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1297 static void Host_Playerskin_f (void)
1299 prvm_prog_t *prog = SVVM_prog;
1301 char newPath[sizeof(host_client->playerskin)];
1303 if (Cmd_Argc () == 1)
1305 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1309 if (Cmd_Argc () == 2)
1310 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1312 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1314 for (i = 0, j = 0;newPath[i];i++)
1315 if (newPath[i] != '\r' && newPath[i] != '\n')
1316 newPath[j++] = newPath[i];
1319 if (cmd_source == src_command)
1321 Cvar_Set ("_cl_playerskin", newPath);
1326 if (realtime < host_client->nametime)
1328 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1332 host_client->nametime = realtime + 5;
1335 // point the string back at updateclient->name to keep it safe
1336 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1337 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1338 if (strcmp(host_client->old_skin, host_client->playerskin))
1340 //if (host_client->begun)
1341 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1342 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1343 /*// send notification to all clients
1344 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1345 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1346 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1350 static void Host_Version_f (void)
1352 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1355 static void Host_Say(qboolean teamonly)
1357 prvm_prog_t *prog = SVVM_prog;
1362 // LordHavoc: long say messages
1364 qboolean fromServer = false;
1366 if (cmd_source == src_command)
1368 if (cls.state == ca_dedicated)
1375 Cmd_ForwardToServer ();
1380 if (Cmd_Argc () < 2)
1383 if (!teamplay.integer)
1393 // note this uses the chat prefix \001
1394 if (!fromServer && !teamonly)
1395 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1396 else if (!fromServer && teamonly)
1397 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1398 else if(*(sv_adminnick.string))
1399 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1401 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1402 p2 = text + strlen(text);
1403 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1405 if (p2[-1] == '\"' && quoted)
1410 strlcat(text, "\n", sizeof(text));
1412 // note: save is not a valid edict if fromServer is true
1414 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1415 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1416 SV_ClientPrint(text);
1419 if (cls.state == ca_dedicated)
1420 Con_Print(&text[1]);
1424 static void Host_Say_f(void)
1430 static void Host_Say_Team_f(void)
1436 static void Host_Tell_f(void)
1438 const char *playername_start = NULL;
1439 size_t playername_length = 0;
1440 int playernumber = 0;
1443 const char *p1, *p2;
1444 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1445 qboolean fromServer = false;
1447 if (cmd_source == src_command)
1449 if (cls.state == ca_dedicated)
1453 Cmd_ForwardToServer ();
1458 if (Cmd_Argc () < 2)
1461 // note this uses the chat prefix \001
1463 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1464 else if(*(sv_adminnick.string))
1465 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1467 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1470 p2 = p1 + strlen(p1);
1471 // remove the target name
1472 while (p1 < p2 && *p1 == ' ')
1477 while (p1 < p2 && *p1 == ' ')
1479 while (p1 < p2 && isdigit(*p1))
1481 playernumber = playernumber * 10 + (*p1 - '0');
1489 playername_start = p1;
1490 while (p1 < p2 && *p1 != '"')
1492 playername_length = p1 - playername_start;
1498 playername_start = p1;
1499 while (p1 < p2 && *p1 != ' ')
1501 playername_length = p1 - playername_start;
1503 while (p1 < p2 && *p1 == ' ')
1505 if(playername_start)
1507 // set playernumber to the right client
1509 if(playername_length >= sizeof(namebuf))
1512 Con_Print("Host_Tell: too long player name/ID\n");
1514 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1517 memcpy(namebuf, playername_start, playername_length);
1518 namebuf[playername_length] = 0;
1519 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1521 if (!svs.clients[playernumber].active)
1523 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1527 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1530 Con_Print("Host_Tell: invalid player name/ID\n");
1532 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1535 // remove trailing newlines
1536 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1538 // remove quotes if present
1544 else if (fromServer)
1545 Con_Print("Host_Tell: missing end quote\n");
1547 SV_ClientPrint("Host_Tell: missing end quote\n");
1549 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1552 return; // empty say
1553 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1559 host_client = svs.clients + playernumber;
1560 SV_ClientPrint(text);
1570 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1571 static void Host_Color(int changetop, int changebottom)
1573 prvm_prog_t *prog = SVVM_prog;
1574 int top, bottom, playercolor;
1576 // get top and bottom either from the provided values or the current values
1577 // (allows changing only top or bottom, or both at once)
1578 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1579 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1583 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1589 playercolor = top*16 + bottom;
1591 if (cmd_source == src_command)
1593 Cvar_SetValueQuick(&cl_color, playercolor);
1597 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1600 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1602 Con_DPrint("Calling SV_ChangeTeam\n");
1603 prog->globals.fp[OFS_PARM0] = playercolor;
1604 PRVM_serverglobalfloat(time) = sv.time;
1605 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1606 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1610 if (host_client->edict)
1612 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1613 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1615 host_client->colors = playercolor;
1616 if (host_client->old_colors != host_client->colors)
1618 host_client->old_colors = host_client->colors;
1619 // send notification to all clients
1620 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1621 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1622 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1627 static void Host_Color_f(void)
1631 if (Cmd_Argc() == 1)
1633 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1634 Con_Print("color <0-15> [0-15]\n");
1638 if (Cmd_Argc() == 2)
1639 top = bottom = atoi(Cmd_Argv(1));
1642 top = atoi(Cmd_Argv(1));
1643 bottom = atoi(Cmd_Argv(2));
1645 Host_Color(top, bottom);
1648 static void Host_TopColor_f(void)
1650 if (Cmd_Argc() == 1)
1652 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1653 Con_Print("topcolor <0-15>\n");
1657 Host_Color(atoi(Cmd_Argv(1)), -1);
1660 static void Host_BottomColor_f(void)
1662 if (Cmd_Argc() == 1)
1664 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1665 Con_Print("bottomcolor <0-15>\n");
1669 Host_Color(-1, atoi(Cmd_Argv(1)));
1672 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1673 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)"};
1674 static void Host_Rate_f(void)
1678 if (Cmd_Argc() != 2)
1680 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1681 Con_Print("rate <bytespersecond>\n");
1685 rate = atoi(Cmd_Argv(1));
1687 if (cmd_source == src_command)
1689 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1693 host_client->rate = rate;
1695 static void Host_Rate_BurstSize_f(void)
1699 if (Cmd_Argc() != 2)
1701 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1702 Con_Print("rate_burstsize <bytes>\n");
1706 rate_burstsize = atoi(Cmd_Argv(1));
1708 if (cmd_source == src_command)
1710 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1714 host_client->rate_burstsize = rate_burstsize;
1722 static void Host_Kill_f (void)
1724 prvm_prog_t *prog = SVVM_prog;
1725 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1727 SV_ClientPrint("Can't suicide -- already dead!\n");
1731 PRVM_serverglobalfloat(time) = sv.time;
1732 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1733 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1742 static void Host_Pause_f (void)
1744 void (*print) (const char *fmt, ...);
1745 if (cmd_source == src_command)
1747 // if running a client, try to send over network so the pause is handled by the server
1748 if (cls.state == ca_connected)
1750 Cmd_ForwardToServer ();
1756 print = SV_ClientPrintf;
1758 if (!pausable.integer)
1760 if (cmd_source == src_client)
1762 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1764 print("Pause not allowed.\n");
1771 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1772 // send notification to all clients
1773 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1774 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1778 ======================
1780 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1781 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1782 ======================
1784 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)"};
1785 static void Host_PModel_f (void)
1787 prvm_prog_t *prog = SVVM_prog;
1790 if (Cmd_Argc () == 1)
1792 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1795 i = atoi(Cmd_Argv(1));
1797 if (cmd_source == src_command)
1799 if (cl_pmodel.integer == i)
1801 Cvar_SetValue ("_cl_pmodel", i);
1802 if (cls.state == ca_connected)
1803 Cmd_ForwardToServer ();
1807 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1810 //===========================================================================
1818 static void Host_PreSpawn_f (void)
1820 if (host_client->prespawned)
1822 Con_Print("prespawn not valid -- already prespawned\n");
1825 host_client->prespawned = true;
1827 if (host_client->netconnection)
1829 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1830 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1831 MSG_WriteByte (&host_client->netconnection->message, 2);
1832 host_client->sendsignon = 0; // enable unlimited sends again
1835 // reset the name change timer because the client will send name soon
1836 host_client->nametime = 0;
1844 static void Host_Spawn_f (void)
1846 prvm_prog_t *prog = SVVM_prog;
1849 int stats[MAX_CL_STATS];
1851 if (!host_client->prespawned)
1853 Con_Print("Spawn not valid -- not yet prespawned\n");
1856 if (host_client->spawned)
1858 Con_Print("Spawn not valid -- already spawned\n");
1861 host_client->spawned = true;
1863 // reset name change timer again because they might want to change name
1864 // again in the first 5 seconds after connecting
1865 host_client->nametime = 0;
1867 // LordHavoc: moved this above the QC calls at FrikaC's request
1868 // LordHavoc: commented this out
1869 //if (host_client->netconnection)
1870 // SZ_Clear (&host_client->netconnection->message);
1872 // run the entrance script
1875 // loaded games are fully initialized already
1876 if (PRVM_serverfunction(RestoreGame))
1878 Con_DPrint("Calling RestoreGame\n");
1879 PRVM_serverglobalfloat(time) = sv.time;
1880 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1881 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1886 //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);
1888 // copy spawn parms out of the client_t
1889 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1890 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1892 // call the spawn function
1893 host_client->clientconnectcalled = true;
1894 PRVM_serverglobalfloat(time) = sv.time;
1895 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1896 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1898 if (cls.state == ca_dedicated)
1899 Con_Printf("%s connected\n", host_client->name);
1901 PRVM_serverglobalfloat(time) = sv.time;
1902 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1905 if (!host_client->netconnection)
1908 // send time of update
1909 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1910 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1912 // send all current names, colors, and frag counts
1913 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1915 if (!client->active)
1917 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1918 MSG_WriteByte (&host_client->netconnection->message, i);
1919 MSG_WriteString (&host_client->netconnection->message, client->name);
1920 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1921 MSG_WriteByte (&host_client->netconnection->message, i);
1922 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1923 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1924 MSG_WriteByte (&host_client->netconnection->message, i);
1925 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1928 // send all current light styles
1929 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1931 if (sv.lightstyles[i][0])
1933 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1934 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1935 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1940 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1941 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1942 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1944 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1945 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1946 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1948 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1949 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1950 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1952 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1953 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1954 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1957 // Never send a roll angle, because savegames can catch the server
1958 // in a state where it is expecting the client to correct the angle
1959 // and it won't happen if the game was just loaded, so you wind up
1960 // with a permanent head tilt
1963 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1964 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1965 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1966 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1970 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1971 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1972 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1973 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1976 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1978 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1979 MSG_WriteByte (&host_client->netconnection->message, 3);
1987 static void Host_Begin_f (void)
1989 if (!host_client->spawned)
1991 Con_Print("Begin not valid -- not yet spawned\n");
1994 if (host_client->begun)
1996 Con_Print("Begin not valid -- already begun\n");
1999 host_client->begun = true;
2001 // LordHavoc: note: this code also exists in SV_DropClient
2005 for (i = 0;i < svs.maxclients;i++)
2006 if (svs.clients[i].active && !svs.clients[i].spawned)
2008 if (i == svs.maxclients)
2010 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2011 sv.paused = sv.loadgame = false; // we're basically done with loading now
2016 //===========================================================================
2023 Kicks a user off of the server
2026 static void Host_Kick_f (void)
2029 const char *message = NULL;
2032 qboolean byNumber = false;
2039 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2041 i = (int)(atof(Cmd_Argv(2)) - 1);
2042 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2048 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2050 if (!host_client->active)
2052 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2057 if (i < svs.maxclients)
2059 if (cmd_source == src_command)
2061 if (cls.state == ca_dedicated)
2064 who = cl_name.string;
2069 // can't kick yourself!
2070 if (host_client == save)
2075 message = Cmd_Args();
2076 COM_ParseToken_Simple(&message, false, false, true);
2079 message++; // skip the #
2080 while (*message == ' ') // skip white space
2082 message += strlen(Cmd_Argv(2)); // skip the number
2084 while (*message && *message == ' ')
2088 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2090 SV_ClientPrintf("Kicked by %s\n", who);
2091 SV_DropClient (false); // kicked
2098 ===============================================================================
2102 ===============================================================================
2110 static void Host_Give_f (void)
2112 prvm_prog_t *prog = SVVM_prog;
2118 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2123 v = atoi (Cmd_Argv(2));
2137 // MED 01/04/97 added hipnotic give stuff
2138 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2143 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2145 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2147 else if (t[0] == '9')
2148 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2149 else if (t[0] == '0')
2150 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2151 else if (t[0] >= '2')
2152 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2157 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2162 if (gamemode == GAME_ROGUE)
2163 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2165 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2168 if (gamemode == GAME_ROGUE)
2170 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2171 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2172 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2176 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2180 if (gamemode == GAME_ROGUE)
2182 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2183 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2184 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2188 if (gamemode == GAME_ROGUE)
2190 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2191 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2192 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2196 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2200 if (gamemode == GAME_ROGUE)
2202 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2203 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2204 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2208 PRVM_serveredictfloat(host_client->edict, health) = v;
2211 if (gamemode == GAME_ROGUE)
2213 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2214 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2215 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2219 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2223 if (gamemode == GAME_ROGUE)
2225 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2226 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2227 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2233 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2238 for (i=0 ; i<prog->num_edicts ; i++)
2240 e = PRVM_EDICT_NUM(i);
2241 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2244 Con_Print("No viewthing on map\n");
2253 static void Host_Viewmodel_f (void)
2255 prvm_prog_t *prog = SVVM_prog;
2262 e = FindViewthing(prog);
2265 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2266 if (m && m->loaded && m->Draw)
2268 PRVM_serveredictfloat(e, frame) = 0;
2269 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2272 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2281 static void Host_Viewframe_f (void)
2283 prvm_prog_t *prog = SVVM_prog;
2291 e = FindViewthing(prog);
2294 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2296 f = atoi(Cmd_Argv(1));
2297 if (f >= m->numframes)
2300 PRVM_serveredictfloat(e, frame) = f;
2305 static void PrintFrameName (dp_model_t *m, int frame)
2308 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2310 Con_Printf("frame %i\n", frame);
2318 static void Host_Viewnext_f (void)
2320 prvm_prog_t *prog = SVVM_prog;
2327 e = FindViewthing(prog);
2330 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2332 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2333 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2334 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2336 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2345 static void Host_Viewprev_f (void)
2347 prvm_prog_t *prog = SVVM_prog;
2354 e = FindViewthing(prog);
2357 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2359 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2360 if (PRVM_serveredictfloat(e, frame) < 0)
2361 PRVM_serveredictfloat(e, frame) = 0;
2363 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2368 ===============================================================================
2372 ===============================================================================
2381 static void Host_Startdemos_f (void)
2385 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2391 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2394 Con_DPrintf("%i demo(s) in loop\n", c);
2396 for (i=1 ; i<c+1 ; i++)
2397 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2399 // LordHavoc: clear the remaining slots
2400 for (;i <= MAX_DEMOS;i++)
2401 cls.demos[i-1][0] = 0;
2403 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2417 Return to looping demos
2420 static void Host_Demos_f (void)
2422 if (cls.state == ca_dedicated)
2424 if (cls.demonum == -1)
2434 Return to looping demos
2437 static void Host_Stopdemo_f (void)
2439 if (!cls.demoplayback)
2442 Host_ShutdownServer ();
2445 static void Host_SendCvar_f (void)
2449 const char *cvarname;
2455 cvarname = Cmd_Argv(1);
2456 if (cls.state == ca_connected)
2458 c = Cvar_FindVar(cvarname);
2459 // LordHavoc: if there is no such cvar or if it is private, send a
2460 // reply indicating that it has no value
2461 if(!c || (c->flags & CVAR_PRIVATE))
2462 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2464 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2467 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2471 if (cls.state != ca_dedicated)
2475 for(;i<svs.maxclients;i++)
2476 if(svs.clients[i].active && svs.clients[i].netconnection)
2478 host_client = &svs.clients[i];
2479 Host_ClientCommands("sendcvar %s\n", cvarname);
2484 static void MaxPlayers_f(void)
2488 if (Cmd_Argc() != 2)
2490 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2496 Con_Print("maxplayers can not be changed while a server is running.\n");
2497 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2500 n = atoi(Cmd_Argv(1));
2501 n = bound(1, n, MAX_SCOREBOARD);
2502 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2504 svs.maxclients_next = n;
2506 Cvar_Set ("deathmatch", "0");
2508 Cvar_Set ("deathmatch", "1");
2512 =====================
2515 ProQuake rcon support
2516 =====================
2518 static void Host_PQRcon_f (void)
2523 lhnetsocket_t *mysocket;
2524 char peer_address[64];
2526 if (Cmd_Argc() == 1)
2528 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2532 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2534 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2538 e = strchr(rcon_password.string, ' ');
2539 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2543 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2547 if (!rcon_address.string[0])
2549 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2552 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2554 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2555 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2559 unsigned char bufdata[64];
2562 MSG_WriteLong(&buf, 0);
2563 MSG_WriteByte(&buf, CCREQ_RCON);
2564 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2565 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2566 MSG_WriteString(&buf, Cmd_Args());
2567 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2568 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2573 //=============================================================================
2575 // QuakeWorld commands
2578 =====================
2581 Send the rest of the command line over as
2582 an unconnected command.
2583 =====================
2585 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2590 lhnetsocket_t *mysocket;
2593 if (Cmd_Argc() == 1)
2595 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2599 if (!rcon_password.string || !rcon_password.string[0])
2601 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2605 e = strchr(rcon_password.string, ' ');
2606 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2609 to = cls.netcon->peeraddress;
2612 if (!rcon_address.string[0])
2614 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2617 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2619 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2620 if (mysocket && Cmd_Args()[0])
2622 // simply put together the rcon packet and send it
2623 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2625 if(cls.rcon_commands[cls.rcon_ringpos][0])
2628 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2629 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]);
2630 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2633 for (i = 0;i < MAX_RCONS;i++)
2634 if(cls.rcon_commands[i][0])
2635 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2639 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2640 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2641 cls.rcon_addresses[cls.rcon_ringpos] = to;
2642 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2643 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2645 else if(rcon_secure.integer > 0)
2649 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2650 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2651 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2654 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2655 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2660 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2666 ====================
2669 user <name or userid>
2671 Dump userdata / masterdata for a user
2672 ====================
2674 static void Host_User_f (void) // credit: taken from QuakeWorld
2679 if (Cmd_Argc() != 2)
2681 Con_Printf ("Usage: user <username / userid>\n");
2685 uid = atoi(Cmd_Argv(1));
2687 for (i = 0;i < cl.maxclients;i++)
2689 if (!cl.scores[i].name[0])
2691 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2693 InfoString_Print(cl.scores[i].qw_userinfo);
2697 Con_Printf ("User not in server.\n");
2701 ====================
2704 Dump userids for all current players
2705 ====================
2707 static void Host_Users_f (void) // credit: taken from QuakeWorld
2713 Con_Printf ("userid frags name\n");
2714 Con_Printf ("------ ----- ----\n");
2715 for (i = 0;i < cl.maxclients;i++)
2717 if (cl.scores[i].name[0])
2719 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2724 Con_Printf ("%i total users\n", c);
2729 Host_FullServerinfo_f
2731 Sent by server when serverinfo changes
2734 // TODO: shouldn't this be a cvar instead?
2735 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2738 if (Cmd_Argc() != 2)
2740 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2744 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2745 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2746 cl.qw_teamplay = atoi(temp);
2753 Allow clients to change userinfo
2757 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2764 if (Cmd_Argc() != 2)
2766 Con_Printf ("fullinfo <complete info string>\n");
2776 while (*s && *s != '\\')
2782 Con_Printf ("MISSING VALUE\n");
2788 while (*s && *s != '\\')
2795 CL_SetInfo(key, value, false, false, false, false);
2803 Allow clients to change userinfo
2806 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2808 if (Cmd_Argc() == 1)
2810 InfoString_Print(cls.userinfo);
2813 if (Cmd_Argc() != 3)
2815 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2818 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2822 ====================
2825 packet <destination> <contents>
2827 Contents allows \n escape character
2828 ====================
2830 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2836 lhnetaddress_t address;
2837 lhnetsocket_t *mysocket;
2839 if (Cmd_Argc() != 3)
2841 Con_Printf ("packet <destination> <contents>\n");
2845 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2847 Con_Printf ("Bad address\n");
2853 send[0] = send[1] = send[2] = send[3] = -1;
2855 l = (int)strlen (in);
2856 for (i=0 ; i<l ; i++)
2858 if (out >= send + sizeof(send) - 1)
2860 if (in[i] == '\\' && in[i+1] == 'n')
2865 else if (in[i] == '\\' && in[i+1] == '0')
2870 else if (in[i] == '\\' && in[i+1] == 't')
2875 else if (in[i] == '\\' && in[i+1] == 'r')
2880 else if (in[i] == '\\' && in[i+1] == '"')
2889 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2891 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2893 NetConn_Write(mysocket, send, out - send, &address);
2897 ====================
2900 Send back ping and packet loss update for all current players to this player
2901 ====================
2903 void Host_Pings_f (void)
2905 int i, j, ping, packetloss, movementloss;
2908 if (!host_client->netconnection)
2911 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2913 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2914 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2916 for (i = 0;i < svs.maxclients;i++)
2920 if (svs.clients[i].netconnection)
2922 for (j = 0;j < NETGRAPH_PACKETS;j++)
2923 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2925 for (j = 0;j < NETGRAPH_PACKETS;j++)
2926 if (svs.clients[i].movement_count[j] < 0)
2929 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2930 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2931 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2932 ping = bound(0, ping, 9999);
2933 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2935 // send qw_svc_updateping and qw_svc_updatepl messages
2936 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2937 MSG_WriteShort(&host_client->netconnection->message, ping);
2938 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2939 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2943 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2945 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2947 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2948 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2951 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2952 MSG_WriteString(&host_client->netconnection->message, "\n");
2955 static void Host_PingPLReport_f(void)
2960 if (l > cl.maxclients)
2962 for (i = 0;i < l;i++)
2964 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2965 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2966 if(errbyte && *errbyte == ',')
2967 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2969 cl.scores[i].qw_movementloss = 0;
2973 //=============================================================================
2980 void Host_InitCommands (void)
2982 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2984 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2985 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2986 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2987 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2988 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2989 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2990 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2991 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2992 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2993 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2994 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2995 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)");
2996 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2997 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2998 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2999 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3000 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3001 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3002 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3003 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3004 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3005 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3007 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3008 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3009 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3011 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3012 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3013 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3014 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3016 Cvar_RegisterVariable (&cl_name);
3017 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3018 Cvar_RegisterVariable (&cl_color);
3019 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3020 Cvar_RegisterVariable (&cl_rate);
3021 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3022 Cvar_RegisterVariable (&cl_rate_burstsize);
3023 Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3024 Cvar_RegisterVariable (&cl_pmodel);
3025 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3027 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3028 Cvar_RegisterVariable (&cl_playermodel);
3029 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3030 Cvar_RegisterVariable (&cl_playerskin);
3031 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3033 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3034 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3035 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)");
3036 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3038 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3040 Cvar_RegisterVariable (&rcon_password);
3041 Cvar_RegisterVariable (&rcon_address);
3042 Cvar_RegisterVariable (&rcon_secure);
3043 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3044 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");
3045 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");
3046 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)");
3047 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3048 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3049 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3050 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3051 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3052 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3053 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3054 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3056 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)");
3057 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)");
3059 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)");
3060 Cvar_RegisterVariable (&r_fixtrans_auto);
3062 Cvar_RegisterVariable (&team);
3063 Cvar_RegisterVariable (&skin);
3064 Cvar_RegisterVariable (&noaim);
3066 Cvar_RegisterVariable(&sv_cheats);
3067 Cvar_RegisterVariable(&sv_adminnick);
3068 Cvar_RegisterVariable(&sv_status_privacy);
3069 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3070 Cvar_RegisterVariable(&sv_namechangetimer);
3073 void Host_NoOperation_f(void)