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->spawned)
1202 SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
1203 strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
1204 // send notification to all clients
1205 MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
1206 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1207 MSG_WriteString (&sv.reliable_datagram, host_client->name);
1208 SV_WriteNetnameIntoDemo(host_client);
1213 ======================
1215 ======================
1217 cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
1218 // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
1219 static void Host_Playermodel_f (void)
1221 prvm_prog_t *prog = SVVM_prog;
1223 char newPath[sizeof(host_client->playermodel)];
1225 if (Cmd_Argc () == 1)
1227 Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
1231 if (Cmd_Argc () == 2)
1232 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1234 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1236 for (i = 0, j = 0;newPath[i];i++)
1237 if (newPath[i] != '\r' && newPath[i] != '\n')
1238 newPath[j++] = newPath[i];
1241 if (cmd_source == src_command)
1243 Cvar_Set ("_cl_playermodel", newPath);
1248 if (realtime < host_client->nametime)
1250 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1254 host_client->nametime = realtime + 5;
1257 // point the string back at updateclient->name to keep it safe
1258 strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
1259 PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
1260 if (strcmp(host_client->old_model, host_client->playermodel))
1262 strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
1263 /*// send notification to all clients
1264 MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
1265 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1266 MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
1271 ======================
1273 ======================
1275 cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
1276 static void Host_Playerskin_f (void)
1278 prvm_prog_t *prog = SVVM_prog;
1280 char newPath[sizeof(host_client->playerskin)];
1282 if (Cmd_Argc () == 1)
1284 Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
1288 if (Cmd_Argc () == 2)
1289 strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
1291 strlcpy (newPath, Cmd_Args(), sizeof (newPath));
1293 for (i = 0, j = 0;newPath[i];i++)
1294 if (newPath[i] != '\r' && newPath[i] != '\n')
1295 newPath[j++] = newPath[i];
1298 if (cmd_source == src_command)
1300 Cvar_Set ("_cl_playerskin", newPath);
1305 if (realtime < host_client->nametime)
1307 SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
1311 host_client->nametime = realtime + 5;
1314 // point the string back at updateclient->name to keep it safe
1315 strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
1316 PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
1317 if (strcmp(host_client->old_skin, host_client->playerskin))
1319 //if (host_client->spawned)
1320 // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
1321 strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
1322 /*// send notification to all clients
1323 MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
1324 MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
1325 MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
1329 static void Host_Version_f (void)
1331 Con_Printf("Version: %s build %s\n", gamename, buildstring);
1334 static void Host_Say(qboolean teamonly)
1336 prvm_prog_t *prog = SVVM_prog;
1341 // LordHavoc: long say messages
1343 qboolean fromServer = false;
1345 if (cmd_source == src_command)
1347 if (cls.state == ca_dedicated)
1354 Cmd_ForwardToServer ();
1359 if (Cmd_Argc () < 2)
1362 if (!teamplay.integer)
1372 // note this uses the chat prefix \001
1373 if (!fromServer && !teamonly)
1374 dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
1375 else if (!fromServer && teamonly)
1376 dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
1377 else if(*(sv_adminnick.string))
1378 dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
1380 dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
1381 p2 = text + strlen(text);
1382 while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
1384 if (p2[-1] == '\"' && quoted)
1389 strlcat(text, "\n", sizeof(text));
1391 // note: save is not a valid edict if fromServer is true
1393 for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
1394 if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
1395 SV_ClientPrint(text);
1398 if (cls.state == ca_dedicated)
1399 Con_Print(&text[1]);
1403 static void Host_Say_f(void)
1409 static void Host_Say_Team_f(void)
1415 static void Host_Tell_f(void)
1417 const char *playername_start = NULL;
1418 size_t playername_length = 0;
1419 int playernumber = 0;
1422 const char *p1, *p2;
1423 char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
1424 qboolean fromServer = false;
1426 if (cmd_source == src_command)
1428 if (cls.state == ca_dedicated)
1432 Cmd_ForwardToServer ();
1437 if (Cmd_Argc () < 2)
1440 // note this uses the chat prefix \001
1442 dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
1443 else if(*(sv_adminnick.string))
1444 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
1446 dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
1449 p2 = p1 + strlen(p1);
1450 // remove the target name
1451 while (p1 < p2 && *p1 == ' ')
1456 while (p1 < p2 && *p1 == ' ')
1458 while (p1 < p2 && isdigit(*p1))
1460 playernumber = playernumber * 10 + (*p1 - '0');
1468 playername_start = p1;
1469 while (p1 < p2 && *p1 != '"')
1471 playername_length = p1 - playername_start;
1477 playername_start = p1;
1478 while (p1 < p2 && *p1 != ' ')
1480 playername_length = p1 - playername_start;
1482 while (p1 < p2 && *p1 == ' ')
1484 if(playername_start)
1486 // set playernumber to the right client
1488 if(playername_length >= sizeof(namebuf))
1491 Con_Print("Host_Tell: too long player name/ID\n");
1493 SV_ClientPrint("Host_Tell: too long player name/ID\n");
1496 memcpy(namebuf, playername_start, playername_length);
1497 namebuf[playername_length] = 0;
1498 for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
1500 if (!svs.clients[playernumber].active)
1502 if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
1506 if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
1509 Con_Print("Host_Tell: invalid player name/ID\n");
1511 SV_ClientPrint("Host_Tell: invalid player name/ID\n");
1514 // remove trailing newlines
1515 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1517 // remove quotes if present
1523 else if (fromServer)
1524 Con_Print("Host_Tell: missing end quote\n");
1526 SV_ClientPrint("Host_Tell: missing end quote\n");
1528 while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
1531 return; // empty say
1532 for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
1538 host_client = svs.clients + playernumber;
1539 SV_ClientPrint(text);
1549 cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
1550 static void Host_Color(int changetop, int changebottom)
1552 prvm_prog_t *prog = SVVM_prog;
1553 int top, bottom, playercolor;
1555 // get top and bottom either from the provided values or the current values
1556 // (allows changing only top or bottom, or both at once)
1557 top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
1558 bottom = changebottom >= 0 ? changebottom : cl_color.integer;
1562 // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
1568 playercolor = top*16 + bottom;
1570 if (cmd_source == src_command)
1572 Cvar_SetValueQuick(&cl_color, playercolor);
1576 if (cls.protocol == PROTOCOL_QUAKEWORLD)
1579 if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
1581 Con_DPrint("Calling SV_ChangeTeam\n");
1582 prog->globals.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->spawned)
1779 Con_Print("prespawn not valid -- already spawned\n");
1783 if (host_client->netconnection)
1785 SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
1786 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1787 MSG_WriteByte (&host_client->netconnection->message, 2);
1788 host_client->sendsignon = 0; // enable unlimited sends again
1791 // reset the name change timer because the client will send name soon
1792 host_client->nametime = 0;
1800 static void Host_Spawn_f (void)
1802 prvm_prog_t *prog = SVVM_prog;
1805 int stats[MAX_CL_STATS];
1807 if (host_client->spawned)
1809 Con_Print("Spawn not valid -- already spawned\n");
1813 // reset name change timer again because they might want to change name
1814 // again in the first 5 seconds after connecting
1815 host_client->nametime = 0;
1817 // LordHavoc: moved this above the QC calls at FrikaC's request
1818 // LordHavoc: commented this out
1819 //if (host_client->netconnection)
1820 // SZ_Clear (&host_client->netconnection->message);
1822 // run the entrance script
1825 // loaded games are fully initialized already
1826 if (PRVM_serverfunction(RestoreGame))
1828 Con_DPrint("Calling RestoreGame\n");
1829 PRVM_serverglobalfloat(time) = sv.time;
1830 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1831 prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
1836 //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);
1838 // copy spawn parms out of the client_t
1839 for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
1840 (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
1842 // call the spawn function
1843 host_client->clientconnectcalled = true;
1844 PRVM_serverglobalfloat(time) = sv.time;
1845 PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
1846 prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
1848 if (cls.state == ca_dedicated)
1849 Con_Printf("%s connected\n", host_client->name);
1851 PRVM_serverglobalfloat(time) = sv.time;
1852 prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
1855 if (!host_client->netconnection)
1858 // send time of update
1859 MSG_WriteByte (&host_client->netconnection->message, svc_time);
1860 MSG_WriteFloat (&host_client->netconnection->message, sv.time);
1862 // send all current names, colors, and frag counts
1863 for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
1865 if (!client->active)
1867 MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
1868 MSG_WriteByte (&host_client->netconnection->message, i);
1869 MSG_WriteString (&host_client->netconnection->message, client->name);
1870 MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
1871 MSG_WriteByte (&host_client->netconnection->message, i);
1872 MSG_WriteShort (&host_client->netconnection->message, client->frags);
1873 MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
1874 MSG_WriteByte (&host_client->netconnection->message, i);
1875 MSG_WriteByte (&host_client->netconnection->message, client->colors);
1878 // send all current light styles
1879 for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
1881 if (sv.lightstyles[i][0])
1883 MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
1884 MSG_WriteByte (&host_client->netconnection->message, (char)i);
1885 MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
1890 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1891 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
1892 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
1894 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1895 MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
1896 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
1898 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1899 MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
1900 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
1902 MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
1903 MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
1904 MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
1907 // Never send a roll angle, because savegames can catch the server
1908 // in a state where it is expecting the client to correct the angle
1909 // and it won't happen if the game was just loaded, so you wind up
1910 // with a permanent head tilt
1913 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1914 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
1915 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
1916 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1920 MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
1921 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
1922 MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
1923 MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
1926 SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
1928 MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
1929 MSG_WriteByte (&host_client->netconnection->message, 3);
1937 static void Host_Begin_f (void)
1939 host_client->spawned = true;
1941 // LordHavoc: note: this code also exists in SV_DropClient
1945 for (i = 0;i < svs.maxclients;i++)
1946 if (svs.clients[i].active && !svs.clients[i].spawned)
1948 if (i == svs.maxclients)
1950 Con_Printf("Loaded game, everyone rejoined - unpausing\n");
1951 sv.paused = sv.loadgame = false; // we're basically done with loading now
1956 //===========================================================================
1963 Kicks a user off of the server
1966 static void Host_Kick_f (void)
1969 const char *message = NULL;
1972 qboolean byNumber = false;
1979 if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
1981 i = (int)(atof(Cmd_Argv(2)) - 1);
1982 if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
1988 for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
1990 if (!host_client->active)
1992 if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
1997 if (i < svs.maxclients)
1999 if (cmd_source == src_command)
2001 if (cls.state == ca_dedicated)
2004 who = cl_name.string;
2009 // can't kick yourself!
2010 if (host_client == save)
2015 message = Cmd_Args();
2016 COM_ParseToken_Simple(&message, false, false, true);
2019 message++; // skip the #
2020 while (*message == ' ') // skip white space
2022 message += strlen(Cmd_Argv(2)); // skip the number
2024 while (*message && *message == ' ')
2028 SV_ClientPrintf("Kicked by %s: %s\n", who, message);
2030 SV_ClientPrintf("Kicked by %s\n", who);
2031 SV_DropClient (false); // kicked
2038 ===============================================================================
2042 ===============================================================================
2050 static void Host_Give_f (void)
2052 prvm_prog_t *prog = SVVM_prog;
2058 SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
2063 v = atoi (Cmd_Argv(2));
2077 // MED 01/04/97 added hipnotic give stuff
2078 if (gamemode == GAME_HIPNOTIC)
2083 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
2085 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
2087 else if (t[0] == '9')
2088 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
2089 else if (t[0] == '0')
2090 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
2091 else if (t[0] >= '2')
2092 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2097 PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
2102 if (gamemode == GAME_ROGUE)
2103 PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
2105 PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
2108 if (gamemode == GAME_ROGUE)
2110 PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
2111 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2112 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2116 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2120 if (gamemode == GAME_ROGUE)
2122 PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
2123 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2124 PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
2128 if (gamemode == GAME_ROGUE)
2130 PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
2131 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2132 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2136 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2140 if (gamemode == GAME_ROGUE)
2142 PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
2143 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2144 PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
2148 PRVM_serveredictfloat(host_client->edict, health) = v;
2151 if (gamemode == GAME_ROGUE)
2153 PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
2154 if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
2155 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2159 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2163 if (gamemode == GAME_ROGUE)
2165 PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
2166 if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
2167 PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
2173 static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
2178 for (i=0 ; i<prog->num_edicts ; i++)
2180 e = PRVM_EDICT_NUM(i);
2181 if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
2184 Con_Print("No viewthing on map\n");
2193 static void Host_Viewmodel_f (void)
2195 prvm_prog_t *prog = SVVM_prog;
2202 e = FindViewthing(prog);
2205 m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
2206 if (m && m->loaded && m->Draw)
2208 PRVM_serveredictfloat(e, frame) = 0;
2209 cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
2212 Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
2221 static void Host_Viewframe_f (void)
2223 prvm_prog_t *prog = SVVM_prog;
2231 e = FindViewthing(prog);
2234 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2236 f = atoi(Cmd_Argv(1));
2237 if (f >= m->numframes)
2240 PRVM_serveredictfloat(e, frame) = f;
2245 static void PrintFrameName (dp_model_t *m, int frame)
2248 Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
2250 Con_Printf("frame %i\n", frame);
2258 static void Host_Viewnext_f (void)
2260 prvm_prog_t *prog = SVVM_prog;
2267 e = FindViewthing(prog);
2270 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2272 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
2273 if (PRVM_serveredictfloat(e, frame) >= m->numframes)
2274 PRVM_serveredictfloat(e, frame) = m->numframes - 1;
2276 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2285 static void Host_Viewprev_f (void)
2287 prvm_prog_t *prog = SVVM_prog;
2294 e = FindViewthing(prog);
2297 m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
2299 PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
2300 if (PRVM_serveredictfloat(e, frame) < 0)
2301 PRVM_serveredictfloat(e, frame) = 0;
2303 PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
2308 ===============================================================================
2312 ===============================================================================
2321 static void Host_Startdemos_f (void)
2325 if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
2331 Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
2334 Con_DPrintf("%i demo(s) in loop\n", c);
2336 for (i=1 ; i<c+1 ; i++)
2337 strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
2339 // LordHavoc: clear the remaining slots
2340 for (;i <= MAX_DEMOS;i++)
2341 cls.demos[i-1][0] = 0;
2343 if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
2357 Return to looping demos
2360 static void Host_Demos_f (void)
2362 if (cls.state == ca_dedicated)
2364 if (cls.demonum == -1)
2374 Return to looping demos
2377 static void Host_Stopdemo_f (void)
2379 if (!cls.demoplayback)
2382 Host_ShutdownServer ();
2385 static void Host_SendCvar_f (void)
2389 const char *cvarname;
2395 cvarname = Cmd_Argv(1);
2396 if (cls.state == ca_connected)
2398 c = Cvar_FindVar(cvarname);
2399 // LordHavoc: if there is no such cvar or if it is private, send a
2400 // reply indicating that it has no value
2401 if(!c || (c->flags & CVAR_PRIVATE))
2402 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
2404 Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
2407 if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
2411 if (cls.state != ca_dedicated)
2415 for(;i<svs.maxclients;i++)
2416 if(svs.clients[i].active && svs.clients[i].netconnection)
2418 host_client = &svs.clients[i];
2419 Host_ClientCommands("sendcvar %s\n", cvarname);
2424 static void MaxPlayers_f(void)
2428 if (Cmd_Argc() != 2)
2430 Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
2436 Con_Print("maxplayers can not be changed while a server is running.\n");
2437 Con_Print("It will be changed on next server startup (\"map\" command).\n");
2440 n = atoi(Cmd_Argv(1));
2441 n = bound(1, n, MAX_SCOREBOARD);
2442 Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
2444 svs.maxclients_next = n;
2446 Cvar_Set ("deathmatch", "0");
2448 Cvar_Set ("deathmatch", "1");
2452 =====================
2455 ProQuake rcon support
2456 =====================
2458 static void Host_PQRcon_f (void)
2463 lhnetsocket_t *mysocket;
2464 char peer_address[64];
2466 if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
2468 Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
2472 e = strchr(rcon_password.string, ' ');
2473 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2477 InfoString_GetValue(cls.userinfo, "*ip", peer_address, sizeof(peer_address));
2481 if (!rcon_address.string[0])
2483 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2486 strlcpy(peer_address, rcon_address.string, strlen(rcon_address.string)+1);
2488 LHNETADDRESS_FromString(&to, peer_address, sv_netport.integer);
2489 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2493 unsigned char bufdata[64];
2496 MSG_WriteLong(&buf, 0);
2497 MSG_WriteByte(&buf, CCREQ_RCON);
2498 SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
2499 MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
2500 MSG_WriteString(&buf, Cmd_Args());
2501 StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
2502 NetConn_Write(mysocket, buf.data, buf.cursize, &to);
2507 //=============================================================================
2509 // QuakeWorld commands
2512 =====================
2515 Send the rest of the command line over as
2516 an unconnected command.
2517 =====================
2519 static void Host_Rcon_f (void) // credit: taken from QuakeWorld
2524 lhnetsocket_t *mysocket;
2527 if (!rcon_password.string || !rcon_password.string[0])
2529 Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
2533 e = strchr(rcon_password.string, ' ');
2534 n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
2537 to = cls.netcon->peeraddress;
2540 if (!rcon_address.string[0])
2542 Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
2545 LHNETADDRESS_FromString(&to, rcon_address.string, sv_netport.integer);
2547 mysocket = NetConn_ChooseClientSocketForAddress(&to);
2548 if (mysocket && Cmd_Args()[0])
2550 // simply put together the rcon packet and send it
2551 if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
2553 if(cls.rcon_commands[cls.rcon_ringpos][0])
2556 LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
2557 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]);
2558 cls.rcon_commands[cls.rcon_ringpos][0] = 0;
2561 for (i = 0;i < MAX_RCONS;i++)
2562 if(cls.rcon_commands[i][0])
2563 if (!LHNETADDRESS_Compare(&to, &cls.rcon_addresses[i]))
2567 NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &to); // otherwise we'll request the challenge later
2568 strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
2569 cls.rcon_addresses[cls.rcon_ringpos] = to;
2570 cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
2571 cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
2573 else if(rcon_secure.integer > 0)
2577 dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
2578 memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
2579 if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, strlen(argbuf), (unsigned char *) rcon_password.string, n))
2582 strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
2583 NetConn_Write(mysocket, buf, 41 + strlen(buf + 41), &to);
2588 NetConn_WriteString(mysocket, va(vabuf, sizeof(vabuf), "\377\377\377\377rcon %.*s %s", n, rcon_password.string, Cmd_Args()), &to);
2594 ====================
2597 user <name or userid>
2599 Dump userdata / masterdata for a user
2600 ====================
2602 static void Host_User_f (void) // credit: taken from QuakeWorld
2607 if (Cmd_Argc() != 2)
2609 Con_Printf ("Usage: user <username / userid>\n");
2613 uid = atoi(Cmd_Argv(1));
2615 for (i = 0;i < cl.maxclients;i++)
2617 if (!cl.scores[i].name[0])
2619 if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
2621 InfoString_Print(cl.scores[i].qw_userinfo);
2625 Con_Printf ("User not in server.\n");
2629 ====================
2632 Dump userids for all current players
2633 ====================
2635 static void Host_Users_f (void) // credit: taken from QuakeWorld
2641 Con_Printf ("userid frags name\n");
2642 Con_Printf ("------ ----- ----\n");
2643 for (i = 0;i < cl.maxclients;i++)
2645 if (cl.scores[i].name[0])
2647 Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
2652 Con_Printf ("%i total users\n", c);
2657 Host_FullServerinfo_f
2659 Sent by server when serverinfo changes
2662 // TODO: shouldn't this be a cvar instead?
2663 static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
2666 if (Cmd_Argc() != 2)
2668 Con_Printf ("usage: fullserverinfo <complete info string>\n");
2672 strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
2673 InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
2674 cl.qw_teamplay = atoi(temp);
2681 Allow clients to change userinfo
2685 static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
2692 if (Cmd_Argc() != 2)
2694 Con_Printf ("fullinfo <complete info string>\n");
2704 while (*s && *s != '\\')
2710 Con_Printf ("MISSING VALUE\n");
2716 while (*s && *s != '\\')
2723 CL_SetInfo(key, value, false, false, false, false);
2731 Allow clients to change userinfo
2734 static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
2736 if (Cmd_Argc() == 1)
2738 InfoString_Print(cls.userinfo);
2741 if (Cmd_Argc() != 3)
2743 Con_Printf ("usage: setinfo [ <key> <value> ]\n");
2746 CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
2750 ====================
2753 packet <destination> <contents>
2755 Contents allows \n escape character
2756 ====================
2758 static void Host_Packet_f (void) // credit: taken from QuakeWorld
2764 lhnetaddress_t address;
2765 lhnetsocket_t *mysocket;
2767 if (Cmd_Argc() != 3)
2769 Con_Printf ("packet <destination> <contents>\n");
2773 if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
2775 Con_Printf ("Bad address\n");
2781 send[0] = send[1] = send[2] = send[3] = -1;
2783 l = (int)strlen (in);
2784 for (i=0 ; i<l ; i++)
2786 if (out >= send + sizeof(send) - 1)
2788 if (in[i] == '\\' && in[i+1] == 'n')
2793 else if (in[i] == '\\' && in[i+1] == '0')
2798 else if (in[i] == '\\' && in[i+1] == 't')
2803 else if (in[i] == '\\' && in[i+1] == 'r')
2808 else if (in[i] == '\\' && in[i+1] == '"')
2817 mysocket = NetConn_ChooseClientSocketForAddress(&address);
2819 mysocket = NetConn_ChooseServerSocketForAddress(&address);
2821 NetConn_Write(mysocket, send, out - send, &address);
2825 ====================
2828 Send back ping and packet loss update for all current players to this player
2829 ====================
2831 void Host_Pings_f (void)
2833 int i, j, ping, packetloss, movementloss;
2836 if (!host_client->netconnection)
2839 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2841 MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
2842 MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
2844 for (i = 0;i < svs.maxclients;i++)
2848 if (svs.clients[i].netconnection)
2850 for (j = 0;j < NETGRAPH_PACKETS;j++)
2851 if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
2853 for (j = 0;j < NETGRAPH_PACKETS;j++)
2854 if (svs.clients[i].movement_count[j] < 0)
2857 packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2858 movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
2859 ping = (int)floor(svs.clients[i].ping*1000+0.5);
2860 ping = bound(0, ping, 9999);
2861 if (sv.protocol == PROTOCOL_QUAKEWORLD)
2863 // send qw_svc_updateping and qw_svc_updatepl messages
2864 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
2865 MSG_WriteShort(&host_client->netconnection->message, ping);
2866 MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
2867 MSG_WriteByte(&host_client->netconnection->message, packetloss);
2871 // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
2873 dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
2875 dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
2876 MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
2879 if (sv.protocol != PROTOCOL_QUAKEWORLD)
2880 MSG_WriteString(&host_client->netconnection->message, "\n");
2883 static void Host_PingPLReport_f(void)
2888 if (l > cl.maxclients)
2890 for (i = 0;i < l;i++)
2892 cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
2893 cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
2894 if(errbyte && *errbyte == ',')
2895 cl.scores[i].qw_movementloss = atoi(errbyte + 1);
2897 cl.scores[i].qw_movementloss = 0;
2901 //=============================================================================
2908 void Host_InitCommands (void)
2910 dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
2912 Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
2913 Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
2914 Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
2915 Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
2916 Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
2917 Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
2918 Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
2919 Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
2920 Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
2921 Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
2922 Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
2923 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)");
2924 Cmd_AddCommand ("version", Host_Version_f, "print engine version");
2925 Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
2926 Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
2927 Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
2928 Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
2929 Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
2930 Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
2931 Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
2932 Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
2933 Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
2935 Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
2936 Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
2937 Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
2939 Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
2940 Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
2941 Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
2942 Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
2944 Cvar_RegisterVariable (&cl_name);
2945 Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
2946 Cvar_RegisterVariable (&cl_color);
2947 Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
2948 Cvar_RegisterVariable (&cl_rate);
2949 Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
2950 Cvar_RegisterVariable (&cl_pmodel);
2951 Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
2953 // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
2954 Cvar_RegisterVariable (&cl_playermodel);
2955 Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
2956 Cvar_RegisterVariable (&cl_playerskin);
2957 Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
2959 Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
2960 Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
2961 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)");
2962 Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
2964 Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
2966 Cvar_RegisterVariable (&rcon_password);
2967 Cvar_RegisterVariable (&rcon_address);
2968 Cvar_RegisterVariable (&rcon_secure);
2969 Cvar_RegisterVariable (&rcon_secure_challengetimeout);
2970 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");
2971 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");
2972 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)");
2973 Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
2974 Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
2975 Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
2976 Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
2977 Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
2978 Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
2979 Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
2980 Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
2982 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)");
2983 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)");
2985 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)");
2986 Cvar_RegisterVariable (&r_fixtrans_auto);
2988 Cvar_RegisterVariable (&team);
2989 Cvar_RegisterVariable (&skin);
2990 Cvar_RegisterVariable (&noaim);
2992 Cvar_RegisterVariable(&sv_cheats);
2993 Cvar_RegisterVariable(&sv_adminnick);
2994 Cvar_RegisterVariable(&sv_status_privacy);
2995 Cvar_RegisterVariable(&sv_status_show_qcstatus);
2996 Cvar_RegisterVariable(&sv_namechangetimer);
2999 void Host_NoOperation_f(void)