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.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
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->begun)
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->begun)
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.fp[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 void (*print) (const char *fmt, ...);
1702 if (cmd_source == src_command)
1704 // if running a client, try to send over network so the pause is handled by the server
1705 if (cls.state == ca_connected)
1707 Cmd_ForwardToServer ();
1713 print = SV_ClientPrintf;
1715 if (!pausable.integer)
1717 if (cmd_source == src_client)
1719 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1721 print("Pause not allowed.\n");
1728 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1729 // send notification to all clients
1730 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1731 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1735 ======================
1737 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1738 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1739 ======================
1741 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)"};
1742 static void Host_PModel_f (void)
1744 prvm_prog_t *prog = SVVM_prog;
1747 if (Cmd_Argc () == 1)
1749 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1752 i = atoi(Cmd_Argv(1));
1754 if (cmd_source == src_command)
1756 if (cl_pmodel.integer == i)
1758 Cvar_SetValue ("_cl_pmodel", i);
1759 if (cls.state == ca_connected)
1760 Cmd_ForwardToServer ();
1764 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1767 //===========================================================================
1775 static void Host_PreSpawn_f (void)
1777 if (host_client->prespawned)
1779 Con_Print("prespawn not valid -- already prespawned\n");
1782 host_client->prespawned = true;
1784 if (host_client->netconnection)
1786 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1787 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1788 MSG_WriteByte (&host_client->netconnection->message, 2);
1789 host_client->sendsignon = 0; // enable unlimited sends again
1792 // reset the name change timer because the client will send name soon
1793 host_client->nametime = 0;
1801 static void Host_Spawn_f (void)
1803 prvm_prog_t *prog = SVVM_prog;
1806 int stats[MAX_CL_STATS];
1808 if (!host_client->prespawned)
1810 Con_Print("Spawn not valid -- not yet prespawned\n");
1813 if (host_client->spawned)
1815 Con_Print("Spawn not valid -- already spawned\n");
1818 host_client->spawned = true;
1820 // reset name change timer again because they might want to change name
1821 // again in the first 5 seconds after connecting
1822 host_client->nametime = 0;
1824 // LordHavoc: moved this above the QC calls at FrikaC's request
1825 // LordHavoc: commented this out
1826 //if (host_client->netconnection)
1827 // SZ_Clear (&host_client->netconnection->message);
1829 // run the entrance script
1832 // loaded games are fully initialized already
1833 if (PRVM_serverfunction(RestoreGame))
1835 Con_DPrint("Calling RestoreGame\n");
1836 PRVM_serverglobalfloat(time) = sv.time;
1837 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1838 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1843 //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);
1845 // copy spawn parms out of the client_t
1846 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1847 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1849 // call the spawn function
1850 host_client->clientconnectcalled = true;
1851 PRVM_serverglobalfloat(time) = sv.time;
1852 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1853 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1855 if (cls.state == ca_dedicated)
1856 Con_Printf("%s connected\n", host_client->name);
1858 PRVM_serverglobalfloat(time) = sv.time;
1859 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1862 if (!host_client->netconnection)
1865 // send time of update
1866 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1867 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1869 // send all current names, colors, and frag counts
1870 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1872 if (!client->active)
1874 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1875 MSG_WriteByte (&host_client->netconnection->message, i);
1876 MSG_WriteString (&host_client->netconnection->message, client->name);
1877 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1878 MSG_WriteByte (&host_client->netconnection->message, i);
1879 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1880 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1881 MSG_WriteByte (&host_client->netconnection->message, i);
1882 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1885 // send all current light styles
1886 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1888 if (sv.lightstyles[i][0])
1890 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1891 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1892 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1897 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1898 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1899 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1901 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1902 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1903 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1905 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1906 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1907 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1909 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1910 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1911 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1914 // Never send a roll angle, because savegames can catch the server
1915 // in a state where it is expecting the client to correct the angle
1916 // and it won't happen if the game was just loaded, so you wind up
1917 // with a permanent head tilt
1920 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1921 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1922 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1923 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1927 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1928 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1929 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1930 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1933 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1935 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1936 MSG_WriteByte (&host_client->netconnection->message, 3);
1944 static void Host_Begin_f (void)
1946 if (!host_client->spawned)
1948 Con_Print("Begin not valid -- not yet spawned\n");
1951 if (host_client->begun)
1953 Con_Print("Begin not valid -- already begun\n");
1956 host_client->begun = true;
1958 // LordHavoc: note: this code also exists in SV_DropClient
1962 for (i = 0;i < svs.maxclients;i++)
1963 if (svs.clients[i].active && !svs.clients[i].spawned)
1965 if (i == svs.maxclients)
1967 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1968 sv.paused = sv.loadgame = false; // we're basically done with loading now
1973 //===========================================================================
1980 Kicks a user off of the server
1983 static void Host_Kick_f (void)
1986 const char *message = NULL;
1989 qboolean byNumber = false;
1996 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1998 i = (int)(atof(Cmd_Argv(2)) - 1);
1999 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2005 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2007 if (!host_client->active)
2009 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2014 if (i < svs.maxclients)
2016 if (cmd_source == src_command)
2018 if (cls.state == ca_dedicated)
2021 who = cl_name.string;
2026 // can't kick yourself!
2027 if (host_client == save)
2032 message = Cmd_Args();
2033 COM_ParseToken_Simple(&message, false, false, true);
2036 message++; // skip the #
2037 while (*message == ' ') // skip white space
2039 message += strlen(Cmd_Argv(2)); // skip the number
2041 while (*message && *message == ' ')
2045 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2047 SV_ClientPrintf("Kicked by %s\n", who);
2048 SV_DropClient (false); // kicked
2055 ===============================================================================
2059 ===============================================================================
2067 static void Host_Give_f (void)
2069 prvm_prog_t *prog = SVVM_prog;
2075 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2080 v = atoi (Cmd_Argv(2));
2094 // MED 01/04/97 added hipnotic give stuff
2095 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2100 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2102 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2104 else if (t[0] == '9')
2105 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2106 else if (t[0] == '0')
2107 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2108 else if (t[0] >= '2')
2109 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2114 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2119 if (gamemode == GAME_ROGUE)
2120 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2122 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2125 if (gamemode == GAME_ROGUE)
2127 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2128 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2129 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2133 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2137 if (gamemode == GAME_ROGUE)
2139 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2140 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2141 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2145 if (gamemode == GAME_ROGUE)
2147 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2148 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2149 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2153 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2157 if (gamemode == GAME_ROGUE)
2159 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2160 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2161 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2165 PRVM_serveredictfloat(host_client->edict, health) = v;
2168 if (gamemode == GAME_ROGUE)
2170 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2171 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2172 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2176 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2180 if (gamemode == GAME_ROGUE)
2182 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2183 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2184 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2190 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2195 for (i=0 ; i<prog->num_edicts ; i++)
2197 e = PRVM_EDICT_NUM(i);
2198 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2201 Con_Print("No viewthing on map\n");
2210 static void Host_Viewmodel_f (void)
2212 prvm_prog_t *prog = SVVM_prog;
2219 e = FindViewthing(prog);
2222 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2223 if (m && m->loaded && m->Draw)
2225 PRVM_serveredictfloat(e, frame) = 0;
2226 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2229 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2238 static void Host_Viewframe_f (void)
2240 prvm_prog_t *prog = SVVM_prog;
2248 e = FindViewthing(prog);
2251 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2253 f = atoi(Cmd_Argv(1));
2254 if (f >= m->numframes)
2257 PRVM_serveredictfloat(e, frame) = f;
2262 static void PrintFrameName (dp_model_t *m, int frame)
2265 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2267 Con_Printf("frame %i\n", frame);
2275 static void Host_Viewnext_f (void)
2277 prvm_prog_t *prog = SVVM_prog;
2284 e = FindViewthing(prog);
2287 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2289 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2290 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2291 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2293 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2302 static void Host_Viewprev_f (void)
2304 prvm_prog_t *prog = SVVM_prog;
2311 e = FindViewthing(prog);
2314 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2316 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2317 if (PRVM_serveredictfloat(e, frame) < 0)
2318 PRVM_serveredictfloat(e, frame) = 0;
2320 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2325 ===============================================================================
2329 ===============================================================================
2338 static void Host_Startdemos_f (void)
2342 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2348 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2351 Con_DPrintf("%i demo(s) in loop\n", c);
2353 for (i=1 ; i<c+1 ; i++)
2354 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2356 // LordHavoc: clear the remaining slots
2357 for (;i <= MAX_DEMOS;i++)
2358 cls.demos[i-1][0] = 0;
2360 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2374 Return to looping demos
2377 static void Host_Demos_f (void)
2379 if (cls.state == ca_dedicated)
2381 if (cls.demonum == -1)
2391 Return to looping demos
2394 static void Host_Stopdemo_f (void)
2396 if (!cls.demoplayback)
2399 Host_ShutdownServer ();
2402 static void Host_SendCvar_f (void)
2406 const char *cvarname;
2412 cvarname = Cmd_Argv(1);
2413 if (cls.state == ca_connected)
2415 c = Cvar_FindVar(cvarname);
2416 // LordHavoc: if there is no such cvar or if it is private, send a
2417 // reply indicating that it has no value
2418 if(!c || (c->flags & CVAR_PRIVATE))
2419 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2421 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2424 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2428 if (cls.state != ca_dedicated)
2432 for(;i<svs.maxclients;i++)
2433 if(svs.clients[i].active && svs.clients[i].netconnection)
2435 host_client = &svs.clients[i];
2436 Host_ClientCommands("sendcvar %s\n", cvarname);
2441 static void MaxPlayers_f(void)
2445 if (Cmd_Argc() != 2)
2447 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2453 Con_Print("maxplayers can not be changed while a server is running.\n");
2454 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2457 n = atoi(Cmd_Argv(1));
2458 n = bound(1, n, MAX_SCOREBOARD);
2459 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2461 svs.maxclients_next = n;
2463 Cvar_Set ("deathmatch", "0");
2465 Cvar_Set ("deathmatch", "1");
2469 =====================
2472 ProQuake rcon support
2473 =====================
2475 static void Host_PQRcon_f (void)
2480 lhnetsocket_t *mysocket;
2481 char peer_address[64];
2483 if (Cmd_Argc() == 1)
2485 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2489 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2491 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2495 e = strchr(rcon_password.string, ' ');
2496 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2500 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2504 if (!rcon_address.string[0])
2506 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2509 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2511 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2512 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2516 unsigned char bufdata[64];
2519 MSG_WriteLong(&buf, 0);
2520 MSG_WriteByte(&buf, CCREQ_RCON);
2521 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2522 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2523 MSG_WriteString(&buf, Cmd_Args());
2524 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2525 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2530 //=============================================================================
2532 // QuakeWorld commands
2535 =====================
2538 Send the rest of the command line over as
2539 an unconnected command.
2540 =====================
2542 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2547 lhnetsocket_t *mysocket;
2550 if (Cmd_Argc() == 1)
2552 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2556 if (!rcon_password.string || !rcon_password.string[0])
2558 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2562 e = strchr(rcon_password.string, ' ');
2563 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2566 to = cls.netcon->peeraddress;
2569 if (!rcon_address.string[0])
2571 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2574 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2576 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2577 if (mysocket && Cmd_Args()[0])
2579 // simply put together the rcon packet and send it
2580 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2582 if(cls.rcon_commands[cls.rcon_ringpos][0])
2585 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2586 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]);
2587 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2590 for (i = 0;i < MAX_RCONS;i++)
2591 if(cls.rcon_commands[i][0])
2592 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2596 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2597 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2598 cls.rcon_addresses[cls.rcon_ringpos] = to;
2599 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2600 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2602 else if(rcon_secure.integer > 0)
2606 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2607 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2608 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2611 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2612 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2617 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2623 ====================
2626 user <name or userid>
2628 Dump userdata / masterdata for a user
2629 ====================
2631 static void Host_User_f (void) // credit: taken from QuakeWorld
2636 if (Cmd_Argc() != 2)
2638 Con_Printf ("Usage: user <username / userid>\n");
2642 uid = atoi(Cmd_Argv(1));
2644 for (i = 0;i < cl.maxclients;i++)
2646 if (!cl.scores[i].name[0])
2648 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2650 InfoString_Print(cl.scores[i].qw_userinfo);
2654 Con_Printf ("User not in server.\n");
2658 ====================
2661 Dump userids for all current players
2662 ====================
2664 static void Host_Users_f (void) // credit: taken from QuakeWorld
2670 Con_Printf ("userid frags name\n");
2671 Con_Printf ("------ ----- ----\n");
2672 for (i = 0;i < cl.maxclients;i++)
2674 if (cl.scores[i].name[0])
2676 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2681 Con_Printf ("%i total users\n", c);
2686 Host_FullServerinfo_f
2688 Sent by server when serverinfo changes
2691 // TODO: shouldn't this be a cvar instead?
2692 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2695 if (Cmd_Argc() != 2)
2697 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2701 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2702 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2703 cl.qw_teamplay = atoi(temp);
2710 Allow clients to change userinfo
2714 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2721 if (Cmd_Argc() != 2)
2723 Con_Printf ("fullinfo <complete info string>\n");
2733 while (*s && *s != '\\')
2739 Con_Printf ("MISSING VALUE\n");
2745 while (*s && *s != '\\')
2752 CL_SetInfo(key, value, false, false, false, false);
2760 Allow clients to change userinfo
2763 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2765 if (Cmd_Argc() == 1)
2767 InfoString_Print(cls.userinfo);
2770 if (Cmd_Argc() != 3)
2772 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2775 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2779 ====================
2782 packet <destination> <contents>
2784 Contents allows \n escape character
2785 ====================
2787 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2793 lhnetaddress_t address;
2794 lhnetsocket_t *mysocket;
2796 if (Cmd_Argc() != 3)
2798 Con_Printf ("packet <destination> <contents>\n");
2802 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2804 Con_Printf ("Bad address\n");
2810 send[0] = send[1] = send[2] = send[3] = -1;
2812 l = (int)strlen (in);
2813 for (i=0 ; i<l ; i++)
2815 if (out >= send + sizeof(send) - 1)
2817 if (in[i] == '\\' && in[i+1] == 'n')
2822 else if (in[i] == '\\' && in[i+1] == '0')
2827 else if (in[i] == '\\' && in[i+1] == 't')
2832 else if (in[i] == '\\' && in[i+1] == 'r')
2837 else if (in[i] == '\\' && in[i+1] == '"')
2846 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2848 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2850 NetConn_Write(mysocket, send, out - send, &address);
2854 ====================
2857 Send back ping and packet loss update for all current players to this player
2858 ====================
2860 void Host_Pings_f (void)
2862 int i, j, ping, packetloss, movementloss;
2865 if (!host_client->netconnection)
2868 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2870 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2871 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2873 for (i = 0;i < svs.maxclients;i++)
2877 if (svs.clients[i].netconnection)
2879 for (j = 0;j < NETGRAPH_PACKETS;j++)
2880 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2882 for (j = 0;j < NETGRAPH_PACKETS;j++)
2883 if (svs.clients[i].movement_count[j] < 0)
2886 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2887 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2888 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2889 ping = bound(0, ping, 9999);
2890 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2892 // send qw_svc_updateping and qw_svc_updatepl messages
2893 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2894 MSG_WriteShort(&host_client->netconnection->message, ping);
2895 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2896 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2900 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2902 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2904 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2905 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2908 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2909 MSG_WriteString(&host_client->netconnection->message, "\n");
2912 static void Host_PingPLReport_f(void)
2917 if (l > cl.maxclients)
2919 for (i = 0;i < l;i++)
2921 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2922 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2923 if(errbyte && *errbyte == ',')
2924 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2926 cl.scores[i].qw_movementloss = 0;
2930 //=============================================================================
2937 void Host_InitCommands (void)
2939 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2941 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2942 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2943 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2944 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2945 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2946 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2947 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2948 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2949 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2950 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2951 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2952 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)");
2953 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2954 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2955 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2956 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2957 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2958 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
2959 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2960 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2961 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2962 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2964 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2965 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2966 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2968 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2969 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2970 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2971 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2973 Cvar_RegisterVariable (&cl_name);
2974 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2975 Cvar_RegisterVariable (&cl_color);
2976 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2977 Cvar_RegisterVariable (&cl_rate);
2978 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2979 Cvar_RegisterVariable (&cl_pmodel);
2980 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2982 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2983 Cvar_RegisterVariable (&cl_playermodel);
2984 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2985 Cvar_RegisterVariable (&cl_playerskin);
2986 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2988 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2989 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2990 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)");
2991 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2993 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2995 Cvar_RegisterVariable (&rcon_password);
2996 Cvar_RegisterVariable (&rcon_address);
2997 Cvar_RegisterVariable (&rcon_secure);
2998 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2999 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");
3000 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");
3001 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)");
3002 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3003 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3004 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3005 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3006 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3007 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3008 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3009 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3011 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)");
3012 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)");
3014 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)");
3015 Cvar_RegisterVariable (&r_fixtrans_auto);
3017 Cvar_RegisterVariable (&team);
3018 Cvar_RegisterVariable (&skin);
3019 Cvar_RegisterVariable (&noaim);
3021 Cvar_RegisterVariable(&sv_cheats);
3022 Cvar_RegisterVariable(&sv_adminnick);
3023 Cvar_RegisterVariable(&sv_status_privacy);
3024 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3025 Cvar_RegisterVariable(&sv_namechangetimer);
3028 void Host_NoOperation_f(void)