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 static void Host_Rate_f(void)
1677 if (Cmd_Argc() != 2)
1679 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1680 Con_Print("rate <bytespersecond>\n");
1684 rate = atoi(Cmd_Argv(1));
1686 if (cmd_source == src_command)
1688 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1692 host_client->rate = rate;
1700 static void Host_Kill_f (void)
1702 prvm_prog_t *prog = SVVM_prog;
1703 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1705 SV_ClientPrint("Can't suicide -- already dead!\n");
1709 PRVM_serverglobalfloat(time) = sv.time;
1710 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1711 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1720 static void Host_Pause_f (void)
1722 void (*print) (const char *fmt, ...);
1723 if (cmd_source == src_command)
1725 // if running a client, try to send over network so the pause is handled by the server
1726 if (cls.state == ca_connected)
1728 Cmd_ForwardToServer ();
1734 print = SV_ClientPrintf;
1736 if (!pausable.integer)
1738 if (cmd_source == src_client)
1740 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1742 print("Pause not allowed.\n");
1749 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1750 // send notification to all clients
1751 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1752 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1756 ======================
1758 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1759 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1760 ======================
1762 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)"};
1763 static void Host_PModel_f (void)
1765 prvm_prog_t *prog = SVVM_prog;
1768 if (Cmd_Argc () == 1)
1770 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1773 i = atoi(Cmd_Argv(1));
1775 if (cmd_source == src_command)
1777 if (cl_pmodel.integer == i)
1779 Cvar_SetValue ("_cl_pmodel", i);
1780 if (cls.state == ca_connected)
1781 Cmd_ForwardToServer ();
1785 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1788 //===========================================================================
1796 static void Host_PreSpawn_f (void)
1798 if (host_client->prespawned)
1800 Con_Print("prespawn not valid -- already prespawned\n");
1803 host_client->prespawned = true;
1805 if (host_client->netconnection)
1807 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1808 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1809 MSG_WriteByte (&host_client->netconnection->message, 2);
1810 host_client->sendsignon = 0; // enable unlimited sends again
1813 // reset the name change timer because the client will send name soon
1814 host_client->nametime = 0;
1822 static void Host_Spawn_f (void)
1824 prvm_prog_t *prog = SVVM_prog;
1827 int stats[MAX_CL_STATS];
1829 if (!host_client->prespawned)
1831 Con_Print("Spawn not valid -- not yet prespawned\n");
1834 if (host_client->spawned)
1836 Con_Print("Spawn not valid -- already spawned\n");
1839 host_client->spawned = true;
1841 // reset name change timer again because they might want to change name
1842 // again in the first 5 seconds after connecting
1843 host_client->nametime = 0;
1845 // LordHavoc: moved this above the QC calls at FrikaC's request
1846 // LordHavoc: commented this out
1847 //if (host_client->netconnection)
1848 // SZ_Clear (&host_client->netconnection->message);
1850 // run the entrance script
1853 // loaded games are fully initialized already
1854 if (PRVM_serverfunction(RestoreGame))
1856 Con_DPrint("Calling RestoreGame\n");
1857 PRVM_serverglobalfloat(time) = sv.time;
1858 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1859 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1864 //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);
1866 // copy spawn parms out of the client_t
1867 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1868 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1870 // call the spawn function
1871 host_client->clientconnectcalled = true;
1872 PRVM_serverglobalfloat(time) = sv.time;
1873 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1874 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1876 if (cls.state == ca_dedicated)
1877 Con_Printf("%s connected\n", host_client->name);
1879 PRVM_serverglobalfloat(time) = sv.time;
1880 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1883 if (!host_client->netconnection)
1886 // send time of update
1887 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1888 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1890 // send all current names, colors, and frag counts
1891 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1893 if (!client->active)
1895 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1896 MSG_WriteByte (&host_client->netconnection->message, i);
1897 MSG_WriteString (&host_client->netconnection->message, client->name);
1898 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1899 MSG_WriteByte (&host_client->netconnection->message, i);
1900 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1901 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1902 MSG_WriteByte (&host_client->netconnection->message, i);
1903 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1906 // send all current light styles
1907 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1909 if (sv.lightstyles[i][0])
1911 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1912 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1913 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1918 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1919 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1920 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1922 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1923 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1924 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1926 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1927 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1928 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1930 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1931 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1932 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1935 // Never send a roll angle, because savegames can catch the server
1936 // in a state where it is expecting the client to correct the angle
1937 // and it won't happen if the game was just loaded, so you wind up
1938 // with a permanent head tilt
1941 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1942 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1943 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1944 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1948 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1949 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1950 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1951 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1954 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1956 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1957 MSG_WriteByte (&host_client->netconnection->message, 3);
1965 static void Host_Begin_f (void)
1967 if (!host_client->spawned)
1969 Con_Print("Begin not valid -- not yet spawned\n");
1972 if (host_client->begun)
1974 Con_Print("Begin not valid -- already begun\n");
1977 host_client->begun = true;
1979 // LordHavoc: note: this code also exists in SV_DropClient
1983 for (i = 0;i < svs.maxclients;i++)
1984 if (svs.clients[i].active && !svs.clients[i].spawned)
1986 if (i == svs.maxclients)
1988 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1989 sv.paused = sv.loadgame = false; // we're basically done with loading now
1994 //===========================================================================
2001 Kicks a user off of the server
2004 static void Host_Kick_f (void)
2007 const char *message = NULL;
2010 qboolean byNumber = false;
2017 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2019 i = (int)(atof(Cmd_Argv(2)) - 1);
2020 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2026 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2028 if (!host_client->active)
2030 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2035 if (i < svs.maxclients)
2037 if (cmd_source == src_command)
2039 if (cls.state == ca_dedicated)
2042 who = cl_name.string;
2047 // can't kick yourself!
2048 if (host_client == save)
2053 message = Cmd_Args();
2054 COM_ParseToken_Simple(&message, false, false, true);
2057 message++; // skip the #
2058 while (*message == ' ') // skip white space
2060 message += strlen(Cmd_Argv(2)); // skip the number
2062 while (*message && *message == ' ')
2066 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2068 SV_ClientPrintf("Kicked by %s\n", who);
2069 SV_DropClient (false); // kicked
2076 ===============================================================================
2080 ===============================================================================
2088 static void Host_Give_f (void)
2090 prvm_prog_t *prog = SVVM_prog;
2096 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2101 v = atoi (Cmd_Argv(2));
2115 // MED 01/04/97 added hipnotic give stuff
2116 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2121 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2123 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2125 else if (t[0] == '9')
2126 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2127 else if (t[0] == '0')
2128 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2129 else if (t[0] >= '2')
2130 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2135 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2140 if (gamemode == GAME_ROGUE)
2141 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2143 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2146 if (gamemode == GAME_ROGUE)
2148 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2149 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2150 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2154 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2158 if (gamemode == GAME_ROGUE)
2160 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2161 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2162 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2166 if (gamemode == GAME_ROGUE)
2168 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2169 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2170 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2174 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2178 if (gamemode == GAME_ROGUE)
2180 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2181 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2182 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2186 PRVM_serveredictfloat(host_client->edict, health) = v;
2189 if (gamemode == GAME_ROGUE)
2191 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2192 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2193 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2197 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2201 if (gamemode == GAME_ROGUE)
2203 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2204 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2205 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2211 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2216 for (i=0 ; i<prog->num_edicts ; i++)
2218 e = PRVM_EDICT_NUM(i);
2219 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2222 Con_Print("No viewthing on map\n");
2231 static void Host_Viewmodel_f (void)
2233 prvm_prog_t *prog = SVVM_prog;
2240 e = FindViewthing(prog);
2243 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2244 if (m && m->loaded && m->Draw)
2246 PRVM_serveredictfloat(e, frame) = 0;
2247 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2250 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2259 static void Host_Viewframe_f (void)
2261 prvm_prog_t *prog = SVVM_prog;
2269 e = FindViewthing(prog);
2272 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2274 f = atoi(Cmd_Argv(1));
2275 if (f >= m->numframes)
2278 PRVM_serveredictfloat(e, frame) = f;
2283 static void PrintFrameName (dp_model_t *m, int frame)
2286 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2288 Con_Printf("frame %i\n", frame);
2296 static void Host_Viewnext_f (void)
2298 prvm_prog_t *prog = SVVM_prog;
2305 e = FindViewthing(prog);
2308 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2310 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2311 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2312 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2314 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2323 static void Host_Viewprev_f (void)
2325 prvm_prog_t *prog = SVVM_prog;
2332 e = FindViewthing(prog);
2335 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2337 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2338 if (PRVM_serveredictfloat(e, frame) < 0)
2339 PRVM_serveredictfloat(e, frame) = 0;
2341 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2346 ===============================================================================
2350 ===============================================================================
2359 static void Host_Startdemos_f (void)
2363 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2369 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2372 Con_DPrintf("%i demo(s) in loop\n", c);
2374 for (i=1 ; i<c+1 ; i++)
2375 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2377 // LordHavoc: clear the remaining slots
2378 for (;i <= MAX_DEMOS;i++)
2379 cls.demos[i-1][0] = 0;
2381 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2395 Return to looping demos
2398 static void Host_Demos_f (void)
2400 if (cls.state == ca_dedicated)
2402 if (cls.demonum == -1)
2412 Return to looping demos
2415 static void Host_Stopdemo_f (void)
2417 if (!cls.demoplayback)
2420 Host_ShutdownServer ();
2423 static void Host_SendCvar_f (void)
2427 const char *cvarname;
2433 cvarname = Cmd_Argv(1);
2434 if (cls.state == ca_connected)
2436 c = Cvar_FindVar(cvarname);
2437 // LordHavoc: if there is no such cvar or if it is private, send a
2438 // reply indicating that it has no value
2439 if(!c || (c->flags & CVAR_PRIVATE))
2440 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2442 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2445 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2449 if (cls.state != ca_dedicated)
2453 for(;i<svs.maxclients;i++)
2454 if(svs.clients[i].active && svs.clients[i].netconnection)
2456 host_client = &svs.clients[i];
2457 Host_ClientCommands("sendcvar %s\n", cvarname);
2462 static void MaxPlayers_f(void)
2466 if (Cmd_Argc() != 2)
2468 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2474 Con_Print("maxplayers can not be changed while a server is running.\n");
2475 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2478 n = atoi(Cmd_Argv(1));
2479 n = bound(1, n, MAX_SCOREBOARD);
2480 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2482 svs.maxclients_next = n;
2484 Cvar_Set ("deathmatch", "0");
2486 Cvar_Set ("deathmatch", "1");
2490 =====================
2493 ProQuake rcon support
2494 =====================
2496 static void Host_PQRcon_f (void)
2501 lhnetsocket_t *mysocket;
2502 char peer_address[64];
2504 if (Cmd_Argc() == 1)
2506 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2510 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2512 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2516 e = strchr(rcon_password.string, ' ');
2517 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2521 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2525 if (!rcon_address.string[0])
2527 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2530 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2532 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2533 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2537 unsigned char bufdata[64];
2540 MSG_WriteLong(&buf, 0);
2541 MSG_WriteByte(&buf, CCREQ_RCON);
2542 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2543 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2544 MSG_WriteString(&buf, Cmd_Args());
2545 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2546 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2551 //=============================================================================
2553 // QuakeWorld commands
2556 =====================
2559 Send the rest of the command line over as
2560 an unconnected command.
2561 =====================
2563 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2568 lhnetsocket_t *mysocket;
2571 if (Cmd_Argc() == 1)
2573 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2577 if (!rcon_password.string || !rcon_password.string[0])
2579 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2583 e = strchr(rcon_password.string, ' ');
2584 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2587 to = cls.netcon->peeraddress;
2590 if (!rcon_address.string[0])
2592 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2595 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2597 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2598 if (mysocket && Cmd_Args()[0])
2600 // simply put together the rcon packet and send it
2601 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2603 if(cls.rcon_commands[cls.rcon_ringpos][0])
2606 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2607 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]);
2608 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2611 for (i = 0;i < MAX_RCONS;i++)
2612 if(cls.rcon_commands[i][0])
2613 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2617 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2618 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2619 cls.rcon_addresses[cls.rcon_ringpos] = to;
2620 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2621 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2623 else if(rcon_secure.integer > 0)
2627 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2628 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2629 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2632 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2633 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2638 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2644 ====================
2647 user <name or userid>
2649 Dump userdata / masterdata for a user
2650 ====================
2652 static void Host_User_f (void) // credit: taken from QuakeWorld
2657 if (Cmd_Argc() != 2)
2659 Con_Printf ("Usage: user <username / userid>\n");
2663 uid = atoi(Cmd_Argv(1));
2665 for (i = 0;i < cl.maxclients;i++)
2667 if (!cl.scores[i].name[0])
2669 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2671 InfoString_Print(cl.scores[i].qw_userinfo);
2675 Con_Printf ("User not in server.\n");
2679 ====================
2682 Dump userids for all current players
2683 ====================
2685 static void Host_Users_f (void) // credit: taken from QuakeWorld
2691 Con_Printf ("userid frags name\n");
2692 Con_Printf ("------ ----- ----\n");
2693 for (i = 0;i < cl.maxclients;i++)
2695 if (cl.scores[i].name[0])
2697 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2702 Con_Printf ("%i total users\n", c);
2707 Host_FullServerinfo_f
2709 Sent by server when serverinfo changes
2712 // TODO: shouldn't this be a cvar instead?
2713 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2716 if (Cmd_Argc() != 2)
2718 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2722 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2723 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2724 cl.qw_teamplay = atoi(temp);
2731 Allow clients to change userinfo
2735 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2742 if (Cmd_Argc() != 2)
2744 Con_Printf ("fullinfo <complete info string>\n");
2754 while (*s && *s != '\\')
2760 Con_Printf ("MISSING VALUE\n");
2766 while (*s && *s != '\\')
2773 CL_SetInfo(key, value, false, false, false, false);
2781 Allow clients to change userinfo
2784 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2786 if (Cmd_Argc() == 1)
2788 InfoString_Print(cls.userinfo);
2791 if (Cmd_Argc() != 3)
2793 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2796 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2800 ====================
2803 packet <destination> <contents>
2805 Contents allows \n escape character
2806 ====================
2808 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2814 lhnetaddress_t address;
2815 lhnetsocket_t *mysocket;
2817 if (Cmd_Argc() != 3)
2819 Con_Printf ("packet <destination> <contents>\n");
2823 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2825 Con_Printf ("Bad address\n");
2831 send[0] = send[1] = send[2] = send[3] = -1;
2833 l = (int)strlen (in);
2834 for (i=0 ; i<l ; i++)
2836 if (out >= send + sizeof(send) - 1)
2838 if (in[i] == '\\' && in[i+1] == 'n')
2843 else if (in[i] == '\\' && in[i+1] == '0')
2848 else if (in[i] == '\\' && in[i+1] == 't')
2853 else if (in[i] == '\\' && in[i+1] == 'r')
2858 else if (in[i] == '\\' && in[i+1] == '"')
2867 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2869 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2871 NetConn_Write(mysocket, send, out - send, &address);
2875 ====================
2878 Send back ping and packet loss update for all current players to this player
2879 ====================
2881 void Host_Pings_f (void)
2883 int i, j, ping, packetloss, movementloss;
2886 if (!host_client->netconnection)
2889 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2891 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2892 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2894 for (i = 0;i < svs.maxclients;i++)
2898 if (svs.clients[i].netconnection)
2900 for (j = 0;j < NETGRAPH_PACKETS;j++)
2901 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2903 for (j = 0;j < NETGRAPH_PACKETS;j++)
2904 if (svs.clients[i].movement_count[j] < 0)
2907 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2908 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2909 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2910 ping = bound(0, ping, 9999);
2911 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2913 // send qw_svc_updateping and qw_svc_updatepl messages
2914 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2915 MSG_WriteShort(&host_client->netconnection->message, ping);
2916 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2917 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2921 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2923 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2925 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2926 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2929 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2930 MSG_WriteString(&host_client->netconnection->message, "\n");
2933 static void Host_PingPLReport_f(void)
2938 if (l > cl.maxclients)
2940 for (i = 0;i < l;i++)
2942 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2943 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2944 if(errbyte && *errbyte == ',')
2945 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2947 cl.scores[i].qw_movementloss = 0;
2951 //=============================================================================
2958 void Host_InitCommands (void)
2960 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2962 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2963 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2964 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2965 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2966 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2967 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2968 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2969 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2970 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2971 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2972 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2973 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)");
2974 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2975 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2976 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2977 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2978 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2979 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
2980 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2981 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2982 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2983 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2985 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2986 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2987 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2989 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2990 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2991 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2992 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2994 Cvar_RegisterVariable (&cl_name);
2995 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2996 Cvar_RegisterVariable (&cl_color);
2997 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2998 Cvar_RegisterVariable (&cl_rate);
2999 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3000 Cvar_RegisterVariable (&cl_pmodel);
3001 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3003 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3004 Cvar_RegisterVariable (&cl_playermodel);
3005 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3006 Cvar_RegisterVariable (&cl_playerskin);
3007 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3009 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3010 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3011 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)");
3012 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3014 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3016 Cvar_RegisterVariable (&rcon_password);
3017 Cvar_RegisterVariable (&rcon_address);
3018 Cvar_RegisterVariable (&rcon_secure);
3019 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3020 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");
3021 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");
3022 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)");
3023 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3024 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3025 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3026 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3027 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3028 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3029 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3030 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3032 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)");
3033 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)");
3035 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)");
3036 Cvar_RegisterVariable (&r_fixtrans_auto);
3038 Cvar_RegisterVariable (&team);
3039 Cvar_RegisterVariable (&skin);
3040 Cvar_RegisterVariable (&noaim);
3042 Cvar_RegisterVariable(&sv_cheats);
3043 Cvar_RegisterVariable(&sv_adminnick);
3044 Cvar_RegisterVariable(&sv_status_privacy);
3045 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3046 Cvar_RegisterVariable(&sv_namechangetimer);
3049 void Host_NoOperation_f(void)