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->progs->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_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playermodel)->string = 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_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.playerskin)->string = 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");
1587 if (host_client->edict)
1589 if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.clientcolors)))
1590 val->_float = playercolor;
1591 host_client->edict->fields.server->team = bottom + 1;
1593 host_client->colors = playercolor;
1594 if (host_client->old_colors != host_client->colors)
1596 host_client->old_colors = host_client->colors;
1597 // send notification to all clients
1598 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1599 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1600 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1605 void Host_Color_f(void)
1609 if (Cmd_Argc() == 1)
1611 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1612 Con_Print("color <0-15> [0-15]\n");
1616 if (Cmd_Argc() == 2)
1617 top = bottom = atoi(Cmd_Argv(1));
1620 top = atoi(Cmd_Argv(1));
1621 bottom = atoi(Cmd_Argv(2));
1623 Host_Color(top, bottom);
1626 void Host_TopColor_f(void)
1628 if (Cmd_Argc() == 1)
1630 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1631 Con_Print("topcolor <0-15>\n");
1635 Host_Color(atoi(Cmd_Argv(1)), -1);
1638 void Host_BottomColor_f(void)
1640 if (Cmd_Argc() == 1)
1642 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1643 Con_Print("bottomcolor <0-15>\n");
1647 Host_Color(-1, atoi(Cmd_Argv(1)));
1650 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1651 void Host_Rate_f(void)
1655 if (Cmd_Argc() != 2)
1657 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1658 Con_Print("rate <bytespersecond>\n");
1662 rate = atoi(Cmd_Argv(1));
1664 if (cmd_source == src_command)
1666 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1670 host_client->rate = rate;
1678 void Host_Kill_f (void)
1680 if (host_client->edict->fields.server->health <= 0)
1682 SV_ClientPrint("Can't suicide -- already dead!\n");
1686 prog->globals.server->time = sv.time;
1687 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1688 PRVM_ExecuteProgram (prog->globals.server->ClientKill, "QC function ClientKill is missing");
1697 void Host_Pause_f (void)
1699 if (!pausable.integer)
1700 SV_ClientPrint("Pause not allowed.\n");
1704 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1705 // send notification to all clients
1706 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1707 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1712 ======================
1714 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1715 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1716 ======================
1718 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)"};
1719 static void Host_PModel_f (void)
1724 if (Cmd_Argc () == 1)
1726 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1729 i = atoi(Cmd_Argv(1));
1731 if (cmd_source == src_command)
1733 if (cl_pmodel.integer == i)
1735 Cvar_SetValue ("_cl_pmodel", i);
1736 if (cls.state == ca_connected)
1737 Cmd_ForwardToServer ();
1741 if (host_client->edict && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.pmodel)))
1745 //===========================================================================
1753 void Host_PreSpawn_f (void)
1755 if (host_client->spawned)
1757 Con_Print("prespawn not valid -- already spawned\n");
1761 if (host_client->netconnection)
1763 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1764 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1765 MSG_WriteByte (&host_client->netconnection->message, 2);
1766 host_client->sendsignon = 0; // enable unlimited sends again
1769 // reset the name change timer because the client will send name soon
1770 host_client->nametime = 0;
1778 void Host_Spawn_f (void)
1782 int stats[MAX_CL_STATS];
1784 if (host_client->spawned)
1786 Con_Print("Spawn not valid -- already spawned\n");
1790 // reset name change timer again because they might want to change name
1791 // again in the first 5 seconds after connecting
1792 host_client->nametime = 0;
1794 // LordHavoc: moved this above the QC calls at FrikaC's request
1795 // LordHavoc: commented this out
1796 //if (host_client->netconnection)
1797 // SZ_Clear (&host_client->netconnection->message);
1799 // run the entrance script
1802 // loaded games are fully initialized already
1803 if (prog->funcoffsets.RestoreGame)
1805 Con_DPrint("Calling RestoreGame\n");
1806 prog->globals.server->time = sv.time;
1807 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1808 PRVM_ExecuteProgram(prog->funcoffsets.RestoreGame, "QC function RestoreGame is missing");
1813 //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);
1815 // copy spawn parms out of the client_t
1816 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1817 (&prog->globals.server->parm1)[i] = host_client->spawn_parms[i];
1819 // call the spawn function
1820 host_client->clientconnectcalled = true;
1821 prog->globals.server->time = sv.time;
1822 prog->globals.server->self = PRVM_EDICT_TO_PROG(host_client->edict);
1823 PRVM_ExecuteProgram (prog->globals.server->ClientConnect, "QC function ClientConnect is missing");
1825 if (cls.state == ca_dedicated)
1826 Con_Printf("%s connected\n", host_client->name);
1828 PRVM_ExecuteProgram (prog->globals.server->PutClientInServer, "QC function PutClientInServer is missing");
1831 if (!host_client->netconnection)
1834 // send time of update
1835 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1836 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1838 // send all current names, colors, and frag counts
1839 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1841 if (!client->active)
1843 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1844 MSG_WriteByte (&host_client->netconnection->message, i);
1845 MSG_WriteString (&host_client->netconnection->message, client->name);
1846 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1847 MSG_WriteByte (&host_client->netconnection->message, i);
1848 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1849 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1850 MSG_WriteByte (&host_client->netconnection->message, i);
1851 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1854 // send all current light styles
1855 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1857 if (sv.lightstyles[i][0])
1859 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1860 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1861 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1866 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1867 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1868 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_secrets);
1870 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1871 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1872 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->total_monsters);
1874 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1875 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1876 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->found_secrets);
1878 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1879 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1880 MSG_WriteLong (&host_client->netconnection->message, (int)prog->globals.server->killed_monsters);
1883 // Never send a roll angle, because savegames can catch the server
1884 // in a state where it is expecting the client to correct the angle
1885 // and it won't happen if the game was just loaded, so you wind up
1886 // with a permanent head tilt
1889 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1890 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[0], sv.protocol);
1891 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->v_angle[1], sv.protocol);
1892 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1896 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1897 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[0], sv.protocol);
1898 MSG_WriteAngle (&host_client->netconnection->message, host_client->edict->fields.server->angles[1], sv.protocol);
1899 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1902 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1904 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1905 MSG_WriteByte (&host_client->netconnection->message, 3);
1913 void Host_Begin_f (void)
1915 host_client->spawned = true;
1917 // LordHavoc: note: this code also exists in SV_DropClient
1921 for (i = 0;i < svs.maxclients;i++)
1922 if (svs.clients[i].active && !svs.clients[i].spawned)
1924 if (i == svs.maxclients)
1926 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1927 sv.paused = sv.loadgame = false; // we're basically done with loading now
1932 //===========================================================================
1939 Kicks a user off of the server
1942 void Host_Kick_f (void)
1945 const char *message = NULL;
1948 qboolean byNumber = false;
1956 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1958 i = (int)(atof(Cmd_Argv(2)) - 1);
1959 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1965 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1967 if (!host_client->active)
1969 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
1974 if (i < svs.maxclients)
1976 if (cmd_source == src_command)
1978 if (cls.state == ca_dedicated)
1981 who = cl_name.string;
1986 // can't kick yourself!
1987 if (host_client == save)
1992 message = Cmd_Args();
1993 COM_ParseToken_Simple(&message, false, false);
1996 message++; // skip the #
1997 while (*message == ' ') // skip white space
1999 message += strlen(Cmd_Argv(2)); // skip the number
2001 while (*message && *message == ' ')
2005 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2007 SV_ClientPrintf("Kicked by %s\n", who);
2008 SV_DropClient (false); // kicked
2016 ===============================================================================
2020 ===============================================================================
2028 void Host_Give_f (void)
2036 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2041 v = atoi (Cmd_Argv(2));
2055 // MED 01/04/97 added hipnotic give stuff
2056 if (gamemode == GAME_HIPNOTIC)
2061 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_PROXIMITY_GUN;
2063 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | IT_GRENADE_LAUNCHER;
2065 else if (t[0] == '9')
2066 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_LASER_CANNON;
2067 else if (t[0] == '0')
2068 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | HIT_MJOLNIR;
2069 else if (t[0] >= '2')
2070 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2075 host_client->edict->fields.server->items = (int)host_client->edict->fields.server->items | (IT_SHOTGUN << (t[0] - '2'));
2080 if (gamemode == GAME_ROGUE && (val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_shells1)))
2083 host_client->edict->fields.server->ammo_shells = v;
2086 if (gamemode == GAME_ROGUE)
2088 if ((val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_nails1)))
2091 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2092 host_client->edict->fields.server->ammo_nails = v;
2097 host_client->edict->fields.server->ammo_nails = v;
2101 if (gamemode == GAME_ROGUE)
2103 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_lava_nails);
2107 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2108 host_client->edict->fields.server->ammo_nails = v;
2113 if (gamemode == GAME_ROGUE)
2115 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_rockets1);
2119 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2120 host_client->edict->fields.server->ammo_rockets = v;
2125 host_client->edict->fields.server->ammo_rockets = v;
2129 if (gamemode == GAME_ROGUE)
2131 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_multi_rockets);
2135 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2136 host_client->edict->fields.server->ammo_rockets = v;
2141 host_client->edict->fields.server->health = v;
2144 if (gamemode == GAME_ROGUE)
2146 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_cells1);
2150 if (host_client->edict->fields.server->weapon <= IT_LIGHTNING)
2151 host_client->edict->fields.server->ammo_cells = v;
2156 host_client->edict->fields.server->ammo_cells = v;
2160 if (gamemode == GAME_ROGUE)
2162 val = PRVM_EDICTFIELDVALUE(host_client->edict, prog->fieldoffsets.ammo_plasma);
2166 if (host_client->edict->fields.server->weapon > IT_LIGHTNING)
2167 host_client->edict->fields.server->ammo_cells = v;
2174 prvm_edict_t *FindViewthing (void)
2179 for (i=0 ; i<prog->num_edicts ; i++)
2181 e = PRVM_EDICT_NUM(i);
2182 if (!strcmp (PRVM_GetString(e->fields.server->classname), "viewthing"))
2185 Con_Print("No viewthing on map\n");
2194 void Host_Viewmodel_f (void)
2203 e = FindViewthing ();
2208 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2209 if (!m || !m->loaded || !m->Draw)
2211 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2215 e->fields.server->frame = 0;
2216 cl.model_precache[(int)e->fields.server->modelindex] = m;
2224 void Host_Viewframe_f (void)
2234 e = FindViewthing ();
2238 m = cl.model_precache[(int)e->fields.server->modelindex];
2240 f = atoi(Cmd_Argv(1));
2241 if (f >= m->numframes)
2244 e->fields.server->frame = f;
2248 void PrintFrameName (dp_model_t *m, int frame)
2251 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2253 Con_Printf("frame %i\n", frame);
2261 void Host_Viewnext_f (void)
2270 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 >= m->numframes)
2278 e->fields.server->frame = m->numframes - 1;
2280 PrintFrameName (m, (int)e->fields.server->frame);
2288 void Host_Viewprev_f (void)
2297 e = FindViewthing ();
2302 m = cl.model_precache[(int)e->fields.server->modelindex];
2304 e->fields.server->frame = e->fields.server->frame - 1;
2305 if (e->fields.server->frame < 0)
2306 e->fields.server->frame = 0;
2308 PrintFrameName (m, (int)e->fields.server->frame);
2312 ===============================================================================
2316 ===============================================================================
2325 void Host_Startdemos_f (void)
2329 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2335 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2338 Con_DPrintf("%i demo(s) in loop\n", c);
2340 for (i=1 ; i<c+1 ; i++)
2341 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2343 // LordHavoc: clear the remaining slots
2344 for (;i <= MAX_DEMOS;i++)
2345 cls.demos[i-1][0] = 0;
2347 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2361 Return to looping demos
2364 void Host_Demos_f (void)
2366 if (cls.state == ca_dedicated)
2368 if (cls.demonum == -1)
2378 Return to looping demos
2381 void Host_Stopdemo_f (void)
2383 if (!cls.demoplayback)
2386 Host_ShutdownServer ();
2389 void Host_SendCvar_f (void)
2393 const char *cvarname;
2398 cvarname = Cmd_Argv(1);
2399 if (cls.state == ca_connected)
2401 c = Cvar_FindVar(cvarname);
2402 // LordHavoc: if there is no such cvar or if it is private, send a
2403 // reply indicating that it has no value
2404 if(!c || (c->flags & CVAR_PRIVATE))
2405 Cmd_ForwardStringToServer(va("sentcvar %s", cvarname));
2407 Cmd_ForwardStringToServer(va("sentcvar %s \"%s\"", c->name, c->string));
2410 if(!sv.active)// || !prog->funcoffsets.SV_ParseClientCommand)
2414 if (cls.state != ca_dedicated)
2418 for(;i<svs.maxclients;i++)
2419 if(svs.clients[i].active && svs.clients[i].netconnection)
2421 host_client = &svs.clients[i];
2422 Host_ClientCommands("sendcvar %s\n", cvarname);
2427 static void MaxPlayers_f(void)
2431 if (Cmd_Argc() != 2)
2433 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2439 Con_Print("maxplayers can not be changed while a server is running.\n");
2440 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2443 n = atoi(Cmd_Argv(1));
2444 n = bound(1, n, MAX_SCOREBOARD);
2445 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2447 svs.maxclients_next = n;
2449 Cvar_Set ("deathmatch", "0");
2451 Cvar_Set ("deathmatch", "1");
2455 =====================
2458 ProQuake rcon support
2459 =====================
2461 void Host_PQRcon_f (void)
2466 lhnetsocket_t *mysocket;
2467 char peer_address[64];
2469 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2471 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2475 e = strchr(rcon_password.string, ' ');
2476 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2480 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2484 if (!rcon_address.string[0])
2486 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2489 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2491 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2492 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2495 SZ_Clear(&net_message);
2496 MSG_WriteLong (&net_message, 0);
2497 MSG_WriteByte (&net_message, CCREQ_RCON);
2498 SZ_Write(&net_message, (const unsigned char*)rcon_password.string, n);
2499 MSG_WriteByte (&net_message, 0); // terminate the (possibly partial) string
2500 MSG_WriteString (&net_message, Cmd_Args());
2501 StoreBigLong(net_message.data, NETFLAG_CTL | (net_message.cursize & NETFLAG_LENGTH_MASK));
2502 NetConn_Write(mysocket, net_message.data, net_message.cursize, &to);
2503 SZ_Clear (&net_message);
2507 //=============================================================================
2509 // QuakeWorld commands
2512 =====================
2515 Send the rest of the command line over as
2516 an unconnected command.
2517 =====================
2519 void Host_Rcon_f (void) // credit: taken from QuakeWorld
2524 lhnetsocket_t *mysocket;
2526 if (!rcon_password.string || !rcon_password.string[0])
2528 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2532 e = strchr(rcon_password.string, ' ');
2533 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2536 to = cls.netcon->peeraddress;
2539 if (!rcon_address.string[0])
2541 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2544 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2546 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2547 if (mysocket && Cmd_Args()[0])
2549 // simply put together the rcon packet and send it
2550 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2552 if(cls.rcon_commands[cls.rcon_ringpos][0])
2555 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2556 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]);
2557 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2560 for (i = 0;i < MAX_RCONS;i++)
2561 if(cls.rcon_commands[i][0])
2562 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2566 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2567 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2568 cls.rcon_addresses[cls.rcon_ringpos] = to;
2569 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2570 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2572 else if(rcon_secure.integer > 0)
2576 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2577 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2578 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2581 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2582 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2587 NetConn_WriteString(mysocket, va("\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2593 ====================
2596 user <name or userid>
2598 Dump userdata / masterdata for a user
2599 ====================
2601 void Host_User_f (void) // credit: taken from QuakeWorld
2606 if (Cmd_Argc() != 2)
2608 Con_Printf ("Usage: user <username / userid>\n");
2612 uid = atoi(Cmd_Argv(1));
2614 for (i = 0;i < cl.maxclients;i++)
2616 if (!cl.scores[i].name[0])
2618 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2620 InfoString_Print(cl.scores[i].qw_userinfo);
2624 Con_Printf ("User not in server.\n");
2628 ====================
2631 Dump userids for all current players
2632 ====================
2634 void Host_Users_f (void) // credit: taken from QuakeWorld
2640 Con_Printf ("userid frags name\n");
2641 Con_Printf ("------ ----- ----\n");
2642 for (i = 0;i < cl.maxclients;i++)
2644 if (cl.scores[i].name[0])
2646 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2651 Con_Printf ("%i total users\n", c);
2656 Host_FullServerinfo_f
2658 Sent by server when serverinfo changes
2661 // TODO: shouldn't this be a cvar instead?
2662 void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2665 if (Cmd_Argc() != 2)
2667 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2671 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2672 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2673 cl.qw_teamplay = atoi(temp);
2680 Allow clients to change userinfo
2684 void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2691 if (Cmd_Argc() != 2)
2693 Con_Printf ("fullinfo <complete info string>\n");
2703 while (*s && *s != '\\')
2709 Con_Printf ("MISSING VALUE\n");
2715 while (*s && *s != '\\')
2722 CL_SetInfo(key, value, false, false, false, false);
2730 Allow clients to change userinfo
2733 void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2735 if (Cmd_Argc() == 1)
2737 InfoString_Print(cls.userinfo);
2740 if (Cmd_Argc() != 3)
2742 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2745 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2749 ====================
2752 packet <destination> <contents>
2754 Contents allows \n escape character
2755 ====================
2757 void Host_Packet_f (void) // credit: taken from QuakeWorld
2763 lhnetaddress_t address;
2764 lhnetsocket_t *mysocket;
2766 if (Cmd_Argc() != 3)
2768 Con_Printf ("packet <destination> <contents>\n");
2772 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2774 Con_Printf ("Bad address\n");
2780 send[0] = send[1] = send[2] = send[3] = -1;
2782 l = (int)strlen (in);
2783 for (i=0 ; i<l ; i++)
2785 if (out >= send + sizeof(send) - 1)
2787 if (in[i] == '\\' && in[i+1] == 'n')
2792 else if (in[i] == '\\' && in[i+1] == '0')
2797 else if (in[i] == '\\' && in[i+1] == 't')
2802 else if (in[i] == '\\' && in[i+1] == 'r')
2807 else if (in[i] == '\\' && in[i+1] == '"')
2816 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2818 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2820 NetConn_Write(mysocket, send, out - send, &address);
2824 ====================
2827 Send back ping and packet loss update for all current players to this player
2828 ====================
2830 void Host_Pings_f (void)
2832 int i, j, ping, packetloss, movementloss;
2835 if (!host_client->netconnection)
2838 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2840 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2841 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2843 for (i = 0;i < svs.maxclients;i++)
2847 if (svs.clients[i].netconnection)
2849 for (j = 0;j < NETGRAPH_PACKETS;j++)
2850 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2852 for (j = 0;j < NETGRAPH_PACKETS;j++)
2853 if (svs.clients[i].movement_count[j] < 0)
2856 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2857 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2858 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2859 ping = bound(0, ping, 9999);
2860 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2862 // send qw_svc_updateping and qw_svc_updatepl messages
2863 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2864 MSG_WriteShort(&host_client->netconnection->message, ping);
2865 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2866 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2870 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2872 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2874 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2875 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2878 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2879 MSG_WriteString(&host_client->netconnection->message, "\n");
2882 void Host_PingPLReport_f(void)
2887 if (l > cl.maxclients)
2889 for (i = 0;i < l;i++)
2891 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2892 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2893 if(errbyte && *errbyte == ',')
2894 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2896 cl.scores[i].qw_movementloss = 0;
2900 //=============================================================================
2907 void Host_InitCommands (void)
2909 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2911 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2912 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2913 if (gamemode == GAME_NEHAHRA)
2915 Cmd_AddCommand_WithClientCommand ("max", NULL, Host_God_f, "god mode (invulnerability)");
2916 Cmd_AddCommand_WithClientCommand ("monster", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2917 Cmd_AddCommand_WithClientCommand ("scrag", NULL, Host_Fly_f, "fly mode (flight)");
2918 Cmd_AddCommand_WithClientCommand ("wraith", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2919 Cmd_AddCommand_WithClientCommand ("gimme", NULL, Host_Give_f, "alter inventory");
2923 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2924 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2925 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2926 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2927 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2929 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2930 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2931 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2932 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2933 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)");
2934 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2935 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2936 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2937 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2938 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2939 Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
2940 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2941 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2942 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2943 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2945 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2946 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2947 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2949 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2950 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2951 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2952 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2954 Cvar_RegisterVariable (&cl_name);
2955 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2956 Cvar_RegisterVariable (&cl_color);
2957 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2958 Cvar_RegisterVariable (&cl_rate);
2959 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2960 if (gamemode == GAME_NEHAHRA)
2962 Cvar_RegisterVariable (&cl_pmodel);
2963 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "change your player model choice (Nehahra specific)");
2966 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2967 Cvar_RegisterVariable (&cl_playermodel);
2968 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2969 Cvar_RegisterVariable (&cl_playerskin);
2970 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2972 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2973 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2974 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)");
2975 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2977 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2979 Cvar_RegisterVariable (&rcon_password);
2980 Cvar_RegisterVariable (&rcon_address);
2981 Cvar_RegisterVariable (&rcon_secure);
2982 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2983 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");
2984 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");
2985 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)");
2986 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2987 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2988 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2989 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2990 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2991 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2992 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2993 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2995 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)");
2996 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)");
2998 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)");
2999 Cvar_RegisterVariable (&r_fixtrans_auto);
3001 Cvar_RegisterVariable (&team);
3002 Cvar_RegisterVariable (&skin);
3003 Cvar_RegisterVariable (&noaim);
3005 Cvar_RegisterVariable(&sv_cheats);
3006 Cvar_RegisterVariable(&sv_adminnick);
3007 Cvar_RegisterVariable(&sv_status_privacy);
3008 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3011 void Host_NoOperation_f(void)