File: game\p_hud.c

    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 
  572