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);
384 if (key_dest == key_menu || key_dest == key_menu_grabbed)
389 svs.serverflags = 0; // haven't completed an episode yet
390 allowcheats = sv_cheats.integer != 0;
391 strlcpy(level, Cmd_Argv(1), sizeof(level));
392 SV_SpawnServer(level);
393 if (sv.active && cls.state == ca_disconnected)
394 CL_EstablishConnection("local:1", -2);
401 Goes to a new map, taking all clients along
404 static void Host_Changelevel_f (void)
406 char level[MAX_QPATH];
410 Con_Print("changelevel <levelname> : continue game on a new level\n");
421 if (key_dest == key_menu || key_dest == key_menu_grabbed)
426 SV_SaveSpawnparms ();
427 allowcheats = sv_cheats.integer != 0;
428 strlcpy(level, Cmd_Argv(1), sizeof(level));
429 SV_SpawnServer(level);
430 if (sv.active && cls.state == ca_disconnected)
431 CL_EstablishConnection("local:1", -2);
438 Restarts the current server for a dead player
441 static void Host_Restart_f (void)
443 char mapname[MAX_QPATH];
447 Con_Print("restart : restart current level\n");
452 Con_Print("Only the server may restart\n");
458 if (key_dest == key_menu || key_dest == key_menu_grabbed)
463 allowcheats = sv_cheats.integer != 0;
464 strlcpy(mapname, sv.name, sizeof(mapname));
465 SV_SpawnServer(mapname);
466 if (sv.active && cls.state == ca_disconnected)
467 CL_EstablishConnection("local:1", -2);
474 This command causes the client to wait for the signon messages again.
475 This is sent just before a server changes levels
478 void Host_Reconnect_f (void)
481 // if not connected, reconnect to the most recent server
484 // if we have connected to a server recently, the userinfo
485 // will still contain its IP address, so get the address...
486 InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
488 CL_EstablishConnection(temp, -1);
490 Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
493 // if connected, do something based on protocol
494 if (cls.protocol == PROTOCOL_QUAKEWORLD)
496 // quakeworld can just re-login
497 if (cls.qw_downloadmemory) // don't change when downloading
502 if (cls.state == ca_connected && cls.signon < SIGNONS)
504 Con_Printf("reconnecting...\n");
505 MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
506 MSG_WriteString(&cls.netcon->message, "new");
511 // netquake uses reconnect on level changes (silly)
514 Con_Print("reconnect : wait for signon messages again\n");
519 Con_Print("reconnect: no signon, ignoring reconnect\n");
522 cls.signon = 0; // need new connection messages
527 =====================
530 User command to connect to server
531 =====================
533 static void Host_Connect_f (void)
537 Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
540 // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
541 if(rcon_secure.integer <= 0)
542 Cvar_SetQuick(&rcon_password, "");
543 CL_EstablishConnection(Cmd_Argv(1), 2);
548 ===============================================================================
552 ===============================================================================
555 #define SAVEGAME_VERSION 5
557 void Host_Savegame_to(prvm_prog_t *prog, const char *name)
560 int i, k, l, numbuffers, lightstyles = 64;
561 char comment[SAVEGAME_COMMENT_LENGTH+1];
562 char line[MAX_INPUTLINE];
566 // first we have to figure out if this can be saved in 64 lightstyles
567 // (for Quake compatibility)
568 for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
569 if (sv.lightstyles[i][0])
572 isserver = prog == SVVM_prog;
574 Con_Printf("Saving game to %s...\n", name);
575 f = FS_OpenRealFile(name, "wb", false);
578 Con_Print("ERROR: couldn't open.\n");
582 FS_Printf(f, "%i\n", SAVEGAME_VERSION);
584 memset(comment, 0, sizeof(comment));
586 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));
588 dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
589 // convert space to _ to make stdio happy
590 // LordHavoc: convert control characters to _ as well
591 for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
592 if (ISWHITESPACEORCONTROL(comment[i]))
594 comment[SAVEGAME_COMMENT_LENGTH] = '\0';
596 FS_Printf(f, "%s\n", comment);
599 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
600 FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
601 FS_Printf(f, "%d\n", current_skill);
602 FS_Printf(f, "%s\n", sv.name);
603 FS_Printf(f, "%f\n",sv.time);
607 for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
608 FS_Printf(f, "(dummy)\n");
609 FS_Printf(f, "%d\n", 0);
610 FS_Printf(f, "%s\n", "(dummy)");
611 FS_Printf(f, "%f\n", realtime);
614 // write the light styles
615 for (i=0 ; i<lightstyles ; i++)
617 if (isserver && sv.lightstyles[i][0])
618 FS_Printf(f, "%s\n", sv.lightstyles[i]);
623 PRVM_ED_WriteGlobals (prog, f);
624 for (i=0 ; i<prog->num_edicts ; i++)
626 FS_Printf(f,"// edict %d\n", i);
627 //Con_Printf("edict %d...\n", i);
628 PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
633 FS_Printf(f,"// DarkPlaces extended savegame\n");
634 // darkplaces extension - extra lightstyles, support for color lightstyles
635 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
636 if (isserver && sv.lightstyles[i][0])
637 FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
639 // darkplaces extension - model precaches
640 for (i=1 ; i<MAX_MODELS ; i++)
641 if (sv.model_precache[i][0])
642 FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
644 // darkplaces extension - sound precaches
645 for (i=1 ; i<MAX_SOUNDS ; i++)
646 if (sv.sound_precache[i][0])
647 FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
649 // darkplaces extension - save buffers
650 numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
651 for (i = 0; i < numbuffers; i++)
653 prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
654 if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
656 FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
657 for(k = 0; k < stringbuffer->num_strings; k++)
659 if (!stringbuffer->strings[k])
661 // Parse the string a bit to turn special characters
662 // (like newline, specifically) into escape codes
663 s = stringbuffer->strings[k];
664 for (l = 0;l < (int)sizeof(line) - 2 && *s;)
691 FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
699 Con_Print("done.\n");
707 static void Host_Savegame_f (void)
709 prvm_prog_t *prog = SVVM_prog;
710 char name[MAX_QPATH];
711 qboolean deadflag = false;
715 Con_Print("Can't save - no server running.\n");
719 deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
723 // singleplayer checks
726 Con_Print("Can't save in intermission.\n");
732 Con_Print("Can't savegame with a dead player\n");
737 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");
741 Con_Print("save <savename> : save a game\n");
745 if (strstr(Cmd_Argv(1), ".."))
747 Con_Print("Relative pathnames are not allowed.\n");
751 strlcpy (name, Cmd_Argv(1), sizeof (name));
752 FS_DefaultExtension (name, ".sav", sizeof (name));
754 Host_Savegame_to(prog, name);
764 prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, char *format);
765 void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str);
766 void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer);
767 void BufStr_Flush(prvm_prog_t *prog);
769 static void Host_Loadgame_f (void)
771 prvm_prog_t *prog = SVVM_prog;
772 char filename[MAX_QPATH];
773 char mapname[MAX_QPATH];
780 int i, k, numbuffers;
783 float spawn_parms[NUM_SPAWN_PARMS];
784 prvm_stringbuffer_t *stringbuffer;
788 Con_Print("load <savename> : load a game\n");
792 strlcpy (filename, Cmd_Argv(1), sizeof(filename));
793 FS_DefaultExtension (filename, ".sav", sizeof (filename));
795 Con_Printf("Loading game from %s...\n", filename);
797 // stop playing demos
798 if (cls.demoplayback)
803 if (key_dest == key_menu || key_dest == key_menu_grabbed)
808 cls.demonum = -1; // stop demo loop in case this fails
810 t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
813 Con_Print("ERROR: couldn't open.\n");
817 if(developer_entityparsing.integer)
818 Con_Printf("Host_Loadgame_f: loading version\n");
821 COM_ParseToken_Simple(&t, false, false, true);
822 version = atoi(com_token);
823 if (version != SAVEGAME_VERSION)
826 Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
830 if(developer_entityparsing.integer)
831 Con_Printf("Host_Loadgame_f: loading description\n");
834 COM_ParseToken_Simple(&t, false, false, true);
836 for (i = 0;i < NUM_SPAWN_PARMS;i++)
838 COM_ParseToken_Simple(&t, false, false, true);
839 spawn_parms[i] = atof(com_token);
842 COM_ParseToken_Simple(&t, false, false, true);
843 // this silliness is so we can load 1.06 save files, which have float skill values
844 current_skill = (int)(atof(com_token) + 0.5);
845 Cvar_SetValue ("skill", (float)current_skill);
847 if(developer_entityparsing.integer)
848 Con_Printf("Host_Loadgame_f: loading mapname\n");
851 COM_ParseToken_Simple(&t, false, false, true);
852 strlcpy (mapname, com_token, sizeof(mapname));
854 if(developer_entityparsing.integer)
855 Con_Printf("Host_Loadgame_f: loading time\n");
858 COM_ParseToken_Simple(&t, false, false, true);
859 time = atof(com_token);
861 allowcheats = sv_cheats.integer != 0;
863 if(developer_entityparsing.integer)
864 Con_Printf("Host_Loadgame_f: spawning server\n");
866 SV_SpawnServer (mapname);
870 Con_Print("Couldn't load map\n");
873 sv.paused = true; // pause until all clients connect
876 if(developer_entityparsing.integer)
877 Con_Printf("Host_Loadgame_f: loading light styles\n");
879 // load the light styles
884 for (i = 0;i < MAX_LIGHTSTYLES;i++)
888 COM_ParseToken_Simple(&t, false, false, true);
889 // if this is a 64 lightstyle savegame produced by Quake, stop now
890 // we have to check this because darkplaces may save more than 64
891 if (com_token[0] == '{')
896 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
899 if(developer_entityparsing.integer)
900 Con_Printf("Host_Loadgame_f: skipping until globals\n");
902 // now skip everything before the first opening brace
903 // (this is for forward compatibility, so that older versions (at
904 // least ones with this fix) can load savegames with extra data before the
905 // first brace, as might be produced by a later engine version)
909 if (!COM_ParseToken_Simple(&t, false, false, true))
911 if (com_token[0] == '{')
918 // unlink all entities
919 World_UnlinkAll(&sv.world);
921 // load the edicts out of the savegame file
926 while (COM_ParseToken_Simple(&t, false, false, true))
927 if (!strcmp(com_token, "}"))
929 if (!COM_ParseToken_Simple(&start, false, false, true))
934 if (strcmp(com_token,"{"))
937 Host_Error ("First token isn't a brace");
942 if(developer_entityparsing.integer)
943 Con_Printf("Host_Loadgame_f: loading globals\n");
945 // parse the global vars
946 PRVM_ED_ParseGlobals (prog, start);
948 // restore the autocvar globals
949 Cvar_UpdateAllAutoCvars();
954 if (entnum >= MAX_EDICTS)
957 Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
959 while (entnum >= prog->max_edicts)
960 PRVM_MEM_IncreaseEdicts(prog);
961 ent = PRVM_EDICT_NUM(entnum);
962 memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
963 ent->priv.server->free = false;
965 if(developer_entityparsing.integer)
966 Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
968 PRVM_ED_ParseEdict (prog, start, ent);
970 // link it into the bsp tree
971 if (!ent->priv.server->free)
979 prog->num_edicts = entnum;
982 for (i = 0;i < NUM_SPAWN_PARMS;i++)
983 svs.clients[0].spawn_parms[i] = spawn_parms[i];
985 if(developer_entityparsing.integer)
986 Con_Printf("Host_Loadgame_f: skipping until extended data\n");
988 // read extended data if present
989 // the extended data is stored inside a /* */ comment block, which the
990 // parser intentionally skips, so we have to check for it manually here
993 while (*end == '\r' || *end == '\n')
995 if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
997 if(developer_entityparsing.integer)
998 Con_Printf("Host_Loadgame_f: loading extended data\n");
1000 Con_Printf("Loading extended DarkPlaces savegame\n");
1002 memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
1003 memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
1004 memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
1007 while (COM_ParseToken_Simple(&t, false, false, true))
1009 if (!strcmp(com_token, "sv.lightstyles"))
1011 COM_ParseToken_Simple(&t, false, false, true);
1012 i = atoi(com_token);
1013 COM_ParseToken_Simple(&t, false, false, true);
1014 if (i >= 0 && i < MAX_LIGHTSTYLES)
1015 strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
1017 Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
1019 else if (!strcmp(com_token, "sv.model_precache"))
1021 COM_ParseToken_Simple(&t, false, false, true);
1022 i = atoi(com_token);
1023 COM_ParseToken_Simple(&t, false, false, true);
1024 if (i >= 0 && i < MAX_MODELS)
1026 strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
1027 sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
1030 Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
1032 else if (!strcmp(com_token, "sv.sound_precache"))
1034 COM_ParseToken_Simple(&t, false, false, true);
1035 i = atoi(com_token);
1036 COM_ParseToken_Simple(&t, false, false, true);
1037 if (i >= 0 && i < MAX_SOUNDS)
1038 strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
1040 Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
1042 else if (!strcmp(com_token, "sv.buffer"))
1044 if (COM_ParseToken_Simple(&t, false, false, true))
1046 i = atoi(com_token);
1049 k = STRINGBUFFER_SAVED;
1050 if (COM_ParseToken_Simple(&t, false, false, true))
1051 k |= atoi(com_token);
1052 if (!BufStr_FindCreateReplace(prog, i, k, "string"))
1053 Con_Printf("failed to create stringbuffer %i\n", i);
1056 Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
1059 Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
1061 else if (!strcmp(com_token, "sv.bufstr"))
1063 if (!COM_ParseToken_Simple(&t, false, false, true))
1064 Con_Printf("unexpected end of line when parsing sv.bufstr\n");
1067 i = atoi(com_token);
1068 stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
1071 if (COM_ParseToken_Simple(&t, false, false, true))
1073 k = atoi(com_token);
1074 if (COM_ParseToken_Simple(&t, false, false, true))
1075 BufStr_Set(prog, stringbuffer, k, com_token);
1077 Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
1080 Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
1083 Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
1086 // skip any trailing text or unrecognized commands
1087 while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
1094 // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
1095 numbuffers = Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
1096 for (i = 0; i < numbuffers; i++)
1098 if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
1099 if (stringbuffer->flags & STRINGBUFFER_TEMP)
1100 BufStr_Del(prog, stringbuffer);
1103 if(developer_entityparsing.integer)
1104 Con_Printf("Host_Loadgame_f: finished\n");
1106 // make sure we're connected to loopback
1107 if (sv.active && cls.state == ca_disconnected)
1108 CL_EstablishConnection("local:1", -2);
1111 //============================================================================
1114 ======================
1116 ======================
1118 cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
1119 static void Host_Name_f (void)
1121 prvm_prog_t *prog = SVVM_prog;
1123 qboolean valid_colors;
1124 const char *newNameSource;
1125 char newName[sizeof(host_client->name)];
1127 if (Cmd_Argc () == 1)
1129 Con_Printf("name: %s\n", cl_name.string);
1133 if (Cmd_Argc () == 2)
1134 newNameSource = Cmd_Argv(1);
1136 newNameSource = Cmd_Args();
1138 strlcpy(newName, newNameSource, sizeof(newName));
1140 if (cmd_source == src_command)
1142 Cvar_Set ("_cl_name", newName);
1143 if (strlen(newNameSource) >= sizeof(newName)) // overflowed
1145 Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
1146 Con_Printf("name: %s\n", cl_name.string);
1151 if (realtime < host_client->nametime)
1153 SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
1157 host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
1159 // point the string back at updateclient->name to keep it safe
1160 strlcpy (host_client->name, newName, sizeof (host_client->name));
1162 for (i = 0, j = 0;host_client->name[i];i++)
1163 if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
1164 host_client->name[j++] = host_client->name[i];
1165 host_client->name[j] = 0;
1167 if(host_client->name[0] == 1 || host_client->name[0] == 2)
1168 // may interfere with chat area, and will needlessly beep; so let's add a ^7
1170 memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
1171 host_client->name[sizeof(host_client->name) - 1] = 0;
1172 host_client->name[0] = STRING_COLOR_TAG;
1173 host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
1176 u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
1177 if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
1180 l = strlen(host_client->name);
1181 if(l < sizeof(host_client->name) - 1)
1183 // duplicate the color tag to escape it
1184 host_client->name[i] = STRING_COLOR_TAG;
1185 host_client->name[i+1] = 0;
1186 //Con_DPrintf("abuse detected, adding another trailing color tag\n");
1190 // remove the last character to fix the color code
1191 host_client->name[l-1] = 0;
1192 //Con_DPrintf("abuse detected, removing a trailing color tag\n");
1196 // find the last color tag offset and decide if we need to add a reset tag
1197 for (i = 0, j = -1;host_client->name[i];i++)
1199 if (host_client->name[i] == STRING_COLOR_TAG)
1201 if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
1204 // if this happens to be a reset tag then we don't need one
1205 if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
1210 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]))
1216 if (host_client->name[i+1] == STRING_COLOR_TAG)
1223 // does not end in the default color string, so add it
1224 if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
1225 memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
1227 PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
1228 if (strcmp(host_client->old_name, host_client->name))
1230 if (host_client->begun)
1231 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1232 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1233 // send notification to all clients
1234 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1235 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1236 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1237 SV_WriteNetnameIntoDemo(host_client);
1242 ======================
1244 ======================
1246 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1247 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1248 static void Host_Playermodel_f (void)
1250 prvm_prog_t *prog = SVVM_prog;
1252 char newPath[sizeof(host_client->playermodel)];
1254 if (Cmd_Argc () == 1)
1256 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1260 if (Cmd_Argc () == 2)
1261 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1263 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1265 for (i = 0, j = 0;newPath[i];i++)
1266 if (newPath[i] != '\r' && newPath[i] != '\n')
1267 newPath[j++] = newPath[i];
1270 if (cmd_source == src_command)
1272 Cvar_Set ("_cl_playermodel", newPath);
1277 if (realtime < host_client->nametime)
1279 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1283 host_client->nametime = realtime + 5;
1286 // point the string back at updateclient->name to keep it safe
1287 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1288 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1289 if (strcmp(host_client->old_model, host_client->playermodel))
1291 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1292 /*// send notification to all clients
1293 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1294 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1295 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1300 ======================
1302 ======================
1304 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1305 static void Host_Playerskin_f (void)
1307 prvm_prog_t *prog = SVVM_prog;
1309 char newPath[sizeof(host_client->playerskin)];
1311 if (Cmd_Argc () == 1)
1313 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1317 if (Cmd_Argc () == 2)
1318 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1320 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1322 for (i = 0, j = 0;newPath[i];i++)
1323 if (newPath[i] != '\r' && newPath[i] != '\n')
1324 newPath[j++] = newPath[i];
1327 if (cmd_source == src_command)
1329 Cvar_Set ("_cl_playerskin", newPath);
1334 if (realtime < host_client->nametime)
1336 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1340 host_client->nametime = realtime + 5;
1343 // point the string back at updateclient->name to keep it safe
1344 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1345 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1346 if (strcmp(host_client->old_skin, host_client->playerskin))
1348 //if (host_client->begun)
1349 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1350 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1351 /*// send notification to all clients
1352 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1353 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1354 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1358 static void Host_Version_f (void)
1360 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1363 static void Host_Say(qboolean teamonly)
1365 prvm_prog_t *prog = SVVM_prog;
1370 // LordHavoc: long say messages
1372 qboolean fromServer = false;
1374 if (cmd_source == src_command)
1376 if (cls.state == ca_dedicated)
1383 Cmd_ForwardToServer ();
1388 if (Cmd_Argc () < 2)
1391 if (!teamplay.integer)
1401 // note this uses the chat prefix \001
1402 if (!fromServer && !teamonly)
1403 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1404 else if (!fromServer && teamonly)
1405 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1406 else if(*(sv_adminnick.string))
1407 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1409 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1410 p2 = text + strlen(text);
1411 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1413 if (p2[-1] == '\"' && quoted)
1418 strlcat(text, "\n", sizeof(text));
1420 // note: save is not a valid edict if fromServer is true
1422 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1423 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1424 SV_ClientPrint(text);
1427 if (cls.state == ca_dedicated)
1428 Con_Print(&text[1]);
1432 static void Host_Say_f(void)
1438 static void Host_Say_Team_f(void)
1444 static void Host_Tell_f(void)
1446 const char *playername_start = NULL;
1447 size_t playername_length = 0;
1448 int playernumber = 0;
1451 const char *p1, *p2;
1452 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1453 qboolean fromServer = false;
1455 if (cmd_source == src_command)
1457 if (cls.state == ca_dedicated)
1461 Cmd_ForwardToServer ();
1466 if (Cmd_Argc () < 2)
1469 // note this uses the chat prefix \001
1471 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1472 else if(*(sv_adminnick.string))
1473 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1475 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1478 p2 = p1 + strlen(p1);
1479 // remove the target name
1480 while (p1 < p2 && *p1 == ' ')
1485 while (p1 < p2 && *p1 == ' ')
1487 while (p1 < p2 && isdigit(*p1))
1489 playernumber = playernumber * 10 + (*p1 - '0');
1497 playername_start = p1;
1498 while (p1 < p2 && *p1 != '"')
1500 playername_length = p1 - playername_start;
1506 playername_start = p1;
1507 while (p1 < p2 && *p1 != ' ')
1509 playername_length = p1 - playername_start;
1511 while (p1 < p2 && *p1 == ' ')
1513 if(playername_start)
1515 // set playernumber to the right client
1517 if(playername_length >= sizeof(namebuf))
1520 Con_Print("Host_Tell: too long player name/ID\n");
1522 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1525 memcpy(namebuf, playername_start, playername_length);
1526 namebuf[playername_length] = 0;
1527 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1529 if (!svs.clients[playernumber].active)
1531 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1535 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1538 Con_Print("Host_Tell: invalid player name/ID\n");
1540 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1543 // remove trailing newlines
1544 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1546 // remove quotes if present
1552 else if (fromServer)
1553 Con_Print("Host_Tell: missing end quote\n");
1555 SV_ClientPrint("Host_Tell: missing end quote\n");
1557 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1560 return; // empty say
1561 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1567 host_client = svs.clients + playernumber;
1568 SV_ClientPrint(text);
1578 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1579 static void Host_Color(int changetop, int changebottom)
1581 prvm_prog_t *prog = SVVM_prog;
1582 int top, bottom, playercolor;
1584 // get top and bottom either from the provided values or the current values
1585 // (allows changing only top or bottom, or both at once)
1586 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1587 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1591 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1597 playercolor = top*16 + bottom;
1599 if (cmd_source == src_command)
1601 Cvar_SetValueQuick(&cl_color, playercolor);
1605 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1608 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1610 Con_DPrint("Calling SV_ChangeTeam\n");
1611 prog->globals.fp[OFS_PARM0] = playercolor;
1612 PRVM_serverglobalfloat(time) = sv.time;
1613 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1614 prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
1618 if (host_client->edict)
1620 PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
1621 PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
1623 host_client->colors = playercolor;
1624 if (host_client->old_colors != host_client->colors)
1626 host_client->old_colors = host_client->colors;
1627 // send notification to all clients
1628 MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
1629 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1630 MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
1635 static void Host_Color_f(void)
1639 if (Cmd_Argc() == 1)
1641 Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
1642 Con_Print("color <0-15> [0-15]\n");
1646 if (Cmd_Argc() == 2)
1647 top = bottom = atoi(Cmd_Argv(1));
1650 top = atoi(Cmd_Argv(1));
1651 bottom = atoi(Cmd_Argv(2));
1653 Host_Color(top, bottom);
1656 static void Host_TopColor_f(void)
1658 if (Cmd_Argc() == 1)
1660 Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
1661 Con_Print("topcolor <0-15>\n");
1665 Host_Color(atoi(Cmd_Argv(1)), -1);
1668 static void Host_BottomColor_f(void)
1670 if (Cmd_Argc() == 1)
1672 Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
1673 Con_Print("bottomcolor <0-15>\n");
1677 Host_Color(-1, atoi(Cmd_Argv(1)));
1680 cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
1681 cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"};
1682 static void Host_Rate_f(void)
1686 if (Cmd_Argc() != 2)
1688 Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
1689 Con_Print("rate <bytespersecond>\n");
1693 rate = atoi(Cmd_Argv(1));
1695 if (cmd_source == src_command)
1697 Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
1701 host_client->rate = rate;
1703 static void Host_Rate_BurstSize_f(void)
1707 if (Cmd_Argc() != 2)
1709 Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
1710 Con_Print("rate_burstsize <bytes>\n");
1714 rate_burstsize = atoi(Cmd_Argv(1));
1716 if (cmd_source == src_command)
1718 Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
1722 host_client->rate_burstsize = rate_burstsize;
1730 static void Host_Kill_f (void)
1732 prvm_prog_t *prog = SVVM_prog;
1733 if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
1735 SV_ClientPrint("Can't suicide -- already dead!\n");
1739 PRVM_serverglobalfloat(time) = sv.time;
1740 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1741 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
1750 static void Host_Pause_f (void)
1752 void (*print) (const char *fmt, ...);
1753 if (cmd_source == src_command)
1755 // if running a client, try to send over network so the pause is handled by the server
1756 if (cls.state == ca_connected)
1758 Cmd_ForwardToServer ();
1764 print = SV_ClientPrintf;
1766 if (!pausable.integer)
1768 if (cmd_source == src_client)
1770 if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
1772 print("Pause not allowed.\n");
1779 SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
1780 // send notification to all clients
1781 MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
1782 MSG_WriteByte(&sv.reliable_datagram, sv.paused);
1786 ======================
1788 LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
1789 LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
1790 ======================
1792 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)"};
1793 static void Host_PModel_f (void)
1795 prvm_prog_t *prog = SVVM_prog;
1798 if (Cmd_Argc () == 1)
1800 Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
1803 i = atoi(Cmd_Argv(1));
1805 if (cmd_source == src_command)
1807 if (cl_pmodel.integer == i)
1809 Cvar_SetValue ("_cl_pmodel", i);
1810 if (cls.state == ca_connected)
1811 Cmd_ForwardToServer ();
1815 PRVM_serveredictfloat(host_client->edict, pmodel) = i;
1818 //===========================================================================
1826 static void Host_PreSpawn_f (void)
1828 if (host_client->prespawned)
1830 Con_Print("prespawn not valid -- already prespawned\n");
1833 host_client->prespawned = true;
1835 if (host_client->netconnection)
1837 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1838 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1839 MSG_WriteByte (&host_client->netconnection->message, 2);
1840 host_client->sendsignon = 0; // enable unlimited sends again
1843 // reset the name change timer because the client will send name soon
1844 host_client->nametime = 0;
1852 static void Host_Spawn_f (void)
1854 prvm_prog_t *prog = SVVM_prog;
1857 int stats[MAX_CL_STATS];
1859 if (!host_client->prespawned)
1861 Con_Print("Spawn not valid -- not yet prespawned\n");
1864 if (host_client->spawned)
1866 Con_Print("Spawn not valid -- already spawned\n");
1869 host_client->spawned = true;
1871 // reset name change timer again because they might want to change name
1872 // again in the first 5 seconds after connecting
1873 host_client->nametime = 0;
1875 // LordHavoc: moved this above the QC calls at FrikaC's request
1876 // LordHavoc: commented this out
1877 //if (host_client->netconnection)
1878 // SZ_Clear (&host_client->netconnection->message);
1880 // run the entrance script
1883 // loaded games are fully initialized already
1884 if (PRVM_serverfunction(RestoreGame))
1886 Con_DPrint("Calling RestoreGame\n");
1887 PRVM_serverglobalfloat(time) = sv.time;
1888 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1889 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1894 //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);
1896 // copy spawn parms out of the client_t
1897 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1898 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1900 // call the spawn function
1901 host_client->clientconnectcalled = true;
1902 PRVM_serverglobalfloat(time) = sv.time;
1903 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1904 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1906 if (cls.state == ca_dedicated)
1907 Con_Printf("%s connected\n", host_client->name);
1909 PRVM_serverglobalfloat(time) = sv.time;
1910 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1913 if (!host_client->netconnection)
1916 // send time of update
1917 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1918 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1920 // send all current names, colors, and frag counts
1921 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1923 if (!client->active)
1925 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1926 MSG_WriteByte (&host_client->netconnection->message, i);
1927 MSG_WriteString (&host_client->netconnection->message, client->name);
1928 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1929 MSG_WriteByte (&host_client->netconnection->message, i);
1930 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1931 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1932 MSG_WriteByte (&host_client->netconnection->message, i);
1933 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1936 // send all current light styles
1937 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1939 if (sv.lightstyles[i][0])
1941 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1942 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1943 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1948 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1949 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1950 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1952 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1953 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1954 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1956 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1957 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1958 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1960 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1961 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1962 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1965 // Never send a roll angle, because savegames can catch the server
1966 // in a state where it is expecting the client to correct the angle
1967 // and it won't happen if the game was just loaded, so you wind up
1968 // with a permanent head tilt
1971 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1972 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1973 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1974 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1978 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1979 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1980 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1981 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1984 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1986 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1987 MSG_WriteByte (&host_client->netconnection->message, 3);
1995 static void Host_Begin_f (void)
1997 if (!host_client->spawned)
1999 Con_Print("Begin not valid -- not yet spawned\n");
2002 if (host_client->begun)
2004 Con_Print("Begin not valid -- already begun\n");
2007 host_client->begun = true;
2009 // LordHavoc: note: this code also exists in SV_DropClient
2013 for (i = 0;i < svs.maxclients;i++)
2014 if (svs.clients[i].active && !svs.clients[i].spawned)
2016 if (i == svs.maxclients)
2018 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
2019 sv.paused = sv.loadgame = false; // we're basically done with loading now
2024 //===========================================================================
2031 Kicks a user off of the server
2034 static void Host_Kick_f (void)
2037 const char *message = NULL;
2040 qboolean byNumber = false;
2047 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
2049 i = (int)(atof(Cmd_Argv(2)) - 1);
2050 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
2056 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
2058 if (!host_client->active)
2060 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
2065 if (i < svs.maxclients)
2067 if (cmd_source == src_command)
2069 if (cls.state == ca_dedicated)
2072 who = cl_name.string;
2077 // can't kick yourself!
2078 if (host_client == save)
2083 message = Cmd_Args();
2084 COM_ParseToken_Simple(&message, false, false, true);
2087 message++; // skip the #
2088 while (*message == ' ') // skip white space
2090 message += strlen(Cmd_Argv(2)); // skip the number
2092 while (*message && *message == ' ')
2096 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2098 SV_ClientPrintf("Kicked by %s\n", who);
2099 SV_DropClient (false); // kicked
2106 ===============================================================================
2110 ===============================================================================
2118 static void Host_Give_f (void)
2120 prvm_prog_t *prog = SVVM_prog;
2126 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2131 v = atoi (Cmd_Argv(2));
2145 // MED 01/04/97 added hipnotic give stuff
2146 if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
2151 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2153 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2155 else if (t[0] == '9')
2156 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2157 else if (t[0] == '0')
2158 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2159 else if (t[0] >= '2')
2160 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2165 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2170 if (gamemode == GAME_ROGUE)
2171 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2173 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2176 if (gamemode == GAME_ROGUE)
2178 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2179 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2180 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2184 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2188 if (gamemode == GAME_ROGUE)
2190 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2191 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2192 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2196 if (gamemode == GAME_ROGUE)
2198 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2199 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2200 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2204 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2208 if (gamemode == GAME_ROGUE)
2210 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2211 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2212 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2216 PRVM_serveredictfloat(host_client->edict, health) = v;
2219 if (gamemode == GAME_ROGUE)
2221 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2222 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2223 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2227 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2231 if (gamemode == GAME_ROGUE)
2233 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2234 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2235 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2241 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2246 for (i=0 ; i<prog->num_edicts ; i++)
2248 e = PRVM_EDICT_NUM(i);
2249 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2252 Con_Print("No viewthing on map\n");
2261 static void Host_Viewmodel_f (void)
2263 prvm_prog_t *prog = SVVM_prog;
2270 e = FindViewthing(prog);
2273 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2274 if (m && m->loaded && m->Draw)
2276 PRVM_serveredictfloat(e, frame) = 0;
2277 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2280 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2289 static void Host_Viewframe_f (void)
2291 prvm_prog_t *prog = SVVM_prog;
2299 e = FindViewthing(prog);
2302 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2304 f = atoi(Cmd_Argv(1));
2305 if (f >= m->numframes)
2308 PRVM_serveredictfloat(e, frame) = f;
2313 static void PrintFrameName (dp_model_t *m, int frame)
2316 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2318 Con_Printf("frame %i\n", frame);
2326 static void Host_Viewnext_f (void)
2328 prvm_prog_t *prog = SVVM_prog;
2335 e = FindViewthing(prog);
2338 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2340 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2341 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2342 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2344 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2353 static void Host_Viewprev_f (void)
2355 prvm_prog_t *prog = SVVM_prog;
2362 e = FindViewthing(prog);
2365 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2367 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2368 if (PRVM_serveredictfloat(e, frame) < 0)
2369 PRVM_serveredictfloat(e, frame) = 0;
2371 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2376 ===============================================================================
2380 ===============================================================================
2389 static void Host_Startdemos_f (void)
2393 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2399 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2402 Con_DPrintf("%i demo(s) in loop\n", c);
2404 for (i=1 ; i<c+1 ; i++)
2405 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2407 // LordHavoc: clear the remaining slots
2408 for (;i <= MAX_DEMOS;i++)
2409 cls.demos[i-1][0] = 0;
2411 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2425 Return to looping demos
2428 static void Host_Demos_f (void)
2430 if (cls.state == ca_dedicated)
2432 if (cls.demonum == -1)
2442 Return to looping demos
2445 static void Host_Stopdemo_f (void)
2447 if (!cls.demoplayback)
2450 Host_ShutdownServer ();
2453 static void Host_SendCvar_f (void)
2457 const char *cvarname;
2463 cvarname = Cmd_Argv(1);
2464 if (cls.state == ca_connected)
2466 c = Cvar_FindVar(cvarname);
2467 // LordHavoc: if there is no such cvar or if it is private, send a
2468 // reply indicating that it has no value
2469 if(!c || (c->flags & CVAR_PRIVATE))
2470 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2472 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2475 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2479 if (cls.state != ca_dedicated)
2483 for(;i<svs.maxclients;i++)
2484 if(svs.clients[i].active && svs.clients[i].netconnection)
2486 host_client = &svs.clients[i];
2487 Host_ClientCommands("sendcvar %s\n", cvarname);
2492 static void MaxPlayers_f(void)
2496 if (Cmd_Argc() != 2)
2498 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2504 Con_Print("maxplayers can not be changed while a server is running.\n");
2505 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2508 n = atoi(Cmd_Argv(1));
2509 n = bound(1, n, MAX_SCOREBOARD);
2510 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2512 svs.maxclients_next = n;
2514 Cvar_Set ("deathmatch", "0");
2516 Cvar_Set ("deathmatch", "1");
2520 =====================
2523 ProQuake rcon support
2524 =====================
2526 static void Host_PQRcon_f (void)
2531 lhnetsocket_t *mysocket;
2532 char peer_address[64];
2534 if (Cmd_Argc() == 1)
2536 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2540 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2542 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2546 e = strchr(rcon_password.string, ' ');
2547 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2551 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2555 if (!rcon_address.string[0])
2557 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2560 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2562 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2563 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2567 unsigned char bufdata[64];
2570 MSG_WriteLong(&buf, 0);
2571 MSG_WriteByte(&buf, CCREQ_RCON);
2572 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2573 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2574 MSG_WriteString(&buf, Cmd_Args());
2575 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2576 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2581 //=============================================================================
2583 // QuakeWorld commands
2586 =====================
2589 Send the rest of the command line over as
2590 an unconnected command.
2591 =====================
2593 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2598 lhnetsocket_t *mysocket;
2601 if (Cmd_Argc() == 1)
2603 Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
2607 if (!rcon_password.string || !rcon_password.string[0])
2609 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2613 e = strchr(rcon_password.string, ' ');
2614 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2617 to = cls.netcon->peeraddress;
2620 if (!rcon_address.string[0])
2622 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2625 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2627 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2628 if (mysocket && Cmd_Args()[0])
2630 // simply put together the rcon packet and send it
2631 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2633 if(cls.rcon_commands[cls.rcon_ringpos][0])
2636 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2637 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]);
2638 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2641 for (i = 0;i < MAX_RCONS;i++)
2642 if(cls.rcon_commands[i][0])
2643 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2647 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2648 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2649 cls.rcon_addresses[cls.rcon_ringpos] = to;
2650 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2651 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2653 else if(rcon_secure.integer > 0)
2657 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2658 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2659 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2662 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2663 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2668 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2674 ====================
2677 user <name or userid>
2679 Dump userdata / masterdata for a user
2680 ====================
2682 static void Host_User_f (void) // credit: taken from QuakeWorld
2687 if (Cmd_Argc() != 2)
2689 Con_Printf ("Usage: user <username / userid>\n");
2693 uid = atoi(Cmd_Argv(1));
2695 for (i = 0;i < cl.maxclients;i++)
2697 if (!cl.scores[i].name[0])
2699 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2701 InfoString_Print(cl.scores[i].qw_userinfo);
2705 Con_Printf ("User not in server.\n");
2709 ====================
2712 Dump userids for all current players
2713 ====================
2715 static void Host_Users_f (void) // credit: taken from QuakeWorld
2721 Con_Printf ("userid frags name\n");
2722 Con_Printf ("------ ----- ----\n");
2723 for (i = 0;i < cl.maxclients;i++)
2725 if (cl.scores[i].name[0])
2727 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2732 Con_Printf ("%i total users\n", c);
2737 Host_FullServerinfo_f
2739 Sent by server when serverinfo changes
2742 // TODO: shouldn't this be a cvar instead?
2743 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2746 if (Cmd_Argc() != 2)
2748 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2752 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2753 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2754 cl.qw_teamplay = atoi(temp);
2761 Allow clients to change userinfo
2765 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2772 if (Cmd_Argc() != 2)
2774 Con_Printf ("fullinfo <complete info string>\n");
2784 while (*s && *s != '\\')
2790 Con_Printf ("MISSING VALUE\n");
2796 while (*s && *s != '\\')
2803 CL_SetInfo(key, value, false, false, false, false);
2811 Allow clients to change userinfo
2814 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2816 if (Cmd_Argc() == 1)
2818 InfoString_Print(cls.userinfo);
2821 if (Cmd_Argc() != 3)
2823 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2826 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2830 ====================
2833 packet <destination> <contents>
2835 Contents allows \n escape character
2836 ====================
2838 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2844 lhnetaddress_t address;
2845 lhnetsocket_t *mysocket;
2847 if (Cmd_Argc() != 3)
2849 Con_Printf ("packet <destination> <contents>\n");
2853 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2855 Con_Printf ("Bad address\n");
2861 send[0] = send[1] = send[2] = send[3] = -1;
2863 l = (int)strlen (in);
2864 for (i=0 ; i<l ; i++)
2866 if (out >= send + sizeof(send) - 1)
2868 if (in[i] == '\\' && in[i+1] == 'n')
2873 else if (in[i] == '\\' && in[i+1] == '0')
2878 else if (in[i] == '\\' && in[i+1] == 't')
2883 else if (in[i] == '\\' && in[i+1] == 'r')
2888 else if (in[i] == '\\' && in[i+1] == '"')
2897 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2899 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2901 NetConn_Write(mysocket, send, out - send, &address);
2905 ====================
2908 Send back ping and packet loss update for all current players to this player
2909 ====================
2911 void Host_Pings_f (void)
2913 int i, j, ping, packetloss, movementloss;
2916 if (!host_client->netconnection)
2919 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2921 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2922 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2924 for (i = 0;i < svs.maxclients;i++)
2928 if (svs.clients[i].netconnection)
2930 for (j = 0;j < NETGRAPH_PACKETS;j++)
2931 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2933 for (j = 0;j < NETGRAPH_PACKETS;j++)
2934 if (svs.clients[i].movement_count[j] < 0)
2937 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2938 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2939 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2940 ping = bound(0, ping, 9999);
2941 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2943 // send qw_svc_updateping and qw_svc_updatepl messages
2944 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2945 MSG_WriteShort(&host_client->netconnection->message, ping);
2946 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2947 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2951 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2953 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2955 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2956 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2959 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2960 MSG_WriteString(&host_client->netconnection->message, "\n");
2963 static void Host_PingPLReport_f(void)
2968 if (l > cl.maxclients)
2970 for (i = 0;i < l;i++)
2972 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2973 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2974 if(errbyte && *errbyte == ',')
2975 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2977 cl.scores[i].qw_movementloss = 0;
2981 //=============================================================================
2988 void Host_InitCommands (void)
2990 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2992 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2993 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2994 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2995 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2996 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2997 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2998 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2999 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
3000 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
3001 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
3002 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
3003 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)");
3004 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
3005 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
3006 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
3007 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
3008 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
3009 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
3010 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
3011 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
3012 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
3013 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
3015 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
3016 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
3017 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
3019 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
3020 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
3021 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
3022 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
3024 Cvar_RegisterVariable (&cl_name);
3025 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
3026 Cvar_RegisterVariable (&cl_color);
3027 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
3028 Cvar_RegisterVariable (&cl_rate);
3029 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
3030 Cvar_RegisterVariable (&cl_rate_burstsize);
3031 Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
3032 Cvar_RegisterVariable (&cl_pmodel);
3033 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
3035 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
3036 Cvar_RegisterVariable (&cl_playermodel);
3037 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
3038 Cvar_RegisterVariable (&cl_playerskin);
3039 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
3041 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
3042 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
3043 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)");
3044 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
3046 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
3048 Cvar_RegisterVariable (&rcon_password);
3049 Cvar_RegisterVariable (&rcon_address);
3050 Cvar_RegisterVariable (&rcon_secure);
3051 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
3052 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");
3053 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");
3054 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)");
3055 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
3056 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
3057 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
3058 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
3059 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
3060 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
3061 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
3062 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
3064 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)");
3065 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)");
3067 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)");
3068 Cvar_RegisterVariable (&r_fixtrans_auto);
3070 Cvar_RegisterVariable (&team);
3071 Cvar_RegisterVariable (&skin);
3072 Cvar_RegisterVariable (&noaim);
3074 Cvar_RegisterVariable(&sv_cheats);
3075 Cvar_RegisterVariable(&sv_adminnick);
3076 Cvar_RegisterVariable(&sv_status_privacy);
3077 Cvar_RegisterVariable(&sv_status_show_qcstatus);
3078 Cvar_RegisterVariable(&sv_namechangetimer);
3081 void Host_NoOperation_f(void)