2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 // for secure rcon authentication
33 cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
34 cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
35 cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
36 cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."};
37 cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"};
38 cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"};
39 cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"};
40 cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"};
41 cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
42 cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
43 cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
44 cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
45 cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"};
46 qboolean allowcheats = false;
48 extern qboolean host_shuttingdown;
49 extern cvar_t developer_entityparsing;
57 void Host_Quit_f (void)
60 Con_Printf("shutting down already!\n");
70 static void Host_Status_f (void)
72 prvm_prog_t *prog = SVVM_prog;
75 int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
76 void (*print) (const char *fmt, ...);
77 char ip[48]; // can contain a full length v6 address with [] and a port
81 if (cmd_source == src_command)
83 // if running a client, try to send over network so the client's status report parser will see the report
84 if (cls.state == ca_connected)
86 Cmd_ForwardToServer ();
92 print = SV_ClientPrintf;
100 if (strcmp(Cmd_Argv(1), "1") == 0)
102 else if (strcmp(Cmd_Argv(1), "2") == 0)
106 for (players = 0, i = 0;i < svs.maxclients;i++)
107 if (svs.clients[i].active)
109 print ("host: %s\n", Cvar_VariableString ("hostname"));
110 print ("version: %s build %s\n", gamename, buildstring);
111 print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
112 print ("map: %s\n", sv.name);
113 print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
114 print ("players: %i active (%i max)\n\n", players, svs.maxclients);
117 print ("^2IP %%pl ping time frags no name\n");
119 print ("^5IP no name\n");
121 for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
128 if (in == 0 || in == 1)
130 seconds = (int)(realtime - client->connecttime);
131 minutes = seconds / 60;
134 seconds -= (minutes * 60);
135 hours = minutes / 60;
137 minutes -= (hours * 60);
143 if (client->netconnection)
144 for (j = 0;j < NETGRAPH_PACKETS;j++)
145 if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
147 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
148 ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
151 if(sv_status_privacy.integer && cmd_source != src_command)
152 strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
154 strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
156 frags = client->frags;
158 if(sv_status_show_qcstatus.integer)
160 prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
161 const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
167 for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
168 if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
172 frags = atoi(qcstatus);
176 if (in == 0) // default layout
178 if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
180 // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
181 print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
186 // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
187 print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
191 else if (in == 1) // extended layout
193 print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name);
195 else if (in == 2) // reduced layout
197 print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
207 Sets client to godmode
210 static void Host_God_f (void)
212 prvm_prog_t *prog = SVVM_prog;
215 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
219 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
220 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
221 SV_ClientPrint("godmode OFF\n");
223 SV_ClientPrint("godmode ON\n");
226 static void Host_Notarget_f (void)
228 prvm_prog_t *prog = SVVM_prog;
231 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
235 PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
236 if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
237 SV_ClientPrint("notarget OFF\n");
239 SV_ClientPrint("notarget ON\n");
242 qboolean noclip_anglehack;
244 static void Host_Noclip_f (void)
246 prvm_prog_t *prog = SVVM_prog;
249 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
253 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
255 noclip_anglehack = true;
256 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
257 SV_ClientPrint("noclip ON\n");
261 noclip_anglehack = false;
262 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
263 SV_ClientPrint("noclip OFF\n");
271 Sets client to flymode
274 static void Host_Fly_f (void)
276 prvm_prog_t *prog = SVVM_prog;
279 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
283 if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
285 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
286 SV_ClientPrint("flymode ON\n");
290 PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
291 SV_ClientPrint("flymode OFF\n");
302 void Host_Pings_f (void); // called by Host_Ping_f
303 static void Host_Ping_f (void)
307 void (*print) (const char *fmt, ...);
309 if (cmd_source == src_command)
311 // if running a client, try to send over network so the client's ping report parser will see the report
312 if (cls.state == ca_connected)
314 Cmd_ForwardToServer ();
320 print = SV_ClientPrintf;
325 print("Client ping times:\n");
326 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
330 print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
333 // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report)
334 // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
339 ===============================================================================
343 ===============================================================================
347 ======================
352 command from the console. Active clients are kicked off.
353 ======================
355 static void Host_Map_f (void)
357 char level[MAX_QPATH];
361 Con_Print("map <levelname> : start a new game (kicks off all players)\n");
365 // GAME_DELUXEQUAKE - clear warpmark (used by QC)
366 if (gamemode == GAME_DELUXEQUAKE)
367 Cvar_Set("warpmark", "");
369 cls.demonum = -1; // stop demo loop in case this fails
372 Host_ShutdownServer();
374 if(svs.maxclients != svs.maxclients_next)
376 svs.maxclients = svs.maxclients_next;
378 Mem_Free(svs.clients);
379 svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
383 if (key_dest == key_menu || key_dest == key_menu_grabbed)
387 svs.serverflags = 0; // haven't completed an episode yet
388 allowcheats = sv_cheats.integer != 0;
389 strlcpy(level, Cmd_Argv(1), sizeof(level));
390 SV_SpawnServer(level);
391 if (sv.active && cls.state == ca_disconnected)
392 CL_EstablishConnection("local:1", -2);
399 Goes to a new map, taking all clients along
402 static void Host_Changelevel_f (void)
404 char level[MAX_QPATH];
408 Con_Print("changelevel <levelname> : continue game on a new level\n");
418 if (key_dest == key_menu || key_dest == key_menu_grabbed)
422 SV_SaveSpawnparms ();
423 allowcheats = sv_cheats.integer != 0;
424 strlcpy(level, Cmd_Argv(1), sizeof(level));
425 SV_SpawnServer(level);
426 if (sv.active && cls.state == ca_disconnected)
427 CL_EstablishConnection("local:1", -2);
434 Restarts the current server for a dead player
437 static void Host_Restart_f (void)
439 char mapname[MAX_QPATH];
443 Con_Print("restart : restart current level\n");
448 Con_Print("Only the server may restart\n");
453 if (key_dest == key_menu || key_dest == key_menu_grabbed)
457 allowcheats = sv_cheats.integer != 0;
458 strlcpy(mapname, sv.name, sizeof(mapname));
459 SV_SpawnServer(mapname);
460 if (sv.active && cls.state == ca_disconnected)
461 CL_EstablishConnection("local:1", -2);
468 This command causes the client to wait for the signon messages again.
469 This is sent just before a server changes levels
472 void Host_Reconnect_f (void)
475 // if not connected, reconnect to the most recent server
478 // if we have connected to a server recently, the userinfo
479 // will still contain its IP address, so get the address...
480 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
482 CL_EstablishConnection(temp, -1);
484 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
487 // if connected, do something based on protocol
488 if (cls.protocol == PROTOCOL_QUAKEWORLD)
490 // quakeworld can just re-login
491 if (cls.qw_downloadmemory) // don't change when downloading
496 if (cls.state == ca_connected && cls.signon < SIGNONS)
498 Con_Printf("reconnecting...\n");
499 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
500 MSG_WriteString(&cls.netcon->message, "new");
505 // netquake uses reconnect on level changes (silly)
508 Con_Print("reconnect : wait for signon messages again\n");
513 Con_Print("reconnect: no signon, ignoring reconnect\n");
516 cls.signon = 0; // need new connection messages
521 =====================
524 User command to connect to server
525 =====================
527 static void Host_Connect_f (void)
531 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
534 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
535 if(rcon_secure.integer <= 0)
536 Cvar_SetQuick(&rcon_password, "");
537 CL_EstablishConnection(Cmd_Argv(1), 2);
542 ===============================================================================
546 ===============================================================================
549 #define SAVEGAME_VERSION 5
551 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
554 int i, k, l, lightstyles = 64;
555 char comment[SAVEGAME_COMMENT_LENGTH+1];
556 char line[MAX_INPUTLINE];
560 // first we have to figure out if this can be saved in 64 lightstyles
561 // (for Quake compatibility)
562 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
563 if (sv.lightstyles[i][0])
566 isserver = prog == SVVM_prog;
568 Con_Printf("Saving game to %s...\n", name);
569 f = FS_OpenRealFile(name, "wb", false);
572 Con_Print("ERROR: couldn't open.\n");
576 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
578 memset(comment, 0, sizeof(comment));
580 dpsnprintf(comment, sizeof(comment), "%-21.21s kills:%3i/%3i", PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters));
582 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
583 // convert space to _ to make stdio happy
584 // LordHavoc: convert control characters to _ as well
585 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
586 if (ISWHITESPACEORCONTROL(comment[i]))
588 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
590 FS_Printf(f, "%s\n", comment);
593 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
594 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
595 FS_Printf(f, "%d\n", current_skill);
596 FS_Printf(f, "%s\n", sv.name);
597 FS_Printf(f, "%f\n",sv.time);
601 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
602 FS_Printf(f, "(dummy)\n");
603 FS_Printf(f, "%d\n", 0);
604 FS_Printf(f, "%s\n", "(dummy)");
605 FS_Printf(f, "%f\n", realtime);
608 // write the light styles
609 for (i=0 ; i<lightstyles ; i++)
611 if (isserver && sv.lightstyles[i][0])
612 FS_Printf(f, "%s\n", sv.lightstyles[i]);
617 PRVM_ED_WriteGlobals (prog, f);
618 for (i=0 ; i<prog->num_edicts ; i++)
620 FS_Printf(f,"// edict %d\n", i);
621 //Con_Printf("edict %d...\n", i);
622 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
627 FS_Printf(f,"// DarkPlaces extended savegame\n");
628 // darkplaces extension - extra lightstyles, support for color lightstyles
629 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
630 if (isserver && sv.lightstyles[i][0])
631 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
633 // darkplaces extension - model precaches
634 for (i=1 ; i<MAX_MODELS ; i++)
635 if (sv.model_precache[i][0])
636 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
638 // darkplaces extension - sound precaches
639 for (i=1 ; i<MAX_SOUNDS ; i++)
640 if (sv.sound_precache[i][0])
641 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
643 // darkplaces extension - save buffers
644 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 static void Host_Savegame_f (void)
701 prvm_prog_t *prog = SVVM_prog;
702 char name[MAX_QPATH];
703 qboolean deadflag = false;
707 Con_Print("Can't save - no server running.\n");
711 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
715 // singleplayer checks
718 Con_Print("Can't save in intermission.\n");
724 Con_Print("Can't savegame with a dead player\n");
729 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");
733 Con_Print("save <savename> : save a game\n");
737 if (strstr(Cmd_Argv(1), ".."))
739 Con_Print("Relative pathnames are not allowed.\n");
743 strlcpy (name, Cmd_Argv(1), sizeof (name));
744 FS_DefaultExtension (name, ".sav", sizeof (name));
746 Host_Savegame_to(prog, name);
756 static void Host_Loadgame_f (void)
758 prvm_prog_t *prog = SVVM_prog;
759 char filename[MAX_QPATH];
760 char mapname[MAX_QPATH];
770 float spawn_parms[NUM_SPAWN_PARMS];
771 prvm_stringbuffer_t *stringbuffer;
776 Con_Print("load <savename> : load a game\n");
780 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
781 FS_DefaultExtension (filename, ".sav", sizeof (filename));
783 Con_Printf("Loading game from %s...\n", filename);
785 // stop playing demos
786 if (cls.demoplayback)
790 if (key_dest == key_menu || key_dest == key_menu_grabbed)
794 cls.demonum = -1; // stop demo loop in case this fails
796 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
799 Con_Print("ERROR: couldn't open.\n");
803 if(developer_entityparsing.integer)
804 Con_Printf("Host_Loadgame_f: loading version\n");
807 COM_ParseToken_Simple(&t, false, false, true);
808 version = atoi(com_token);
809 if (version != SAVEGAME_VERSION)
812 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
816 if(developer_entityparsing.integer)
817 Con_Printf("Host_Loadgame_f: loading description\n");
820 COM_ParseToken_Simple(&t, false, false, true);
822 for (i = 0;i < NUM_SPAWN_PARMS;i++)
824 COM_ParseToken_Simple(&t, false, false, true);
825 spawn_parms[i] = atof(com_token);
828 COM_ParseToken_Simple(&t, false, false, true);
829 // this silliness is so we can load 1.06 save files, which have float skill values
830 current_skill = (int)(atof(com_token) + 0.5);
831 Cvar_SetValue ("skill", (float)current_skill);
833 if(developer_entityparsing.integer)
834 Con_Printf("Host_Loadgame_f: loading mapname\n");
837 COM_ParseToken_Simple(&t, false, false, true);
838 strlcpy (mapname, com_token, sizeof(mapname));
840 if(developer_entityparsing.integer)
841 Con_Printf("Host_Loadgame_f: loading time\n");
844 COM_ParseToken_Simple(&t, false, false, true);
845 time = atof(com_token);
847 allowcheats = sv_cheats.integer != 0;
849 if(developer_entityparsing.integer)
850 Con_Printf("Host_Loadgame_f: spawning server\n");
852 SV_SpawnServer (mapname);
856 Con_Print("Couldn't load map\n");
859 sv.paused = true; // pause until all clients connect
862 if(developer_entityparsing.integer)
863 Con_Printf("Host_Loadgame_f: loading light styles\n");
865 // load the light styles
870 for (i = 0;i < MAX_LIGHTSTYLES;i++)
874 COM_ParseToken_Simple(&t, false, false, true);
875 // if this is a 64 lightstyle savegame produced by Quake, stop now
876 // we have to check this because darkplaces may save more than 64
877 if (com_token[0] == '{')
882 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
885 if(developer_entityparsing.integer)
886 Con_Printf("Host_Loadgame_f: skipping until globals\n");
888 // now skip everything before the first opening brace
889 // (this is for forward compatibility, so that older versions (at
890 // least ones with this fix) can load savegames with extra data before the
891 // first brace, as might be produced by a later engine version)
895 if (!COM_ParseToken_Simple(&t, false, false, true))
897 if (com_token[0] == '{')
904 // unlink all entities
905 World_UnlinkAll(&sv.world);
907 // load the edicts out of the savegame file
912 while (COM_ParseToken_Simple(&t, false, false, true))
913 if (!strcmp(com_token, "}"))
915 if (!COM_ParseToken_Simple(&start, false, false, true))
920 if (strcmp(com_token,"{"))
923 Host_Error ("First token isn't a brace");
928 if(developer_entityparsing.integer)
929 Con_Printf("Host_Loadgame_f: loading globals\n");
931 // parse the global vars
932 PRVM_ED_ParseGlobals (prog, start);
934 // restore the autocvar globals
935 Cvar_UpdateAllAutoCvars();
940 if (entnum >= MAX_EDICTS)
943 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
945 while (entnum >= prog->max_edicts)
946 PRVM_MEM_IncreaseEdicts(prog);
947 ent = PRVM_EDICT_NUM(entnum);
948 memset(ent->fields.vp, 0, prog->entityfields * 4);
949 ent->priv.server->free = false;
951 if(developer_entityparsing.integer)
952 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
954 PRVM_ED_ParseEdict (prog, start, ent);
956 // link it into the bsp tree
957 if (!ent->priv.server->free)
965 prog->num_edicts = entnum;
968 for (i = 0;i < NUM_SPAWN_PARMS;i++)
969 svs.clients[0].spawn_parms[i] = spawn_parms[i];
971 if(developer_entityparsing.integer)
972 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
974 // read extended data if present
975 // the extended data is stored inside a /* */ comment block, which the
976 // parser intentionally skips, so we have to check for it manually here
979 while (*end == '\r' || *end == '\n')
981 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
983 if(developer_entityparsing.integer)
984 Con_Printf("Host_Loadgame_f: loading extended data\n");
986 Con_Printf("Loading extended DarkPlaces savegame\n");
988 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
989 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
990 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
991 while (COM_ParseToken_Simple(&t, false, false, true))
993 if (!strcmp(com_token, "sv.lightstyles"))
995 COM_ParseToken_Simple(&t, false, false, true);
997 COM_ParseToken_Simple(&t, false, false, true);
998 if (i >= 0 && i < MAX_LIGHTSTYLES)
999 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1001 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1003 else if (!strcmp(com_token, "sv.model_precache"))
1005 COM_ParseToken_Simple(&t, false, false, true);
1006 i = atoi(com_token);
1007 COM_ParseToken_Simple(&t, false, false, true);
1008 if (i >= 0 && i < MAX_MODELS)
1010 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1011 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1014 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1016 else if (!strcmp(com_token, "sv.sound_precache"))
1018 COM_ParseToken_Simple(&t, false, false, true);
1019 i = atoi(com_token);
1020 COM_ParseToken_Simple(&t, false, false, true);
1021 if (i >= 0 && i < MAX_SOUNDS)
1022 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1024 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1026 else if (!strcmp(com_token, "sv.bufstr"))
1028 COM_ParseToken_Simple(&t, false, false, true);
1029 i = atoi(com_token);
1030 COM_ParseToken_Simple(&t, false, false, true);
1031 k = atoi(com_token);
1032 COM_ParseToken_Simple(&t, false, false, true);
1033 stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
1034 // VorteX: nasty code, cleanup required
1035 // create buffer at this index
1037 stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecordAtIndex(&prog->stringbuffersarray, i);
1039 Con_Printf("cant write string %i into buffer %i\n", k, i);
1042 // code copied from VM_bufstr_set
1044 if (stringbuffer->max_strings <= i)
1046 char **oldstrings = stringbuffer->strings;
1047 stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128);
1048 while (stringbuffer->max_strings <= i)
1049 stringbuffer->max_strings *= 2;
1050 stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0]));
1051 if (stringbuffer->num_strings > 0)
1052 memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0]));
1054 Mem_Free(oldstrings);
1057 stringbuffer->num_strings = max(stringbuffer->num_strings, k + 1);
1058 if(stringbuffer->strings[k])
1059 Mem_Free(stringbuffer->strings[k]);
1060 stringbuffer->strings[k] = NULL;
1061 alloclen = strlen(com_token) + 1;
1062 stringbuffer->strings[k] = (char *)Mem_Alloc(prog->progs_mempool, alloclen);
1063 memcpy(stringbuffer->strings[k], com_token, alloclen);
1066 // skip any trailing text or unrecognized commands
1067 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1074 if(developer_entityparsing.integer)
1075 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 static void Host_Name_f (void)
1092 prvm_prog_t *prog = SVVM_prog;
1094 qboolean valid_colors;
1095 const char *newNameSource;
1096 char newName[sizeof(host_client->name)];
1098 if (Cmd_Argc () == 1)
1100 Con_Printf("name: %s\n", cl_name.string);
1104 if (Cmd_Argc () == 2)
1105 newNameSource = Cmd_Argv(1);
1107 newNameSource = Cmd_Args();
1109 strlcpy(newName, newNameSource, sizeof(newName));
1111 if (cmd_source == src_command)
1113 Cvar_Set ("_cl_name", newName);
1114 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1116 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1117 Con_Printf("name: %s\n", cl_name.string);
1122 if (realtime < host_client->nametime)
1124 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1128 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1130 // point the string back at updateclient->name to keep it safe
1131 strlcpy (host_client->name, newName, sizeof (host_client->name));
1133 for (i = 0, j = 0;host_client->name[i];i++)
1134 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1135 host_client->name[j++] = host_client->name[i];
1136 host_client->name[j] = 0;
1138 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1139 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1141 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1142 host_client->name[sizeof(host_client->name) - 1] = 0;
1143 host_client->name[0] = STRING_COLOR_TAG;
1144 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1147 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1148 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1151 l = strlen(host_client->name);
1152 if(l < sizeof(host_client->name) - 1)
1154 // duplicate the color tag to escape it
1155 host_client->name[i] = STRING_COLOR_TAG;
1156 host_client->name[i+1] = 0;
1157 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1161 // remove the last character to fix the color code
1162 host_client->name[l-1] = 0;
1163 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1167 // find the last color tag offset and decide if we need to add a reset tag
1168 for (i = 0, j = -1;host_client->name[i];i++)
1170 if (host_client->name[i] == STRING_COLOR_TAG)
1172 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1175 // if this happens to be a reset tag then we don't need one
1176 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1181 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]))
1187 if (host_client->name[i+1] == STRING_COLOR_TAG)
1194 // does not end in the default color string, so add it
1195 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1196 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1198 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1199 if (strcmp(host_client->old_name, host_client->name))
1201 if (host_client->spawned)
1202 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1203 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1204 // send notification to all clients
1205 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1206 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1207 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1208 SV_WriteNetnameIntoDemo(host_client);
1213 ======================
1215 ======================
1217 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1218 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1219 static void Host_Playermodel_f (void)
1221 prvm_prog_t *prog = SVVM_prog;
1223 char newPath[sizeof(host_client->playermodel)];
1225 if (Cmd_Argc () == 1)
1227 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1231 if (Cmd_Argc () == 2)
1232 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1234 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1236 for (i = 0, j = 0;newPath[i];i++)
1237 if (newPath[i] != '\r' && newPath[i] != '\n')
1238 newPath[j++] = newPath[i];
1241 if (cmd_source == src_command)
1243 Cvar_Set ("_cl_playermodel", newPath);
1248 if (realtime < host_client->nametime)
1250 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1254 host_client->nametime = realtime + 5;
1257 // point the string back at updateclient->name to keep it safe
1258 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1259 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1260 if (strcmp(host_client->old_model, host_client->playermodel))
1262 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1263 /*// send notification to all clients
1264 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1265 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1266 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1271 ======================
1273 ======================
1275 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1276 static void Host_Playerskin_f (void)
1278 prvm_prog_t *prog = SVVM_prog;
1280 char newPath[sizeof(host_client->playerskin)];
1282 if (Cmd_Argc () == 1)
1284 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1288 if (Cmd_Argc () == 2)
1289 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1291 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1293 for (i = 0, j = 0;newPath[i];i++)
1294 if (newPath[i] != '\r' && newPath[i] != '\n')
1295 newPath[j++] = newPath[i];
1298 if (cmd_source == src_command)
1300 Cvar_Set ("_cl_playerskin", newPath);
1305 if (realtime < host_client->nametime)
1307 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1311 host_client->nametime = realtime + 5;
1314 // point the string back at updateclient->name to keep it safe
1315 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1316 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1317 if (strcmp(host_client->old_skin, host_client->playerskin))
1319 //if (host_client->spawned)
1320 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1321 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1322 /*// send notification to all clients
1323 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1324 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1325 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1329 static void Host_Version_f (void)
1331 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1334 static void Host_Say(qboolean teamonly)
1336 prvm_prog_t *prog = SVVM_prog;
1341 // LordHavoc: long say messages
1343 qboolean fromServer = false;
1345 if (cmd_source == src_command)
1347 if (cls.state == ca_dedicated)
1354 Cmd_ForwardToServer ();
1359 if (Cmd_Argc () < 2)
1362 if (!teamplay.integer)
1372 // note this uses the chat prefix \001
1373 if (!fromServer && !teamonly)
1374 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1375 else if (!fromServer && teamonly)
1376 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1377 else if(*(sv_adminnick.string))
1378 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1380 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1381 p2 = text + strlen(text);
1382 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1384 if (p2[-1] == '\"' && quoted)
1389 strlcat(text, "\n", sizeof(text));
1391 // note: save is not a valid edict if fromServer is true
1393 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1394 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1395 SV_ClientPrint(text);
1398 if (cls.state == ca_dedicated)
1399 Con_Print(&text[1]);
1403 static void Host_Say_f(void)
1409 static void Host_Say_Team_f(void)
1415 static void Host_Tell_f(void)
1417 const char *playername_start = NULL;
1418 size_t playername_length = 0;
1419 int playernumber = 0;
1422 const char *p1, *p2;
1423 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1424 qboolean fromServer = false;
1426 if (cmd_source == src_command)
1428 if (cls.state == ca_dedicated)
1432 Cmd_ForwardToServer ();
1437 if (Cmd_Argc () < 2)
1440 // note this uses the chat prefix \001
1442 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1443 else if(*(sv_adminnick.string))
1444 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1446 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1449 p2 = p1 + strlen(p1);
1450 // remove the target name
1451 while (p1 < p2 && *p1 == ' ')
1456 while (p1 < p2 && *p1 == ' ')
1458 while (p1 < p2 && isdigit(*p1))
1460 playernumber = playernumber * 10 + (*p1 - '0');
1468 playername_start = p1;
1469 while (p1 < p2 && *p1 != '"')
1471 playername_length = p1 - playername_start;
1477 playername_start = p1;
1478 while (p1 < p2 && *p1 != ' ')
1480 playername_length = p1 - playername_start;
1482 while (p1 < p2 && *p1 == ' ')
1484 if(playername_start)
1486 // set playernumber to the right client
1488 if(playername_length >= sizeof(namebuf))
1491 Con_Print("Host_Tell: too long player name/ID\n");
1493 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1496 memcpy(namebuf, playername_start, playername_length);
1497 namebuf[playername_length] = 0;
1498 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1500 if (!svs.clients[playernumber].active)
1502 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1506 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1509 Con_Print("Host_Tell: invalid player name/ID\n");
1511 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1514 // remove trailing newlines
1515 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1517 // remove quotes if present
1523 else if (fromServer)
1524 Con_Print("Host_Tell: missing end quote\n");
1526 SV_ClientPrint("Host_Tell: missing end quote\n");
1528 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1531 return; // empty say
1532 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1538 host_client = svs.clients + playernumber;
1539 SV_ClientPrint(text);
1549 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1550 static void Host_Color(int changetop, int changebottom)
1552 prvm_prog_t *prog = SVVM_prog;
1553 int top, bottom, playercolor;
1555 // get top and bottom either from the provided values or the current values
1556 // (allows changing only top or bottom, or both at once)
1557 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1558 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1562 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1568 playercolor = top*16 + bottom;
1570 if (cmd_source == src_command)
1572 Cvar_SetValueQuick(&cl_color, playercolor);
1576 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1579 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1581 Con_DPrint("Calling SV_ChangeTeam\n");
1582 prog->globals.generic[OFS_PARM0] = playercolor;
1583 PRVM_serverglobalfloat(time) = sv.time;
1584 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1585 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1589 if (host_client->edict)
1591 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1592 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1594 host_client->colors = playercolor;
1595 if (host_client->old_colors != host_client->colors)
1597 host_client->old_colors = host_client->colors;
1598 // send notification to all clients
1599 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1600 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1601 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1606 static void Host_Color_f(void)
1610 if (Cmd_Argc() == 1)
1612 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1613 Con_Print("color <0-15> [0-15]\n");
1617 if (Cmd_Argc() == 2)
1618 top = bottom = atoi(Cmd_Argv(1));
1621 top = atoi(Cmd_Argv(1));
1622 bottom = atoi(Cmd_Argv(2));
1624 Host_Color(top, bottom);
1627 static void Host_TopColor_f(void)
1629 if (Cmd_Argc() == 1)
1631 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1632 Con_Print("topcolor <0-15>\n");
1636 Host_Color(atoi(Cmd_Argv(1)), -1);
1639 static void Host_BottomColor_f(void)
1641 if (Cmd_Argc() == 1)
1643 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1644 Con_Print("bottomcolor <0-15>\n");
1648 Host_Color(-1, atoi(Cmd_Argv(1)));
1651 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1652 static void Host_Rate_f(void)
1656 if (Cmd_Argc() != 2)
1658 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1659 Con_Print("rate <bytespersecond>\n");
1663 rate = atoi(Cmd_Argv(1));
1665 if (cmd_source == src_command)
1667 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1671 host_client->rate = rate;
1679 static void Host_Kill_f (void)
1681 prvm_prog_t *prog = SVVM_prog;
1682 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1684 SV_ClientPrint("Can't suicide -- already dead!\n");
1688 PRVM_serverglobalfloat(time) = sv.time;
1689 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1690 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1699 static void Host_Pause_f (void)
1701 if (!pausable.integer)
1702 SV_ClientPrint("Pause not allowed.\n");
1706 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1707 // send notification to all clients
1708 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1709 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1714 ======================
1716 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1717 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1718 ======================
1720 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)"};
1721 static void Host_PModel_f (void)
1723 prvm_prog_t *prog = SVVM_prog;
1726 if (Cmd_Argc () == 1)
1728 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1731 i = atoi(Cmd_Argv(1));
1733 if (cmd_source == src_command)
1735 if (cl_pmodel.integer == i)
1737 Cvar_SetValue ("_cl_pmodel", i);
1738 if (cls.state == ca_connected)
1739 Cmd_ForwardToServer ();
1743 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1746 //===========================================================================
1754 static void Host_PreSpawn_f (void)
1756 if (host_client->spawned)
1758 Con_Print("prespawn not valid -- already spawned\n");
1762 if (host_client->netconnection)
1764 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1765 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1766 MSG_WriteByte (&host_client->netconnection->message, 2);
1767 host_client->sendsignon = 0; // enable unlimited sends again
1770 // reset the name change timer because the client will send name soon
1771 host_client->nametime = 0;
1779 static void Host_Spawn_f (void)
1781 prvm_prog_t *prog = SVVM_prog;
1784 int stats[MAX_CL_STATS];
1786 if (host_client->spawned)
1788 Con_Print("Spawn not valid -- already spawned\n");
1792 // reset name change timer again because they might want to change name
1793 // again in the first 5 seconds after connecting
1794 host_client->nametime = 0;
1796 // LordHavoc: moved this above the QC calls at FrikaC's request
1797 // LordHavoc: commented this out
1798 //if (host_client->netconnection)
1799 // SZ_Clear (&host_client->netconnection->message);
1801 // run the entrance script
1804 // loaded games are fully initialized already
1805 if (PRVM_serverfunction(RestoreGame))
1807 Con_DPrint("Calling RestoreGame\n");
1808 PRVM_serverglobalfloat(time) = sv.time;
1809 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1810 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1815 //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name);
1817 // copy spawn parms out of the client_t
1818 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1819 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1821 // call the spawn function
1822 host_client->clientconnectcalled = true;
1823 PRVM_serverglobalfloat(time) = sv.time;
1824 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1825 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1827 if (cls.state == ca_dedicated)
1828 Con_Printf("%s connected\n", host_client->name);
1830 PRVM_serverglobalfloat(time) = sv.time;
1831 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1834 if (!host_client->netconnection)
1837 // send time of update
1838 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1839 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1841 // send all current names, colors, and frag counts
1842 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1844 if (!client->active)
1846 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1847 MSG_WriteByte (&host_client->netconnection->message, i);
1848 MSG_WriteString (&host_client->netconnection->message, client->name);
1849 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1850 MSG_WriteByte (&host_client->netconnection->message, i);
1851 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1852 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1853 MSG_WriteByte (&host_client->netconnection->message, i);
1854 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1857 // send all current light styles
1858 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1860 if (sv.lightstyles[i][0])
1862 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1863 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1864 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1869 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1870 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1871 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1873 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1874 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1875 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1877 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1878 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1879 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1881 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1882 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1883 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1886 // Never send a roll angle, because savegames can catch the server
1887 // in a state where it is expecting the client to correct the angle
1888 // and it won't happen if the game was just loaded, so you wind up
1889 // with a permanent head tilt
1892 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1893 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1894 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1895 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1899 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1900 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1901 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1902 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1905 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1907 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1908 MSG_WriteByte (&host_client->netconnection->message, 3);
1916 static void Host_Begin_f (void)
1918 host_client->spawned = true;
1920 // LordHavoc: note: this code also exists in SV_DropClient
1924 for (i = 0;i < svs.maxclients;i++)
1925 if (svs.clients[i].active && !svs.clients[i].spawned)
1927 if (i == svs.maxclients)
1929 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1930 sv.paused = sv.loadgame = false; // we're basically done with loading now
1935 //===========================================================================
1942 Kicks a user off of the server
1945 static void Host_Kick_f (void)
1948 const char *message = NULL;
1951 qboolean byNumber = false;
1958 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1960 i = (int)(atof(Cmd_Argv(2)) - 1);
1961 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1967 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1969 if (!host_client->active)
1971 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
1976 if (i < svs.maxclients)
1978 if (cmd_source == src_command)
1980 if (cls.state == ca_dedicated)
1983 who = cl_name.string;
1988 // can't kick yourself!
1989 if (host_client == save)
1994 message = Cmd_Args();
1995 COM_ParseToken_Simple(&message, false, false, true);
1998 message++; // skip the #
1999 while (*message == ' ') // skip white space
2001 message += strlen(Cmd_Argv(2)); // skip the number
2003 while (*message && *message == ' ')
2007 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2009 SV_ClientPrintf("Kicked by %s\n", who);
2010 SV_DropClient (false); // kicked
2017 ===============================================================================
2021 ===============================================================================
2029 static void Host_Give_f (void)
2031 prvm_prog_t *prog = SVVM_prog;
2037 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2042 v = atoi (Cmd_Argv(2));
2056 // MED 01/04/97 added hipnotic give stuff
2057 if (gamemode == GAME_HIPNOTIC)
2062 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2064 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2066 else if (t[0] == '9')
2067 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2068 else if (t[0] == '0')
2069 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2070 else if (t[0] >= '2')
2071 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2076 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2081 if (gamemode == GAME_ROGUE)
2082 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2084 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2087 if (gamemode == GAME_ROGUE)
2089 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2090 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2091 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2095 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2099 if (gamemode == GAME_ROGUE)
2101 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2102 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2103 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2107 if (gamemode == GAME_ROGUE)
2109 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2110 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2111 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2115 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2119 if (gamemode == GAME_ROGUE)
2121 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2122 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2123 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2127 PRVM_serveredictfloat(host_client->edict, health) = v;
2130 if (gamemode == GAME_ROGUE)
2132 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2133 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2134 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2138 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2142 if (gamemode == GAME_ROGUE)
2144 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2145 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2146 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2152 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2157 for (i=0 ; i<prog->num_edicts ; i++)
2159 e = PRVM_EDICT_NUM(i);
2160 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2163 Con_Print("No viewthing on map\n");
2172 static void Host_Viewmodel_f (void)
2174 prvm_prog_t *prog = SVVM_prog;
2181 e = FindViewthing(prog);
2184 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2185 if (m && m->loaded && m->Draw)
2187 PRVM_serveredictfloat(e, frame) = 0;
2188 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2191 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2200 static void Host_Viewframe_f (void)
2202 prvm_prog_t *prog = SVVM_prog;
2210 e = FindViewthing(prog);
2213 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2215 f = atoi(Cmd_Argv(1));
2216 if (f >= m->numframes)
2219 PRVM_serveredictfloat(e, frame) = f;
2224 static void PrintFrameName (dp_model_t *m, int frame)
2227 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2229 Con_Printf("frame %i\n", frame);
2237 static void Host_Viewnext_f (void)
2239 prvm_prog_t *prog = SVVM_prog;
2246 e = FindViewthing(prog);
2249 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2251 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2252 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2253 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2255 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2264 static void Host_Viewprev_f (void)
2266 prvm_prog_t *prog = SVVM_prog;
2273 e = FindViewthing(prog);
2276 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2278 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2279 if (PRVM_serveredictfloat(e, frame) < 0)
2280 PRVM_serveredictfloat(e, frame) = 0;
2282 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2287 ===============================================================================
2291 ===============================================================================
2300 static void Host_Startdemos_f (void)
2304 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2310 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2313 Con_DPrintf("%i demo(s) in loop\n", c);
2315 for (i=1 ; i<c+1 ; i++)
2316 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2318 // LordHavoc: clear the remaining slots
2319 for (;i <= MAX_DEMOS;i++)
2320 cls.demos[i-1][0] = 0;
2322 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2336 Return to looping demos
2339 static void Host_Demos_f (void)
2341 if (cls.state == ca_dedicated)
2343 if (cls.demonum == -1)
2353 Return to looping demos
2356 static void Host_Stopdemo_f (void)
2358 if (!cls.demoplayback)
2361 Host_ShutdownServer ();
2364 static void Host_SendCvar_f (void)
2368 const char *cvarname;
2374 cvarname = Cmd_Argv(1);
2375 if (cls.state == ca_connected)
2377 c = Cvar_FindVar(cvarname);
2378 // LordHavoc: if there is no such cvar or if it is private, send a
2379 // reply indicating that it has no value
2380 if(!c || (c->flags & CVAR_PRIVATE))
2381 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2383 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2386 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2390 if (cls.state != ca_dedicated)
2394 for(;i<svs.maxclients;i++)
2395 if(svs.clients[i].active && svs.clients[i].netconnection)
2397 host_client = &svs.clients[i];
2398 Host_ClientCommands("sendcvar %s\n", cvarname);
2403 static void MaxPlayers_f(void)
2407 if (Cmd_Argc() != 2)
2409 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2415 Con_Print("maxplayers can not be changed while a server is running.\n");
2416 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2419 n = atoi(Cmd_Argv(1));
2420 n = bound(1, n, MAX_SCOREBOARD);
2421 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2423 svs.maxclients_next = n;
2425 Cvar_Set ("deathmatch", "0");
2427 Cvar_Set ("deathmatch", "1");
2431 =====================
2434 ProQuake rcon support
2435 =====================
2437 static void Host_PQRcon_f (void)
2442 lhnetsocket_t *mysocket;
2443 char peer_address[64];
2445 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2447 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2451 e = strchr(rcon_password.string, ' ');
2452 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2456 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2460 if (!rcon_address.string[0])
2462 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2465 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2467 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2468 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2472 unsigned char bufdata[64];
2475 MSG_WriteLong(&buf, 0);
2476 MSG_WriteByte(&buf, CCREQ_RCON);
2477 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2478 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2479 MSG_WriteString(&buf, Cmd_Args());
2480 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2481 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2486 //=============================================================================
2488 // QuakeWorld commands
2491 =====================
2494 Send the rest of the command line over as
2495 an unconnected command.
2496 =====================
2498 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2503 lhnetsocket_t *mysocket;
2506 if (!rcon_password.string || !rcon_password.string[0])
2508 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2512 e = strchr(rcon_password.string, ' ');
2513 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2516 to = cls.netcon->peeraddress;
2519 if (!rcon_address.string[0])
2521 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2524 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2526 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2527 if (mysocket && Cmd_Args()[0])
2529 // simply put together the rcon packet and send it
2530 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2532 if(cls.rcon_commands[cls.rcon_ringpos][0])
2535 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2536 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]);
2537 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2540 for (i = 0;i < MAX_RCONS;i++)
2541 if(cls.rcon_commands[i][0])
2542 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2546 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2547 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2548 cls.rcon_addresses[cls.rcon_ringpos] = to;
2549 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2550 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2552 else if(rcon_secure.integer > 0)
2556 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2557 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2558 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2561 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2562 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2567 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2573 ====================
2576 user <name or userid>
2578 Dump userdata / masterdata for a user
2579 ====================
2581 static void Host_User_f (void) // credit: taken from QuakeWorld
2586 if (Cmd_Argc() != 2)
2588 Con_Printf ("Usage: user <username / userid>\n");
2592 uid = atoi(Cmd_Argv(1));
2594 for (i = 0;i < cl.maxclients;i++)
2596 if (!cl.scores[i].name[0])
2598 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2600 InfoString_Print(cl.scores[i].qw_userinfo);
2604 Con_Printf ("User not in server.\n");
2608 ====================
2611 Dump userids for all current players
2612 ====================
2614 static void Host_Users_f (void) // credit: taken from QuakeWorld
2620 Con_Printf ("userid frags name\n");
2621 Con_Printf ("------ ----- ----\n");
2622 for (i = 0;i < cl.maxclients;i++)
2624 if (cl.scores[i].name[0])
2626 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2631 Con_Printf ("%i total users\n", c);
2636 Host_FullServerinfo_f
2638 Sent by server when serverinfo changes
2641 // TODO: shouldn't this be a cvar instead?
2642 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2645 if (Cmd_Argc() != 2)
2647 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2651 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2652 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2653 cl.qw_teamplay = atoi(temp);
2660 Allow clients to change userinfo
2664 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2671 if (Cmd_Argc() != 2)
2673 Con_Printf ("fullinfo <complete info string>\n");
2683 while (*s && *s != '\\')
2689 Con_Printf ("MISSING VALUE\n");
2695 while (*s && *s != '\\')
2702 CL_SetInfo(key, value, false, false, false, false);
2710 Allow clients to change userinfo
2713 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2715 if (Cmd_Argc() == 1)
2717 InfoString_Print(cls.userinfo);
2720 if (Cmd_Argc() != 3)
2722 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2725 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2729 ====================
2732 packet <destination> <contents>
2734 Contents allows \n escape character
2735 ====================
2737 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2743 lhnetaddress_t address;
2744 lhnetsocket_t *mysocket;
2746 if (Cmd_Argc() != 3)
2748 Con_Printf ("packet <destination> <contents>\n");
2752 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2754 Con_Printf ("Bad address\n");
2760 send[0] = send[1] = send[2] = send[3] = -1;
2762 l = (int)strlen (in);
2763 for (i=0 ; i<l ; i++)
2765 if (out >= send + sizeof(send) - 1)
2767 if (in[i] == '\\' && in[i+1] == 'n')
2772 else if (in[i] == '\\' && in[i+1] == '0')
2777 else if (in[i] == '\\' && in[i+1] == 't')
2782 else if (in[i] == '\\' && in[i+1] == 'r')
2787 else if (in[i] == '\\' && in[i+1] == '"')
2796 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2798 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2800 NetConn_Write(mysocket, send, out - send, &address);
2804 ====================
2807 Send back ping and packet loss update for all current players to this player
2808 ====================
2810 void Host_Pings_f (void)
2812 int i, j, ping, packetloss, movementloss;
2815 if (!host_client->netconnection)
2818 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2820 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2821 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2823 for (i = 0;i < svs.maxclients;i++)
2827 if (svs.clients[i].netconnection)
2829 for (j = 0;j < NETGRAPH_PACKETS;j++)
2830 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2832 for (j = 0;j < NETGRAPH_PACKETS;j++)
2833 if (svs.clients[i].movement_count[j] < 0)
2836 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2837 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2838 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2839 ping = bound(0, ping, 9999);
2840 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2842 // send qw_svc_updateping and qw_svc_updatepl messages
2843 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2844 MSG_WriteShort(&host_client->netconnection->message, ping);
2845 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2846 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2850 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2852 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2854 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2855 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2858 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2859 MSG_WriteString(&host_client->netconnection->message, "\n");
2862 static void Host_PingPLReport_f(void)
2867 if (l > cl.maxclients)
2869 for (i = 0;i < l;i++)
2871 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2872 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2873 if(errbyte && *errbyte == ',')
2874 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2876 cl.scores[i].qw_movementloss = 0;
2880 //=============================================================================
2887 void Host_InitCommands (void)
2889 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2891 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2892 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2893 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2894 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2895 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2896 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2897 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2898 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2899 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2900 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2901 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2902 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)");
2903 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2904 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2905 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2906 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2907 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2908 Cmd_AddCommand_WithClientCommand ("pause", NULL, Host_Pause_f, "pause the game (if the server allows pausing)");
2909 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2910 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2911 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2912 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2914 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2915 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2916 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2918 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2919 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2920 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2921 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2923 Cvar_RegisterVariable (&cl_name);
2924 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2925 Cvar_RegisterVariable (&cl_color);
2926 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2927 Cvar_RegisterVariable (&cl_rate);
2928 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2929 Cvar_RegisterVariable (&cl_pmodel);
2930 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2932 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2933 Cvar_RegisterVariable (&cl_playermodel);
2934 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2935 Cvar_RegisterVariable (&cl_playerskin);
2936 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2938 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2939 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2940 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)");
2941 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2943 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2945 Cvar_RegisterVariable (&rcon_password);
2946 Cvar_RegisterVariable (&rcon_address);
2947 Cvar_RegisterVariable (&rcon_secure);
2948 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2949 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");
2950 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");
2951 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)");
2952 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2953 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2954 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2955 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2956 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2957 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2958 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2959 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2961 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)");
2962 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)");
2964 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)");
2965 Cvar_RegisterVariable (&r_fixtrans_auto);
2967 Cvar_RegisterVariable (&team);
2968 Cvar_RegisterVariable (&skin);
2969 Cvar_RegisterVariable (&noaim);
2971 Cvar_RegisterVariable(&sv_cheats);
2972 Cvar_RegisterVariable(&sv_adminnick);
2973 Cvar_RegisterVariable(&sv_status_privacy);
2974 Cvar_RegisterVariable(&sv_status_show_qcstatus);
2975 Cvar_RegisterVariable(&sv_namechangetimer);
2978 void Host_NoOperation_f(void)