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