Quake-2

Quake 2 GPL Source Release
Log | Files | Refs

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