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 (cmd_source == src_command)
1703 // if running a client, try to send over network so the pause is handled by the server
1704 if (cls.state == ca_connected)
1706 Cmd_ForwardToServer ();
1712 print = SV_ClientPrintf;
1714 if (!pausable.integer)
1716 if (cmd_source == src_client)
1718 if(cls.state == ca_dedicated || host_client == &svs.clients[0]) // non-admin
1720 print("Pause not allowed.\n");
1727 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1728 // send notification to all clients
1729 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1730 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1734 ======================
1736 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1737 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1738 ======================
1740 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)"};
1741 static void Host_PModel_f (void)
1743 prvm_prog_t *prog = SVVM_prog;
1746 if (Cmd_Argc () == 1)
1748 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1751 i = atoi(Cmd_Argv(1));
1753 if (cmd_source == src_command)
1755 if (cl_pmodel.integer == i)
1757 Cvar_SetValue ("_cl_pmodel", i);
1758 if (cls.state == ca_connected)
1759 Cmd_ForwardToServer ();
1763 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1766 //===========================================================================
1774 static void Host_PreSpawn_f (void)
1776 if (host_client->spawned)
1778 Con_Print("prespawn not valid -- already spawned\n");
1782 if (host_client->netconnection)
1784 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1785 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1786 MSG_WriteByte (&host_client->netconnection->message, 2);
1787 host_client->sendsignon = 0; // enable unlimited sends again
1790 // reset the name change timer because the client will send name soon
1791 host_client->nametime = 0;
1799 static void Host_Spawn_f (void)
1801 prvm_prog_t *prog = SVVM_prog;
1804 int stats[MAX_CL_STATS];
1806 if (host_client->spawned)
1808 Con_Print("Spawn not valid -- already spawned\n");
1812 // reset name change timer again because they might want to change name
1813 // again in the first 5 seconds after connecting
1814 host_client->nametime = 0;
1816 // LordHavoc: moved this above the QC calls at FrikaC's request
1817 // LordHavoc: commented this out
1818 //if (host_client->netconnection)
1819 // SZ_Clear (&host_client->netconnection->message);
1821 // run the entrance script
1824 // loaded games are fully initialized already
1825 if (PRVM_serverfunction(RestoreGame))
1827 Con_DPrint("Calling RestoreGame\n");
1828 PRVM_serverglobalfloat(time) = sv.time;
1829 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1830 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1835 //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);
1837 // copy spawn parms out of the client_t
1838 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1839 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1841 // call the spawn function
1842 host_client->clientconnectcalled = true;
1843 PRVM_serverglobalfloat(time) = sv.time;
1844 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1845 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1847 if (cls.state == ca_dedicated)
1848 Con_Printf("%s connected\n", host_client->name);
1850 PRVM_serverglobalfloat(time) = sv.time;
1851 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1854 if (!host_client->netconnection)
1857 // send time of update
1858 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1859 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1861 // send all current names, colors, and frag counts
1862 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1864 if (!client->active)
1866 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1867 MSG_WriteByte (&host_client->netconnection->message, i);
1868 MSG_WriteString (&host_client->netconnection->message, client->name);
1869 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1870 MSG_WriteByte (&host_client->netconnection->message, i);
1871 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1872 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1873 MSG_WriteByte (&host_client->netconnection->message, i);
1874 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1877 // send all current light styles
1878 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1880 if (sv.lightstyles[i][0])
1882 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1883 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1884 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1889 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1890 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1891 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1893 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1894 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1895 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1897 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1898 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1899 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1901 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1902 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1903 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1906 // Never send a roll angle, because savegames can catch the server
1907 // in a state where it is expecting the client to correct the angle
1908 // and it won't happen if the game was just loaded, so you wind up
1909 // with a permanent head tilt
1912 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1913 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1914 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1915 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1919 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1920 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1921 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1922 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1925 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1927 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1928 MSG_WriteByte (&host_client->netconnection->message, 3);
1936 static void Host_Begin_f (void)
1938 host_client->spawned = true;
1940 // LordHavoc: note: this code also exists in SV_DropClient
1944 for (i = 0;i < svs.maxclients;i++)
1945 if (svs.clients[i].active && !svs.clients[i].spawned)
1947 if (i == svs.maxclients)
1949 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1950 sv.paused = sv.loadgame = false; // we're basically done with loading now
1955 //===========================================================================
1962 Kicks a user off of the server
1965 static void Host_Kick_f (void)
1968 const char *message = NULL;
1971 qboolean byNumber = false;
1978 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1980 i = (int)(atof(Cmd_Argv(2)) - 1);
1981 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1987 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1989 if (!host_client->active)
1991 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
1996 if (i < svs.maxclients)
1998 if (cmd_source == src_command)
2000 if (cls.state == ca_dedicated)
2003 who = cl_name.string;
2008 // can't kick yourself!
2009 if (host_client == save)
2014 message = Cmd_Args();
2015 COM_ParseToken_Simple(&message, false, false, true);
2018 message++; // skip the #
2019 while (*message == ' ') // skip white space
2021 message += strlen(Cmd_Argv(2)); // skip the number
2023 while (*message && *message == ' ')
2027 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2029 SV_ClientPrintf("Kicked by %s\n", who);
2030 SV_DropClient (false); // kicked
2037 ===============================================================================
2041 ===============================================================================
2049 static void Host_Give_f (void)
2051 prvm_prog_t *prog = SVVM_prog;
2057 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2062 v = atoi (Cmd_Argv(2));
2076 // MED 01/04/97 added hipnotic give stuff
2077 if (gamemode == GAME_HIPNOTIC)
2082 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2084 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2086 else if (t[0] == '9')
2087 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2088 else if (t[0] == '0')
2089 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2090 else if (t[0] >= '2')
2091 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2096 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2101 if (gamemode == GAME_ROGUE)
2102 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2104 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2107 if (gamemode == GAME_ROGUE)
2109 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2110 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2111 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2115 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2119 if (gamemode == GAME_ROGUE)
2121 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2122 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2123 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2127 if (gamemode == GAME_ROGUE)
2129 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2130 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2131 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2135 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2139 if (gamemode == GAME_ROGUE)
2141 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2142 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2143 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2147 PRVM_serveredictfloat(host_client->edict, health) = v;
2150 if (gamemode == GAME_ROGUE)
2152 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2153 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2154 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2158 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2162 if (gamemode == GAME_ROGUE)
2164 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2165 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2166 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2172 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2177 for (i=0 ; i<prog->num_edicts ; i++)
2179 e = PRVM_EDICT_NUM(i);
2180 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2183 Con_Print("No viewthing on map\n");
2192 static void Host_Viewmodel_f (void)
2194 prvm_prog_t *prog = SVVM_prog;
2201 e = FindViewthing(prog);
2204 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2205 if (m && m->loaded && m->Draw)
2207 PRVM_serveredictfloat(e, frame) = 0;
2208 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2211 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2220 static void Host_Viewframe_f (void)
2222 prvm_prog_t *prog = SVVM_prog;
2230 e = FindViewthing(prog);
2233 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2235 f = atoi(Cmd_Argv(1));
2236 if (f >= m->numframes)
2239 PRVM_serveredictfloat(e, frame) = f;
2244 static void PrintFrameName (dp_model_t *m, int frame)
2247 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2249 Con_Printf("frame %i\n", frame);
2257 static void Host_Viewnext_f (void)
2259 prvm_prog_t *prog = SVVM_prog;
2266 e = FindViewthing(prog);
2269 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2271 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2272 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2273 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2275 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2284 static void Host_Viewprev_f (void)
2286 prvm_prog_t *prog = SVVM_prog;
2293 e = FindViewthing(prog);
2296 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2298 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2299 if (PRVM_serveredictfloat(e, frame) < 0)
2300 PRVM_serveredictfloat(e, frame) = 0;
2302 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2307 ===============================================================================
2311 ===============================================================================
2320 static void Host_Startdemos_f (void)
2324 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2330 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2333 Con_DPrintf("%i demo(s) in loop\n", c);
2335 for (i=1 ; i<c+1 ; i++)
2336 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2338 // LordHavoc: clear the remaining slots
2339 for (;i <= MAX_DEMOS;i++)
2340 cls.demos[i-1][0] = 0;
2342 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2356 Return to looping demos
2359 static void Host_Demos_f (void)
2361 if (cls.state == ca_dedicated)
2363 if (cls.demonum == -1)
2373 Return to looping demos
2376 static void Host_Stopdemo_f (void)
2378 if (!cls.demoplayback)
2381 Host_ShutdownServer ();
2384 static void Host_SendCvar_f (void)
2388 const char *cvarname;
2394 cvarname = Cmd_Argv(1);
2395 if (cls.state == ca_connected)
2397 c = Cvar_FindVar(cvarname);
2398 // LordHavoc: if there is no such cvar or if it is private, send a
2399 // reply indicating that it has no value
2400 if(!c || (c->flags & CVAR_PRIVATE))
2401 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2403 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2406 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2410 if (cls.state != ca_dedicated)
2414 for(;i<svs.maxclients;i++)
2415 if(svs.clients[i].active && svs.clients[i].netconnection)
2417 host_client = &svs.clients[i];
2418 Host_ClientCommands("sendcvar %s\n", cvarname);
2423 static void MaxPlayers_f(void)
2427 if (Cmd_Argc() != 2)
2429 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2435 Con_Print("maxplayers can not be changed while a server is running.\n");
2436 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2439 n = atoi(Cmd_Argv(1));
2440 n = bound(1, n, MAX_SCOREBOARD);
2441 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2443 svs.maxclients_next = n;
2445 Cvar_Set ("deathmatch", "0");
2447 Cvar_Set ("deathmatch", "1");
2451 =====================
2454 ProQuake rcon support
2455 =====================
2457 static void Host_PQRcon_f (void)
2462 lhnetsocket_t *mysocket;
2463 char peer_address[64];
2465 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2467 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2471 e = strchr(rcon_password.string, ' ');
2472 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2476 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2480 if (!rcon_address.string[0])
2482 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2485 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2487 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2488 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2492 unsigned char bufdata[64];
2495 MSG_WriteLong(&buf, 0);
2496 MSG_WriteByte(&buf, CCREQ_RCON);
2497 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2498 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2499 MSG_WriteString(&buf, Cmd_Args());
2500 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2501 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2506 //=============================================================================
2508 // QuakeWorld commands
2511 =====================
2514 Send the rest of the command line over as
2515 an unconnected command.
2516 =====================
2518 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2523 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(vabuf, sizeof(vabuf), "\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 static 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 static 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 static 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 static 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 static 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 static 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 static 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 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2914 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2915 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2916 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2917 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2918 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2919 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2920 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2921 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2922 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)");
2923 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2924 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2925 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2926 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2927 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2928 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
2929 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2930 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2931 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2932 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2934 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2935 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2936 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2938 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2939 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2940 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2941 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2943 Cvar_RegisterVariable (&cl_name);
2944 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2945 Cvar_RegisterVariable (&cl_color);
2946 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2947 Cvar_RegisterVariable (&cl_rate);
2948 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2949 Cvar_RegisterVariable (&cl_pmodel);
2950 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2952 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2953 Cvar_RegisterVariable (&cl_playermodel);
2954 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2955 Cvar_RegisterVariable (&cl_playerskin);
2956 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2958 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2959 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2960 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)");
2961 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2963 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2965 Cvar_RegisterVariable (&rcon_password);
2966 Cvar_RegisterVariable (&rcon_address);
2967 Cvar_RegisterVariable (&rcon_secure);
2968 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2969 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");
2970 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");
2971 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)");
2972 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2973 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2974 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2975 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2976 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2977 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2978 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2979 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2981 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)");
2982 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)");
2984 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)");
2985 Cvar_RegisterVariable (&r_fixtrans_auto);
2987 Cvar_RegisterVariable (&team);
2988 Cvar_RegisterVariable (&skin);
2989 Cvar_RegisterVariable (&noaim);
2991 Cvar_RegisterVariable(&sv_cheats);
2992 Cvar_RegisterVariable(&sv_adminnick);
2993 Cvar_RegisterVariable(&sv_status_privacy);
2994 Cvar_RegisterVariable(&sv_status_show_qcstatus);
2995 Cvar_RegisterVariable(&sv_namechangetimer);
2998 void Host_NoOperation_f(void)