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