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, ...);
75 char ip[48]; // can contain a full length v6 address with [] and a port
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", 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 && 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 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
179 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
180 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
185 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
186 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
190 else if (in == 1) // extended layout
192 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);
194 else if (in == 2) // reduced layout
196 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
200 if(cmd_source == src_command)
209 Sets client to godmode
212 void Host_God_f (void)
216 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
220 host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_GODMODE;
221 if (!((int)host_client->edict->fields.server->flags & FL_GODMODE) )
222 SV_ClientPrint("godmode OFF\n");
224 SV_ClientPrint("godmode ON\n");
227 void Host_Notarget_f (void)
231 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
235 host_client->edict->fields.server->flags = (int)host_client->edict->fields.server->flags ^ FL_NOTARGET;
236 if (!((int)host_client->edict->fields.server->flags & FL_NOTARGET) )
237 SV_ClientPrint("notarget OFF\n");
239 SV_ClientPrint("notarget ON\n");
242 qboolean noclip_anglehack;
244 void Host_Noclip_f (void)
248 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
252 if (host_client->edict->fields.server->movetype != MOVETYPE_NOCLIP)
254 noclip_anglehack = true;
255 host_client->edict->fields.server->movetype = MOVETYPE_NOCLIP;
256 SV_ClientPrint("noclip ON\n");
260 noclip_anglehack = false;
261 host_client->edict->fields.server->movetype = MOVETYPE_WALK;
262 SV_ClientPrint("noclip OFF\n");
270 Sets client to flymode
273 void Host_Fly_f (void)
277 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
281 if (host_client->edict->fields.server->movetype != MOVETYPE_FLY)
283 host_client->edict->fields.server->movetype = MOVETYPE_FLY;
284 SV_ClientPrint("flymode ON\n");
288 host_client->edict->fields.server->movetype = MOVETYPE_WALK;
289 SV_ClientPrint("flymode OFF\n");
300 void Host_Pings_f (void); // called by Host_Ping_f
301 void Host_Ping_f (void)
305 void (*print) (const char *fmt, ...);
307 if (cmd_source == src_command)
309 // if running a client, try to send over network so the client's ping report parser will see the report
310 if (cls.state == ca_connected)
312 Cmd_ForwardToServer ();
318 print = SV_ClientPrintf;
323 print("Client ping times:\n");
324 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
328 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
331 // 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)
332 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
337 ===============================================================================
341 ===============================================================================
345 ======================
350 command from the console. Active clients are kicked off.
351 ======================
353 void Host_Map_f (void)
355 char level[MAX_QPATH];
359 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
363 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
364 if (gamemode == GAME_DELUXEQUAKE)
365 Cvar_Set("warpmark", "");
367 cls.demonum = -1; // stop demo loop in case this fails
370 Host_ShutdownServer();
372 if(svs.maxclients != svs.maxclients_next)
374 svs.maxclients = svs.maxclients_next;
376 Mem_Free(svs.clients);
377 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
381 if (key_dest == key_menu || key_dest == key_menu_grabbed)
385 svs.serverflags = 0; // haven't completed an episode yet
386 allowcheats = sv_cheats.integer != 0;
387 strlcpy(level, Cmd_Argv(1), sizeof(level));
388 SV_SpawnServer(level);
389 if (sv.active && cls.state == ca_disconnected)
390 CL_EstablishConnection("local:1", -2);
397 Goes to a new map, taking all clients along
400 void Host_Changelevel_f (void)
402 char level[MAX_QPATH];
406 Con_Print("changelevel <levelname> : continue game on a new level\n");
416 if (key_dest == key_menu || key_dest == key_menu_grabbed)
421 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 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 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 (const char *name)
554 int i, k, l, 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 = !strcmp(PRVM_NAME, "server");
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->edicts->fields.server->message), (int)prog->globals.server->killed_monsters, (int)prog->globals.server->total_monsters);
582 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", PRVM_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 (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 (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 for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); i++)
646 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
647 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
649 for(k = 0; k < stringbuffer->num_strings; k++)
651 if (!stringbuffer->strings[k])
653 // Parse the string a bit to turn special characters
654 // (like newline, specifically) into escape codes
655 s = stringbuffer->strings[k];
656 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
683 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
691 Con_Print("done.\n");
699 void Host_Savegame_f (void)
701 char name[MAX_QPATH];
705 Con_Print("Can't save - no server running.\n");
711 // singleplayer checks
714 Con_Print("Can't save in intermission.\n");
718 if (svs.clients[0].active && svs.clients[0].edict->fields.server->deadflag)
720 Con_Print("Can't savegame with a dead player\n");
725 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");
729 Con_Print("save <savename> : save a game\n");
733 if (strstr(Cmd_Argv(1), ".."))
735 Con_Print("Relative pathnames are not allowed.\n");
739 strlcpy (name, Cmd_Argv(1), sizeof (name));
740 FS_DefaultExtension (name, ".sav", sizeof (name));
743 Host_Savegame_to(name);
754 void Host_Loadgame_f (void)
756 char filename[MAX_QPATH];
757 char mapname[MAX_QPATH];
767 float spawn_parms[NUM_SPAWN_PARMS];
768 prvm_stringbuffer_t *stringbuffer;
773 Con_Print("load <savename> : load a game\n");
777 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
778 FS_DefaultExtension (filename, ".sav", sizeof (filename));
780 Con_Printf("Loading game from %s...\n", filename);
782 // stop playing demos
783 if (cls.demoplayback)
787 if (key_dest == key_menu || key_dest == key_menu_grabbed)
791 cls.demonum = -1; // stop demo loop in case this fails
793 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
796 Con_Print("ERROR: couldn't open.\n");
800 if(developer_entityparsing.integer)
801 Con_Printf("Host_Loadgame_f: loading version\n");
804 COM_ParseToken_Simple(&t, false, false);
805 version = atoi(com_token);
806 if (version != SAVEGAME_VERSION)
809 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
813 if(developer_entityparsing.integer)
814 Con_Printf("Host_Loadgame_f: loading description\n");
817 COM_ParseToken_Simple(&t, false, false);
819 for (i = 0;i < NUM_SPAWN_PARMS;i++)
821 COM_ParseToken_Simple(&t, false, false);
822 spawn_parms[i] = atof(com_token);
825 COM_ParseToken_Simple(&t, false, false);
826 // this silliness is so we can load 1.06 save files, which have float skill values
827 current_skill = (int)(atof(com_token) + 0.5);
828 Cvar_SetValue ("skill", (float)current_skill);
830 if(developer_entityparsing.integer)
831 Con_Printf("Host_Loadgame_f: loading mapname\n");
834 COM_ParseToken_Simple(&t, false, false);
835 strlcpy (mapname, com_token, sizeof(mapname));
837 if(developer_entityparsing.integer)
838 Con_Printf("Host_Loadgame_f: loading time\n");
841 COM_ParseToken_Simple(&t, false, false);
842 time = atof(com_token);
844 allowcheats = sv_cheats.integer != 0;
846 if(developer_entityparsing.integer)
847 Con_Printf("Host_Loadgame_f: spawning server\n");
849 SV_SpawnServer (mapname);
853 Con_Print("Couldn't load map\n");
856 sv.paused = true; // pause until all clients connect
859 if(developer_entityparsing.integer)
860 Con_Printf("Host_Loadgame_f: loading light styles\n");
862 // load the light styles
868 for (i = 0;i < MAX_LIGHTSTYLES;i++)
872 COM_ParseToken_Simple(&t, false, false);
873 // if this is a 64 lightstyle savegame produced by Quake, stop now
874 // we have to check this because darkplaces may save more than 64
875 if (com_token[0] == '{')
880 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
883 if(developer_entityparsing.integer)
884 Con_Printf("Host_Loadgame_f: skipping until globals\n");
886 // now skip everything before the first opening brace
887 // (this is for forward compatibility, so that older versions (at
888 // least ones with this fix) can load savegames with extra data before the
889 // first brace, as might be produced by a later engine version)
893 if (!COM_ParseToken_Simple(&t, false, false))
895 if (com_token[0] == '{')
902 // unlink all entities
903 World_UnlinkAll(&sv.world);
905 // load the edicts out of the savegame file
910 while (COM_ParseToken_Simple(&t, false, false))
911 if (!strcmp(com_token, "}"))
913 if (!COM_ParseToken_Simple(&start, false, false))
918 if (strcmp(com_token,"{"))
921 Host_Error ("First token isn't a brace");
926 if(developer_entityparsing.integer)
927 Con_Printf("Host_Loadgame_f: loading globals\n");
929 // parse the global vars
930 PRVM_ED_ParseGlobals (start);
932 // restore the autocvar globals
933 Cvar_UpdateAllAutoCvars();
938 if (entnum >= MAX_EDICTS)
941 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
943 while (entnum >= prog->max_edicts)
944 PRVM_MEM_IncreaseEdicts();
945 ent = PRVM_EDICT_NUM(entnum);
946 memset (ent->fields.server, 0, prog->entityfields * 4);
947 ent->priv.server->free = false;
949 if(developer_entityparsing.integer)
950 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
952 PRVM_ED_ParseEdict (start, ent);
954 // link it into the bsp tree
955 if (!ent->priv.server->free)
963 prog->num_edicts = entnum;
966 for (i = 0;i < NUM_SPAWN_PARMS;i++)
967 svs.clients[0].spawn_parms[i] = spawn_parms[i];
969 if(developer_entityparsing.integer)
970 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
972 // read extended data if present
973 // the extended data is stored inside a /* */ comment block, which the
974 // parser intentionally skips, so we have to check for it manually here
977 while (*end == '\r' || *end == '\n')
979 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
981 if(developer_entityparsing.integer)
982 Con_Printf("Host_Loadgame_f: loading extended data\n");
984 Con_Printf("Loading extended DarkPlaces savegame\n");
986 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
987 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
988 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
989 while (COM_ParseToken_Simple(&t, false, false))
991 if (!strcmp(com_token, "sv.lightstyles"))
993 COM_ParseToken_Simple(&t, false, false);
995 COM_ParseToken_Simple(&t, false, false);
996 if (i >= 0 && i < MAX_LIGHTSTYLES)
997 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
999 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1001 else if (!strcmp(com_token, "sv.model_precache"))
1003 COM_ParseToken_Simple(&t, false, false);
1004 i = atoi(com_token);
1005 COM_ParseToken_Simple(&t, false, false);
1006 if (i >= 0 && i < MAX_MODELS)
1008 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1009 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1012 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1014 else if (!strcmp(com_token, "sv.sound_precache"))
1016 COM_ParseToken_Simple(&t, false, false);
1017 i = atoi(com_token);
1018 COM_ParseToken_Simple(&t, false, false);
1019 if (i >= 0 && i < MAX_SOUNDS)
1020 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1022 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1024 else if (!strcmp(com_token, "sv.bufstr"))
1026 COM_ParseToken_Simple(&t, false, false);
1027 i = atoi(com_token);
1028 COM_ParseToken_Simple(&t, false, false);
1029 k = atoi(com_token);
1030 COM_ParseToken_Simple(&t, false, false);
1031 stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
1032 // VorteX: nasty code, cleanup required
1033 // create buffer at this index
1035 stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i);
1037 Con_Printf("cant write string %i into buffer %i\n", k, i);
1040 // code copied from VM_bufstr_set
1042 if (stringbuffer->max_strings <= i)
1044 char **oldstrings = stringbuffer->strings;
1045 stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128);
1046 while (stringbuffer->max_strings <= i)
1047 stringbuffer->max_strings *= 2;
1048 stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0]));
1049 if (stringbuffer->num_strings > 0)
1050 memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0]));
1052 Mem_Free(oldstrings);
1055 stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1);
1056 if(stringbuffer->strings[k])
1057 Mem_Free(stringbuffer->strings[k]);
1058 stringbuffer->strings[k] = NULL;
1059 alloclen = strlen(com_token) + 1;
1060 stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen);
1061 memcpy(stringbuffer->strings[k], com_token, alloclen);
1064 // skip any trailing text or unrecognized commands
1065 while (COM_ParseToken_Simple(&t, true, false) && strcmp(com_token, "\n"))
1072 if(developer_entityparsing.integer)
1073 Con_Printf("Host_Loadgame_f: finished\n");
1077 // make sure we're connected to loopback
1078 if (sv.active && cls.state == ca_disconnected)
1079 CL_EstablishConnection("local:1", -2);
1082 //============================================================================
1085 ======================
1087 ======================
1089 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1090 void Host_Name_f (void)
1093 qboolean valid_colors;
1094 const char *newNameSource;
1095 char newName[sizeof(host_client->name)];
1097 if (Cmd_Argc () == 1)
1099 Con_Printf("name: %s\n", cl_name.string);
1103 if (Cmd_Argc () == 2)
1104 newNameSource = Cmd_Argv(1);
1106 newNameSource = Cmd_Args();
1108 strlcpy(newName, newNameSource, sizeof(newName));
1110 if (cmd_source == src_command)
1112 Cvar_Set ("_cl_name", newName);
1113 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1115 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1116 Con_Printf("name: %s\n", cl_name.string);
1121 if (realtime < host_client->nametime)
1123 SV_ClientPrintf("You can't change name more than once every 5 seconds!\n");
1127 host_client->nametime = realtime + 5;
1129 // point the string back at updateclient->name to keep it safe
1130 strlcpy (host_client->name, newName, sizeof (host_client->name));
1132 for (i = 0, j = 0;host_client->name[i];i++)
1133 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1134 host_client->name[j++] = host_client->name[i];
1135 host_client->name[j] = 0;
1137 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1138 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1140 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1141 host_client->name[sizeof(host_client->name) - 1] = 0;
1142 host_client->name[0] = STRING_COLOR_TAG;
1143 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1146 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1147 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1150 l = strlen(host_client->name);
1151 if(l < sizeof(host_client->name) - 1)
1153 // duplicate the color tag to escape it
1154 host_client->name[i] = STRING_COLOR_TAG;
1155 host_client->name[i+1] = 0;
1156 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1160 // remove the last character to fix the color code
1161 host_client->name[l-1] = 0;
1162 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1166 // find the last color tag offset and decide if we need to add a reset tag
1167 for (i = 0, j = -1;host_client->name[i];i++)
1169 if (host_client->name[i] == STRING_COLOR_TAG)
1171 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1174 // if this happens to be a reset tag then we don't need one
1175 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1180 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]))
1186 if (host_client->name[i+1] == STRING_COLOR_TAG)
1193 // does not end in the default color string, so add it
1194 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1195 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1197 host_client->edict->fields.server->netname = PRVM_SetEngineString(host_client->name);
1198 if (strcmp(host_client->old_name, host_client->name))
1200 if (host_client->spawned)
1201 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1202 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1203 // send notification to all clients
1204 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1205 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1206 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1207 SV_WriteNetnameIntoDemo(host_client);
1212 ======================
1214 ======================
1216 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1217 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1218 void Host_Playermodel_f (void)
1221 char newPath[sizeof(host_client->playermodel)];
1223 if (Cmd_Argc () == 1)
1225 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1229 if (Cmd_Argc () == 2)
1230 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1232 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1234 for (i = 0, j = 0;newPath[i];i++)
1235 if (newPath[i] != '\r' && newPath[i] != '\n')
1236 newPath[j++] = newPath[i];
1239 if (cmd_source == src_command)
1241 Cvar_Set ("_cl_playermodel", newPath);
1246 if (realtime < host_client->nametime)
1248 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1252 host_client->nametime = realtime + 5;
1255 // point the string back at updateclient->name to keep it safe
1256 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1257 if (prog->fieldoffsets.playermodel >= 0)
1258 PRVM_EDICTFIELDSTRING(host_client->edict, prog->fieldoffsets.playermodel) = PRVM_SetEngineString(host_client->playermodel);
1259 if (strcmp(host_client->old_model, host_client->playermodel))
1261 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1262 /*// send notification to all clients
1263 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1264 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1265 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1270 ======================
1272 ======================
1274 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1275 void Host_Playerskin_f (void)
1278 char newPath[sizeof(host_client->playerskin)];
1280 if (Cmd_Argc () == 1)
1282 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1286 if (Cmd_Argc () == 2)
1287 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1289 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1291 for (i = 0, j = 0;newPath[i];i++)
1292 if (newPath[i] != '\r' && newPath[i] != '\n')
1293 newPath[j++] = newPath[i];
1296 if (cmd_source == src_command)
1298 Cvar_Set ("_cl_playerskin", newPath);
1303 if (realtime < host_client->nametime)
1305 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1309 host_client->nametime = realtime + 5;
1312 // point the string back at updateclient->name to keep it safe
1313 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1314 if (prog->fieldoffsets.playerskin >= 0)
1315 PRVM_EDICTFIELDSTRING(host_client->edict, prog->fieldoffsets.playerskin) = PRVM_SetEngineString(host_client->playerskin);
1316 if (strcmp(host_client->old_skin, host_client->playerskin))
1318 //if (host_client->spawned)
1319 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1320 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1321 /*// send notification to all clients
1322 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1323 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1324 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1328 void Host_Version_f (void)
1330 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1333 void Host_Say(qboolean teamonly)
1339 // LordHavoc: long say messages
1341 qboolean fromServer = false;
1343 if (cmd_source == src_command)
1345 if (cls.state == ca_dedicated)
1352 Cmd_ForwardToServer ();
1357 if (Cmd_Argc () < 2)
1360 if (!teamplay.integer)
1370 // note this uses the chat prefix \001
1371 if (!fromServer && !teamonly)
1372 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1373 else if (!fromServer && teamonly)
1374 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1375 else if(*(sv_adminnick.string))
1376 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1378 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1379 p2 = text + strlen(text);
1380 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1382 if (p2[-1] == '\"' && quoted)
1387 strlcat(text, "\n", sizeof(text));
1389 // note: save is not a valid edict if fromServer is true
1391 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1392 if (host_client->active && (!teamonly || host_client->edict->fields.server->team == save->edict->fields.server->team))
1393 SV_ClientPrint(text);
1396 if (cls.state == ca_dedicated)
1397 Con_Print(&text[1]);
1401 void Host_Say_f(void)
1407 void Host_Say_Team_f(void)
1413 void Host_Tell_f(void)
1415 const char *playername_start = NULL;
1416 size_t playername_length = 0;
1417 int playernumber = 0;
1420 const char *p1, *p2;
1421 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1422 qboolean fromServer = false;
1424 if (cmd_source == src_command)
1426 if (cls.state == ca_dedicated)
1430 Cmd_ForwardToServer ();
1435 if (Cmd_Argc () < 2)
1438 // note this uses the chat prefix \001
1440 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1441 else if(*(sv_adminnick.string))
1442 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1444 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1447 p2 = p1 + strlen(p1);
1448 // remove the target name
1449 while (p1 < p2 && *p1 == ' ')
1454 while (p1 < p2 && *p1 == ' ')
1456 while (p1 < p2 && isdigit(*p1))
1458 playernumber = playernumber * 10 + (*p1 - '0');
1466 playername_start = p1;
1467 while (p1 < p2 && *p1 != '"')
1469 playername_length = p1 - playername_start;
1475 playername_start = p1;
1476 while (p1 < p2 && *p1 != ' ')
1478 playername_length = p1 - playername_start;
1480 while (p1 < p2 && *p1 == ' ')
1482 if(playername_start)
1484 // set playernumber to the right client
1486 if(playername_length >= sizeof(namebuf))
1489 Con_Print("Host_Tell: too long player name/ID\n");
1491 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1494 memcpy(namebuf, playername_start, playername_length);
1495 namebuf[playername_length] = 0;
1496 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1498 if (!svs.clients[playernumber].active)
1500 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1504 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1507 Con_Print("Host_Tell: invalid player name/ID\n");
1509 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1512 // remove trailing newlines
1513 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1515 // remove quotes if present
1521 else if (fromServer)
1522 Con_Print("Host_Tell: missing end quote\n");
1524 SV_ClientPrint("Host_Tell: missing end quote\n");
1526 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1529 return; // empty say
1530 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1536 host_client = svs.clients + playernumber;
1537 SV_ClientPrint(text);
1547 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1548 void Host_Color(int changetop, int changebottom)
1550 int top, bottom, playercolor;
1552 // get top and bottom either from the provided values or the current values
1553 // (allows changing only top or bottom, or both at once)
1554 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1555 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1559 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1565 playercolor = top*16 + bottom;
1567 if (cmd_source == src_command)
1569 Cvar_SetValueQuick(&cl_color, playercolor);
1573 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1576 if (host_client->edict && prog->funcoffsets.SV_ChangeTeam)
1578 Con_DPrint("Calling SV_ChangeTeam\n");
1579 prog->globals.server->time = sv.time;
1580 prog->globals.generic[OFS_PARM0] = playercolor;
1581 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1582 PRVM_ExecuteProgram(prog->funcoffsets.SV_ChangeTeam, "QC function SV_ChangeTeam is missing");
1586 if (host_client->edict)
1588 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.clientcolors) = playercolor;
1589 host_client->edict->fields.server->team = bottom + 1;
1591 host_client->colors = playercolor;
1592 if (host_client->old_colors != host_client->colors)
1594 host_client->old_colors = host_client->colors;
1595 // send notification to all clients
1596 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1597 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1598 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1603 void Host_Color_f(void)
1607 if (Cmd_Argc() == 1)
1609 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1610 Con_Print("color <0-15> [0-15]\n");
1614 if (Cmd_Argc() == 2)
1615 top = bottom = atoi(Cmd_Argv(1));
1618 top = atoi(Cmd_Argv(1));
1619 bottom = atoi(Cmd_Argv(2));
1621 Host_Color(top, bottom);
1624 void Host_TopColor_f(void)
1626 if (Cmd_Argc() == 1)
1628 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1629 Con_Print("topcolor <0-15>\n");
1633 Host_Color(atoi(Cmd_Argv(1)), -1);
1636 void Host_BottomColor_f(void)
1638 if (Cmd_Argc() == 1)
1640 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1641 Con_Print("bottomcolor <0-15>\n");
1645 Host_Color(-1, atoi(Cmd_Argv(1)));
1648 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1649 void Host_Rate_f(void)
1653 if (Cmd_Argc() != 2)
1655 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1656 Con_Print("rate <bytespersecond>\n");
1660 rate = atoi(Cmd_Argv(1));
1662 if (cmd_source == src_command)
1664 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1668 host_client->rate = rate;
1676 void Host_Kill_f (void)
1678 if (host_client->edict->fields.server->health <= 0)
1680 SV_ClientPrint("Can't suicide -- already dead!\n");
1684 prog->globals.server->time = sv.time;
1685 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1686 PRVM_ExecuteProgram (prog->globals.server->ClientKill, "QC function ClientKill is missing");
1695 void Host_Pause_f (void)
1697 if (!pausable.integer)
1698 SV_ClientPrint("Pause not allowed.\n");
1702 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1703 // send notification to all clients
1704 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1705 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1710 ======================
1712 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1713 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1714 ======================
1716 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)"};
1717 static void Host_PModel_f (void)
1721 if (Cmd_Argc () == 1)
1723 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1726 i = atoi(Cmd_Argv(1));
1728 if (cmd_source == src_command)
1730 if (cl_pmodel.integer == i)
1732 Cvar_SetValue ("_cl_pmodel", i);
1733 if (cls.state == ca_connected)
1734 Cmd_ForwardToServer ();
1738 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.pmodel) = i;
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)
2031 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2036 v = atoi (Cmd_Argv(2));
2050 // MED 01/04/97 added hipnotic give stuff
2051 if (gamemode == GAME_HIPNOTIC)
2056 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_PROXIMITY_GUN;
2058 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | IT_GRENADE_LAUNCHER;
2060 else if (t[0] == '9')
2061 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_LASER_CANNON;
2062 else if (t[0] == '0')
2063 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_MJOLNIR;
2064 else if (t[0] >= '2')
2065 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2070 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2075 if (gamemode == GAME_ROGUE)
2076 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_shells1) = v;
2078 host_client->edict->fields.server->ammo_shells = v;
2081 if (gamemode == GAME_ROGUE)
2083 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_nails1) = v;
2084 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2085 host_client->edict->fields.server->ammo_nails = v;
2089 host_client->edict->fields.server->ammo_nails = v;
2093 if (gamemode == GAME_ROGUE)
2095 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_lava_nails) = v;
2096 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2097 host_client->edict->fields.server->ammo_nails = v;
2101 if (gamemode == GAME_ROGUE)
2103 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_rockets1) = v;
2104 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2105 host_client->edict->fields.server->ammo_rockets = v;
2109 host_client->edict->fields.server->ammo_rockets = v;
2113 if (gamemode == GAME_ROGUE)
2115 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_multi_rockets) = v;
2116 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2117 host_client->edict->fields.server->ammo_rockets = v;
2121 host_client->edict->fields.server->health = v;
2124 if (gamemode == GAME_ROGUE)
2126 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_cells1) = v;
2127 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2128 host_client->edict->fields.server->ammo_cells = v;
2132 host_client->edict->fields.server->ammo_cells = v;
2136 if (gamemode == GAME_ROGUE)
2138 PRVM_EDICTFIELDFLOAT(host_client->edict, prog->fieldoffsets.ammo_plasma) = v;
2139 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2140 host_client->edict->fields.server->ammo_cells = v;
2146 prvm_edict_t *FindViewthing (void)
2151 for (i=0 ; i<prog->num_edicts ; i++)
2153 e = PRVM_EDICT_NUM(i);
2154 if (!strcmp (PRVM_GetString(e->fields.server->classname), "viewthing"))
2157 Con_Print("No viewthing on map\n");
2166 void Host_Viewmodel_f (void)
2175 e = FindViewthing ();
2180 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2181 if (!m || !m->loaded || !m->Draw)
2183 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2187 e->fields.server->frame = 0;
2188 cl.model_precache[(int)e->fields.server->modelindex] = m;
2196 void Host_Viewframe_f (void)
2206 e = FindViewthing ();
2210 m = cl.model_precache[(int)e->fields.server->modelindex];
2212 f = atoi(Cmd_Argv(1));
2213 if (f >= m->numframes)
2216 e->fields.server->frame = f;
2220 void PrintFrameName (dp_model_t *m, int frame)
2223 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2225 Con_Printf("frame %i\n", frame);
2233 void Host_Viewnext_f (void)
2242 e = FindViewthing ();
2246 m = cl.model_precache[(int)e->fields.server->modelindex];
2248 e->fields.server->frame = e->fields.server->frame + 1;
2249 if (e->fields.server->frame >= m->numframes)
2250 e->fields.server->frame = m->numframes - 1;
2252 PrintFrameName (m, (int)e->fields.server->frame);
2260 void Host_Viewprev_f (void)
2269 e = FindViewthing ();
2274 m = cl.model_precache[(int)e->fields.server->modelindex];
2276 e->fields.server->frame = e->fields.server->frame - 1;
2277 if (e->fields.server->frame < 0)
2278 e->fields.server->frame = 0;
2280 PrintFrameName (m, (int)e->fields.server->frame);
2284 ===============================================================================
2288 ===============================================================================
2297 void Host_Startdemos_f (void)
2301 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2307 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2310 Con_DPrintf("%i demo(s) in loop\n", c);
2312 for (i=1 ; i<c+1 ; i++)
2313 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2315 // LordHavoc: clear the remaining slots
2316 for (;i <= MAX_DEMOS;i++)
2317 cls.demos[i-1][0] = 0;
2319 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2333 Return to looping demos
2336 void Host_Demos_f (void)
2338 if (cls.state == ca_dedicated)
2340 if (cls.demonum == -1)
2350 Return to looping demos
2353 void Host_Stopdemo_f (void)
2355 if (!cls.demoplayback)
2358 Host_ShutdownServer ();
2361 void Host_SendCvar_f (void)
2365 const char *cvarname;
2370 cvarname = Cmd_Argv(1);
2371 if (cls.state == ca_connected)
2373 c = Cvar_FindVar(cvarname);
2374 // LordHavoc: if there is no such cvar or if it is private, send a
2375 // reply indicating that it has no value
2376 if(!c || (c->flags & CVAR_PRIVATE))
2377 Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
2379 Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
2382 if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand)
2386 if (cls.state != ca_dedicated)
2390 for(;i<svs.maxclients;i++)
2391 if(svs.clients[i].active && svs.clients[i].netconnection)
2393 host_client = &svs.clients[i];
2394 Host_ClientCommands("sendcvar %s\n", cvarname);
2399 static void MaxPlayers_f(void)
2403 if (Cmd_Argc() != 2)
2405 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2411 Con_Print("maxplayers can not be changed while a server is running.\n");
2412 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2415 n = atoi(Cmd_Argv(1));
2416 n = bound(1, n, MAX_SCOREBOARD);
2417 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2419 svs.maxclients_next = n;
2421 Cvar_Set ("deathmatch", "0");
2423 Cvar_Set ("deathmatch", "1");
2427 =====================
2430 ProQuake rcon support
2431 =====================
2433 void Host_PQRcon_f (void)
2438 lhnetsocket_t *mysocket;
2439 char peer_address[64];
2441 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2443 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2447 e = strchr(rcon_password.string, ' ');
2448 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2452 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2456 if (!rcon_address.string[0])
2458 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2461 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2463 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2464 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2467 SZ_Clear(&net_message);
2468 MSG_WriteLong (&net_message, 0);
2469 MSG_WriteByte (&net_message, CCREQ_RCON);
2470 SZ_Write(&net_message, (const unsigned char*)rcon_password.string, n);
2471 MSG_WriteByte (&net_message, 0); // terminate the (possibly partial) string
2472 MSG_WriteString (&net_message, Cmd_Args());
2473 StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
2474 NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
2475 SZ_Clear (&net_message);
2479 //=============================================================================
2481 // QuakeWorld commands
2484 =====================
2487 Send the rest of the command line over as
2488 an unconnected command.
2489 =====================
2491 void Host_Rcon_f (void) // credit: taken from QuakeWorld
2496 lhnetsocket_t *mysocket;
2498 if (!rcon_password.string || !rcon_password.string[0])
2500 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2504 e = strchr(rcon_password.string, ' ');
2505 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2508 to = cls.netcon->peeraddress;
2511 if (!rcon_address.string[0])
2513 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2516 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2518 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2519 if (mysocket && Cmd_Args()[0])
2521 // simply put together the rcon packet and send it
2522 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2524 if(cls.rcon_commands[cls.rcon_ringpos][0])
2527 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2528 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]);
2529 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2532 for (i = 0;i < MAX_RCONS;i++)
2533 if(cls.rcon_commands[i][0])
2534 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2538 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2539 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2540 cls.rcon_addresses[cls.rcon_ringpos] = to;
2541 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2542 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2544 else if(rcon_secure.integer > 0)
2548 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2549 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2550 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2553 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2554 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2559 NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2565 ====================
2568 user <name or userid>
2570 Dump userdata / masterdata for a user
2571 ====================
2573 void Host_User_f (void) // credit: taken from QuakeWorld
2578 if (Cmd_Argc() != 2)
2580 Con_Printf ("Usage: user <username / userid>\n");
2584 uid = atoi(Cmd_Argv(1));
2586 for (i = 0;i < cl.maxclients;i++)
2588 if (!cl.scores[i].name[0])
2590 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2592 InfoString_Print(cl.scores[i].qw_userinfo);
2596 Con_Printf ("User not in server.\n");
2600 ====================
2603 Dump userids for all current players
2604 ====================
2606 void Host_Users_f (void) // credit: taken from QuakeWorld
2612 Con_Printf ("userid frags name\n");
2613 Con_Printf ("------ ----- ----\n");
2614 for (i = 0;i < cl.maxclients;i++)
2616 if (cl.scores[i].name[0])
2618 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2623 Con_Printf ("%i total users\n", c);
2628 Host_FullServerinfo_f
2630 Sent by server when serverinfo changes
2633 // TODO: shouldn't this be a cvar instead?
2634 void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2637 if (Cmd_Argc() != 2)
2639 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2643 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2644 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2645 cl.qw_teamplay = atoi(temp);
2652 Allow clients to change userinfo
2656 void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2663 if (Cmd_Argc() != 2)
2665 Con_Printf ("fullinfo <complete info string>\n");
2675 while (*s && *s != '\\')
2681 Con_Printf ("MISSING VALUE\n");
2687 while (*s && *s != '\\')
2694 CL_SetInfo(key, value, false, false, false, false);
2702 Allow clients to change userinfo
2705 void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2707 if (Cmd_Argc() == 1)
2709 InfoString_Print(cls.userinfo);
2712 if (Cmd_Argc() != 3)
2714 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2717 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2721 ====================
2724 packet <destination> <contents>
2726 Contents allows \n escape character
2727 ====================
2729 void Host_Packet_f (void) // credit: taken from QuakeWorld
2735 lhnetaddress_t address;
2736 lhnetsocket_t *mysocket;
2738 if (Cmd_Argc() != 3)
2740 Con_Printf ("packet <destination> <contents>\n");
2744 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2746 Con_Printf ("Bad address\n");
2752 send[0] = send[1] = send[2] = send[3] = -1;
2754 l = (int)strlen (in);
2755 for (i=0 ; i<l ; i++)
2757 if (out >= send + sizeof(send) - 1)
2759 if (in[i] == '\\' && in[i+1] == 'n')
2764 else if (in[i] == '\\' && in[i+1] == '0')
2769 else if (in[i] == '\\' && in[i+1] == 't')
2774 else if (in[i] == '\\' && in[i+1] == 'r')
2779 else if (in[i] == '\\' && in[i+1] == '"')
2788 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2790 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2792 NetConn_Write(mysocket, send, out - send, &address);
2796 ====================
2799 Send back ping and packet loss update for all current players to this player
2800 ====================
2802 void Host_Pings_f (void)
2804 int i, j, ping, packetloss, movementloss;
2807 if (!host_client->netconnection)
2810 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2812 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2813 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2815 for (i = 0;i < svs.maxclients;i++)
2819 if (svs.clients[i].netconnection)
2821 for (j = 0;j < NETGRAPH_PACKETS;j++)
2822 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2824 for (j = 0;j < NETGRAPH_PACKETS;j++)
2825 if (svs.clients[i].movement_count[j] < 0)
2828 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2829 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2830 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2831 ping = bound(0, ping, 9999);
2832 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2834 // send qw_svc_updateping and qw_svc_updatepl messages
2835 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2836 MSG_WriteShort(&host_client->netconnection->message, ping);
2837 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2838 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2842 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2844 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2846 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2847 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2850 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2851 MSG_WriteString(&host_client->netconnection->message, "\n");
2854 void Host_PingPLReport_f(void)
2859 if (l > cl.maxclients)
2861 for (i = 0;i < l;i++)
2863 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2864 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2865 if(errbyte && *errbyte == ',')
2866 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2868 cl.scores[i].qw_movementloss = 0;
2872 //=============================================================================
2879 void Host_InitCommands (void)
2881 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2883 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2884 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2885 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2886 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2887 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2888 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2889 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2890 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2891 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2892 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2893 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2894 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)");
2895 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2896 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2897 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2898 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2899 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2900 Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
2901 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2902 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2903 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2904 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2906 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2907 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2908 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2910 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2911 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2912 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2913 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2915 Cvar_RegisterVariable (&cl_name);
2916 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2917 Cvar_RegisterVariable (&cl_color);
2918 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2919 Cvar_RegisterVariable (&cl_rate);
2920 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2921 Cvar_RegisterVariable (&cl_pmodel);
2922 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2924 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2925 Cvar_RegisterVariable (&cl_playermodel);
2926 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2927 Cvar_RegisterVariable (&cl_playerskin);
2928 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2930 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2931 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2932 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)");
2933 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2935 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2937 Cvar_RegisterVariable (&rcon_password);
2938 Cvar_RegisterVariable (&rcon_address);
2939 Cvar_RegisterVariable (&rcon_secure);
2940 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2941 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");
2942 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");
2943 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)");
2944 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2945 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2946 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2947 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2948 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2949 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2950 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2951 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2953 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)");
2954 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)");
2956 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)");
2957 Cvar_RegisterVariable (&r_fixtrans_auto);
2959 Cvar_RegisterVariable (&team);
2960 Cvar_RegisterVariable (&skin);
2961 Cvar_RegisterVariable (&noaim);
2963 Cvar_RegisterVariable(&sv_cheats);
2964 Cvar_RegisterVariable(&sv_adminnick);
2965 Cvar_RegisterVariable(&sv_status_privacy);
2966 Cvar_RegisterVariable(&sv_status_show_qcstatus);
2969 void Host_NoOperation_f(void)