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 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"};
38 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"};
39 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"};
40 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
41 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
42 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
43 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
44 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)"};
45 qboolean allowcheats = false;
47 extern qboolean host_shuttingdown;
48 extern cvar_t developer_entityparsing;
56 void Host_Quit_f (void)
59 Con_Printf("shutting down already!\n");
69 void Host_Status_f (void)
73 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
74 void (*print) (const char *fmt, ...);
78 if (cmd_source == src_command)
80 // if running a client, try to send over network so the client's status report parser will see the report
81 if (cls.state == ca_connected)
83 Cmd_ForwardToServer ();
89 print = SV_ClientPrintf;
94 if(cmd_source == src_command)
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());
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" , 22);
154 strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 22);
156 frags = client->frags;
158 if(sv_status_show_qcstatus.integer && prog->fieldoffsets.clientstatus >= 0)
160 const char *str = PRVM_E_STRING(PRVM_EDICT_NUM(i + 1), prog->fieldoffsets.clientstatus);
166 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
167 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
171 frags = atoi(qcstatus);
175 if (in == 0) // default layout
177 // LordHavoc: we must use multiple prints for ProQuake compatibility
178 print ("#%-3u ", i+1);
179 print ("%-16.16s ", client->name);
180 print ("%4i ", frags);
181 print ("%2i:%02i:%02i\n ", hours, minutes, seconds);
183 // print ("#%-3u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
184 // print (" %s\n", ip);
186 else if (in == 1) // extended layout
188 print ("%s%-21s %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);
190 else if (in == 2) // reduced layout
192 print ("%s%-21s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
196 if(cmd_source == src_command)
205 Sets client to godmode
208 void Host_God_f (void)
212 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
216 host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_GODMODE;
217 if (!((int)host_client->edict->fields.server->flags & FL_GODMODE) )
218 SV_ClientPrint("godmode OFF\n");
220 SV_ClientPrint("godmode ON\n");
223 void Host_Notarget_f (void)
227 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
231 host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_NOTARGET;
232 if (!((int)host_client->edict->fields.server->flags & FL_NOTARGET) )
233 SV_ClientPrint("notarget OFF\n");
235 SV_ClientPrint("notarget ON\n");
238 qboolean noclip_anglehack;
240 void Host_Noclip_f (void)
244 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
248 if (host_client->edict->fields.server->movetype != MOVETYPE_NOCLIP)
250 noclip_anglehack = true;
251 host_client->edict->fields.server->movetype = MOVETYPE_NOCLIP;
252 SV_ClientPrint("noclip ON\n");
256 noclip_anglehack = false;
257 host_client->edict->fields.server->movetype = MOVETYPE_WALK;
258 SV_ClientPrint("noclip OFF\n");
266 Sets client to flymode
269 void Host_Fly_f (void)
273 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
277 if (host_client->edict->fields.server->movetype != MOVETYPE_FLY)
279 host_client->edict->fields.server->movetype = MOVETYPE_FLY;
280 SV_ClientPrint("flymode ON\n");
284 host_client->edict->fields.server->movetype = MOVETYPE_WALK;
285 SV_ClientPrint("flymode OFF\n");
296 void Host_Pings_f (void); // called by Host_Ping_f
297 void Host_Ping_f (void)
301 void (*print) (const char *fmt, ...);
303 if (cmd_source == src_command)
305 // if running a client, try to send over network so the client's ping report parser will see the report
306 if (cls.state == ca_connected)
308 Cmd_ForwardToServer ();
314 print = SV_ClientPrintf;
319 print("Client ping times:\n");
320 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
324 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
327 // 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)
328 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
333 ===============================================================================
337 ===============================================================================
341 ======================
346 command from the console. Active clients are kicked off.
347 ======================
349 void Host_Map_f (void)
351 char level[MAX_QPATH];
355 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
359 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
360 if (gamemode == GAME_DELUXEQUAKE)
361 Cvar_Set("warpmark", "");
363 cls.demonum = -1; // stop demo loop in case this fails
366 Host_ShutdownServer();
368 if(svs.maxclients != svs.maxclients_next)
370 svs.maxclients = svs.maxclients_next;
372 Mem_Free(svs.clients);
373 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
377 if (key_dest == key_menu || key_dest == key_menu_grabbed)
381 svs.serverflags = 0; // haven't completed an episode yet
382 allowcheats = sv_cheats.integer != 0;
383 strlcpy(level, Cmd_Argv(1), sizeof(level));
384 SV_SpawnServer(level);
385 if (sv.active && cls.state == ca_disconnected)
386 CL_EstablishConnection("local:1", -2);
393 Goes to a new map, taking all clients along
396 void Host_Changelevel_f (void)
398 char level[MAX_QPATH];
402 Con_Print("changelevel <levelname> : continue game on a new level\n");
412 if (key_dest == key_menu || key_dest == key_menu_grabbed)
417 SV_SaveSpawnparms ();
419 allowcheats = sv_cheats.integer != 0;
420 strlcpy(level, Cmd_Argv(1), sizeof(level));
421 SV_SpawnServer(level);
422 if (sv.active && cls.state == ca_disconnected)
423 CL_EstablishConnection("local:1", -2);
430 Restarts the current server for a dead player
433 void Host_Restart_f (void)
435 char mapname[MAX_QPATH];
439 Con_Print("restart : restart current level\n");
444 Con_Print("Only the server may restart\n");
449 if (key_dest == key_menu || key_dest == key_menu_grabbed)
453 allowcheats = sv_cheats.integer != 0;
454 strlcpy(mapname, sv.name, sizeof(mapname));
455 SV_SpawnServer(mapname);
456 if (sv.active && cls.state == ca_disconnected)
457 CL_EstablishConnection("local:1", -2);
464 This command causes the client to wait for the signon messages again.
465 This is sent just before a server changes levels
468 void Host_Reconnect_f (void)
471 // if not connected, reconnect to the most recent server
474 // if we have connected to a server recently, the userinfo
475 // will still contain its IP address, so get the address...
476 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
478 CL_EstablishConnection(temp, -1);
480 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
483 // if connected, do something based on protocol
484 if (cls.protocol == PROTOCOL_QUAKEWORLD)
486 // quakeworld can just re-login
487 if (cls.qw_downloadmemory) // don't change when downloading
492 if (cls.state == ca_connected && cls.signon < SIGNONS)
494 Con_Printf("reconnecting...\n");
495 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
496 MSG_WriteString(&cls.netcon->message, "new");
501 // netquake uses reconnect on level changes (silly)
504 Con_Print("reconnect : wait for signon messages again\n");
509 Con_Print("reconnect: no signon, ignoring reconnect\n");
512 cls.signon = 0; // need new connection messages
517 =====================
520 User command to connect to server
521 =====================
523 void Host_Connect_f (void)
527 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
530 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
531 if(rcon_secure.integer <= 0)
532 Cvar_SetQuick(&rcon_password, "");
533 CL_EstablishConnection(Cmd_Argv(1), 2);
538 ===============================================================================
542 ===============================================================================
545 #define SAVEGAME_VERSION 5
547 void Host_Savegame_to (const char *name)
550 int i, k, l, lightstyles = 64;
551 char comment[SAVEGAME_COMMENT_LENGTH+1];
552 char line[MAX_INPUTLINE];
556 // first we have to figure out if this can be saved in 64 lightstyles
557 // (for Quake compatibility)
558 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
559 if (sv.lightstyles[i][0])
562 isserver = !strcmp(PRVM_NAME, "server");
564 Con_Printf("Saving game to %s...\n", name);
565 f = FS_OpenRealFile(name, "wb", false);
568 Con_Print("ERROR: couldn't open.\n");
572 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
574 memset(comment, 0, sizeof(comment));
576 dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog->edicts->fields.server->message), (int)prog->globals.server->killed_monsters, (int)prog->globals.server->total_monsters);
578 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_NAME);
579 // convert space to _ to make stdio happy
580 // LordHavoc: convert control characters to _ as well
581 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
582 if (ISWHITESPACEORCONTROL(comment[i]))
584 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
586 FS_Printf(f, "%s\n", comment);
589 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
590 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
591 FS_Printf(f, "%d\n", current_skill);
592 FS_Printf(f, "%s\n", sv.name);
593 FS_Printf(f, "%f\n",sv.time);
597 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
598 FS_Printf(f, "(dummy)\n");
599 FS_Printf(f, "%d\n", 0);
600 FS_Printf(f, "%s\n", "(dummy)");
601 FS_Printf(f, "%f\n", realtime);
604 // write the light styles
605 for (i=0 ; i<lightstyles ; i++)
607 if (isserver && sv.lightstyles[i][0])
608 FS_Printf(f, "%s\n", sv.lightstyles[i]);
613 PRVM_ED_WriteGlobals (f);
614 for (i=0 ; i<prog->num_edicts ; i++)
616 FS_Printf(f,"// edict %d\n", i);
617 //Con_Printf("edict %d...\n", i);
618 PRVM_ED_Write (f, PRVM_EDICT_NUM(i));
623 FS_Printf(f,"// DarkPlaces extended savegame\n");
624 // darkplaces extension - extra lightstyles, support for color lightstyles
625 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
626 if (isserver && sv.lightstyles[i][0])
627 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
629 // darkplaces extension - model precaches
630 for (i=1 ; i<MAX_MODELS ; i++)
631 if (sv.model_precache[i][0])
632 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
634 // darkplaces extension - sound precaches
635 for (i=1 ; i<MAX_SOUNDS ; i++)
636 if (sv.sound_precache[i][0])
637 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
639 // darkplaces extension - save buffers
640 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); i++)
642 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
643 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
645 for(k = 0; k < stringbuffer->num_strings; k++)
647 if (!stringbuffer->strings[k])
649 // Parse the string a bit to turn special characters
650 // (like newline, specifically) into escape codes
651 s = stringbuffer->strings[k];
652 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
679 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
687 Con_Print("done.\n");
695 void Host_Savegame_f (void)
697 char name[MAX_QPATH];
701 Con_Print("Can't save - no server running.\n");
707 // singleplayer checks
710 Con_Print("Can't save in intermission.\n");
714 if (svs.clients[0].active && svs.clients[0].edict->fields.server->deadflag)
716 Con_Print("Can't savegame with a dead player\n");
721 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");
725 Con_Print("save <savename> : save a game\n");
729 if (strstr(Cmd_Argv(1), ".."))
731 Con_Print("Relative pathnames are not allowed.\n");
735 strlcpy (name, Cmd_Argv(1), sizeof (name));
736 FS_DefaultExtension (name, ".sav", sizeof (name));
739 Host_Savegame_to(name);
750 void Host_Loadgame_f (void)
752 char filename[MAX_QPATH];
753 char mapname[MAX_QPATH];
763 float spawn_parms[NUM_SPAWN_PARMS];
764 prvm_stringbuffer_t *stringbuffer;
769 Con_Print("load <savename> : load a game\n");
773 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
774 FS_DefaultExtension (filename, ".sav", sizeof (filename));
776 Con_Printf("Loading game from %s...\n", filename);
778 // stop playing demos
779 if (cls.demoplayback)
783 if (key_dest == key_menu || key_dest == key_menu_grabbed)
787 cls.demonum = -1; // stop demo loop in case this fails
789 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
792 Con_Print("ERROR: couldn't open.\n");
796 if(developer_entityparsing.integer)
797 Con_Printf("Host_Loadgame_f: loading version\n");
800 COM_ParseToken_Simple(&t, false, false);
801 version = atoi(com_token);
802 if (version != SAVEGAME_VERSION)
805 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
809 if(developer_entityparsing.integer)
810 Con_Printf("Host_Loadgame_f: loading description\n");
813 COM_ParseToken_Simple(&t, false, false);
815 for (i = 0;i < NUM_SPAWN_PARMS;i++)
817 COM_ParseToken_Simple(&t, false, false);
818 spawn_parms[i] = atof(com_token);
821 COM_ParseToken_Simple(&t, false, false);
822 // this silliness is so we can load 1.06 save files, which have float skill values
823 current_skill = (int)(atof(com_token) + 0.5);
824 Cvar_SetValue ("skill", (float)current_skill);
826 if(developer_entityparsing.integer)
827 Con_Printf("Host_Loadgame_f: loading mapname\n");
830 COM_ParseToken_Simple(&t, false, false);
831 strlcpy (mapname, com_token, sizeof(mapname));
833 if(developer_entityparsing.integer)
834 Con_Printf("Host_Loadgame_f: loading time\n");
837 COM_ParseToken_Simple(&t, false, false);
838 time = atof(com_token);
840 allowcheats = sv_cheats.integer != 0;
842 if(developer_entityparsing.integer)
843 Con_Printf("Host_Loadgame_f: spawning server\n");
845 SV_SpawnServer (mapname);
849 Con_Print("Couldn't load map\n");
852 sv.paused = true; // pause until all clients connect
855 if(developer_entityparsing.integer)
856 Con_Printf("Host_Loadgame_f: loading light styles\n");
858 // load the light styles
864 for (i = 0;i < MAX_LIGHTSTYLES;i++)
868 COM_ParseToken_Simple(&t, false, false);
869 // if this is a 64 lightstyle savegame produced by Quake, stop now
870 // we have to check this because darkplaces may save more than 64
871 if (com_token[0] == '{')
876 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
879 if(developer_entityparsing.integer)
880 Con_Printf("Host_Loadgame_f: skipping until globals\n");
882 // now skip everything before the first opening brace
883 // (this is for forward compatibility, so that older versions (at
884 // least ones with this fix) can load savegames with extra data before the
885 // first brace, as might be produced by a later engine version)
889 if (!COM_ParseToken_Simple(&t, false, false))
891 if (com_token[0] == '{')
898 // unlink all entities
899 World_UnlinkAll(&sv.world);
901 // load the edicts out of the savegame file
906 while (COM_ParseToken_Simple(&t, false, false))
907 if (!strcmp(com_token, "}"))
909 if (!COM_ParseToken_Simple(&start, false, false))
914 if (strcmp(com_token,"{"))
917 Host_Error ("First token isn't a brace");
922 if(developer_entityparsing.integer)
923 Con_Printf("Host_Loadgame_f: loading globals\n");
925 // parse the global vars
926 PRVM_ED_ParseGlobals (start);
928 // restore the autocvar globals
929 Cvar_UpdateAllAutoCvars();
934 if (entnum >= MAX_EDICTS)
937 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
939 while (entnum >= prog->max_edicts)
940 PRVM_MEM_IncreaseEdicts();
941 ent = PRVM_EDICT_NUM(entnum);
942 memset (ent->fields.server, 0, prog->progs->entityfields * 4);
943 ent->priv.server->free = false;
945 if(developer_entityparsing.integer)
946 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
948 PRVM_ED_ParseEdict (start, ent);
950 // link it into the bsp tree
951 if (!ent->priv.server->free)
959 prog->num_edicts = entnum;
962 for (i = 0;i < NUM_SPAWN_PARMS;i++)
963 svs.clients[0].spawn_parms[i] = spawn_parms[i];
965 if(developer_entityparsing.integer)
966 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
968 // read extended data if present
969 // the extended data is stored inside a /* */ comment block, which the
970 // parser intentionally skips, so we have to check for it manually here
973 while (*end == '\r' || *end == '\n')
975 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
977 if(developer_entityparsing.integer)
978 Con_Printf("Host_Loadgame_f: loading extended data\n");
980 Con_Printf("Loading extended DarkPlaces savegame\n");
982 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
983 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
984 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
985 while (COM_ParseToken_Simple(&t, false, false))
987 if (!strcmp(com_token, "sv.lightstyles"))
989 COM_ParseToken_Simple(&t, false, false);
991 COM_ParseToken_Simple(&t, false, false);
992 if (i >= 0 && i < MAX_LIGHTSTYLES)
993 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
995 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
997 else if (!strcmp(com_token, "sv.model_precache"))
999 COM_ParseToken_Simple(&t, false, false);
1000 i = atoi(com_token);
1001 COM_ParseToken_Simple(&t, false, false);
1002 if (i >= 0 && i < MAX_MODELS)
1004 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1005 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1008 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1010 else if (!strcmp(com_token, "sv.sound_precache"))
1012 COM_ParseToken_Simple(&t, false, false);
1013 i = atoi(com_token);
1014 COM_ParseToken_Simple(&t, false, false);
1015 if (i >= 0 && i < MAX_SOUNDS)
1016 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1018 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1020 else if (!strcmp(com_token, "sv.bufstr"))
1022 COM_ParseToken_Simple(&t, false, false);
1023 i = atoi(com_token);
1024 COM_ParseToken_Simple(&t, false, false);
1025 k = atoi(com_token);
1026 COM_ParseToken_Simple(&t, false, false);
1027 stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
1028 // VorteX: nasty code, cleanup required
1029 // create buffer at this index
1031 stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i);
1033 Con_Printf("cant write string %i into buffer %i\n", k, i);
1036 // code copied from VM_bufstr_set
1038 if (stringbuffer->max_strings <= i)
1040 char **oldstrings = stringbuffer->strings;
1041 stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128);
1042 while (stringbuffer->max_strings <= i)
1043 stringbuffer->max_strings *= 2;
1044 stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0]));
1045 if (stringbuffer->num_strings > 0)
1046 memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0]));
1048 Mem_Free(oldstrings);
1051 stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1);
1052 if(stringbuffer->strings[k])
1053 Mem_Free(stringbuffer->strings[k]);
1054 stringbuffer->strings[k] = NULL;
1055 alloclen = strlen(com_token) + 1;
1056 stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen);
1057 memcpy(stringbuffer->strings[k], com_token, alloclen);
1060 // skip any trailing text or unrecognized commands
1061 while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n"))
1068 if(developer_entityparsing.integer)
1069 Con_Printf("Host_Loadgame_f: finished\n");
1073 // make sure we're connected to loopback
1074 if (sv.active && cls.state == ca_disconnected)
1075 CL_EstablishConnection("local:1", -2);
1078 //============================================================================
1081 ======================
1083 ======================
1085 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1086 void Host_Name_f (void)
1089 qboolean valid_colors;
1090 const char *newNameSource;
1091 char newName[sizeof(host_client->name)];
1093 if (Cmd_Argc () == 1)
1095 Con_Printf("name: %s\n", cl_name.string);
1099 if (Cmd_Argc () == 2)
1100 newNameSource = Cmd_Argv(1);
1102 newNameSource = Cmd_Args();
1104 strlcpy(newName, newNameSource, sizeof(newName));
1106 if (cmd_source == src_command)
1108 Cvar_Set ("_cl_name", newName);
1109 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1111 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1112 Con_Printf("name: %s\n", cl_name.string);
1117 if (realtime < host_client->nametime)
1119 SV_ClientPrintf("You can't change name more than once every 5 seconds!\n");
1123 host_client->nametime = realtime + 5;
1125 // point the string back at updateclient->name to keep it safe
1126 strlcpy (host_client->name, newName, sizeof (host_client->name));
1128 for (i = 0, j = 0;host_client->name[i];i++)
1129 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1130 host_client->name[j++] = host_client->name[i];
1131 host_client->name[j] = 0;
1133 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1134 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1136 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1137 host_client->name[sizeof(host_client->name) - 1] = 0;
1138 host_client->name[0] = STRING_COLOR_TAG;
1139 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1142 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1143 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1146 l = strlen(host_client->name);
1147 if(l < sizeof(host_client->name) - 1)
1149 // duplicate the color tag to escape it
1150 host_client->name[i] = STRING_COLOR_TAG;
1151 host_client->name[i+1] = 0;
1152 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1156 // remove the last character to fix the color code
1157 host_client->name[l-1] = 0;
1158 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1162 // find the last color tag offset and decide if we need to add a reset tag
1163 for (i = 0, j = -1;host_client->name[i];i++)
1165 if (host_client->name[i] == STRING_COLOR_TAG)
1167 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1170 // if this happens to be a reset tag then we don't need one
1171 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1176 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]))
1182 if (host_client->name[i+1] == STRING_COLOR_TAG)
1189 // does not end in the default color string, so add it
1190 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1191 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1193 host_client->edict->fields.server->netname = PRVM_SetEngineString(host_client->name);
1194 if (strcmp(host_client->old_name, host_client->name))
1196 if (host_client->spawned)
1197 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1198 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1199 // send notification to all clients
1200 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1201 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1202 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1203 SV_WriteNetnameIntoDemo(host_client);
1208 ======================
1210 ======================
1212 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1213 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1214 void Host_Playermodel_f (void)
1217 char newPath[sizeof(host_client->playermodel)];
1219 if (Cmd_Argc () == 1)
1221 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1225 if (Cmd_Argc () == 2)
1226 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1228 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1230 for (i = 0, j = 0;newPath[i];i++)
1231 if (newPath[i] != '\r' && newPath[i] != '\n')
1232 newPath[j++] = newPath[i];
1235 if (cmd_source == src_command)
1237 Cvar_Set ("_cl_playermodel", newPath);
1242 if (realtime < host_client->nametime)
1244 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1248 host_client->nametime = realtime + 5;
1251 // point the string back at updateclient->name to keep it safe
1252 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1253 if( prog->fieldoffsets.playermodel >= 0 )
1254 PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playermodel)->string = PRVM_SetEngineString(host_client->playermodel);
1255 if (strcmp(host_client->old_model, host_client->playermodel))
1257 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1258 /*// send notification to all clients
1259 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1260 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1261 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1266 ======================
1268 ======================
1270 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1271 void Host_Playerskin_f (void)
1274 char newPath[sizeof(host_client->playerskin)];
1276 if (Cmd_Argc () == 1)
1278 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1282 if (Cmd_Argc () == 2)
1283 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1285 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1287 for (i = 0, j = 0;newPath[i];i++)
1288 if (newPath[i] != '\r' && newPath[i] != '\n')
1289 newPath[j++] = newPath[i];
1292 if (cmd_source == src_command)
1294 Cvar_Set ("_cl_playerskin", newPath);
1299 if (realtime < host_client->nametime)
1301 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1305 host_client->nametime = realtime + 5;
1308 // point the string back at updateclient->name to keep it safe
1309 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1310 if( prog->fieldoffsets.playerskin >= 0 )
1311 PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playerskin)->string = PRVM_SetEngineString(host_client->playerskin);
1312 if (strcmp(host_client->old_skin, host_client->playerskin))
1314 //if (host_client->spawned)
1315 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1316 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1317 /*// send notification to all clients
1318 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1319 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1320 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1324 void Host_Version_f (void)
1326 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1329 void Host_Say(qboolean teamonly)
1335 // LordHavoc: long say messages
1337 qboolean fromServer = false;
1339 if (cmd_source == src_command)
1341 if (cls.state == ca_dedicated)
1348 Cmd_ForwardToServer ();
1353 if (Cmd_Argc () < 2)
1356 if (!teamplay.integer)
1366 // note this uses the chat prefix \001
1367 if (!fromServer && !teamonly)
1368 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1369 else if (!fromServer && teamonly)
1370 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1371 else if(*(sv_adminnick.string))
1372 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1374 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1375 p2 = text + strlen(text);
1376 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1378 if (p2[-1] == '\"' && quoted)
1383 strlcat(text, "\n", sizeof(text));
1385 // note: save is not a valid edict if fromServer is true
1387 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1388 if (host_client->active && (!teamonly || host_client->edict->fields.server->team == save->edict->fields.server->team))
1389 SV_ClientPrint(text);
1392 if (cls.state == ca_dedicated)
1393 Con_Print(&text[1]);
1397 void Host_Say_f(void)
1403 void Host_Say_Team_f(void)
1409 void Host_Tell_f(void)
1411 const char *playername_start = NULL;
1412 size_t playername_length = 0;
1413 int playernumber = 0;
1416 const char *p1, *p2;
1417 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1418 qboolean fromServer = false;
1420 if (cmd_source == src_command)
1422 if (cls.state == ca_dedicated)
1426 Cmd_ForwardToServer ();
1431 if (Cmd_Argc () < 2)
1434 // note this uses the chat prefix \001
1436 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1437 else if(*(sv_adminnick.string))
1438 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1440 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1443 p2 = p1 + strlen(p1);
1444 // remove the target name
1445 while (p1 < p2 && *p1 == ' ')
1450 while (p1 < p2 && *p1 == ' ')
1452 while (p1 < p2 && isdigit(*p1))
1454 playernumber = playernumber * 10 + (*p1 - '0');
1462 playername_start = p1;
1463 while (p1 < p2 && *p1 != '"')
1465 playername_length = p1 - playername_start;
1471 playername_start = p1;
1472 while (p1 < p2 && *p1 != ' ')
1474 playername_length = p1 - playername_start;
1476 while (p1 < p2 && *p1 == ' ')
1478 if(playername_start)
1480 // set playernumber to the right client
1482 if(playername_length >= sizeof(namebuf))
1485 Con_Print("Host_Tell: too long player name/ID\n");
1487 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1490 memcpy(namebuf, playername_start, playername_length);
1491 namebuf[playername_length] = 0;
1492 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1494 if (!svs.clients[playernumber].active)
1496 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1500 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1503 Con_Print("Host_Tell: invalid player name/ID\n");
1505 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1508 // remove trailing newlines
1509 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1511 // remove quotes if present
1517 else if (fromServer)
1518 Con_Print("Host_Tell: missing end quote\n");
1520 SV_ClientPrint("Host_Tell: missing end quote\n");
1522 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1525 return; // empty say
1526 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1532 host_client = svs.clients + playernumber;
1533 SV_ClientPrint(text);
1543 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1544 void Host_Color(int changetop, int changebottom)
1546 int top, bottom, playercolor;
1548 // get top and bottom either from the provided values or the current values
1549 // (allows changing only top or bottom, or both at once)
1550 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1551 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1555 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1561 playercolor = top*16 + bottom;
1563 if (cmd_source == src_command)
1565 Cvar_SetValueQuick(&cl_color, playercolor);
1569 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1572 if (host_client->edict && prog->funcoffsets.SV_ChangeTeam)
1574 Con_DPrint("Calling SV_ChangeTeam\n");
1575 prog->globals.server->time = sv.time;
1576 prog->globals.generic[OFS_PARM0] = playercolor;
1577 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1578 PRVM_ExecuteProgram(prog->funcoffsets.SV_ChangeTeam, "QC function SV_ChangeTeam is missing");
1583 if (host_client->edict)
1585 if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.clientcolors)))
1586 val->_float = playercolor;
1587 host_client->edict->fields.server->team = bottom + 1;
1589 host_client->colors = playercolor;
1590 if (host_client->old_colors != host_client->colors)
1592 host_client->old_colors = host_client->colors;
1593 // send notification to all clients
1594 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1595 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1596 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1601 void Host_Color_f(void)
1605 if (Cmd_Argc() == 1)
1607 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1608 Con_Print("color <0-15> [0-15]\n");
1612 if (Cmd_Argc() == 2)
1613 top = bottom = atoi(Cmd_Argv(1));
1616 top = atoi(Cmd_Argv(1));
1617 bottom = atoi(Cmd_Argv(2));
1619 Host_Color(top, bottom);
1622 void Host_TopColor_f(void)
1624 if (Cmd_Argc() == 1)
1626 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1627 Con_Print("topcolor <0-15>\n");
1631 Host_Color(atoi(Cmd_Argv(1)), -1);
1634 void Host_BottomColor_f(void)
1636 if (Cmd_Argc() == 1)
1638 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1639 Con_Print("bottomcolor <0-15>\n");
1643 Host_Color(-1, atoi(Cmd_Argv(1)));
1646 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1647 void Host_Rate_f(void)
1651 if (Cmd_Argc() != 2)
1653 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1654 Con_Print("rate <bytespersecond>\n");
1658 rate = atoi(Cmd_Argv(1));
1660 if (cmd_source == src_command)
1662 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1666 host_client->rate = rate;
1674 void Host_Kill_f (void)
1676 if (host_client->edict->fields.server->health <= 0)
1678 SV_ClientPrint("Can't suicide -- already dead!\n");
1682 prog->globals.server->time = sv.time;
1683 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1684 PRVM_ExecuteProgram (prog->globals.server->ClientKill, "QC function ClientKill is missing");
1693 void Host_Pause_f (void)
1695 if (!pausable.integer)
1696 SV_ClientPrint("Pause not allowed.\n");
1700 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1701 // send notification to all clients
1702 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1703 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1708 ======================
1710 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1711 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1712 ======================
1714 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)"};
1715 static void Host_PModel_f (void)
1720 if (Cmd_Argc () == 1)
1722 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1725 i = atoi(Cmd_Argv(1));
1727 if (cmd_source == src_command)
1729 if (cl_pmodel.integer == i)
1731 Cvar_SetValue ("_cl_pmodel", i);
1732 if (cls.state == ca_connected)
1733 Cmd_ForwardToServer ();
1737 if (host_client->edict && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.pmodel)))
1741 //===========================================================================
1749 void Host_PreSpawn_f (void)
1751 if (host_client->spawned)
1753 Con_Print("prespawn not valid -- already spawned\n");
1757 if (host_client->netconnection)
1759 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1760 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1761 MSG_WriteByte (&host_client->netconnection->message, 2);
1762 host_client->sendsignon = 0; // enable unlimited sends again
1765 // reset the name change timer because the client will send name soon
1766 host_client->nametime = 0;
1774 void Host_Spawn_f (void)
1778 int stats[MAX_CL_STATS];
1780 if (host_client->spawned)
1782 Con_Print("Spawn not valid -- already spawned\n");
1786 // reset name change timer again because they might want to change name
1787 // again in the first 5 seconds after connecting
1788 host_client->nametime = 0;
1790 // LordHavoc: moved this above the QC calls at FrikaC's request
1791 // LordHavoc: commented this out
1792 //if (host_client->netconnection)
1793 // SZ_Clear (&host_client->netconnection->message);
1795 // run the entrance script
1798 // loaded games are fully initialized already
1799 if (prog->funcoffsets.RestoreGame)
1801 Con_DPrint("Calling RestoreGame\n");
1802 prog->globals.server->time = sv.time;
1803 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1804 PRVM_ExecuteProgram(prog->funcoffsets.RestoreGame, "QC function RestoreGame is missing");
1809 //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(host_client->edict->fields.server->netname), PRVM_GetString(host_client->edict->fields.server->netname), host_client->name);
1811 // copy spawn parms out of the client_t
1812 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1813 (&prog->globals.server->parm1)[i] = host_client->spawn_parms[i];
1815 // call the spawn function
1816 host_client->clientconnectcalled = true;
1817 prog->globals.server->time = sv.time;
1818 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1819 PRVM_ExecuteProgram (prog->globals.server->ClientConnect, "QC function ClientConnect is missing");
1821 if (cls.state == ca_dedicated)
1822 Con_Printf("%s connected\n", host_client->name);
1824 PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing");
1827 if (!host_client->netconnection)
1830 // send time of update
1831 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1832 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1834 // send all current names, colors, and frag counts
1835 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1837 if (!client->active)
1839 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1840 MSG_WriteByte (&host_client->netconnection->message, i);
1841 MSG_WriteString (&host_client->netconnection->message, client->name);
1842 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1843 MSG_WriteByte (&host_client->netconnection->message, i);
1844 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1845 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1846 MSG_WriteByte (&host_client->netconnection->message, i);
1847 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1850 // send all current light styles
1851 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1853 if (sv.lightstyles[i][0])
1855 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1856 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1857 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1862 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1863 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1864 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_secrets);
1866 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1867 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1868 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_monsters);
1870 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1871 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1872 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->found_secrets);
1874 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1875 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1876 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->killed_monsters);
1879 // Never send a roll angle, because savegames can catch the server
1880 // in a state where it is expecting the client to correct the angle
1881 // and it won't happen if the game was just loaded, so you wind up
1882 // with a permanent head tilt
1885 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1886 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[0], sv.protocol);
1887 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[1], sv.protocol);
1888 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1892 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1893 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[0], sv.protocol);
1894 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[1], sv.protocol);
1895 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1898 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1900 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1901 MSG_WriteByte (&host_client->netconnection->message, 3);
1909 void Host_Begin_f (void)
1911 host_client->spawned = true;
1913 // LordHavoc: note: this code also exists in SV_DropClient
1917 for (i = 0;i < svs.maxclients;i++)
1918 if (svs.clients[i].active && !svs.clients[i].spawned)
1920 if (i == svs.maxclients)
1922 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1923 sv.paused = sv.loadgame = false; // we're basically done with loading now
1928 //===========================================================================
1935 Kicks a user off of the server
1938 void Host_Kick_f (void)
1941 const char *message = NULL;
1944 qboolean byNumber = false;
1952 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1954 i = (int)(atof(Cmd_Argv(2)) - 1);
1955 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1961 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1963 if (!host_client->active)
1965 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
1970 if (i < svs.maxclients)
1972 if (cmd_source == src_command)
1974 if (cls.state == ca_dedicated)
1977 who = cl_name.string;
1982 // can't kick yourself!
1983 if (host_client == save)
1988 message = Cmd_Args();
1989 COM_ParseToken_Simple(&message, false, false);
1992 message++; // skip the #
1993 while (*message == ' ') // skip white space
1995 message += strlen(Cmd_Argv(2)); // skip the number
1997 while (*message && *message == ' ')
2001 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2003 SV_ClientPrintf("Kicked by %s\n", who);
2004 SV_DropClient (false); // kicked
2012 ===============================================================================
2016 ===============================================================================
2024 void Host_Give_f (void)
2032 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2037 v = atoi (Cmd_Argv(2));
2051 // MED 01/04/97 added hipnotic give stuff
2052 if (gamemode == GAME_HIPNOTIC)
2057 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_PROXIMITY_GUN;
2059 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | IT_GRENADE_LAUNCHER;
2061 else if (t[0] == '9')
2062 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_LASER_CANNON;
2063 else if (t[0] == '0')
2064 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_MJOLNIR;
2065 else if (t[0] >= '2')
2066 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2071 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2076 if (gamemode == GAME_ROGUE && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_shells1)))
2079 host_client->edict->fields.server->ammo_shells = v;
2082 if (gamemode == GAME_ROGUE)
2084 if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_nails1)))
2087 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2088 host_client->edict->fields.server->ammo_nails = v;
2093 host_client->edict->fields.server->ammo_nails = v;
2097 if (gamemode == GAME_ROGUE)
2099 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_lava_nails);
2103 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2104 host_client->edict->fields.server->ammo_nails = v;
2109 if (gamemode == GAME_ROGUE)
2111 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_rockets1);
2115 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2116 host_client->edict->fields.server->ammo_rockets = v;
2121 host_client->edict->fields.server->ammo_rockets = v;
2125 if (gamemode == GAME_ROGUE)
2127 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_multi_rockets);
2131 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2132 host_client->edict->fields.server->ammo_rockets = v;
2137 host_client->edict->fields.server->health = v;
2140 if (gamemode == GAME_ROGUE)
2142 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_cells1);
2146 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2147 host_client->edict->fields.server->ammo_cells = v;
2152 host_client->edict->fields.server->ammo_cells = v;
2156 if (gamemode == GAME_ROGUE)
2158 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_plasma);
2162 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2163 host_client->edict->fields.server->ammo_cells = v;
2170 prvm_edict_t *FindViewthing (void)
2175 for (i=0 ; i<prog->num_edicts ; i++)
2177 e = PRVM_EDICT_NUM(i);
2178 if (!strcmp (PRVM_GetString(e->fields.server->classname), "viewthing"))
2181 Con_Print("No viewthing on map\n");
2190 void Host_Viewmodel_f (void)
2199 e = FindViewthing ();
2204 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2205 if (!m || !m->loaded || !m->Draw)
2207 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2211 e->fields.server->frame = 0;
2212 cl.model_precache[(int)e->fields.server->modelindex] = m;
2220 void Host_Viewframe_f (void)
2230 e = FindViewthing ();
2234 m = cl.model_precache[(int)e->fields.server->modelindex];
2236 f = atoi(Cmd_Argv(1));
2237 if (f >= m->numframes)
2240 e->fields.server->frame = f;
2244 void PrintFrameName (dp_model_t *m, int frame)
2247 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2249 Con_Printf("frame %i\n", frame);
2257 void Host_Viewnext_f (void)
2266 e = FindViewthing ();
2270 m = cl.model_precache[(int)e->fields.server->modelindex];
2272 e->fields.server->frame = e->fields.server->frame + 1;
2273 if (e->fields.server->frame >= m->numframes)
2274 e->fields.server->frame = m->numframes - 1;
2276 PrintFrameName (m, (int)e->fields.server->frame);
2284 void Host_Viewprev_f (void)
2293 e = FindViewthing ();
2298 m = cl.model_precache[(int)e->fields.server->modelindex];
2300 e->fields.server->frame = e->fields.server->frame - 1;
2301 if (e->fields.server->frame < 0)
2302 e->fields.server->frame = 0;
2304 PrintFrameName (m, (int)e->fields.server->frame);
2308 ===============================================================================
2312 ===============================================================================
2321 void Host_Startdemos_f (void)
2325 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2331 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2334 Con_DPrintf("%i demo(s) in loop\n", c);
2336 for (i=1 ; i<c+1 ; i++)
2337 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2339 // LordHavoc: clear the remaining slots
2340 for (;i <= MAX_DEMOS;i++)
2341 cls.demos[i-1][0] = 0;
2343 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2357 Return to looping demos
2360 void Host_Demos_f (void)
2362 if (cls.state == ca_dedicated)
2364 if (cls.demonum == -1)
2374 Return to looping demos
2377 void Host_Stopdemo_f (void)
2379 if (!cls.demoplayback)
2382 Host_ShutdownServer ();
2385 void Host_SendCvar_f (void)
2389 const char *cvarname;
2394 cvarname = Cmd_Argv(1);
2395 if (cls.state == ca_connected)
2397 c = Cvar_FindVar(cvarname);
2398 // LordHavoc: if there is no such cvar or if it is private, send a
2399 // reply indicating that it has no value
2400 if(!c || (c->flags & CVAR_PRIVATE))
2401 Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
2403 Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
2406 if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand)
2410 if (cls.state != ca_dedicated)
2414 for(;i<svs.maxclients;i++)
2415 if(svs.clients[i].active && svs.clients[i].netconnection)
2417 host_client = &svs.clients[i];
2418 Host_ClientCommands("sendcvar %s\n", cvarname);
2423 static void MaxPlayers_f(void)
2427 if (Cmd_Argc() != 2)
2429 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2435 Con_Print("maxplayers can not be changed while a server is running.\n");
2436 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2439 n = atoi(Cmd_Argv(1));
2440 n = bound(1, n, MAX_SCOREBOARD);
2441 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2443 svs.maxclients_next = n;
2445 Cvar_Set ("deathmatch", "0");
2447 Cvar_Set ("deathmatch", "1");
2451 =====================
2454 ProQuake rcon support
2455 =====================
2457 void Host_PQRcon_f (void)
2462 lhnetsocket_t *mysocket;
2463 char peer_address[64];
2465 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2467 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2471 e = strchr(rcon_password.string, ' ');
2472 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2476 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2480 if (!rcon_address.string[0])
2482 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2485 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2487 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2488 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2491 SZ_Clear(&net_message);
2492 MSG_WriteLong (&net_message, 0);
2493 MSG_WriteByte (&net_message, CCREQ_RCON);
2494 SZ_Write(&net_message, (void*)rcon_password.string, n);
2495 MSG_WriteByte (&net_message, 0); // terminate the (possibly partial) string
2496 MSG_WriteString (&net_message, Cmd_Args());
2497 StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
2498 NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
2499 SZ_Clear (&net_message);
2503 //=============================================================================
2505 // QuakeWorld commands
2508 =====================
2511 Send the rest of the command line over as
2512 an unconnected command.
2513 =====================
2515 void Host_Rcon_f (void) // credit: taken from QuakeWorld
2520 lhnetsocket_t *mysocket;
2522 if (!rcon_password.string || !rcon_password.string[0])
2524 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2528 e = strchr(rcon_password.string, ' ');
2529 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2532 to = cls.netcon->peeraddress;
2535 if (!rcon_address.string[0])
2537 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2540 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2542 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2543 if (mysocket && Cmd_Args()[0])
2545 // simply put together the rcon packet and send it
2546 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2548 if(cls.rcon_commands[cls.rcon_ringpos][0])
2551 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2552 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]);
2553 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2556 for (i = 0;i < MAX_RCONS;i++)
2557 if(cls.rcon_commands[i][0])
2558 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2562 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2563 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2564 cls.rcon_addresses[cls.rcon_ringpos] = to;
2565 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2566 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2568 else if(rcon_secure.integer > 0)
2572 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2573 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2574 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2577 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2578 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2583 NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2589 ====================
2592 user <name or userid>
2594 Dump userdata / masterdata for a user
2595 ====================
2597 void Host_User_f (void) // credit: taken from QuakeWorld
2602 if (Cmd_Argc() != 2)
2604 Con_Printf ("Usage: user <username / userid>\n");
2608 uid = atoi(Cmd_Argv(1));
2610 for (i = 0;i < cl.maxclients;i++)
2612 if (!cl.scores[i].name[0])
2614 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2616 InfoString_Print(cl.scores[i].qw_userinfo);
2620 Con_Printf ("User not in server.\n");
2624 ====================
2627 Dump userids for all current players
2628 ====================
2630 void Host_Users_f (void) // credit: taken from QuakeWorld
2636 Con_Printf ("userid frags name\n");
2637 Con_Printf ("------ ----- ----\n");
2638 for (i = 0;i < cl.maxclients;i++)
2640 if (cl.scores[i].name[0])
2642 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2647 Con_Printf ("%i total users\n", c);
2652 Host_FullServerinfo_f
2654 Sent by server when serverinfo changes
2657 // TODO: shouldn't this be a cvar instead?
2658 void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2661 if (Cmd_Argc() != 2)
2663 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2667 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2668 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2669 cl.qw_teamplay = atoi(temp);
2676 Allow clients to change userinfo
2680 void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2687 if (Cmd_Argc() != 2)
2689 Con_Printf ("fullinfo <complete info string>\n");
2699 while (*s && *s != '\\')
2705 Con_Printf ("MISSING VALUE\n");
2711 while (*s && *s != '\\')
2718 CL_SetInfo(key, value, false, false, false, false);
2726 Allow clients to change userinfo
2729 void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2731 if (Cmd_Argc() == 1)
2733 InfoString_Print(cls.userinfo);
2736 if (Cmd_Argc() != 3)
2738 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2741 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2745 ====================
2748 packet <destination> <contents>
2750 Contents allows \n escape character
2751 ====================
2753 void Host_Packet_f (void) // credit: taken from QuakeWorld
2759 lhnetaddress_t address;
2760 lhnetsocket_t *mysocket;
2762 if (Cmd_Argc() != 3)
2764 Con_Printf ("packet <destination> <contents>\n");
2768 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2770 Con_Printf ("Bad address\n");
2776 send[0] = send[1] = send[2] = send[3] = -1;
2778 l = (int)strlen (in);
2779 for (i=0 ; i<l ; i++)
2781 if (out >= send + sizeof(send) - 1)
2783 if (in[i] == '\\' && in[i+1] == 'n')
2788 else if (in[i] == '\\' && in[i+1] == '0')
2793 else if (in[i] == '\\' && in[i+1] == 't')
2798 else if (in[i] == '\\' && in[i+1] == 'r')
2803 else if (in[i] == '\\' && in[i+1] == '"')
2812 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2814 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2816 NetConn_Write(mysocket, send, out - send, &address);
2820 ====================
2823 Send back ping and packet loss update for all current players to this player
2824 ====================
2826 void Host_Pings_f (void)
2828 int i, j, ping, packetloss, movementloss;
2831 if (!host_client->netconnection)
2834 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2836 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2837 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2839 for (i = 0;i < svs.maxclients;i++)
2843 if (svs.clients[i].netconnection)
2845 for (j = 0;j < NETGRAPH_PACKETS;j++)
2846 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2848 for (j = 0;j < NETGRAPH_PACKETS;j++)
2849 if (svs.clients[i].movement_count[j] < 0)
2852 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2853 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2854 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2855 ping = bound(0, ping, 9999);
2856 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2858 // send qw_svc_updateping and qw_svc_updatepl messages
2859 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2860 MSG_WriteShort(&host_client->netconnection->message, ping);
2861 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2862 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2866 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2868 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2870 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2871 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2874 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2875 MSG_WriteString(&host_client->netconnection->message, "\n");
2878 void Host_PingPLReport_f(void)
2883 if (l > cl.maxclients)
2885 for (i = 0;i < l;i++)
2887 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2888 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2889 if(errbyte && *errbyte == ',')
2890 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2892 cl.scores[i].qw_movementloss = 0;
2896 //=============================================================================
2903 void Host_InitCommands (void)
2905 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2907 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2908 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2909 if (gamemode == GAME_NEHAHRA)
2911 Cmd_AddCommand_WithClientCommand ("max", NULL, Host_God_f, "god mode (invulnerability)");
2912 Cmd_AddCommand_WithClientCommand ("monster", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2913 Cmd_AddCommand_WithClientCommand ("scrag", NULL, Host_Fly_f, "fly mode (flight)");
2914 Cmd_AddCommand_WithClientCommand ("wraith", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2915 Cmd_AddCommand_WithClientCommand ("gimme", NULL, Host_Give_f, "alter inventory");
2919 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2920 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2921 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2922 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2923 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2925 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2926 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2927 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2928 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2929 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)");
2930 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2931 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2932 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2933 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2934 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2935 Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
2936 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2937 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2938 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2939 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2941 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2942 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2943 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2945 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2946 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2947 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2948 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2950 Cvar_RegisterVariable (&cl_name);
2951 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2952 Cvar_RegisterVariable (&cl_color);
2953 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2954 Cvar_RegisterVariable (&cl_rate);
2955 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2956 if (gamemode == GAME_NEHAHRA)
2958 Cvar_RegisterVariable (&cl_pmodel);
2959 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "change your player model choice (Nehahra specific)");
2962 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2963 Cvar_RegisterVariable (&cl_playermodel);
2964 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2965 Cvar_RegisterVariable (&cl_playerskin);
2966 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2968 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2969 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2970 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)");
2971 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2973 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2975 Cvar_RegisterVariable (&rcon_password);
2976 Cvar_RegisterVariable (&rcon_address);
2977 Cvar_RegisterVariable (&rcon_secure);
2978 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2979 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");
2980 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");
2981 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)");
2982 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2983 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2984 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2985 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2986 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2987 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2988 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2989 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2991 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)");
2992 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)");
2994 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)");
2995 Cvar_RegisterVariable (&r_fixtrans_auto);
2997 Cvar_RegisterVariable (&team);
2998 Cvar_RegisterVariable (&skin);
2999 Cvar_RegisterVariable (&noaim);
3001 Cvar_RegisterVariable(&sv_cheats);
3002 Cvar_RegisterVariable(&sv_adminnick);
3003 Cvar_RegisterVariable(&sv_status_privacy);
3004 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3007 void Host_NoOperation_f(void)