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