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->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 
BeginIntermission(edict_t * targ)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 */
DeathmatchScoreboardMessage(edict_t * ent,edict_t * killer)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 */
DeathmatchScoreboard(edict_t * ent)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 */
Cmd_Score_f(edict_t * ent)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 */
HelpComputer(edict_t * ent)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 */
Cmd_Help_f(edict_t * ent)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 */
G_SetStats(edict_t * ent)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 	cells = 0;
405 
406 	//
407 	// armor
408 	//
409 	power_armor_type = PowerArmorType (ent);
410 	if (power_armor_type)
411 	{
412 		cells = ent->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
413 		if (cells == 0)
414 		{	// ran out of cells for power armor
415 			ent->flags &= ~FL_POWER_ARMOR;
416 			gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0);
417 			power_armor_type = 0;;
418 		}
419 	}
420 
421 	index = ArmorIndex (ent);
422 	if (power_armor_type && (!index || (level.framenum & 8) ) )
423 	{	// flash between power armor and other armor icon
424 		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex ("i_powershield");
425 		ent->client->ps.stats[STAT_ARMOR] = cells;
426 	}
427 	else if (index)
428 	{
429 		item = GetItemByIndex (index);
430 		ent->client->ps.stats[STAT_ARMOR_ICON] = gi.imageindex (item->icon);
431 		ent->client->ps.stats[STAT_ARMOR] = ent->client->pers.inventory[index];
432 	}
433 	else
434 	{
435 		ent->client->ps.stats[STAT_ARMOR_ICON] = 0;
436 		ent->client->ps.stats[STAT_ARMOR] = 0;
437 	}
438 
439 	//
440 	// pickup message
441 	//
442 	if (level.time > ent->client->pickup_msg_time)
443 	{
444 		ent->client->ps.stats[STAT_PICKUP_ICON] = 0;
445 		ent->client->ps.stats[STAT_PICKUP_STRING] = 0;
446 	}
447 
448 	//
449 	// timers
450 	//
451 	if (ent->client->quad_framenum > level.framenum)
452 	{
453 		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_quad");
454 		ent->client->ps.stats[STAT_TIMER] = (ent->client->quad_framenum - level.framenum)/10;
455 	}
456 	else if (ent->client->invincible_framenum > level.framenum)
457 	{
458 		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_invulnerability");
459 		ent->client->ps.stats[STAT_TIMER] = (ent->client->invincible_framenum - level.framenum)/10;
460 	}
461 	else if (ent->client->enviro_framenum > level.framenum)
462 	{
463 		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_envirosuit");
464 		ent->client->ps.stats[STAT_TIMER] = (ent->client->enviro_framenum - level.framenum)/10;
465 	}
466 	else if (ent->client->breather_framenum > level.framenum)
467 	{
468 		ent->client->ps.stats[STAT_TIMER_ICON] = gi.imageindex ("p_rebreather");
469 		ent->client->ps.stats[STAT_TIMER] = (ent->client->breather_framenum - level.framenum)/10;
470 	}
471 	else
472 	{
473 		ent->client->ps.stats[STAT_TIMER_ICON] = 0;
474 		ent->client->ps.stats[STAT_TIMER] = 0;
475 	}
476 
477 	//
478 	// selected item
479 	//
480 	if (ent->client->pers.selected_item == -1)
481 		ent->client->ps.stats[STAT_SELECTED_ICON] = 0;
482 	else
483 		ent->client->ps.stats[STAT_SELECTED_ICON] = gi.imageindex (itemlist[ent->client->pers.selected_item].icon);
484 
485 	ent->client->ps.stats[STAT_SELECTED_ITEM] = ent->client->pers.selected_item;
486 
487 	//
488 	// layouts
489 	//
490 	ent->client->ps.stats[STAT_LAYOUTS] = 0;
491 
492 	if (deathmatch->value)
493 	{
494 		if (ent->client->pers.health <= 0 || level.intermissiontime
495 			|| ent->client->showscores)
496 			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
497 		if (ent->client->showinventory && ent->client->pers.health > 0)
498 			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
499 	}
500 	else
501 	{
502 		if (ent->client->showscores || ent->client->showhelp)
503 			ent->client->ps.stats[STAT_LAYOUTS] |= 1;
504 		if (ent->client->showinventory && ent->client->pers.health > 0)
505 			ent->client->ps.stats[STAT_LAYOUTS] |= 2;
506 	}
507 
508 	//
509 	// frags
510 	//
511 	ent->client->ps.stats[STAT_FRAGS] = ent->client->resp.score;
512 
513 	//
514 	// help icon / current weapon if not shown
515 	//
516 	if (ent->client->pers.helpchanged && (level.framenum&8) )
517 		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex ("i_help");
518 	else if ( (ent->client->pers.hand == CENTER_HANDED || ent->client->ps.fov > 91)
519 		&& ent->client->pers.weapon)
520 		ent->client->ps.stats[STAT_HELPICON] = gi.imageindex (ent->client->pers.weapon->icon);
521 	else
522 		ent->client->ps.stats[STAT_HELPICON] = 0;
523 
524 	ent->client->ps.stats[STAT_SPECTATOR] = 0;
525 }
526 
527 /*
528 ===============
529 G_CheckChaseStats
530 ===============
531 */
G_CheckChaseStats(edict_t * ent)532 void G_CheckChaseStats (edict_t *ent)
533 {
534 	int i;
535 	gclient_t *cl;
536 
537 	for (i = 1; i <= maxclients->value; i++) {
538 		cl = g_edicts[i].client;
539 		if (!g_edicts[i].inuse || cl->chase_target != ent)
540 			continue;
541 		memcpy(cl->ps.stats, ent->client->ps.stats, sizeof(cl->ps.stats));
542 		G_SetSpectatorStats(g_edicts + i);
543 	}
544 }
545 
546 /*
547 ===============
548 G_SetSpectatorStats
549 ===============
550 */
G_SetSpectatorStats(edict_t * ent)551 void G_SetSpectatorStats (edict_t *ent)
552 {
553 	gclient_t *cl = ent->client;
554 
555 	if (!cl->chase_target)
556 		G_SetStats (ent);
557 
558 	cl->ps.stats[STAT_SPECTATOR] = 1;
559 
560 	// layouts are independant in spectator
561 	cl->ps.stats[STAT_LAYOUTS] = 0;
562 	if (cl->pers.health <= 0 || level.intermissiontime || cl->showscores)
563 		cl->ps.stats[STAT_LAYOUTS] |= 1;
564 	if (cl->showinventory && cl->pers.health > 0)
565 		cl->ps.stats[STAT_LAYOUTS] |= 2;
566 
567 	if (cl->chase_target && cl->chase_target->inuse)
568 		cl->ps.stats[STAT_CHASE] = CS_PLAYERSKINS +
569 			(cl->chase_target - g_edicts) - 1;
570 	else
571 		cl->ps.stats[STAT_CHASE] = 0;
572 }
573 
574