PageRenderTime 57ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/host_cmd.c

https://gitlab.com/xonotic/darkplaces
C | 3109 lines | 2480 code | 278 blank | 351 comment | 609 complexity | deafedcc4a2cfded86908db65dc97312 MD5 | raw file
Possible License(s): GPL-2.0
  1. /*
  2. Copyright (C) 1996-1997 Id Software, Inc.
  3. This program is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU General Public License
  5. as published by the Free Software Foundation; either version 2
  6. of the License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. See the GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. */
  15. #include "quakedef.h"
  16. #include "sv_demo.h"
  17. #include "image.h"
  18. #include "prvm_cmds.h"
  19. #include "utf8lib.h"
  20. // for secure rcon authentication
  21. #include "hmac.h"
  22. #include "mdfour.h"
  23. #include <time.h>
  24. int current_skill;
  25. cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"};
  26. cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"};
  27. cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"};
  28. 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."};
  29. 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"};
  30. 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"};
  31. 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"};
  32. 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"};
  33. cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"};
  34. cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"};
  35. cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"};
  36. cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"};
  37. 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)"};
  38. qboolean allowcheats = false;
  39. extern qboolean host_shuttingdown;
  40. extern cvar_t developer_entityparsing;
  41. /*
  42. ==================
  43. Host_Quit_f
  44. ==================
  45. */
  46. void Host_Quit_f (void)
  47. {
  48. if(host_shuttingdown)
  49. Con_Printf("shutting down already!\n");
  50. else
  51. Sys_Quit (0);
  52. }
  53. /*
  54. ==================
  55. Host_Status_f
  56. ==================
  57. */
  58. static void Host_Status_f (void)
  59. {
  60. prvm_prog_t *prog = SVVM_prog;
  61. char qcstatus[256];
  62. client_t *client;
  63. int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0;
  64. void (*print) (const char *fmt, ...);
  65. char ip[48]; // can contain a full length v6 address with [] and a port
  66. int frags;
  67. char vabuf[1024];
  68. if (cmd_source == src_command)
  69. {
  70. // if running a client, try to send over network so the client's status report parser will see the report
  71. if (cls.state == ca_connected)
  72. {
  73. Cmd_ForwardToServer ();
  74. return;
  75. }
  76. print = Con_Printf;
  77. }
  78. else
  79. print = SV_ClientPrintf;
  80. if (!sv.active)
  81. return;
  82. in = 0;
  83. if (Cmd_Argc() == 2)
  84. {
  85. if (strcmp(Cmd_Argv(1), "1") == 0)
  86. in = 1;
  87. else if (strcmp(Cmd_Argv(1), "2") == 0)
  88. in = 2;
  89. }
  90. for (players = 0, i = 0;i < svs.maxclients;i++)
  91. if (svs.clients[i].active)
  92. players++;
  93. print ("host: %s\n", Cvar_VariableString ("hostname"));
  94. print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername);
  95. print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol));
  96. print ("map: %s\n", sv.name);
  97. print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf)));
  98. print ("players: %i active (%i max)\n\n", players, svs.maxclients);
  99. if (in == 1)
  100. print ("^2IP %%pl ping time frags no name\n");
  101. else if (in == 2)
  102. print ("^5IP no name\n");
  103. for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++)
  104. {
  105. if (!client->active)
  106. continue;
  107. ++k;
  108. if (in == 0 || in == 1)
  109. {
  110. seconds = (int)(realtime - client->connecttime);
  111. minutes = seconds / 60;
  112. if (minutes)
  113. {
  114. seconds -= (minutes * 60);
  115. hours = minutes / 60;
  116. if (hours)
  117. minutes -= (hours * 60);
  118. }
  119. else
  120. hours = 0;
  121. packetloss = 0;
  122. if (client->netconnection)
  123. for (j = 0;j < NETGRAPH_PACKETS;j++)
  124. if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
  125. packetloss++;
  126. packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
  127. ping = bound(0, (int)floor(client->ping*1000+0.5), 9999);
  128. }
  129. if(sv_status_privacy.integer && cmd_source != src_command)
  130. strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48);
  131. else
  132. strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48);
  133. frags = client->frags;
  134. if(sv_status_show_qcstatus.integer)
  135. {
  136. prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1);
  137. const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus));
  138. if(str && *str)
  139. {
  140. char *p;
  141. const char *q;
  142. p = qcstatus;
  143. for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q)
  144. if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q))
  145. *p++ = *q;
  146. *p = 0;
  147. if(*qcstatus)
  148. frags = atoi(qcstatus);
  149. }
  150. }
  151. if (in == 0) // default layout
  152. {
  153. if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99)
  154. {
  155. // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output
  156. print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
  157. print (" %s\n", ip);
  158. }
  159. else
  160. {
  161. // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway...
  162. print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds);
  163. print (" %s\n", ip);
  164. }
  165. }
  166. else if (in == 1) // extended layout
  167. {
  168. 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);
  169. }
  170. else if (in == 2) // reduced layout
  171. {
  172. print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name);
  173. }
  174. }
  175. }
  176. /*
  177. ==================
  178. Host_God_f
  179. Sets client to godmode
  180. ==================
  181. */
  182. static void Host_God_f (void)
  183. {
  184. prvm_prog_t *prog = SVVM_prog;
  185. if (!allowcheats)
  186. {
  187. SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
  188. return;
  189. }
  190. PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE;
  191. if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) )
  192. SV_ClientPrint("godmode OFF\n");
  193. else
  194. SV_ClientPrint("godmode ON\n");
  195. }
  196. static void Host_Notarget_f (void)
  197. {
  198. prvm_prog_t *prog = SVVM_prog;
  199. if (!allowcheats)
  200. {
  201. SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
  202. return;
  203. }
  204. PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET;
  205. if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) )
  206. SV_ClientPrint("notarget OFF\n");
  207. else
  208. SV_ClientPrint("notarget ON\n");
  209. }
  210. qboolean noclip_anglehack;
  211. static void Host_Noclip_f (void)
  212. {
  213. prvm_prog_t *prog = SVVM_prog;
  214. if (!allowcheats)
  215. {
  216. SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
  217. return;
  218. }
  219. if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)
  220. {
  221. noclip_anglehack = true;
  222. PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP;
  223. SV_ClientPrint("noclip ON\n");
  224. }
  225. else
  226. {
  227. noclip_anglehack = false;
  228. PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
  229. SV_ClientPrint("noclip OFF\n");
  230. }
  231. }
  232. /*
  233. ==================
  234. Host_Fly_f
  235. Sets client to flymode
  236. ==================
  237. */
  238. static void Host_Fly_f (void)
  239. {
  240. prvm_prog_t *prog = SVVM_prog;
  241. if (!allowcheats)
  242. {
  243. SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
  244. return;
  245. }
  246. if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY)
  247. {
  248. PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY;
  249. SV_ClientPrint("flymode ON\n");
  250. }
  251. else
  252. {
  253. PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK;
  254. SV_ClientPrint("flymode OFF\n");
  255. }
  256. }
  257. /*
  258. ==================
  259. Host_Ping_f
  260. ==================
  261. */
  262. void Host_Pings_f (void); // called by Host_Ping_f
  263. static void Host_Ping_f (void)
  264. {
  265. int i;
  266. client_t *client;
  267. void (*print) (const char *fmt, ...);
  268. if (cmd_source == src_command)
  269. {
  270. // if running a client, try to send over network so the client's ping report parser will see the report
  271. if (cls.state == ca_connected)
  272. {
  273. Cmd_ForwardToServer ();
  274. return;
  275. }
  276. print = Con_Printf;
  277. }
  278. else
  279. print = SV_ClientPrintf;
  280. if (!sv.active)
  281. return;
  282. print("Client ping times:\n");
  283. for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
  284. {
  285. if (!client->active)
  286. continue;
  287. print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name);
  288. }
  289. // 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)
  290. // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console)
  291. //Host_Pings_f();
  292. }
  293. /*
  294. ===============================================================================
  295. SERVER TRANSITIONS
  296. ===============================================================================
  297. */
  298. /*
  299. ======================
  300. Host_Map_f
  301. handle a
  302. map <servername>
  303. command from the console. Active clients are kicked off.
  304. ======================
  305. */
  306. static void Host_Map_f (void)
  307. {
  308. char level[MAX_QPATH];
  309. if (Cmd_Argc() != 2)
  310. {
  311. Con_Print("map <levelname> : start a new game (kicks off all players)\n");
  312. return;
  313. }
  314. // GAME_DELUXEQUAKE - clear warpmark (used by QC)
  315. if (gamemode == GAME_DELUXEQUAKE)
  316. Cvar_Set("warpmark", "");
  317. cls.demonum = -1; // stop demo loop in case this fails
  318. CL_Disconnect ();
  319. Host_ShutdownServer();
  320. if(svs.maxclients != svs.maxclients_next)
  321. {
  322. svs.maxclients = svs.maxclients_next;
  323. if (svs.clients)
  324. Mem_Free(svs.clients);
  325. svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients);
  326. }
  327. #ifdef CONFIG_MENU
  328. // remove menu
  329. if (key_dest == key_menu || key_dest == key_menu_grabbed)
  330. MR_ToggleMenu(0);
  331. #endif
  332. key_dest = key_game;
  333. svs.serverflags = 0; // haven't completed an episode yet
  334. allowcheats = sv_cheats.integer != 0;
  335. strlcpy(level, Cmd_Argv(1), sizeof(level));
  336. SV_SpawnServer(level);
  337. if (sv.active && cls.state == ca_disconnected)
  338. CL_EstablishConnection("local:1", -2);
  339. }
  340. /*
  341. ==================
  342. Host_Changelevel_f
  343. Goes to a new map, taking all clients along
  344. ==================
  345. */
  346. static void Host_Changelevel_f (void)
  347. {
  348. char level[MAX_QPATH];
  349. if (Cmd_Argc() != 2)
  350. {
  351. Con_Print("changelevel <levelname> : continue game on a new level\n");
  352. return;
  353. }
  354. // HACKHACKHACK
  355. if (!sv.active) {
  356. Host_Map_f();
  357. return;
  358. }
  359. #ifdef CONFIG_MENU
  360. // remove menu
  361. if (key_dest == key_menu || key_dest == key_menu_grabbed)
  362. MR_ToggleMenu(0);
  363. #endif
  364. key_dest = key_game;
  365. SV_SaveSpawnparms ();
  366. allowcheats = sv_cheats.integer != 0;
  367. strlcpy(level, Cmd_Argv(1), sizeof(level));
  368. SV_SpawnServer(level);
  369. if (sv.active && cls.state == ca_disconnected)
  370. CL_EstablishConnection("local:1", -2);
  371. }
  372. /*
  373. ==================
  374. Host_Restart_f
  375. Restarts the current server for a dead player
  376. ==================
  377. */
  378. static void Host_Restart_f (void)
  379. {
  380. char mapname[MAX_QPATH];
  381. if (Cmd_Argc() != 1)
  382. {
  383. Con_Print("restart : restart current level\n");
  384. return;
  385. }
  386. if (!sv.active)
  387. {
  388. Con_Print("Only the server may restart\n");
  389. return;
  390. }
  391. #ifdef CONFIG_MENU
  392. // remove menu
  393. if (key_dest == key_menu || key_dest == key_menu_grabbed)
  394. MR_ToggleMenu(0);
  395. #endif
  396. key_dest = key_game;
  397. allowcheats = sv_cheats.integer != 0;
  398. strlcpy(mapname, sv.name, sizeof(mapname));
  399. SV_SpawnServer(mapname);
  400. if (sv.active && cls.state == ca_disconnected)
  401. CL_EstablishConnection("local:1", -2);
  402. }
  403. /*
  404. ==================
  405. Host_Reconnect_f
  406. This command causes the client to wait for the signon messages again.
  407. This is sent just before a server changes levels
  408. ==================
  409. */
  410. void Host_Reconnect_f (void)
  411. {
  412. char temp[128];
  413. // if not connected, reconnect to the most recent server
  414. if (!cls.netcon)
  415. {
  416. // if we have connected to a server recently, the userinfo
  417. // will still contain its IP address, so get the address...
  418. InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp));
  419. if (temp[0])
  420. CL_EstablishConnection(temp, -1);
  421. else
  422. Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n");
  423. return;
  424. }
  425. // if connected, do something based on protocol
  426. if (cls.protocol == PROTOCOL_QUAKEWORLD)
  427. {
  428. // quakeworld can just re-login
  429. if (cls.qw_downloadmemory) // don't change when downloading
  430. return;
  431. S_StopAllSounds();
  432. if (cls.state == ca_connected && cls.signon < SIGNONS)
  433. {
  434. Con_Printf("reconnecting...\n");
  435. MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
  436. MSG_WriteString(&cls.netcon->message, "new");
  437. }
  438. }
  439. else
  440. {
  441. // netquake uses reconnect on level changes (silly)
  442. if (Cmd_Argc() != 1)
  443. {
  444. Con_Print("reconnect : wait for signon messages again\n");
  445. return;
  446. }
  447. if (!cls.signon)
  448. {
  449. Con_Print("reconnect: no signon, ignoring reconnect\n");
  450. return;
  451. }
  452. cls.signon = 0; // need new connection messages
  453. }
  454. }
  455. /*
  456. =====================
  457. Host_Connect_f
  458. User command to connect to server
  459. =====================
  460. */
  461. static void Host_Connect_f (void)
  462. {
  463. if (Cmd_Argc() < 2)
  464. {
  465. Con_Print("connect <serveraddress> [<key> <value> ...]: connect to a multiplayer game\n");
  466. return;
  467. }
  468. // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command
  469. if(rcon_secure.integer <= 0)
  470. Cvar_SetQuick(&rcon_password, "");
  471. CL_EstablishConnection(Cmd_Argv(1), 2);
  472. }
  473. /*
  474. ===============================================================================
  475. LOAD / SAVE GAME
  476. ===============================================================================
  477. */
  478. #define SAVEGAME_VERSION 5
  479. void Host_Savegame_to(prvm_prog_t *prog, const char *name)
  480. {
  481. qfile_t *f;
  482. int i, k, l, numbuffers, lightstyles = 64;
  483. char comment[SAVEGAME_COMMENT_LENGTH+1];
  484. char line[MAX_INPUTLINE];
  485. qboolean isserver;
  486. char *s;
  487. // first we have to figure out if this can be saved in 64 lightstyles
  488. // (for Quake compatibility)
  489. for (i=64 ; i<MAX_LIGHTSTYLES ; i++)
  490. if (sv.lightstyles[i][0])
  491. lightstyles = i+1;
  492. isserver = prog == SVVM_prog;
  493. Con_Printf("Saving game to %s...\n", name);
  494. f = FS_OpenRealFile(name, "wb", false);
  495. if (!f)
  496. {
  497. Con_Print("ERROR: couldn't open.\n");
  498. return;
  499. }
  500. FS_Printf(f, "%i\n", SAVEGAME_VERSION);
  501. memset(comment, 0, sizeof(comment));
  502. if(isserver)
  503. 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));
  504. else
  505. dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name);
  506. // convert space to _ to make stdio happy
  507. // LordHavoc: convert control characters to _ as well
  508. for (i=0 ; i<SAVEGAME_COMMENT_LENGTH ; i++)
  509. if (ISWHITESPACEORCONTROL(comment[i]))
  510. comment[i] = '_';
  511. comment[SAVEGAME_COMMENT_LENGTH] = '\0';
  512. FS_Printf(f, "%s\n", comment);
  513. if(isserver)
  514. {
  515. for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
  516. FS_Printf(f, "%f\n", svs.clients[0].spawn_parms[i]);
  517. FS_Printf(f, "%d\n", current_skill);
  518. FS_Printf(f, "%s\n", sv.name);
  519. FS_Printf(f, "%f\n",sv.time);
  520. }
  521. else
  522. {
  523. for (i=0 ; i<NUM_SPAWN_PARMS ; i++)
  524. FS_Printf(f, "(dummy)\n");
  525. FS_Printf(f, "%d\n", 0);
  526. FS_Printf(f, "%s\n", "(dummy)");
  527. FS_Printf(f, "%f\n", realtime);
  528. }
  529. // write the light styles
  530. for (i=0 ; i<lightstyles ; i++)
  531. {
  532. if (isserver && sv.lightstyles[i][0])
  533. FS_Printf(f, "%s\n", sv.lightstyles[i]);
  534. else
  535. FS_Print(f,"m\n");
  536. }
  537. PRVM_ED_WriteGlobals (prog, f);
  538. for (i=0 ; i<prog->num_edicts ; i++)
  539. {
  540. FS_Printf(f,"// edict %d\n", i);
  541. //Con_Printf("edict %d...\n", i);
  542. PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i));
  543. }
  544. #if 1
  545. FS_Printf(f,"/*\n");
  546. FS_Printf(f,"// DarkPlaces extended savegame\n");
  547. // darkplaces extension - extra lightstyles, support for color lightstyles
  548. for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
  549. if (isserver && sv.lightstyles[i][0])
  550. FS_Printf(f, "sv.lightstyles %i %s\n", i, sv.lightstyles[i]);
  551. // darkplaces extension - model precaches
  552. for (i=1 ; i<MAX_MODELS ; i++)
  553. if (sv.model_precache[i][0])
  554. FS_Printf(f,"sv.model_precache %i %s\n", i, sv.model_precache[i]);
  555. // darkplaces extension - sound precaches
  556. for (i=1 ; i<MAX_SOUNDS ; i++)
  557. if (sv.sound_precache[i][0])
  558. FS_Printf(f,"sv.sound_precache %i %s\n", i, sv.sound_precache[i]);
  559. // darkplaces extension - save buffers
  560. numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
  561. for (i = 0; i < numbuffers; i++)
  562. {
  563. prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);
  564. if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED))
  565. {
  566. FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS);
  567. for(k = 0; k < stringbuffer->num_strings; k++)
  568. {
  569. if (!stringbuffer->strings[k])
  570. continue;
  571. // Parse the string a bit to turn special characters
  572. // (like newline, specifically) into escape codes
  573. s = stringbuffer->strings[k];
  574. for (l = 0;l < (int)sizeof(line) - 2 && *s;)
  575. {
  576. if (*s == '\n')
  577. {
  578. line[l++] = '\\';
  579. line[l++] = 'n';
  580. }
  581. else if (*s == '\r')
  582. {
  583. line[l++] = '\\';
  584. line[l++] = 'r';
  585. }
  586. else if (*s == '\\')
  587. {
  588. line[l++] = '\\';
  589. line[l++] = '\\';
  590. }
  591. else if (*s == '"')
  592. {
  593. line[l++] = '\\';
  594. line[l++] = '"';
  595. }
  596. else
  597. line[l++] = *s;
  598. s++;
  599. }
  600. line[l] = '\0';
  601. FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line);
  602. }
  603. }
  604. }
  605. FS_Printf(f,"*/\n");
  606. #endif
  607. FS_Close (f);
  608. Con_Print("done.\n");
  609. }
  610. /*
  611. ===============
  612. Host_Savegame_f
  613. ===============
  614. */
  615. static void Host_Savegame_f (void)
  616. {
  617. prvm_prog_t *prog = SVVM_prog;
  618. char name[MAX_QPATH];
  619. qboolean deadflag = false;
  620. if (!sv.active)
  621. {
  622. Con_Print("Can't save - no server running.\n");
  623. return;
  624. }
  625. deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag);
  626. if (cl.islocalgame)
  627. {
  628. // singleplayer checks
  629. if (cl.intermission)
  630. {
  631. Con_Print("Can't save in intermission.\n");
  632. return;
  633. }
  634. if (deadflag)
  635. {
  636. Con_Print("Can't savegame with a dead player\n");
  637. return;
  638. }
  639. }
  640. else
  641. 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");
  642. if (Cmd_Argc() != 2)
  643. {
  644. Con_Print("save <savename> : save a game\n");
  645. return;
  646. }
  647. if (strstr(Cmd_Argv(1), ".."))
  648. {
  649. Con_Print("Relative pathnames are not allowed.\n");
  650. return;
  651. }
  652. strlcpy (name, Cmd_Argv(1), sizeof (name));
  653. FS_DefaultExtension (name, ".sav", sizeof (name));
  654. Host_Savegame_to(prog, name);
  655. }
  656. /*
  657. ===============
  658. Host_Loadgame_f
  659. ===============
  660. */
  661. static void Host_Loadgame_f (void)
  662. {
  663. prvm_prog_t *prog = SVVM_prog;
  664. char filename[MAX_QPATH];
  665. char mapname[MAX_QPATH];
  666. float time;
  667. const char *start;
  668. const char *end;
  669. const char *t;
  670. char *text;
  671. prvm_edict_t *ent;
  672. int i, k, numbuffers;
  673. int entnum;
  674. int version;
  675. float spawn_parms[NUM_SPAWN_PARMS];
  676. prvm_stringbuffer_t *stringbuffer;
  677. if (Cmd_Argc() != 2)
  678. {
  679. Con_Print("load <savename> : load a game\n");
  680. return;
  681. }
  682. strlcpy (filename, Cmd_Argv(1), sizeof(filename));
  683. FS_DefaultExtension (filename, ".sav", sizeof (filename));
  684. Con_Printf("Loading game from %s...\n", filename);
  685. // stop playing demos
  686. if (cls.demoplayback)
  687. CL_Disconnect ();
  688. #ifdef CONFIG_MENU
  689. // remove menu
  690. if (key_dest == key_menu || key_dest == key_menu_grabbed)
  691. MR_ToggleMenu(0);
  692. #endif
  693. key_dest = key_game;
  694. cls.demonum = -1; // stop demo loop in case this fails
  695. t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL);
  696. if (!text)
  697. {
  698. Con_Print("ERROR: couldn't open.\n");
  699. return;
  700. }
  701. if(developer_entityparsing.integer)
  702. Con_Printf("Host_Loadgame_f: loading version\n");
  703. // version
  704. COM_ParseToken_Simple(&t, false, false, true);
  705. version = atoi(com_token);
  706. if (version != SAVEGAME_VERSION)
  707. {
  708. Mem_Free(text);
  709. Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION);
  710. return;
  711. }
  712. if(developer_entityparsing.integer)
  713. Con_Printf("Host_Loadgame_f: loading description\n");
  714. // description
  715. COM_ParseToken_Simple(&t, false, false, true);
  716. for (i = 0;i < NUM_SPAWN_PARMS;i++)
  717. {
  718. COM_ParseToken_Simple(&t, false, false, true);
  719. spawn_parms[i] = atof(com_token);
  720. }
  721. // skill
  722. COM_ParseToken_Simple(&t, false, false, true);
  723. // this silliness is so we can load 1.06 save files, which have float skill values
  724. current_skill = (int)(atof(com_token) + 0.5);
  725. Cvar_SetValue ("skill", (float)current_skill);
  726. if(developer_entityparsing.integer)
  727. Con_Printf("Host_Loadgame_f: loading mapname\n");
  728. // mapname
  729. COM_ParseToken_Simple(&t, false, false, true);
  730. strlcpy (mapname, com_token, sizeof(mapname));
  731. if(developer_entityparsing.integer)
  732. Con_Printf("Host_Loadgame_f: loading time\n");
  733. // time
  734. COM_ParseToken_Simple(&t, false, false, true);
  735. time = atof(com_token);
  736. allowcheats = sv_cheats.integer != 0;
  737. if(developer_entityparsing.integer)
  738. Con_Printf("Host_Loadgame_f: spawning server\n");
  739. SV_SpawnServer (mapname);
  740. if (!sv.active)
  741. {
  742. Mem_Free(text);
  743. Con_Print("Couldn't load map\n");
  744. return;
  745. }
  746. sv.paused = true; // pause until all clients connect
  747. sv.loadgame = true;
  748. if(developer_entityparsing.integer)
  749. Con_Printf("Host_Loadgame_f: loading light styles\n");
  750. // load the light styles
  751. // -1 is the globals
  752. entnum = -1;
  753. for (i = 0;i < MAX_LIGHTSTYLES;i++)
  754. {
  755. // light style
  756. start = t;
  757. COM_ParseToken_Simple(&t, false, false, true);
  758. // if this is a 64 lightstyle savegame produced by Quake, stop now
  759. // we have to check this because darkplaces may save more than 64
  760. if (com_token[0] == '{')
  761. {
  762. t = start;
  763. break;
  764. }
  765. strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
  766. }
  767. if(developer_entityparsing.integer)
  768. Con_Printf("Host_Loadgame_f: skipping until globals\n");
  769. // now skip everything before the first opening brace
  770. // (this is for forward compatibility, so that older versions (at
  771. // least ones with this fix) can load savegames with extra data before the
  772. // first brace, as might be produced by a later engine version)
  773. for (;;)
  774. {
  775. start = t;
  776. if (!COM_ParseToken_Simple(&t, false, false, true))
  777. break;
  778. if (com_token[0] == '{')
  779. {
  780. t = start;
  781. break;
  782. }
  783. }
  784. // unlink all entities
  785. World_UnlinkAll(&sv.world);
  786. // load the edicts out of the savegame file
  787. end = t;
  788. for (;;)
  789. {
  790. start = t;
  791. while (COM_ParseToken_Simple(&t, false, false, true))
  792. if (!strcmp(com_token, "}"))
  793. break;
  794. if (!COM_ParseToken_Simple(&start, false, false, true))
  795. {
  796. // end of file
  797. break;
  798. }
  799. if (strcmp(com_token,"{"))
  800. {
  801. Mem_Free(text);
  802. Host_Error ("First token isn't a brace");
  803. }
  804. if (entnum == -1)
  805. {
  806. if(developer_entityparsing.integer)
  807. Con_Printf("Host_Loadgame_f: loading globals\n");
  808. // parse the global vars
  809. PRVM_ED_ParseGlobals (prog, start);
  810. // restore the autocvar globals
  811. Cvar_UpdateAllAutoCvars();
  812. }
  813. else
  814. {
  815. // parse an edict
  816. if (entnum >= MAX_EDICTS)
  817. {
  818. Mem_Free(text);
  819. Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS);
  820. }
  821. while (entnum >= prog->max_edicts)
  822. PRVM_MEM_IncreaseEdicts(prog);
  823. ent = PRVM_EDICT_NUM(entnum);
  824. memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t));
  825. ent->priv.server->free = false;
  826. if(developer_entityparsing.integer)
  827. Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum);
  828. PRVM_ED_ParseEdict (prog, start, ent);
  829. // link it into the bsp tree
  830. if (!ent->priv.server->free)
  831. SV_LinkEdict(ent);
  832. }
  833. end = t;
  834. entnum++;
  835. }
  836. prog->num_edicts = entnum;
  837. sv.time = time;
  838. for (i = 0;i < NUM_SPAWN_PARMS;i++)
  839. svs.clients[0].spawn_parms[i] = spawn_parms[i];
  840. if(developer_entityparsing.integer)
  841. Con_Printf("Host_Loadgame_f: skipping until extended data\n");
  842. // read extended data if present
  843. // the extended data is stored inside a /* */ comment block, which the
  844. // parser intentionally skips, so we have to check for it manually here
  845. if(end)
  846. {
  847. while (*end == '\r' || *end == '\n')
  848. end++;
  849. if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n'))
  850. {
  851. if(developer_entityparsing.integer)
  852. Con_Printf("Host_Loadgame_f: loading extended data\n");
  853. Con_Printf("Loading extended DarkPlaces savegame\n");
  854. t = end + 2;
  855. memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles));
  856. memset(sv.model_precache[0], 0, sizeof(sv.model_precache));
  857. memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache));
  858. BufStr_Flush(prog);
  859. while (COM_ParseToken_Simple(&t, false, false, true))
  860. {
  861. if (!strcmp(com_token, "sv.lightstyles"))
  862. {
  863. COM_ParseToken_Simple(&t, false, false, true);
  864. i = atoi(com_token);
  865. COM_ParseToken_Simple(&t, false, false, true);
  866. if (i >= 0 && i < MAX_LIGHTSTYLES)
  867. strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i]));
  868. else
  869. Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token);
  870. }
  871. else if (!strcmp(com_token, "sv.model_precache"))
  872. {
  873. COM_ParseToken_Simple(&t, false, false, true);
  874. i = atoi(com_token);
  875. COM_ParseToken_Simple(&t, false, false, true);
  876. if (i >= 0 && i < MAX_MODELS)
  877. {
  878. strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i]));
  879. sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL);
  880. }
  881. else
  882. Con_Printf("unsupported model %i \"%s\"\n", i, com_token);
  883. }
  884. else if (!strcmp(com_token, "sv.sound_precache"))
  885. {
  886. COM_ParseToken_Simple(&t, false, false, true);
  887. i = atoi(com_token);
  888. COM_ParseToken_Simple(&t, false, false, true);
  889. if (i >= 0 && i < MAX_SOUNDS)
  890. strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i]));
  891. else
  892. Con_Printf("unsupported sound %i \"%s\"\n", i, com_token);
  893. }
  894. else if (!strcmp(com_token, "sv.buffer"))
  895. {
  896. if (COM_ParseToken_Simple(&t, false, false, true))
  897. {
  898. i = atoi(com_token);
  899. if (i >= 0)
  900. {
  901. k = STRINGBUFFER_SAVED;
  902. if (COM_ParseToken_Simple(&t, false, false, true))
  903. k |= atoi(com_token);
  904. if (!BufStr_FindCreateReplace(prog, i, k, "string"))
  905. Con_Printf("failed to create stringbuffer %i\n", i);
  906. }
  907. else
  908. Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token);
  909. }
  910. else
  911. Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n");
  912. }
  913. else if (!strcmp(com_token, "sv.bufstr"))
  914. {
  915. if (!COM_ParseToken_Simple(&t, false, false, true))
  916. Con_Printf("unexpected end of line when parsing sv.bufstr\n");
  917. else
  918. {
  919. i = atoi(com_token);
  920. stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string");
  921. if (stringbuffer)
  922. {
  923. if (COM_ParseToken_Simple(&t, false, false, true))
  924. {
  925. k = atoi(com_token);
  926. if (COM_ParseToken_Simple(&t, false, false, true))
  927. BufStr_Set(prog, stringbuffer, k, com_token);
  928. else
  929. Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n");
  930. }
  931. else
  932. Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n");
  933. }
  934. else
  935. Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token);
  936. }
  937. }
  938. // skip any trailing text or unrecognized commands
  939. while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n"))
  940. ;
  941. }
  942. }
  943. }
  944. Mem_Free(text);
  945. // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace)
  946. numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray);
  947. for (i = 0; i < numbuffers; i++)
  948. {
  949. if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) )
  950. if (stringbuffer->flags & STRINGBUFFER_TEMP)
  951. BufStr_Del(prog, stringbuffer);
  952. }
  953. if(developer_entityparsing.integer)
  954. Con_Printf("Host_Loadgame_f: finished\n");
  955. // make sure we're connected to loopback
  956. if (sv.active && cls.state == ca_disconnected)
  957. CL_EstablishConnection("local:1", -2);
  958. }
  959. //============================================================================
  960. /*
  961. ======================
  962. Host_Name_f
  963. ======================
  964. */
  965. cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"};
  966. static void Host_Name_f (void)
  967. {
  968. prvm_prog_t *prog = SVVM_prog;
  969. int i, j;
  970. qboolean valid_colors;
  971. const char *newNameSource;
  972. char newName[sizeof(host_client->name)];
  973. if (Cmd_Argc () == 1)
  974. {
  975. if (cmd_source == src_command)
  976. {
  977. Con_Printf("name: %s\n", cl_name.string);
  978. }
  979. return;
  980. }
  981. if (Cmd_Argc () == 2)
  982. newNameSource = Cmd_Argv(1);
  983. else
  984. newNameSource = Cmd_Args();
  985. strlcpy(newName, newNameSource, sizeof(newName));
  986. if (cmd_source == src_command)
  987. {
  988. Cvar_Set ("_cl_name", newName);
  989. if (strlen(newNameSource) >= sizeof(newName)) // overflowed
  990. {
  991. Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1));
  992. Con_Printf("name: %s\n", cl_name.string);
  993. }
  994. return;
  995. }
  996. if (realtime < host_client->nametime)
  997. {
  998. SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value));
  999. return;
  1000. }
  1001. host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value);
  1002. // point the string back at updateclient->name to keep it safe
  1003. strlcpy (host_client->name, newName, sizeof (host_client->name));
  1004. for (i = 0, j = 0;host_client->name[i];i++)
  1005. if (host_client->name[i] != '\r' && host_client->name[i] != '\n')
  1006. host_client->name[j++] = host_client->name[i];
  1007. host_client->name[j] = 0;
  1008. if(host_client->name[0] == 1 || host_client->name[0] == 2)
  1009. // may interfere with chat area, and will needlessly beep; so let's add a ^7
  1010. {
  1011. memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2);
  1012. host_client->name[sizeof(host_client->name) - 1] = 0;
  1013. host_client->name[0] = STRING_COLOR_TAG;
  1014. host_client->name[1] = '0' + STRING_COLOR_DEFAULT;
  1015. }
  1016. u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors);
  1017. if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string
  1018. {
  1019. size_t l;
  1020. l = strlen(host_client->name);
  1021. if(l < sizeof(host_client->name) - 1)
  1022. {
  1023. // duplicate the color tag to escape it
  1024. host_client->name[i] = STRING_COLOR_TAG;
  1025. host_client->name[i+1] = 0;
  1026. //Con_DPrintf("abuse detected, adding another trailing color tag\n");
  1027. }
  1028. else
  1029. {
  1030. // remove the last character to fix the color code
  1031. host_client->name[l-1] = 0;
  1032. //Con_DPrintf("abuse detected, removing a trailing color tag\n");
  1033. }
  1034. }
  1035. // find the last color tag offset and decide if we need to add a reset tag
  1036. for (i = 0, j = -1;host_client->name[i];i++)
  1037. {
  1038. if (host_client->name[i] == STRING_COLOR_TAG)
  1039. {
  1040. if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9')
  1041. {
  1042. j = i;
  1043. // if this happens to be a reset tag then we don't need one
  1044. if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT)
  1045. j = -1;
  1046. i++;
  1047. continue;
  1048. }
  1049. 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]))
  1050. {
  1051. j = i;
  1052. i += 4;
  1053. continue;
  1054. }
  1055. if (host_client->name[i+1] == STRING_COLOR_TAG)
  1056. {
  1057. i++;
  1058. continue;
  1059. }
  1060. }
  1061. }
  1062. // does not end in the default color string, so add it
  1063. if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2)
  1064. memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1);
  1065. PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name);
  1066. if (strcmp(host_client->old_name, host_client->name))
  1067. {
  1068. if (host_client->begun)
  1069. SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name);
  1070. strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name));
  1071. // send notification to all clients
  1072. MSG_WriteByte (&sv.reliable_datagram, svc_updatename);
  1073. MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
  1074. MSG_WriteString (&sv.reliable_datagram, host_client->name);
  1075. SV_WriteNetnameIntoDemo(host_client);
  1076. }
  1077. }
  1078. /*
  1079. ======================
  1080. Host_Playermodel_f
  1081. ======================
  1082. */
  1083. cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"};
  1084. // the old cl_playermodel in cl_main has been renamed to __cl_playermodel
  1085. static void Host_Playermodel_f (void)
  1086. {
  1087. prvm_prog_t *prog = SVVM_prog;
  1088. int i, j;
  1089. char newPath[sizeof(host_client->playermodel)];
  1090. if (Cmd_Argc () == 1)
  1091. {
  1092. if (cmd_source == src_command)
  1093. {
  1094. Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string);
  1095. }
  1096. return;
  1097. }
  1098. if (Cmd_Argc () == 2)
  1099. strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
  1100. else
  1101. strlcpy (newPath, Cmd_Args(), sizeof (newPath));
  1102. for (i = 0, j = 0;newPath[i];i++)
  1103. if (newPath[i] != '\r' && newPath[i] != '\n')
  1104. newPath[j++] = newPath[i];
  1105. newPath[j] = 0;
  1106. if (cmd_source == src_command)
  1107. {
  1108. Cvar_Set ("_cl_playermodel", newPath);
  1109. return;
  1110. }
  1111. /*
  1112. if (realtime < host_client->nametime)
  1113. {
  1114. SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
  1115. return;
  1116. }
  1117. host_client->nametime = realtime + 5;
  1118. */
  1119. // point the string back at updateclient->name to keep it safe
  1120. strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel));
  1121. PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel);
  1122. if (strcmp(host_client->old_model, host_client->playermodel))
  1123. {
  1124. strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model));
  1125. /*// send notification to all clients
  1126. MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel);
  1127. MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
  1128. MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/
  1129. }
  1130. }
  1131. /*
  1132. ======================
  1133. Host_Playerskin_f
  1134. ======================
  1135. */
  1136. cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"};
  1137. static void Host_Playerskin_f (void)
  1138. {
  1139. prvm_prog_t *prog = SVVM_prog;
  1140. int i, j;
  1141. char newPath[sizeof(host_client->playerskin)];
  1142. if (Cmd_Argc () == 1)
  1143. {
  1144. if (cmd_source == src_command)
  1145. {
  1146. Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string);
  1147. }
  1148. return;
  1149. }
  1150. if (Cmd_Argc () == 2)
  1151. strlcpy (newPath, Cmd_Argv(1), sizeof (newPath));
  1152. else
  1153. strlcpy (newPath, Cmd_Args(), sizeof (newPath));
  1154. for (i = 0, j = 0;newPath[i];i++)
  1155. if (newPath[i] != '\r' && newPath[i] != '\n')
  1156. newPath[j++] = newPath[i];
  1157. newPath[j] = 0;
  1158. if (cmd_source == src_command)
  1159. {
  1160. Cvar_Set ("_cl_playerskin", newPath);
  1161. return;
  1162. }
  1163. /*
  1164. if (realtime < host_client->nametime)
  1165. {
  1166. SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n");
  1167. return;
  1168. }
  1169. host_client->nametime = realtime + 5;
  1170. */
  1171. // point the string back at updateclient->name to keep it safe
  1172. strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin));
  1173. PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin);
  1174. if (strcmp(host_client->old_skin, host_client->playerskin))
  1175. {
  1176. //if (host_client->begun)
  1177. // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin);
  1178. strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin));
  1179. /*// send notification to all clients
  1180. MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin);
  1181. MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
  1182. MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/
  1183. }
  1184. }
  1185. static void Host_Version_f (void)
  1186. {
  1187. Con_Printf("Version: %s build %s\n", gamename, buildstring);
  1188. }
  1189. static void Host_Say(qboolean teamonly)
  1190. {
  1191. prvm_prog_t *prog = SVVM_prog;
  1192. client_t *save;
  1193. int j, quoted;
  1194. const char *p1;
  1195. char *p2;
  1196. // LordHavoc: long say messages
  1197. char text[1024];
  1198. qboolean fromServer = false;
  1199. if (cmd_source == src_command)
  1200. {
  1201. if (cls.state == ca_dedicated)
  1202. {
  1203. fromServer = true;
  1204. teamonly = false;
  1205. }
  1206. else
  1207. {
  1208. Cmd_ForwardToServer ();
  1209. return;
  1210. }
  1211. }
  1212. if (Cmd_Argc () < 2)
  1213. return;
  1214. if (!teamplay.integer)
  1215. teamonly = false;
  1216. p1 = Cmd_Args();
  1217. quoted = false;
  1218. if (*p1 == '\"')
  1219. {
  1220. quoted = true;
  1221. p1++;
  1222. }
  1223. // note this uses the chat prefix \001
  1224. if (!fromServer && !teamonly)
  1225. dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1);
  1226. else if (!fromServer && teamonly)
  1227. dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1);
  1228. else if(*(sv_adminnick.string))
  1229. dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1);
  1230. else
  1231. dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1);
  1232. p2 = text + strlen(text);
  1233. while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted)))
  1234. {
  1235. if (p2[-1] == '\"' && quoted)
  1236. quoted = false;
  1237. p2[-1] = 0;
  1238. p2--;
  1239. }
  1240. strlcat(text, "\n", sizeof(text));
  1241. // note: save is not a valid edict if fromServer is true
  1242. save = host_client;
  1243. for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++)
  1244. if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team)))
  1245. SV_ClientPrint(text);
  1246. host_client = save;
  1247. if (cls.state == ca_dedicated)
  1248. Con_Print(&text[1]);
  1249. }
  1250. static void Host_Say_f(void)
  1251. {
  1252. Host_Say(false);
  1253. }
  1254. static void Host_Say_Team_f(void)
  1255. {
  1256. Host_Say(true);
  1257. }
  1258. static void Host_Tell_f(void)
  1259. {
  1260. const char *playername_start = NULL;
  1261. size_t playername_length = 0;
  1262. int playernumber = 0;
  1263. client_t *save;
  1264. int j;
  1265. const char *p1, *p2;
  1266. char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64)
  1267. qboolean fromServer = false;
  1268. if (cmd_source == src_command)
  1269. {
  1270. if (cls.state == ca_dedicated)
  1271. fromServer = true;
  1272. else
  1273. {
  1274. Cmd_ForwardToServer ();
  1275. return;
  1276. }
  1277. }
  1278. if (Cmd_Argc () < 2)
  1279. return;
  1280. // note this uses the chat prefix \001
  1281. if (!fromServer)
  1282. dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name);
  1283. else if(*(sv_adminnick.string))
  1284. dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string);
  1285. else
  1286. dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string);
  1287. p1 = Cmd_Args();
  1288. p2 = p1 + strlen(p1);
  1289. // remove the target name
  1290. while (p1 < p2 && *p1 == ' ')
  1291. p1++;
  1292. if(*p1 == '#')
  1293. {
  1294. ++p1;
  1295. while (p1 < p2 && *p1 == ' ')
  1296. p1++;
  1297. while (p1 < p2 && isdigit(*p1))
  1298. {
  1299. playernumber = playernumber * 10 + (*p1 - '0');
  1300. p1++;
  1301. }
  1302. --playernumber;
  1303. }
  1304. else if(*p1 == '"')
  1305. {
  1306. ++p1;
  1307. playername_start = p1;
  1308. while (p1 < p2 && *p1 != '"')
  1309. p1++;
  1310. playername_length = p1 - playername_start;
  1311. if(p1 < p2)
  1312. p1++;
  1313. }
  1314. else
  1315. {
  1316. playername_start = p1;
  1317. while (p1 < p2 && *p1 != ' ')
  1318. p1++;
  1319. playername_length = p1 - playername_start;
  1320. }
  1321. while (p1 < p2 && *p1 == ' ')
  1322. p1++;
  1323. if(playername_start)
  1324. {
  1325. // set playernumber to the right client
  1326. char namebuf[128];
  1327. if(playername_length >= sizeof(namebuf))
  1328. {
  1329. if (fromServer)
  1330. Con_Print("Host_Tell: too long player name/ID\n");
  1331. else
  1332. SV_ClientPrint("Host_Tell: too long player name/ID\n");
  1333. return;
  1334. }
  1335. memcpy(namebuf, playername_start, playername_length);
  1336. namebuf[playername_length] = 0;
  1337. for (playernumber = 0; playernumber < svs.maxclients; playernumber++)
  1338. {
  1339. if (!svs.clients[playernumber].active)
  1340. continue;
  1341. if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0)
  1342. break;
  1343. }
  1344. }
  1345. if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active))
  1346. {
  1347. if (fromServer)
  1348. Con_Print("Host_Tell: invalid player name/ID\n");
  1349. else
  1350. SV_ClientPrint("Host_Tell: invalid player name/ID\n");
  1351. return;
  1352. }
  1353. // remove trailing newlines
  1354. while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
  1355. p2--;
  1356. // remove quotes if present
  1357. if (*p1 == '"')
  1358. {
  1359. p1++;
  1360. if (p2[-1] == '"')
  1361. p2--;
  1362. else if (fromServer)
  1363. Con_Print("Host_Tell: missing end quote\n");
  1364. else
  1365. SV_ClientPrint("Host_Tell: missing end quote\n");
  1366. }
  1367. while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r'))
  1368. p2--;
  1369. if(p1 == p2)
  1370. return; // empty say
  1371. for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;)
  1372. text[j++] = *p1++;
  1373. text[j++] = '\n';
  1374. text[j++] = 0;
  1375. save = host_client;
  1376. host_client = svs.clients + playernumber;
  1377. SV_ClientPrint(text);
  1378. host_client = save;
  1379. }
  1380. /*
  1381. ==================
  1382. Host_Color_f
  1383. ==================
  1384. */
  1385. cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"};
  1386. static void Host_Color(int changetop, int changebottom)
  1387. {
  1388. prvm_prog_t *prog = SVVM_prog;
  1389. int top, bottom, playercolor;
  1390. // get top and bottom either from the provided values or the current values
  1391. // (allows changing only top or bottom, or both at once)
  1392. top = changetop >= 0 ? changetop : (cl_color.integer >> 4);
  1393. bottom = changebottom >= 0 ? changebottom : cl_color.integer;
  1394. top &= 15;
  1395. bottom &= 15;
  1396. // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out
  1397. //if (top > 13)
  1398. // top = 13;
  1399. //if (bottom > 13)
  1400. // bottom = 13;
  1401. playercolor = top*16 + bottom;
  1402. if (cmd_source == src_command)
  1403. {
  1404. Cvar_SetValueQuick(&cl_color, playercolor);
  1405. return;
  1406. }
  1407. if (cls.protocol == PROTOCOL_QUAKEWORLD)
  1408. return;
  1409. if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam))
  1410. {
  1411. Con_DPrint("Calling SV_ChangeTeam\n");
  1412. prog->globals.fp[OFS_PARM0] = playercolor;
  1413. PRVM_serverglobalfloat(time) = sv.time;
  1414. PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
  1415. prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing");
  1416. }
  1417. else
  1418. {
  1419. if (host_client->edict)
  1420. {
  1421. PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor;
  1422. PRVM_serveredictfloat(host_client->edict, team) = bottom + 1;
  1423. }
  1424. host_client->colors = playercolor;
  1425. if (host_client->old_colors != host_client->colors)
  1426. {
  1427. host_client->old_colors = host_client->colors;
  1428. // send notification to all clients
  1429. MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors);
  1430. MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients);
  1431. MSG_WriteByte (&sv.reliable_datagram, host_client->colors);
  1432. }
  1433. }
  1434. }
  1435. static void Host_Color_f(void)
  1436. {
  1437. int top, bottom;
  1438. if (Cmd_Argc() == 1)
  1439. {
  1440. if (cmd_source == src_command)
  1441. {
  1442. Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15);
  1443. Con_Print("color <0-15> [0-15]\n");
  1444. }
  1445. return;
  1446. }
  1447. if (Cmd_Argc() == 2)
  1448. top = bottom = atoi(Cmd_Argv(1));
  1449. else
  1450. {
  1451. top = atoi(Cmd_Argv(1));
  1452. bottom = atoi(Cmd_Argv(2));
  1453. }
  1454. Host_Color(top, bottom);
  1455. }
  1456. static void Host_TopColor_f(void)
  1457. {
  1458. if (Cmd_Argc() == 1)
  1459. {
  1460. if (cmd_source == src_command)
  1461. {
  1462. Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15);
  1463. Con_Print("topcolor <0-15>\n");
  1464. }
  1465. return;
  1466. }
  1467. Host_Color(atoi(Cmd_Argv(1)), -1);
  1468. }
  1469. static void Host_BottomColor_f(void)
  1470. {
  1471. if (Cmd_Argc() == 1)
  1472. {
  1473. if (cmd_source == src_command)
  1474. {
  1475. Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15);
  1476. Con_Print("bottomcolor <0-15>\n");
  1477. }
  1478. return;
  1479. }
  1480. Host_Color(-1, atoi(Cmd_Argv(1)));
  1481. }
  1482. cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"};
  1483. 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)"};
  1484. static void Host_Rate_f(void)
  1485. {
  1486. int rate;
  1487. if (Cmd_Argc() != 2)
  1488. {
  1489. if (cmd_source == src_command)
  1490. {
  1491. Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer);
  1492. Con_Print("rate <bytespersecond>\n");
  1493. }
  1494. return;
  1495. }
  1496. rate = atoi(Cmd_Argv(1));
  1497. if (cmd_source == src_command)
  1498. {
  1499. Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate));
  1500. return;
  1501. }
  1502. host_client->rate = rate;
  1503. }
  1504. static void Host_Rate_BurstSize_f(void)
  1505. {
  1506. int rate_burstsize;
  1507. if (Cmd_Argc() != 2)
  1508. {
  1509. Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer);
  1510. Con_Print("rate_burstsize <bytes>\n");
  1511. return;
  1512. }
  1513. rate_burstsize = atoi(Cmd_Argv(1));
  1514. if (cmd_source == src_command)
  1515. {
  1516. Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize);
  1517. return;
  1518. }
  1519. host_client->rate_burstsize = rate_burstsize;
  1520. }
  1521. /*
  1522. ==================
  1523. Host_Kill_f
  1524. ==================
  1525. */
  1526. static void Host_Kill_f (void)
  1527. {
  1528. prvm_prog_t *prog = SVVM_prog;
  1529. if (PRVM_serveredictfloat(host_client->edict, health) <= 0)
  1530. {
  1531. SV_ClientPrint("Can't suicide -- already dead!\n");
  1532. return;
  1533. }
  1534. PRVM_serverglobalfloat(time) = sv.time;
  1535. PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
  1536. prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing");
  1537. }
  1538. /*
  1539. ==================
  1540. Host_Pause_f
  1541. ==================
  1542. */
  1543. static void Host_Pause_f (void)
  1544. {
  1545. void (*print) (const char *fmt, ...);
  1546. if (cmd_source == src_command)
  1547. {
  1548. // if running a client, try to send over network so the pause is handled by the server
  1549. if (cls.state == ca_connected)
  1550. {
  1551. Cmd_ForwardToServer ();
  1552. return;
  1553. }
  1554. print = Con_Printf;
  1555. }
  1556. else
  1557. print = SV_ClientPrintf;
  1558. if (!pausable.integer)
  1559. {
  1560. if (cmd_source == src_client)
  1561. {
  1562. if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin
  1563. {
  1564. print("Pause not allowed.\n");
  1565. return;
  1566. }
  1567. }
  1568. }
  1569. sv.paused ^= 1;
  1570. if (cmd_source != src_command)
  1571. SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un");
  1572. else if(*(sv_adminnick.string))
  1573. SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un");
  1574. else
  1575. SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un");
  1576. // send notification to all clients
  1577. MSG_WriteByte(&sv.reliable_datagram, svc_setpause);
  1578. MSG_WriteByte(&sv.reliable_datagram, sv.paused);
  1579. }
  1580. /*
  1581. ======================
  1582. Host_PModel_f
  1583. LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen.
  1584. LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility.
  1585. ======================
  1586. */
  1587. 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)"};
  1588. static void Host_PModel_f (void)
  1589. {
  1590. prvm_prog_t *prog = SVVM_prog;
  1591. int i;
  1592. if (Cmd_Argc () == 1)
  1593. {
  1594. if (cmd_source == src_command)
  1595. {
  1596. Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string);
  1597. }
  1598. return;
  1599. }
  1600. i = atoi(Cmd_Argv(1));
  1601. if (cmd_source == src_command)
  1602. {
  1603. if (cl_pmodel.integer == i)
  1604. return;
  1605. Cvar_SetValue ("_cl_pmodel", i);
  1606. if (cls.state == ca_connected)
  1607. Cmd_ForwardToServer ();
  1608. return;
  1609. }
  1610. PRVM_serveredictfloat(host_client->edict, pmodel) = i;
  1611. }
  1612. //===========================================================================
  1613. /*
  1614. ==================
  1615. Host_PreSpawn_f
  1616. ==================
  1617. */
  1618. static void Host_PreSpawn_f (void)
  1619. {
  1620. if (host_client->prespawned)
  1621. {
  1622. Con_Print("prespawn not valid -- already prespawned\n");
  1623. return;
  1624. }
  1625. host_client->prespawned = true;
  1626. if (host_client->netconnection)
  1627. {
  1628. SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize);
  1629. MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
  1630. MSG_WriteByte (&host_client->netconnection->message, 2);
  1631. host_client->sendsignon = 0; // enable unlimited sends again
  1632. }
  1633. // reset the name change timer because the client will send name soon
  1634. host_client->nametime = 0;
  1635. }
  1636. /*
  1637. ==================
  1638. Host_Spawn_f
  1639. ==================
  1640. */
  1641. static void Host_Spawn_f (void)
  1642. {
  1643. prvm_prog_t *prog = SVVM_prog;
  1644. int i;
  1645. client_t *client;
  1646. int stats[MAX_CL_STATS];
  1647. if (!host_client->prespawned)
  1648. {
  1649. Con_Print("Spawn not valid -- not yet prespawned\n");
  1650. return;
  1651. }
  1652. if (host_client->spawned)
  1653. {
  1654. Con_Print("Spawn not valid -- already spawned\n");
  1655. return;
  1656. }
  1657. host_client->spawned = true;
  1658. // reset name change timer again because they might want to change name
  1659. // again in the first 5 seconds after connecting
  1660. host_client->nametime = 0;
  1661. // LordHavoc: moved this above the QC calls at FrikaC's request
  1662. // LordHavoc: commented this out
  1663. //if (host_client->netconnection)
  1664. // SZ_Clear (&host_client->netconnection->message);
  1665. // run the entrance script
  1666. if (sv.loadgame)
  1667. {
  1668. // loaded games are fully initialized already
  1669. if (PRVM_serverfunction(RestoreGame))
  1670. {
  1671. Con_DPrint("Calling RestoreGame\n");
  1672. PRVM_serverglobalfloat(time) = sv.time;
  1673. PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
  1674. prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing");
  1675. }
  1676. }
  1677. else
  1678. {
  1679. //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);
  1680. // copy spawn parms out of the client_t
  1681. for (i=0 ; i< NUM_SPAWN_PARMS ; i++)
  1682. (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i];
  1683. // call the spawn function
  1684. host_client->clientconnectcalled = true;
  1685. PRVM_serverglobalfloat(time) = sv.time;
  1686. PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict);
  1687. prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing");
  1688. if (cls.state == ca_dedicated)
  1689. Con_Printf("%s connected\n", host_client->name);
  1690. PRVM_serverglobalfloat(time) = sv.time;
  1691. prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing");
  1692. }
  1693. if (!host_client->netconnection)
  1694. return;
  1695. // send time of update
  1696. MSG_WriteByte (&host_client->netconnection->message, svc_time);
  1697. MSG_WriteFloat (&host_client->netconnection->message, sv.time);
  1698. // send all current names, colors, and frag counts
  1699. for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++)
  1700. {
  1701. if (!client->active)
  1702. continue;
  1703. MSG_WriteByte (&host_client->netconnection->message, svc_updatename);
  1704. MSG_WriteByte (&host_client->netconnection->message, i);
  1705. MSG_WriteString (&host_client->netconnection->message, client->name);
  1706. MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags);
  1707. MSG_WriteByte (&host_client->netconnection->message, i);
  1708. MSG_WriteShort (&host_client->netconnection->message, client->frags);
  1709. MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors);
  1710. MSG_WriteByte (&host_client->netconnection->message, i);
  1711. MSG_WriteByte (&host_client->netconnection->message, client->colors);
  1712. }
  1713. // send all current light styles
  1714. for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
  1715. {
  1716. if (sv.lightstyles[i][0])
  1717. {
  1718. MSG_WriteByte (&host_client->netconnection->message, svc_lightstyle);
  1719. MSG_WriteByte (&host_client->netconnection->message, (char)i);
  1720. MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]);
  1721. }
  1722. }
  1723. // send some stats
  1724. MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
  1725. MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS);
  1726. MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets));
  1727. MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
  1728. MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS);
  1729. MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters));
  1730. MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
  1731. MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS);
  1732. MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets));
  1733. MSG_WriteByte (&host_client->netconnection->message, svc_updatestat);
  1734. MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS);
  1735. MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters));
  1736. // send a fixangle
  1737. // Never send a roll angle, because savegames can catch the server
  1738. // in a state where it is expecting the client to correct the angle
  1739. // and it won't happen if the game was just loaded, so you wind up
  1740. // with a permanent head tilt
  1741. if (sv.loadgame)
  1742. {
  1743. MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
  1744. MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol);
  1745. MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol);
  1746. MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
  1747. }
  1748. else
  1749. {
  1750. MSG_WriteByte (&host_client->netconnection->message, svc_setangle);
  1751. MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol);
  1752. MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol);
  1753. MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol);
  1754. }
  1755. SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats);
  1756. MSG_WriteByte (&host_client->netconnection->message, svc_signonnum);
  1757. MSG_WriteByte (&host_client->netconnection->message, 3);
  1758. }
  1759. /*
  1760. ==================
  1761. Host_Begin_f
  1762. ==================
  1763. */
  1764. static void Host_Begin_f (void)
  1765. {
  1766. if (!host_client->spawned)
  1767. {
  1768. Con_Print("Begin not valid -- not yet spawned\n");
  1769. return;
  1770. }
  1771. if (host_client->begun)
  1772. {
  1773. Con_Print("Begin not valid -- already begun\n");
  1774. return;
  1775. }
  1776. host_client->begun = true;
  1777. // LordHavoc: note: this code also exists in SV_DropClient
  1778. if (sv.loadgame)
  1779. {
  1780. int i;
  1781. for (i = 0;i < svs.maxclients;i++)
  1782. if (svs.clients[i].active && !svs.clients[i].spawned)
  1783. break;
  1784. if (i == svs.maxclients)
  1785. {
  1786. Con_Printf("Loaded game, everyone rejoined - unpausing\n");
  1787. sv.paused = sv.loadgame = false; // we're basically done with loading now
  1788. }
  1789. }
  1790. }
  1791. //===========================================================================
  1792. /*
  1793. ==================
  1794. Host_Kick_f
  1795. Kicks a user off of the server
  1796. ==================
  1797. */
  1798. static void Host_Kick_f (void)
  1799. {
  1800. const char *who;
  1801. const char *message = NULL;
  1802. client_t *save;
  1803. int i;
  1804. qboolean byNumber = false;
  1805. if (!sv.active)
  1806. return;
  1807. save = host_client;
  1808. if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0)
  1809. {
  1810. i = (int)(atof(Cmd_Argv(2)) - 1);
  1811. if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active)
  1812. return;
  1813. byNumber = true;
  1814. }
  1815. else
  1816. {
  1817. for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++)
  1818. {
  1819. if (!host_client->active)
  1820. continue;
  1821. if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0)
  1822. break;
  1823. }
  1824. }
  1825. if (i < svs.maxclients)
  1826. {
  1827. if (cmd_source == src_command)
  1828. {
  1829. if (cls.state == ca_dedicated)
  1830. who = "Console";
  1831. else
  1832. who = cl_name.string;
  1833. }
  1834. else
  1835. who = save->name;
  1836. // can't kick yourself!
  1837. if (host_client == save)
  1838. return;
  1839. if (Cmd_Argc() > 2)
  1840. {
  1841. message = Cmd_Args();
  1842. COM_ParseToken_Simple(&message, false, false, true);
  1843. if (byNumber)
  1844. {
  1845. message++; // skip the #
  1846. while (*message == ' ') // skip white space
  1847. message++;
  1848. message += strlen(Cmd_Argv(2)); // skip the number
  1849. }
  1850. while (*message && *message == ' ')
  1851. message++;
  1852. }
  1853. if (message)
  1854. SV_ClientPrintf("Kicked by %s: %s\n", who, message);
  1855. else
  1856. SV_ClientPrintf("Kicked by %s\n", who);
  1857. SV_DropClient (false); // kicked
  1858. }
  1859. host_client = save;
  1860. }
  1861. /*
  1862. ===============================================================================
  1863. DEBUGGING TOOLS
  1864. ===============================================================================
  1865. */
  1866. /*
  1867. ==================
  1868. Host_Give_f
  1869. ==================
  1870. */
  1871. static void Host_Give_f (void)
  1872. {
  1873. prvm_prog_t *prog = SVVM_prog;
  1874. const char *t;
  1875. int v;
  1876. if (!allowcheats)
  1877. {
  1878. SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n");
  1879. return;
  1880. }
  1881. t = Cmd_Argv(1);
  1882. v = atoi (Cmd_Argv(2));
  1883. switch (t[0])
  1884. {
  1885. case '0':
  1886. case '1':
  1887. case '2':
  1888. case '3':
  1889. case '4':
  1890. case '5':
  1891. case '6':
  1892. case '7':
  1893. case '8':
  1894. case '9':
  1895. // MED 01/04/97 added hipnotic give stuff
  1896. if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH)
  1897. {
  1898. if (t[0] == '6')
  1899. {
  1900. if (t[1] == 'a')
  1901. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN;
  1902. else
  1903. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER;
  1904. }
  1905. else if (t[0] == '9')
  1906. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON;
  1907. else if (t[0] == '0')
  1908. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR;
  1909. else if (t[0] >= '2')
  1910. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
  1911. }
  1912. else
  1913. {
  1914. if (t[0] >= '2')
  1915. PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2'));
  1916. }
  1917. break;
  1918. case 's':
  1919. if (gamemode == GAME_ROGUE)
  1920. PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v;
  1921. PRVM_serveredictfloat(host_client->edict, ammo_shells) = v;
  1922. break;
  1923. case 'n':
  1924. if (gamemode == GAME_ROGUE)
  1925. {
  1926. PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v;
  1927. if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
  1928. PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
  1929. }
  1930. else
  1931. {
  1932. PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
  1933. }
  1934. break;
  1935. case 'l':
  1936. if (gamemode == GAME_ROGUE)
  1937. {
  1938. PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v;
  1939. if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
  1940. PRVM_serveredictfloat(host_client->edict, ammo_nails) = v;
  1941. }
  1942. break;
  1943. case 'r':
  1944. if (gamemode == GAME_ROGUE)
  1945. {
  1946. PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v;
  1947. if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
  1948. PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
  1949. }
  1950. else
  1951. {
  1952. PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
  1953. }
  1954. break;
  1955. case 'm':
  1956. if (gamemode == GAME_ROGUE)
  1957. {
  1958. PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v;
  1959. if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
  1960. PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v;
  1961. }
  1962. break;
  1963. case 'h':
  1964. PRVM_serveredictfloat(host_client->edict, health) = v;
  1965. break;
  1966. case 'c':
  1967. if (gamemode == GAME_ROGUE)
  1968. {
  1969. PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v;
  1970. if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING)
  1971. PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
  1972. }
  1973. else
  1974. {
  1975. PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
  1976. }
  1977. break;
  1978. case 'p':
  1979. if (gamemode == GAME_ROGUE)
  1980. {
  1981. PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v;
  1982. if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING)
  1983. PRVM_serveredictfloat(host_client->edict, ammo_cells) = v;
  1984. }
  1985. break;
  1986. }
  1987. }
  1988. static prvm_edict_t *FindViewthing(prvm_prog_t *prog)
  1989. {
  1990. int i;
  1991. prvm_edict_t *e;
  1992. for (i=0 ; i<prog->num_edicts ; i++)
  1993. {
  1994. e = PRVM_EDICT_NUM(i);
  1995. if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing"))
  1996. return e;
  1997. }
  1998. Con_Print("No viewthing on map\n");
  1999. return NULL;
  2000. }
  2001. /*
  2002. ==================
  2003. Host_Viewmodel_f
  2004. ==================
  2005. */
  2006. static void Host_Viewmodel_f (void)
  2007. {
  2008. prvm_prog_t *prog = SVVM_prog;
  2009. prvm_edict_t *e;
  2010. dp_model_t *m;
  2011. if (!sv.active)
  2012. return;
  2013. e = FindViewthing(prog);
  2014. if (e)
  2015. {
  2016. m = Mod_ForName (Cmd_Argv(1), false, true, NULL);
  2017. if (m && m->loaded && m->Draw)
  2018. {
  2019. PRVM_serveredictfloat(e, frame) = 0;
  2020. cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m;
  2021. }
  2022. else
  2023. Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1));
  2024. }
  2025. }
  2026. /*
  2027. ==================
  2028. Host_Viewframe_f
  2029. ==================
  2030. */
  2031. static void Host_Viewframe_f (void)
  2032. {
  2033. prvm_prog_t *prog = SVVM_prog;
  2034. prvm_edict_t *e;
  2035. int f;
  2036. dp_model_t *m;
  2037. if (!sv.active)
  2038. return;
  2039. e = FindViewthing(prog);
  2040. if (e)
  2041. {
  2042. m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
  2043. f = atoi(Cmd_Argv(1));
  2044. if (f >= m->numframes)
  2045. f = m->numframes-1;
  2046. PRVM_serveredictfloat(e, frame) = f;
  2047. }
  2048. }
  2049. static void PrintFrameName (dp_model_t *m, int frame)
  2050. {
  2051. if (m->animscenes)
  2052. Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name);
  2053. else
  2054. Con_Printf("frame %i\n", frame);
  2055. }
  2056. /*
  2057. ==================
  2058. Host_Viewnext_f
  2059. ==================
  2060. */
  2061. static void Host_Viewnext_f (void)
  2062. {
  2063. prvm_prog_t *prog = SVVM_prog;
  2064. prvm_edict_t *e;
  2065. dp_model_t *m;
  2066. if (!sv.active)
  2067. return;
  2068. e = FindViewthing(prog);
  2069. if (e)
  2070. {
  2071. m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
  2072. PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1;
  2073. if (PRVM_serveredictfloat(e, frame) >= m->numframes)
  2074. PRVM_serveredictfloat(e, frame) = m->numframes - 1;
  2075. PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
  2076. }
  2077. }
  2078. /*
  2079. ==================
  2080. Host_Viewprev_f
  2081. ==================
  2082. */
  2083. static void Host_Viewprev_f (void)
  2084. {
  2085. prvm_prog_t *prog = SVVM_prog;
  2086. prvm_edict_t *e;
  2087. dp_model_t *m;
  2088. if (!sv.active)
  2089. return;
  2090. e = FindViewthing(prog);
  2091. if (e)
  2092. {
  2093. m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)];
  2094. PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1;
  2095. if (PRVM_serveredictfloat(e, frame) < 0)
  2096. PRVM_serveredictfloat(e, frame) = 0;
  2097. PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame));
  2098. }
  2099. }
  2100. /*
  2101. ===============================================================================
  2102. DEMO LOOP CONTROL
  2103. ===============================================================================
  2104. */
  2105. /*
  2106. ==================
  2107. Host_Startdemos_f
  2108. ==================
  2109. */
  2110. static void Host_Startdemos_f (void)
  2111. {
  2112. int i, c;
  2113. if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
  2114. return;
  2115. c = Cmd_Argc() - 1;
  2116. if (c > MAX_DEMOS)
  2117. {
  2118. Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS);
  2119. c = MAX_DEMOS;
  2120. }
  2121. Con_DPrintf("%i demo(s) in loop\n", c);
  2122. for (i=1 ; i<c+1 ; i++)
  2123. strlcpy (cls.demos[i-1], Cmd_Argv(i), sizeof (cls.demos[i-1]));
  2124. // LordHavoc: clear the remaining slots
  2125. for (;i <= MAX_DEMOS;i++)
  2126. cls.demos[i-1][0] = 0;
  2127. if (!sv.active && cls.demonum != -1 && !cls.demoplayback)
  2128. {
  2129. cls.demonum = 0;
  2130. CL_NextDemo ();
  2131. }
  2132. else
  2133. cls.demonum = -1;
  2134. }
  2135. /*
  2136. ==================
  2137. Host_Demos_f
  2138. Return to looping demos
  2139. ==================
  2140. */
  2141. static void Host_Demos_f (void)
  2142. {
  2143. if (cls.state == ca_dedicated)
  2144. return;
  2145. if (cls.demonum == -1)
  2146. cls.demonum = 1;
  2147. CL_Disconnect_f ();
  2148. CL_NextDemo ();
  2149. }
  2150. /*
  2151. ==================
  2152. Host_Stopdemo_f
  2153. Return to looping demos
  2154. ==================
  2155. */
  2156. static void Host_Stopdemo_f (void)
  2157. {
  2158. if (!cls.demoplayback)
  2159. return;
  2160. CL_Disconnect ();
  2161. Host_ShutdownServer ();
  2162. }
  2163. static void Host_SendCvar_f (void)
  2164. {
  2165. int i;
  2166. cvar_t *c;
  2167. const char *cvarname;
  2168. client_t *old;
  2169. char vabuf[1024];
  2170. if(Cmd_Argc() != 2)
  2171. return;
  2172. cvarname = Cmd_Argv(1);
  2173. if (cls.state == ca_connected)
  2174. {
  2175. c = Cvar_FindVar(cvarname);
  2176. // LordHavoc: if there is no such cvar or if it is private, send a
  2177. // reply indicating that it has no value
  2178. if(!c || (c->flags & CVAR_PRIVATE))
  2179. Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname));
  2180. else
  2181. Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string));
  2182. return;
  2183. }
  2184. if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand))
  2185. return;
  2186. old = host_client;
  2187. if (cls.state != ca_dedicated)
  2188. i = 1;
  2189. else
  2190. i = 0;
  2191. for(;i<svs.maxclients;i++)
  2192. if(svs.clients[i].active && svs.clients[i].netconnection)
  2193. {
  2194. host_client = &svs.clients[i];
  2195. Host_ClientCommands("sendcvar %s\n", cvarname);
  2196. }
  2197. host_client = old;
  2198. }
  2199. static void MaxPlayers_f(void)
  2200. {
  2201. int n;
  2202. if (Cmd_Argc() != 2)
  2203. {
  2204. Con_Printf("\"maxplayers\" is \"%u\"\n", svs.maxclients_next);
  2205. return;
  2206. }
  2207. if (sv.active)
  2208. {
  2209. Con_Print("maxplayers can not be changed while a server is running.\n");
  2210. Con_Print("It will be changed on next server startup (\"map\" command).\n");
  2211. }
  2212. n = atoi(Cmd_Argv(1));
  2213. n = bound(1, n, MAX_SCOREBOARD);
  2214. Con_Printf("\"maxplayers\" set to \"%u\"\n", n);
  2215. svs.maxclients_next = n;
  2216. if (n == 1)
  2217. Cvar_Set ("deathmatch", "0");
  2218. else
  2219. Cvar_Set ("deathmatch", "1");
  2220. }
  2221. /*
  2222. =====================
  2223. Host_PQRcon_f
  2224. ProQuake rcon support
  2225. =====================
  2226. */
  2227. static void Host_PQRcon_f (void)
  2228. {
  2229. int n;
  2230. const char *e;
  2231. lhnetsocket_t *mysocket;
  2232. if (Cmd_Argc() == 1)
  2233. {
  2234. Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
  2235. return;
  2236. }
  2237. if (!rcon_password.string || !rcon_password.string[0] || rcon_secure.integer > 0)
  2238. {
  2239. Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n");
  2240. return;
  2241. }
  2242. e = strchr(rcon_password.string, ' ');
  2243. n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
  2244. if (cls.netcon)
  2245. cls.rcon_address = cls.netcon->peeraddress;
  2246. else
  2247. {
  2248. if (!rcon_address.string[0])
  2249. {
  2250. Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
  2251. return;
  2252. }
  2253. LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
  2254. }
  2255. mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
  2256. if (mysocket)
  2257. {
  2258. sizebuf_t buf;
  2259. unsigned char bufdata[64];
  2260. buf.data = bufdata;
  2261. SZ_Clear(&buf);
  2262. MSG_WriteLong(&buf, 0);
  2263. MSG_WriteByte(&buf, CCREQ_RCON);
  2264. SZ_Write(&buf, (const unsigned char*)rcon_password.string, n);
  2265. MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string
  2266. MSG_WriteString(&buf, Cmd_Args());
  2267. StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK));
  2268. NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address);
  2269. SZ_Clear(&buf);
  2270. }
  2271. }
  2272. //=============================================================================
  2273. // QuakeWorld commands
  2274. /*
  2275. =====================
  2276. Host_Rcon_f
  2277. Send the rest of the command line over as
  2278. an unconnected command.
  2279. =====================
  2280. */
  2281. static void Host_Rcon_f (void) // credit: taken from QuakeWorld
  2282. {
  2283. int i, n;
  2284. const char *e;
  2285. lhnetsocket_t *mysocket;
  2286. if (Cmd_Argc() == 1)
  2287. {
  2288. Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0));
  2289. return;
  2290. }
  2291. if (!rcon_password.string || !rcon_password.string[0])
  2292. {
  2293. Con_Printf ("You must set rcon_password before issuing an rcon command.\n");
  2294. return;
  2295. }
  2296. e = strchr(rcon_password.string, ' ');
  2297. n = e ? e-rcon_password.string : (int)strlen(rcon_password.string);
  2298. if (cls.netcon)
  2299. cls.rcon_address = cls.netcon->peeraddress;
  2300. else
  2301. {
  2302. if (!rcon_address.string[0])
  2303. {
  2304. Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n");
  2305. return;
  2306. }
  2307. LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer);
  2308. }
  2309. mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address);
  2310. if (mysocket && Cmd_Args()[0])
  2311. {
  2312. // simply put together the rcon packet and send it
  2313. if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1)
  2314. {
  2315. if(cls.rcon_commands[cls.rcon_ringpos][0])
  2316. {
  2317. char s[128];
  2318. LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true);
  2319. 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]);
  2320. cls.rcon_commands[cls.rcon_ringpos][0] = 0;
  2321. --cls.rcon_trying;
  2322. }
  2323. for (i = 0;i < MAX_RCONS;i++)
  2324. if(cls.rcon_commands[i][0])
  2325. if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i]))
  2326. break;
  2327. ++cls.rcon_trying;
  2328. if(i >= MAX_RCONS)
  2329. NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later
  2330. strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos]));
  2331. cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address;
  2332. cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value;
  2333. cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS;
  2334. }
  2335. else if(rcon_secure.integer > 0)
  2336. {
  2337. char buf[1500];
  2338. char argbuf[1500];
  2339. dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args());
  2340. memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24);
  2341. if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n))
  2342. {
  2343. buf[40] = ' ';
  2344. strlcpy(buf + 41, argbuf, sizeof(buf) - 41);
  2345. NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address);
  2346. }
  2347. }
  2348. else
  2349. {
  2350. char buf[1500];
  2351. memcpy(buf, "\377\377\377\377", 4);
  2352. dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args());
  2353. NetConn_WriteString(mysocket, buf, &cls.rcon_address);
  2354. }
  2355. }
  2356. }
  2357. /*
  2358. ====================
  2359. Host_User_f
  2360. user <name or userid>
  2361. Dump userdata / masterdata for a user
  2362. ====================
  2363. */
  2364. static void Host_User_f (void) // credit: taken from QuakeWorld
  2365. {
  2366. int uid;
  2367. int i;
  2368. if (Cmd_Argc() != 2)
  2369. {
  2370. Con_Printf ("Usage: user <username / userid>\n");
  2371. return;
  2372. }
  2373. uid = atoi(Cmd_Argv(1));
  2374. for (i = 0;i < cl.maxclients;i++)
  2375. {
  2376. if (!cl.scores[i].name[0])
  2377. continue;
  2378. if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1)))
  2379. {
  2380. InfoString_Print(cl.scores[i].qw_userinfo);
  2381. return;
  2382. }
  2383. }
  2384. Con_Printf ("User not in server.\n");
  2385. }
  2386. /*
  2387. ====================
  2388. Host_Users_f
  2389. Dump userids for all current players
  2390. ====================
  2391. */
  2392. static void Host_Users_f (void) // credit: taken from QuakeWorld
  2393. {
  2394. int i;
  2395. int c;
  2396. c = 0;
  2397. Con_Printf ("userid frags name\n");
  2398. Con_Printf ("------ ----- ----\n");
  2399. for (i = 0;i < cl.maxclients;i++)
  2400. {
  2401. if (cl.scores[i].name[0])
  2402. {
  2403. Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name);
  2404. c++;
  2405. }
  2406. }
  2407. Con_Printf ("%i total users\n", c);
  2408. }
  2409. /*
  2410. ==================
  2411. Host_FullServerinfo_f
  2412. Sent by server when serverinfo changes
  2413. ==================
  2414. */
  2415. // TODO: shouldn't this be a cvar instead?
  2416. static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
  2417. {
  2418. char temp[512];
  2419. if (Cmd_Argc() != 2)
  2420. {
  2421. Con_Printf ("usage: fullserverinfo <complete info string>\n");
  2422. return;
  2423. }
  2424. strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
  2425. InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
  2426. cl.qw_teamplay = atoi(temp);
  2427. }
  2428. /*
  2429. ==================
  2430. Host_FullInfo_f
  2431. Allow clients to change userinfo
  2432. ==================
  2433. Casey was here :)
  2434. */
  2435. static void Host_FullInfo_f (void) // credit: taken from QuakeWorld
  2436. {
  2437. char key[512];
  2438. char value[512];
  2439. const char *s;
  2440. if (Cmd_Argc() != 2)
  2441. {
  2442. Con_Printf ("fullinfo <complete info string>\n");
  2443. return;
  2444. }
  2445. s = Cmd_Argv(1);
  2446. if (*s == '\\')
  2447. s++;
  2448. while (*s)
  2449. {
  2450. size_t len = strcspn(s, "\\");
  2451. if (len >= sizeof(key)) {
  2452. len = sizeof(key) - 1;
  2453. }
  2454. strlcpy(key, s, len + 1);
  2455. s += len;
  2456. if (!*s)
  2457. {
  2458. Con_Printf ("MISSING VALUE\n");
  2459. return;
  2460. }
  2461. ++s; // Skip over backslash.
  2462. len = strcspn(s, "\\");
  2463. if (len >= sizeof(value)) {
  2464. len = sizeof(value) - 1;
  2465. }
  2466. strlcpy(value, s, len + 1);
  2467. CL_SetInfo(key, value, false, false, false, false);
  2468. s += len;
  2469. if (!*s)
  2470. {
  2471. break;
  2472. }
  2473. ++s; // Skip over backslash.
  2474. }
  2475. }
  2476. /*
  2477. ==================
  2478. CL_SetInfo_f
  2479. Allow clients to change userinfo
  2480. ==================
  2481. */
  2482. static void Host_SetInfo_f (void) // credit: taken from QuakeWorld
  2483. {
  2484. if (Cmd_Argc() == 1)
  2485. {
  2486. InfoString_Print(cls.userinfo);
  2487. return;
  2488. }
  2489. if (Cmd_Argc() != 3)
  2490. {
  2491. Con_Printf ("usage: setinfo [ <key> <value> ]\n");
  2492. return;
  2493. }
  2494. CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false);
  2495. }
  2496. /*
  2497. ====================
  2498. Host_Packet_f
  2499. packet <destination> <contents>
  2500. Contents allows \n escape character
  2501. ====================
  2502. */
  2503. static void Host_Packet_f (void) // credit: taken from QuakeWorld
  2504. {
  2505. char send[2048];
  2506. int i, l;
  2507. const char *in;
  2508. char *out;
  2509. lhnetaddress_t address;
  2510. lhnetsocket_t *mysocket;
  2511. if (Cmd_Argc() != 3)
  2512. {
  2513. Con_Printf ("packet <destination> <contents>\n");
  2514. return;
  2515. }
  2516. if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer))
  2517. {
  2518. Con_Printf ("Bad address\n");
  2519. return;
  2520. }
  2521. in = Cmd_Argv(2);
  2522. out = send+4;
  2523. send[0] = send[1] = send[2] = send[3] = -1;
  2524. l = (int)strlen (in);
  2525. for (i=0 ; i<l ; i++)
  2526. {
  2527. if (out >= send + sizeof(send) - 1)
  2528. break;
  2529. if (in[i] == '\\' && in[i+1] == 'n')
  2530. {
  2531. *out++ = '\n';
  2532. i++;
  2533. }
  2534. else if (in[i] == '\\' && in[i+1] == '0')
  2535. {
  2536. *out++ = '\0';
  2537. i++;
  2538. }
  2539. else if (in[i] == '\\' && in[i+1] == 't')
  2540. {
  2541. *out++ = '\t';
  2542. i++;
  2543. }
  2544. else if (in[i] == '\\' && in[i+1] == 'r')
  2545. {
  2546. *out++ = '\r';
  2547. i++;
  2548. }
  2549. else if (in[i] == '\\' && in[i+1] == '"')
  2550. {
  2551. *out++ = '\"';
  2552. i++;
  2553. }
  2554. else
  2555. *out++ = in[i];
  2556. }
  2557. mysocket = NetConn_ChooseClientSocketForAddress(&address);
  2558. if (!mysocket)
  2559. mysocket = NetConn_ChooseServerSocketForAddress(&address);
  2560. if (mysocket)
  2561. NetConn_Write(mysocket, send, out - send, &address);
  2562. }
  2563. /*
  2564. ====================
  2565. Host_Pings_f
  2566. Send back ping and packet loss update for all current players to this player
  2567. ====================
  2568. */
  2569. void Host_Pings_f (void)
  2570. {
  2571. int i, j, ping, packetloss, movementloss;
  2572. char temp[128];
  2573. if (!host_client->netconnection)
  2574. return;
  2575. if (sv.protocol != PROTOCOL_QUAKEWORLD)
  2576. {
  2577. MSG_WriteByte(&host_client->netconnection->message, svc_stufftext);
  2578. MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport");
  2579. }
  2580. for (i = 0;i < svs.maxclients;i++)
  2581. {
  2582. packetloss = 0;
  2583. movementloss = 0;
  2584. if (svs.clients[i].netconnection)
  2585. {
  2586. for (j = 0;j < NETGRAPH_PACKETS;j++)
  2587. if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET)
  2588. packetloss++;
  2589. for (j = 0;j < NETGRAPH_PACKETS;j++)
  2590. if (svs.clients[i].movement_count[j] < 0)
  2591. movementloss++;
  2592. }
  2593. packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
  2594. movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS;
  2595. ping = (int)floor(svs.clients[i].ping*1000+0.5);
  2596. ping = bound(0, ping, 9999);
  2597. if (sv.protocol == PROTOCOL_QUAKEWORLD)
  2598. {
  2599. // send qw_svc_updateping and qw_svc_updatepl messages
  2600. MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping);
  2601. MSG_WriteShort(&host_client->netconnection->message, ping);
  2602. MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl);
  2603. MSG_WriteByte(&host_client->netconnection->message, packetloss);
  2604. }
  2605. else
  2606. {
  2607. // write the string into the packet as multiple unterminated strings to avoid needing a local buffer
  2608. if(movementloss)
  2609. dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss);
  2610. else
  2611. dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss);
  2612. MSG_WriteUnterminatedString(&host_client->netconnection->message, temp);
  2613. }
  2614. }
  2615. if (sv.protocol != PROTOCOL_QUAKEWORLD)
  2616. MSG_WriteString(&host_client->netconnection->message, "\n");
  2617. }
  2618. static void Host_PingPLReport_f(void)
  2619. {
  2620. char *errbyte;
  2621. int i;
  2622. int l = Cmd_Argc();
  2623. if (l > cl.maxclients)
  2624. l = cl.maxclients;
  2625. for (i = 0;i < l;i++)
  2626. {
  2627. cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2));
  2628. cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0);
  2629. if(errbyte && *errbyte == ',')
  2630. cl.scores[i].qw_movementloss = atoi(errbyte + 1);
  2631. else
  2632. cl.scores[i].qw_movementloss = 0;
  2633. }
  2634. }
  2635. //=============================================================================
  2636. /*
  2637. ==================
  2638. Host_InitCommands
  2639. ==================
  2640. */
  2641. void Host_InitCommands (void)
  2642. {
  2643. dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp");
  2644. Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information");
  2645. Cmd_AddCommand ("quit", Host_Quit_f, "quit the game");
  2646. Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)");
  2647. Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)");
  2648. Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)");
  2649. Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)");
  2650. Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory");
  2651. Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level");
  2652. Cmd_AddCommand ("restart", Host_Restart_f, "restart current level");
  2653. Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients");
  2654. Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname");
  2655. 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)");
  2656. Cmd_AddCommand ("version", Host_Version_f, "print engine version");
  2657. Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server");
  2658. Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server");
  2659. Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server");
  2660. Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly");
  2661. Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)");
  2662. Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name");
  2663. Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server");
  2664. Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file");
  2665. Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file");
  2666. Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)");
  2667. Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command");
  2668. Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos");
  2669. Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level");
  2670. Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level");
  2671. Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level");
  2672. Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level");
  2673. Cvar_RegisterVariable (&cl_name);
  2674. Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name");
  2675. Cvar_RegisterVariable (&cl_color);
  2676. Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors");
  2677. Cvar_RegisterVariable (&cl_rate);
  2678. Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed");
  2679. Cvar_RegisterVariable (&cl_rate_burstsize);
  2680. Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed");
  2681. Cvar_RegisterVariable (&cl_pmodel);
  2682. Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice");
  2683. // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first)
  2684. Cvar_RegisterVariable (&cl_playermodel);
  2685. Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model");
  2686. Cvar_RegisterVariable (&cl_playerskin);
  2687. Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number");
  2688. Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)");
  2689. Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)");
  2690. 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)");
  2691. Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once");
  2692. Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC");
  2693. Cvar_RegisterVariable (&rcon_password);
  2694. Cvar_RegisterVariable (&rcon_address);
  2695. Cvar_RegisterVariable (&rcon_secure);
  2696. Cvar_RegisterVariable (&rcon_secure_challengetimeout);
  2697. 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");
  2698. 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");
  2699. 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)");
  2700. Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard");
  2701. Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard");
  2702. Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string");
  2703. Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo");
  2704. Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo");
  2705. Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string");
  2706. Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color");
  2707. Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color");
  2708. 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)");
  2709. 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)");
  2710. 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)");
  2711. Cvar_RegisterVariable (&r_fixtrans_auto);
  2712. Cvar_RegisterVariable (&team);
  2713. Cvar_RegisterVariable (&skin);
  2714. Cvar_RegisterVariable (&noaim);
  2715. Cvar_RegisterVariable(&sv_cheats);
  2716. Cvar_RegisterVariable(&sv_adminnick);
  2717. Cvar_RegisterVariable(&sv_status_privacy);
  2718. Cvar_RegisterVariable(&sv_status_show_qcstatus);
  2719. Cvar_RegisterVariable(&sv_namechangetimer);
  2720. }
  2721. void Host_NoOperation_f(void)
  2722. {
  2723. }