p_hud.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. // Copyright (c) ZeniMax Media Inc.
  2. // Licensed under the GNU General Public License 2.0.
  3. #include "g_local.h"
  4. /*
  5. ======================================================================
  6. INTERMISSION
  7. ======================================================================
  8. */
  9. void MoveClientToIntermission (edict_t *ent)
  10. {
  11. if (deathmatch->value || coop->value)
  12. ent->client->showscores = true;
  13. VectorCopy (level.intermission_origin, ent->s.origin);
  14. ent->client->ps.pmove.origin[0] = level.intermission_origin[0]*8;
  15. ent->client->ps.pmove.origin[1] = level.intermission_origin[1]*8;
  16. ent->client->ps.pmove.origin[2] = level.intermission_origin[2]*8;
  17. VectorCopy (level.intermission_angle, ent->client->ps.viewangles);
  18. ent->client->ps.pmove.pm_type = PM_FREEZE;
  19. ent->client->ps.gunindex = 0;
  20. ent->client->ps.blend[3] = 0;
  21. ent->client->ps.rdflags &= ~RDF_UNDERWATER;
  22. // clean up powerup info
  23. ent->client->quad_framenum = 0;
  24. ent->client->invincible_framenum = 0;
  25. ent->client->breather_framenum = 0;
  26. ent->client->enviro_framenum = 0;
  27. ent->client->grenade_blew_up = false;
  28. ent->client->grenade_time = 0;
  29. ent->client->ps.rdflags &= ~RDF_IRGOGGLES; // PGM
  30. ent->client->ir_framenum = 0; // PGM
  31. ent->client->nuke_framenum = 0; // PMM
  32. ent->client->double_framenum = 0; // PMM
  33. ent->viewheight = 0;
  34. ent->s.modelindex = 0;
  35. ent->s.modelindex2 = 0;
  36. ent->s.modelindex3 = 0;
  37. ent->s.modelindex = 0;
  38. ent->s.effects = 0;
  39. ent->s.sound = 0;
  40. ent->solid = SOLID_NOT;
  41. // add the layout
  42. if (deathmatch->value || coop->value)
  43. {
  44. DeathmatchScoreboardMessage (ent, NULL);
  45. gi.unicast (ent, true);
  46. }
  47. }
  48. void BeginIntermission (edict_t *targ)
  49. {
  50. int i, n;
  51. edict_t *ent, *client;
  52. if (level.intermissiontime)
  53. return; // already activated
  54. game.autosaved = false;
  55. // respawn any dead clients
  56. for (i=0 ; i<maxclients->value ; i++)
  57. {
  58. client = g_edicts + 1 + i;
  59. if (!client->inuse)
  60. continue;
  61. if (client->health <= 0)
  62. respawn(client);
  63. }
  64. level.intermissiontime = level.time;
  65. level.changemap = targ->map;
  66. if (strstr(level.changemap, "*"))
  67. {
  68. if (coop->value)
  69. {
  70. for (i=0 ; i<maxclients->value ; i++)
  71. {
  72. client = g_edicts + 1 + i;
  73. if (!client->inuse)
  74. continue;
  75. // strip players of all keys between units
  76. for (n = 0; n < MAX_ITEMS; n++)
  77. {
  78. if (itemlist[n].flags & IT_KEY)
  79. client->client->pers.inventory[n] = 0;
  80. }
  81. }
  82. }
  83. }
  84. else
  85. {
  86. if (!deathmatch->value)
  87. {
  88. level.exitintermission = 1; // go immediately to the next level
  89. return;
  90. }
  91. }
  92. level.exitintermission = 0;
  93. // find an intermission spot
  94. ent = G_Find (NULL, FOFS(classname), "info_player_intermission");
  95. if (!ent)
  96. { // the map creator forgot to put in an intermission point...
  97. ent = G_Find (NULL, FOFS(classname), "info_player_start");
  98. if (!ent)
  99. ent = G_Find (NULL, FOFS(classname), "info_player_deathmatch");
  100. }
  101. else
  102. { // chose one of four spots
  103. i = rand() & 3;
  104. while (i--)
  105. {
  106. ent = G_Find (ent, FOFS(classname), "info_player_intermission");
  107. if (!ent) // wrap around the list
  108. ent = G_Find (ent, FOFS(classname), "info_player_intermission");
  109. }
  110. }
  111. VectorCopy (ent->s.origin, level.intermission_origin);
  112. VectorCopy (ent->s.angles, level.intermission_angle);
  113. // move all clients to the intermission point
  114. for (i=0 ; i<maxclients->value ; i++)
  115. {
  116. client = g_edicts + 1 + i;
  117. if (!client->inuse)
  118. continue;
  119. MoveClientToIntermission (client);
  120. }
  121. }
  122. /*
  123. ==================
  124. DeathmatchScoreboardMessage
  125. ==================
  126. */
  127. void DeathmatchScoreboardMessage (edict_t *ent, edict_t *killer)
  128. {
  129. char entry[1024];
  130. char string[1400];
  131. int stringlength;
  132. int i, j, k;
  133. int sorted[MAX_CLIENTS];
  134. int sortedscores[MAX_CLIENTS];
  135. int score, total;
  136. int picnum;
  137. int x, y;
  138. gclient_t *cl;
  139. edict_t *cl_ent;
  140. char *tag;
  141. // sort the clients by score
  142. total = 0;
  143. for (i=0 ; i<game.maxclients ; i++)
  144. {
  145. cl_ent = g_edicts + 1 + i;
  146. if (!cl_ent->inuse || game.clients[i].resp.spectator)
  147. continue;
  148. score = game.clients[i].resp.score;
  149. for (j=0 ; j<total ; j++)
  150. {
  151. if (score > sortedscores[j])
  152. break;
  153. }
  154. for (k=total ; k>j ; k--)
  155. {
  156. sorted[k] = sorted[k-1];
  157. sortedscores[k] = sortedscores[k-1];
  158. }
  159. sorted[j] = i;
  160. sortedscores[j] = score;
  161. total++;
  162. }
  163. // print level name and exit rules
  164. string[0] = 0;
  165. stringlength = strlen(string);
  166. // add the clients in sorted order
  167. if (total > 12)
  168. total = 12;
  169. for (i=0 ; i<total ; i++)
  170. {
  171. cl = &game.clients[sorted[i]];
  172. cl_ent = g_edicts + 1 + sorted[i];
  173. picnum = gi.imageindex ("i_fixme");
  174. x = (i>=6) ? 160 : 0;
  175. y = 32 + 32 * (i%6);
  176. // add a dogtag
  177. if (cl_ent == ent)
  178. tag = "tag1";
  179. else if (cl_ent == killer)
  180. tag = "tag2";
  181. else
  182. tag = NULL;
  183. //===============
  184. //ROGUE
  185. // allow new DM games to override the tag picture
  186. if (gamerules && gamerules->value)
  187. {
  188. if(DMGame.DogTag)
  189. DMGame.DogTag(cl_ent, killer, &tag);
  190. }
  191. //ROGUE
  192. //===============
  193. if (tag)
  194. {
  195. Com_sprintf (entry, sizeof(entry),
  196. "xv %i yv %i picn %s ",x+32, y, tag);
  197. j = strlen(entry);
  198. if (stringlength + j > 1024)
  199. break;
  200. strcpy (string + stringlength, entry);
  201. stringlength += j;
  202. }
  203. // send the layout
  204. Com_sprintf (entry, sizeof(entry),
  205. "client %i %i %i %i %i %i ",
  206. x, y, sorted[i], cl->resp.score, cl->ping, (level.framenum - cl->resp.enterframe)/600);
  207. j = strlen(entry);
  208. if (stringlength + j > 1024)
  209. break;
  210. strcpy (string + stringlength, entry);
  211. stringlength += j;
  212. }
  213. gi.WriteByte (svc_layout);
  214. gi.WriteString (string);
  215. }
  216. /*
  217. ==================
  218. DeathmatchScoreboard
  219. Draw instead of help message.
  220. Note that it isn't that hard to overflow the 1400 byte message limit!
  221. ==================
  222. */
  223. void DeathmatchScoreboard (edict_t *ent)
  224. {
  225. DeathmatchScoreboardMessage (ent, ent->enemy);
  226. gi.unicast (ent, true);
  227. }
  228. /*
  229. ==================
  230. Cmd_Score_f
  231. Display the scoreboard
  232. ==================
  233. */
  234. void Cmd_Score_f (edict_t *ent)
  235. {
  236. ent->client->showinventory = false;
  237. ent->client->showhelp = false;
  238. if (!deathmatch->value && !coop->value)
  239. return;
  240. if (ent->client->showscores)
  241. {
  242. ent->client->showscores = false;
  243. return;
  244. }
  245. ent->client->showscores = true;
  246. DeathmatchScoreboard (ent);
  247. }
  248. /*
  249. ==================
  250. HelpComputer
  251. Draw help computer.
  252. ==================
  253. */
  254. void HelpComputer (edict_t *ent)
  255. {
  256. char string[1024];
  257. char *sk;
  258. if (skill->value == 0)
  259. sk = "easy";
  260. else if (skill->value == 1)
  261. sk = "medium";
  262. else if (skill->value == 2)
  263. sk = "hard";
  264. else
  265. sk = "hard+";
  266. // send the layout
  267. Com_sprintf (string, sizeof(string),
  268. "xv 32 yv 8 picn help " // background
  269. "xv 202 yv 12 string2 \"%s\" " // skill
  270. "xv 0 yv 24 cstring2 \"%s\" " // level name
  271. "xv 0 yv 54 cstring2 \"%s\" " // help 1
  272. "xv 0 yv 110 cstring2 \"%s\" " // help 2
  273. "xv 50 yv 164 string2 \" kills goals secrets\" "
  274. "xv 50 yv 172 string2 \"%3i/%3i %i/%i %i/%i\" ",
  275. sk,
  276. level.level_name,
  277. game.helpmessage1,
  278. game.helpmessage2,
  279. level.killed_monsters, level.total_monsters,
  280. level.found_goals, level.total_goals,
  281. level.found_secrets, level.total_secrets);
  282. gi.WriteByte (svc_layout);
  283. gi.WriteString (string);
  284. gi.unicast (ent, true);
  285. }
  286. /*
  287. ==================
  288. Cmd_Help_f
  289. Display the current help message
  290. ==================
  291. */
  292. void Cmd_Help_f (edict_t *ent)
  293. {
  294. // this is for backwards compatability
  295. if (deathmatch->value)
  296. {
  297. Cmd_Score_f (ent);
  298. return;
  299. }
  300. ent->client->showinventory = false;
  301. ent->client->showscores = false;
  302. if (ent->client->showhelp && (ent->client->pers.game_helpchanged == game.helpchanged))
  303. {
  304. ent->client->showhelp = false;
  305. return;
  306. }
  307. ent->client->showhelp = true;
  308. ent->client->pers.helpchanged = 0;
  309. HelpComputer (ent);
  310. }
  311. //=======================================================================
  312. /*
  313. ===============
  314. G_SetStats
  315. ===============
  316. */
  317. void G_SetStats (edict_t *ent)
  318. {
  319. gitem_t *item;
  320. int index, cells;
  321. int power_armor_type;
  322. //
  323. // health
  324. //
  325. ent->client->ps.stats[STAT_HEALTH_ICON] = level.pic_health;
  326. ent->client->ps.stats[STAT_HEALTH] = ent->health;
  327. //
  328. // ammo
  329. //
  330. if (!ent->client->ammo_index /* || !ent->client->pers.inventory[ent->client->ammo_index] */)
  331. {
  332. ent->client->ps.stats[STAT_AMMO_ICON] = 0;
  333. ent->client->ps.stats[STAT_AMMO] = 0;
  334. }
  335. else
  336. {
  337. item = &itemlist[ent->client->ammo_index];
  338. ent->client->ps.stats[STAT_AMMO_ICON] = gi.imageindex (item->icon);
  339. ent->client->ps.stats[STAT_AMMO] = ent->client->pers.inventory[ent->client->ammo_index];
  340. }
  341. //
  342. // armor
  343. //
  344. power_armor_type = PowerArmorType (ent);
  345. if (power_armor_type)
  346. {
  347. cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
  348. if (cells == 0)
  349. { // ran out of cells for power armor
  350. ent->flags &= ~FL_POWER_ARMOR;
  351. gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
  352. power_armor_type = 0;;
  353. }
  354. }
  355. index = ArmorIndex (ent);
  356. if (power_armor_type && (!index || (level.framenum & 8) ) )
  357. { // flash between power armor and other armor icon
  358. ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
  359. ent->client->ps.stats[STAT_ARMOR] = cells;
  360. }
  361. else if (index)
  362. {
  363. item = GetItemByIndex (index);
  364. ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
  365. ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
  366. }
  367. else
  368. {
  369. ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
  370. ent->client->ps.stats[STAT_ARMOR] = 0;
  371. }
  372. //
  373. // pickup message
  374. //
  375. if (level.time > ent->client->pickup_msg_time)
  376. {
  377. ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
  378. ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
  379. }
  380. //
  381. // timers
  382. //
  383. if (ent->client->quad_framenum > level.framenum)
  384. {
  385. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
  386. ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
  387. }
  388. // PMM
  389. else if (ent->client->double_framenum > level.framenum)
  390. {
  391. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_double");
  392. ent->client->ps.stats[STAT_TIMER] = (ent->client->double_framenum - level.framenum)/10;
  393. }
  394. // PMM
  395. else if (ent->client->invincible_framenum > level.framenum)
  396. {
  397. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
  398. ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
  399. }
  400. else if (ent->client->enviro_framenum > level.framenum)
  401. {
  402. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
  403. ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
  404. }
  405. else if (ent->client->breather_framenum > level.framenum)
  406. {
  407. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
  408. ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
  409. }
  410. // PGM
  411. else if (ent->client->owned_sphere)
  412. {
  413. if(ent->client->owned_sphere->spawnflags == 1) // defender
  414. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_defender");
  415. else if(ent->client->owned_sphere->spawnflags == 2) // hunter
  416. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_hunter");
  417. else if(ent->client->owned_sphere->spawnflags == 4) // vengeance
  418. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_vengeance");
  419. else // error case
  420. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("i_fixme");
  421. ent->client->ps.stats[STAT_TIMER] = (int)(ent->client->owned_sphere->wait - level.time);
  422. }
  423. else if (ent->client->ir_framenum > level.framenum)
  424. {
  425. ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_ir");
  426. ent->client->ps.stats[STAT_TIMER] = (ent->client->ir_framenum - level.framenum)/10;
  427. }
  428. // PGM
  429. else
  430. {
  431. ent->client->ps.stats[STAT_TIMER_ICON] = 0;
  432. ent->client->ps.stats[STAT_TIMER] = 0;
  433. }
  434. //
  435. // selected item
  436. //
  437. if (ent->client->pers.selected_item == -1)
  438. ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
  439. else
  440. ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
  441. ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
  442. //
  443. // layouts
  444. //
  445. ent->client->ps.stats[STAT_LAYOUTS] = 0;
  446. if (deathmatch->value)
  447. {
  448. if (ent->client->pers.health <= 0 || level.intermissiontime
  449. || ent->client->showscores)
  450. ent->client->ps.stats[STAT_LAYOUTS] |= 1;
  451. if (ent->client->showinventory && ent->client->pers.health > 0)
  452. ent->client->ps.stats[STAT_LAYOUTS] |= 2;
  453. }
  454. else
  455. {
  456. if (ent->client->showscores || ent->client->showhelp)
  457. ent->client->ps.stats[STAT_LAYOUTS] |= 1;
  458. if (ent->client->showinventory && ent->client->pers.health > 0)
  459. ent->client->ps.stats[STAT_LAYOUTS] |= 2;
  460. }
  461. //
  462. // frags
  463. //
  464. ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
  465. //
  466. // help icon / current weapon if not shown
  467. //
  468. if (ent->client->pers.helpchanged && (level.framenum&8) )
  469. ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
  470. else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
  471. && ent->client->pers.weapon)
  472. ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
  473. else
  474. ent->client->ps.stats[STAT_HELPICON] = 0;
  475. ent->client->ps.stats[STAT_SPECTATOR] = 0;
  476. }
  477. /*
  478. ===============
  479. G_CheckChaseStats
  480. ===============
  481. */
  482. void G_CheckChaseStats (edict_t *ent)
  483. {
  484. int i;
  485. gclient_t *cl;
  486. for (i = 1; i <= maxclients->value; i++)
  487. {
  488. cl = g_edicts[i].client;
  489. if (!g_edicts[i].inuse || cl->chase_target != ent)
  490. continue;
  491. memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
  492. G_SetSpectatorStats(g_edicts + i);
  493. }
  494. }
  495. /*
  496. ===============
  497. G_SetSpectatorStats
  498. ===============
  499. */
  500. void G_SetSpectatorStats (edict_t *ent)
  501. {
  502. gclient_t *cl = ent->client;
  503. if (!cl->chase_target)
  504. G_SetStats (ent);
  505. cl->ps.stats[STAT_SPECTATOR] = 1;
  506. // layouts are independant in spectator
  507. cl->ps.stats[STAT_LAYOUTS] = 0;
  508. if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
  509. cl->ps.stats[STAT_LAYOUTS] |= 1;
  510. if (cl->showinventory && cl->pers.health > 0)
  511. cl->ps.stats[STAT_LAYOUTS] |= 2;
  512. if (cl->chase_target && cl->chase_target->inuse)
  513. {
  514. cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
  515. (cl->chase_target - g_edicts) - 1;
  516. }
  517. else
  518. cl->ps.stats[STAT_CHASE] = 0;
  519. }