PageRenderTime 56ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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_S…

Large files files are truncated, but you can click here to view the full file