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