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 #include "m_player.h"
22
23 typedef enum match_s {
24 MATCH_NONE,
25 MATCH_SETUP,
26 MATCH_PREGAME,
27 MATCH_GAME,
28 MATCH_POST
29 } match_t;
30
31 typedef enum {
32 ELECT_NONE,
33 ELECT_MATCH,
34 ELECT_ADMIN,
35 ELECT_MAP
36 } elect_t;
37
38 typedef struct ctfgame_s
39 {
40 int team1, team2;
41 int total1, total2; // these are only set when going into intermission!
42 float last_flag_capture;
43 int last_capture_team;
44
45 match_t match; // match state
46 float matchtime; // time for match start/end (depends on state)
47 int lasttime; // last time update
48 qboolean countdown; // has audio countdown started?
49
50 elect_t election; // election type
51 edict_t *etarget; // for admin election, who's being elected
52 char elevel[32]; // for map election, target level
53 int evotes; // votes so far
54 int needvotes; // votes needed
55 float electtime; // remaining time until election times out
56 char emsg[256]; // election name
57 int warnactive; // true if stat string 30 is active
58
59
60 ghost_t ghosts[MAX_CLIENTS]; // ghost codes
61 } ctfgame_t;
62
63 ctfgame_t ctfgame;
64
65 cvar_t *ctf;
66 cvar_t *ctf_forcejoin;
67
68 cvar_t *competition;
69 cvar_t *matchlock;
70 cvar_t *electpercentage;
71 cvar_t *matchtime;
72 cvar_t *matchsetuptime;
73 cvar_t *matchstarttime;
74 cvar_t *admin_password;
75 cvar_t *allow_admin;
76 cvar_t *warp_list;
77 cvar_t *warn_unbalanced;
78
79 // Index for various CTF pics, this saves us from calling gi.imageindex
80 // all the time and saves a few CPU cycles since we don't have to do
81 // a bunch of string compares all the time.
82 // These are set in CTFPrecache() called from worldspawn
83 int imageindex_i_ctf1;
84 int imageindex_i_ctf2;
85 int imageindex_i_ctf1d;
86 int imageindex_i_ctf2d;
87 int imageindex_i_ctf1t;
88 int imageindex_i_ctf2t;
89 int imageindex_i_ctfj;
90 int imageindex_sbfctf1;
91 int imageindex_sbfctf2;
92 int imageindex_ctfsb1;
93 int imageindex_ctfsb2;
94
95 char *ctf_statusbar =
96 "yb -24 "
97
98 // health
99 "xv 0 "
100 "hnum "
101 "xv 50 "
102 "pic 0 "
103
104 // ammo
105 "if 2 "
106 " xv 100 "
107 " anum "
108 " xv 150 "
109 " pic 2 "
110 "endif "
111
112 // armor
113 "if 4 "
114 " xv 200 "
115 " rnum "
116 " xv 250 "
117 " pic 4 "
118 "endif "
119
120 // selected item
121 "if 6 "
122 " xv 296 "
123 " pic 6 "
124 "endif "
125
126 "yb -50 "
127
128 // picked up item
129 "if 7 "
130 " xv 0 "
131 " pic 7 "
132 " xv 26 "
133 " yb -42 "
134 " stat_string 8 "
135 " yb -50 "
136 "endif "
137
138 // timer
139 "if 9 "
140 "xv 246 "
141 "num 2 10 "
142 "xv 296 "
143 "pic 9 "
144 "endif "
145
146 // help / weapon icon
147 "if 11 "
148 "xv 148 "
149 "pic 11 "
150 "endif "
151
152 // frags
153 "xr -50 "
154 "yt 2 "
155 "num 3 14 "
156
157 //tech
158 "yb -129 "
159 "if 26 "
160 "xr -26 "
161 "pic 26 "
162 "endif "
163
164 // red team
165 "yb -102 "
166 "if 17 "
167 "xr -26 "
168 "pic 17 "
169 "endif "
170 "xr -62 "
171 "num 2 18 "
172 //joined overlay
173 "if 22 "
174 "yb -104 "
175 "xr -28 "
176 "pic 22 "
177 "endif "
178
179 // blue team
180 "yb -75 "
181 "if 19 "
182 "xr -26 "
183 "pic 19 "
184 "endif "
185 "xr -62 "
186 "num 2 20 "
187 "if 23 "
188 "yb -77 "
189 "xr -28 "
190 "pic 23 "
191 "endif "
192
193 // have flag graph
194 "if 21 "
195 "yt 26 "
196 "xr -24 "
197 "pic 21 "
198 "endif "
199
200 // id view state
201 "if 27 "
202 "xv 112 "
203 "yb -58 "
204 "stat_string 27 "
205 "endif "
206
207 "if 29 "
208 "xv 96 "
209 "yb -58 "
210 "pic 29 "
211 "endif "
212
213 "if 28 "
214 "xl 0 "
215 "yb -78 "
216 "stat_string 28 "
217 "endif "
218
219 "if 30 "
220 "xl 0 "
221 "yb -88 "
222 "stat_string 30 "
223 "endif "
224 ;
225
226 static char *tnames[] = {
227 "item_tech1", "item_tech2", "item_tech3", "item_tech4",
228 NULL
229 };
230
stuffcmd(edict_t * ent,char * s)231 void stuffcmd(edict_t *ent, char *s)
232 {
233 gi.WriteByte (11);
234 gi.WriteString (s);
235 gi.unicast (ent, true);
236 }
237
238 /*--------------------------------------------------------------------------*/
239
240 /*
241 =================
242 findradius
243
244 Returns entities that have origins within a spherical area
245
246 findradius (origin, radius)
247 =================
248 */
loc_findradius(edict_t * from,vec3_t org,float rad)249 static edict_t *loc_findradius (edict_t *from, vec3_t org, float rad)
250 {
251 vec3_t eorg;
252 int j;
253
254 if (!from)
255 from = g_edicts;
256 else
257 from++;
258 for ( ; from < &g_edicts[globals.num_edicts]; from++)
259 {
260 if (!from->inuse)
261 continue;
262 #if 0
263 if (from->solid == SOLID_NOT)
264 continue;
265 #endif
266 for (j=0 ; j<3 ; j++)
267 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5);
268 if (VectorLength(eorg) > rad)
269 continue;
270 return from;
271 }
272
273 return NULL;
274 }
275
loc_buildboxpoints(vec3_t p[8],vec3_t org,vec3_t mins,vec3_t maxs)276 static void loc_buildboxpoints(vec3_t p[8], vec3_t org, vec3_t mins, vec3_t maxs)
277 {
278 VectorAdd(org, mins, p[0]);
279 VectorCopy(p[0], p[1]);
280 p[1][0] -= mins[0];
281 VectorCopy(p[0], p[2]);
282 p[2][1] -= mins[1];
283 VectorCopy(p[0], p[3]);
284 p[3][0] -= mins[0];
285 p[3][1] -= mins[1];
286 VectorAdd(org, maxs, p[4]);
287 VectorCopy(p[4], p[5]);
288 p[5][0] -= maxs[0];
289 VectorCopy(p[0], p[6]);
290 p[6][1] -= maxs[1];
291 VectorCopy(p[0], p[7]);
292 p[7][0] -= maxs[0];
293 p[7][1] -= maxs[1];
294 }
295
loc_CanSee(edict_t * targ,edict_t * inflictor)296 static qboolean loc_CanSee (edict_t *targ, edict_t *inflictor)
297 {
298 trace_t trace;
299 vec3_t targpoints[8];
300 int i;
301 vec3_t viewpoint;
302
303 // bmodels need special checking because their origin is 0,0,0
304 if (targ->movetype == MOVETYPE_PUSH)
305 return false; // bmodels not supported
306
307 loc_buildboxpoints(targpoints, targ->s.origin, targ->mins, targ->maxs);
308
309 VectorCopy(inflictor->s.origin, viewpoint);
310 viewpoint[2] += inflictor->viewheight;
311
312 for (i = 0; i < 8; i++) {
313 trace = gi.trace (viewpoint, vec3_origin, vec3_origin, targpoints[i], inflictor, MASK_SOLID);
314 if (trace.fraction == 1.0)
315 return true;
316 }
317
318 return false;
319 }
320
321 /*--------------------------------------------------------------------------*/
322
323 static gitem_t *flag1_item;
324 static gitem_t *flag2_item;
325
CTFSpawn(void)326 void CTFSpawn(void)
327 {
328 if (!flag1_item)
329 flag1_item = FindItemByClassname("item_flag_team1");
330 if (!flag2_item)
331 flag2_item = FindItemByClassname("item_flag_team2");
332 memset(&ctfgame, 0, sizeof(ctfgame));
333 CTFSetupTechSpawn();
334
335 if (competition->value > 1) {
336 ctfgame.match = MATCH_SETUP;
337 ctfgame.matchtime = level.time + matchsetuptime->value * 60;
338 }
339 }
340
CTFInit(void)341 void CTFInit(void)
342 {
343 ctf = gi.cvar("ctf", "1", CVAR_SERVERINFO);
344 ctf_forcejoin = gi.cvar("ctf_forcejoin", "", 0);
345 competition = gi.cvar("competition", "0", CVAR_SERVERINFO);
346 matchlock = gi.cvar("matchlock", "1", CVAR_SERVERINFO);
347 electpercentage = gi.cvar("electpercentage", "66", 0);
348 matchtime = gi.cvar("matchtime", "20", CVAR_SERVERINFO);
349 matchsetuptime = gi.cvar("matchsetuptime", "10", 0);
350 matchstarttime = gi.cvar("matchstarttime", "20", 0);
351 admin_password = gi.cvar("admin_password", "", 0);
352 allow_admin = gi.cvar("allow_admin", "1", 0);
353 warp_list = gi.cvar("warp_list", "q2ctf1 q2ctf2 q2ctf3 q2ctf4 q2ctf5", 0);
354 warn_unbalanced = gi.cvar("warn_unbalanced", "1", 0);
355 }
356
357 /*
358 * Precache CTF items
359 */
360
CTFPrecache(void)361 void CTFPrecache(void)
362 {
363 imageindex_i_ctf1 = gi.imageindex("i_ctf1");
364 imageindex_i_ctf2 = gi.imageindex("i_ctf2");
365 imageindex_i_ctf1d = gi.imageindex("i_ctf1d");
366 imageindex_i_ctf2d = gi.imageindex("i_ctf2d");
367 imageindex_i_ctf1t = gi.imageindex("i_ctf1t");
368 imageindex_i_ctf2t = gi.imageindex("i_ctf2t");
369 imageindex_i_ctfj = gi.imageindex("i_ctfj");
370 imageindex_sbfctf1 = gi.imageindex("sbfctf1");
371 imageindex_sbfctf2 = gi.imageindex("sbfctf2");
372 imageindex_ctfsb1 = gi.imageindex("ctfsb1");
373 imageindex_ctfsb2 = gi.imageindex("ctfsb2");
374 }
375
376 /*--------------------------------------------------------------------------*/
377
CTFTeamName(int team)378 char *CTFTeamName(int team)
379 {
380 switch (team) {
381 case CTF_TEAM1:
382 return "RED";
383 case CTF_TEAM2:
384 return "BLUE";
385 }
386 return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
387 }
388
CTFOtherTeamName(int team)389 char *CTFOtherTeamName(int team)
390 {
391 switch (team) {
392 case CTF_TEAM1:
393 return "BLUE";
394 case CTF_TEAM2:
395 return "RED";
396 }
397 return "UNKNOWN"; // Hanzo pointed out this was spelled wrong as "UKNOWN"
398 }
399
CTFOtherTeam(int team)400 int CTFOtherTeam(int team)
401 {
402 switch (team) {
403 case CTF_TEAM1:
404 return CTF_TEAM2;
405 case CTF_TEAM2:
406 return CTF_TEAM1;
407 }
408 return -1; // invalid value
409 }
410
411 /*--------------------------------------------------------------------------*/
412
413 edict_t *SelectRandomDeathmatchSpawnPoint (void);
414 edict_t *SelectFarthestDeathmatchSpawnPoint (void);
415 float PlayersRangeFromSpot (edict_t *spot);
416
CTFAssignSkin(edict_t * ent,char * s)417 void CTFAssignSkin(edict_t *ent, char *s)
418 {
419 int playernum = ent-g_edicts-1;
420 char *p;
421 char t[64];
422
423 Com_sprintf(t, sizeof(t), "%s", s);
424
425 if ((p = strchr(t, '/')) != NULL)
426 p[1] = 0;
427 else
428 strcpy(t, "male/");
429
430 switch (ent->client->resp.ctf_team) {
431 case CTF_TEAM1:
432 gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s%s",
433 ent->client->pers.netname, t, CTF_TEAM1_SKIN) );
434 break;
435 case CTF_TEAM2:
436 gi.configstring (CS_PLAYERSKINS+playernum,
437 va("%s\\%s%s", ent->client->pers.netname, t, CTF_TEAM2_SKIN) );
438 break;
439 default:
440 gi.configstring (CS_PLAYERSKINS+playernum,
441 va("%s\\%s", ent->client->pers.netname, s) );
442 break;
443 }
444 // safe_cprintf(ent, PRINT_HIGH, "You have been assigned to %s team.\n", ent->client->pers.netname);
445 }
446
CTFAssignTeam(gclient_t * who)447 void CTFAssignTeam(gclient_t *who)
448 {
449 edict_t *player;
450 int i;
451 int team1count = 0, team2count = 0;
452
453 who->resp.ctf_state = 0;
454
455 if (!((int)dmflags->value & DF_CTF_FORCEJOIN)) {
456 who->resp.ctf_team = CTF_NOTEAM;
457 return;
458 }
459
460 for (i = 1; i <= maxclients->value; i++) {
461 player = &g_edicts[i];
462
463 if (!player->inuse || player->client == who)
464 continue;
465
466 switch (player->client->resp.ctf_team) {
467 case CTF_TEAM1:
468 team1count++;
469 break;
470 case CTF_TEAM2:
471 team2count++;
472 }
473 }
474 if (team1count < team2count)
475 who->resp.ctf_team = CTF_TEAM1;
476 else if (team2count < team1count)
477 who->resp.ctf_team = CTF_TEAM2;
478 else if (rand() & 1)
479 who->resp.ctf_team = CTF_TEAM1;
480 else
481 who->resp.ctf_team = CTF_TEAM2;
482 }
483
484 /*
485 ================
486 SelectCTFSpawnPoint
487
488 go to a ctf point, but NOT the two points closest
489 to other players
490 ================
491 */
SelectCTFSpawnPoint(edict_t * ent)492 edict_t *SelectCTFSpawnPoint (edict_t *ent)
493 {
494 edict_t *spot, *spot1, *spot2;
495 int count = 0;
496 int selection;
497 float range, range1, range2;
498 char *cname;
499
500 if (ent->client->resp.ctf_state)
501 if ( (int)(dmflags->value) & DF_SPAWN_FARTHEST)
502 return SelectFarthestDeathmatchSpawnPoint ();
503 else
504 return SelectRandomDeathmatchSpawnPoint ();
505
506 ent->client->resp.ctf_state++;
507
508 switch (ent->client->resp.ctf_team) {
509 case CTF_TEAM1:
510 cname = "info_player_team1";
511 break;
512 case CTF_TEAM2:
513 cname = "info_player_team2";
514 break;
515 default:
516 return SelectRandomDeathmatchSpawnPoint();
517 }
518
519 spot = NULL;
520 range1 = range2 = 99999;
521 spot1 = spot2 = NULL;
522
523 while ((spot = G_Find (spot, FOFS(classname), cname)) != NULL)
524 {
525 count++;
526 range = PlayersRangeFromSpot(spot);
527 if (range < range1)
528 {
529 range1 = range;
530 spot1 = spot;
531 }
532 else if (range < range2)
533 {
534 range2 = range;
535 spot2 = spot;
536 }
537 }
538
539 if (!count)
540 return SelectRandomDeathmatchSpawnPoint();
541
542 if (count <= 2)
543 {
544 spot1 = spot2 = NULL;
545 }
546 else
547 count -= 2;
548
549 selection = rand() % count;
550
551 spot = NULL;
552 do
553 {
554 spot = G_Find (spot, FOFS(classname), cname);
555 if (spot == spot1 || spot == spot2)
556 selection++;
557 } while(selection--);
558
559 return spot;
560 }
561
562 /*------------------------------------------------------------------------*/
563 /*
564 CTFFragBonuses
565
566 Calculate the bonuses for flag defense, flag carrier defense, etc.
567 Note that bonuses are not cumaltive. You get one, they are in importance
568 order.
569 */
CTFFragBonuses(edict_t * targ,edict_t * inflictor,edict_t * attacker)570 void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker)
571 {
572 int i;
573 edict_t *ent;
574 gitem_t *flag_item, *enemy_flag_item;
575 int otherteam;
576 edict_t *flag, *carrier;
577 char *c;
578 vec3_t v1, v2;
579
580 if (targ->client && attacker->client) {
581 if (attacker->client->resp.ghost)
582 if (attacker != targ)
583 attacker->client->resp.ghost->kills++;
584 if (targ->client->resp.ghost)
585 targ->client->resp.ghost->deaths++;
586 }
587
588 // no bonus for fragging yourself
589 if (!targ->client || !attacker->client || targ == attacker)
590 return;
591
592 otherteam = CTFOtherTeam(targ->client->resp.ctf_team);
593 if (otherteam < 0)
594 return; // whoever died isn't on a team
595
596 // same team, if the flag at base, check to he has the enemy flag
597 if (targ->client->resp.ctf_team == CTF_TEAM1) {
598 flag_item = flag1_item;
599 enemy_flag_item = flag2_item;
600 } else {
601 flag_item = flag2_item;
602 enemy_flag_item = flag1_item;
603 }
604
605 // did the attacker frag the flag carrier?
606 if (targ->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
607 attacker->client->resp.ctf_lastfraggedcarrier = level.time;
608 attacker->client->resp.score += CTF_FRAG_CARRIER_BONUS;
609 safe_cprintf(attacker, PRINT_MEDIUM, "BONUS: %d points for fragging enemy flag carrier.\n",
610 CTF_FRAG_CARRIER_BONUS);
611
612 // the target had the flag, clear the hurt carrier
613 // field on the other team
614 for (i = 1; i <= maxclients->value; i++) {
615 ent = g_edicts + i;
616 if (ent->inuse && ent->client->resp.ctf_team == otherteam)
617 ent->client->resp.ctf_lasthurtcarrier = 0;
618 }
619 return;
620 }
621
622 if (targ->client->resp.ctf_lasthurtcarrier &&
623 level.time - targ->client->resp.ctf_lasthurtcarrier < CTF_CARRIER_DANGER_PROTECT_TIMEOUT &&
624 !attacker->client->pers.inventory[ITEM_INDEX(flag_item)]) {
625 // attacker is on the same team as the flag carrier and
626 // fragged a guy who hurt our flag carrier
627 attacker->client->resp.score += CTF_CARRIER_DANGER_PROTECT_BONUS;
628 safe_bprintf(PRINT_MEDIUM, "%s defends %s's flag carrier against an agressive enemy\n",
629 attacker->client->pers.netname,
630 CTFTeamName(attacker->client->resp.ctf_team));
631 if (attacker->client->resp.ghost)
632 attacker->client->resp.ghost->carrierdef++;
633 return;
634 }
635
636 // flag and flag carrier area defense bonuses
637
638 // we have to find the flag and carrier entities
639
640 // find the flag
641 switch (attacker->client->resp.ctf_team) {
642 case CTF_TEAM1:
643 c = "item_flag_team1";
644 break;
645 case CTF_TEAM2:
646 c = "item_flag_team2";
647 break;
648 default:
649 return;
650 }
651
652 flag = NULL;
653 while ((flag = G_Find (flag, FOFS(classname), c)) != NULL) {
654 if (!(flag->spawnflags & DROPPED_ITEM))
655 break;
656 }
657
658 if (!flag)
659 return; // can't find attacker's flag
660
661 // find attacker's team's flag carrier
662 for (i = 1; i <= maxclients->value; i++) {
663 carrier = g_edicts + i;
664 if (carrier->inuse &&
665 carrier->client->pers.inventory[ITEM_INDEX(flag_item)])
666 break;
667 carrier = NULL;
668 }
669
670 // ok we have the attackers flag and a pointer to the carrier
671
672 // check to see if we are defending the base's flag
673 VectorSubtract(targ->s.origin, flag->s.origin, v1);
674 VectorSubtract(attacker->s.origin, flag->s.origin, v2);
675
676 if ((VectorLength(v1) < CTF_TARGET_PROTECT_RADIUS ||
677 VectorLength(v2) < CTF_TARGET_PROTECT_RADIUS ||
678 loc_CanSee(flag, targ) || loc_CanSee(flag, attacker)) &&
679 attacker->client->resp.ctf_team != targ->client->resp.ctf_team) {
680 // we defended the base flag
681 attacker->client->resp.score += CTF_FLAG_DEFENSE_BONUS;
682 if (flag->solid == SOLID_NOT)
683 safe_bprintf(PRINT_MEDIUM, "%s defends the %s base.\n",
684 attacker->client->pers.netname,
685 CTFTeamName(attacker->client->resp.ctf_team));
686 else
687 safe_bprintf(PRINT_MEDIUM, "%s defends the %s flag.\n",
688 attacker->client->pers.netname,
689 CTFTeamName(attacker->client->resp.ctf_team));
690 if (attacker->client->resp.ghost)
691 attacker->client->resp.ghost->basedef++;
692 return;
693 }
694
695 if (carrier && carrier != attacker) {
696 VectorSubtract(targ->s.origin, carrier->s.origin, v1);
697 VectorSubtract(attacker->s.origin, carrier->s.origin, v1);
698
699 if (VectorLength(v1) < CTF_ATTACKER_PROTECT_RADIUS ||
700 VectorLength(v2) < CTF_ATTACKER_PROTECT_RADIUS ||
701 loc_CanSee(carrier, targ) || loc_CanSee(carrier, attacker)) {
702 attacker->client->resp.score += CTF_CARRIER_PROTECT_BONUS;
703 safe_bprintf(PRINT_MEDIUM, "%s defends the %s's flag carrier.\n",
704 attacker->client->pers.netname,
705 CTFTeamName(attacker->client->resp.ctf_team));
706 if (attacker->client->resp.ghost)
707 attacker->client->resp.ghost->carrierdef++;
708 return;
709 }
710 }
711 }
712
CTFCheckHurtCarrier(edict_t * targ,edict_t * attacker)713 void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker)
714 {
715 gitem_t *flag_item;
716
717 if (!targ->client || !attacker->client)
718 return;
719
720 if (targ->client->resp.ctf_team == CTF_TEAM1)
721 flag_item = flag2_item;
722 else
723 flag_item = flag1_item;
724
725 if (targ->client->pers.inventory[ITEM_INDEX(flag_item)] &&
726 targ->client->resp.ctf_team != attacker->client->resp.ctf_team)
727 attacker->client->resp.ctf_lasthurtcarrier = level.time;
728 }
729
730
731 /*------------------------------------------------------------------------*/
732
CTFResetFlag(int ctf_team)733 void CTFResetFlag(int ctf_team)
734 {
735 char *c;
736 edict_t *ent;
737
738 switch (ctf_team) {
739 case CTF_TEAM1:
740 c = "item_flag_team1";
741 break;
742 case CTF_TEAM2:
743 c = "item_flag_team2";
744 break;
745 default:
746 return;
747 }
748
749 ent = NULL;
750 while ((ent = G_Find (ent, FOFS(classname), c)) != NULL) {
751 if (ent->spawnflags & DROPPED_ITEM)
752 G_FreeEdict(ent);
753 else {
754 ent->svflags &= ~SVF_NOCLIENT;
755 ent->solid = SOLID_TRIGGER;
756 gi.linkentity(ent);
757 ent->s.event = EV_ITEM_RESPAWN;
758 }
759 }
760 }
761
CTFResetFlags(void)762 void CTFResetFlags(void)
763 {
764 CTFResetFlag(CTF_TEAM1);
765 CTFResetFlag(CTF_TEAM2);
766 }
767
CTFPickup_Flag(edict_t * ent,edict_t * other)768 qboolean CTFPickup_Flag(edict_t *ent, edict_t *other)
769 {
770 int ctf_team;
771 int i;
772 edict_t *player;
773 gitem_t *flag_item, *enemy_flag_item;
774
775 // figure out what team this flag is
776 if (strcmp(ent->classname, "item_flag_team1") == 0)
777 ctf_team = CTF_TEAM1;
778 else if (strcmp(ent->classname, "item_flag_team2") == 0)
779 ctf_team = CTF_TEAM2;
780 else {
781 safe_cprintf(ent, PRINT_HIGH, "Don't know what team the flag is on.\n");
782 return false;
783 }
784
785 // same team, if the flag at base, check to he has the enemy flag
786 if (ctf_team == CTF_TEAM1) {
787 flag_item = flag1_item;
788 enemy_flag_item = flag2_item;
789 } else {
790 flag_item = flag2_item;
791 enemy_flag_item = flag1_item;
792 }
793
794 if (ctf_team == other->client->resp.ctf_team) {
795
796 if (!(ent->spawnflags & DROPPED_ITEM)) {
797 // the flag is at home base. if the player has the enemy
798 // flag, he's just won!
799
800 if (other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)]) {
801 safe_bprintf(PRINT_HIGH, "%s captured the %s flag!\n",
802 other->client->pers.netname, CTFOtherTeamName(ctf_team));
803 other->client->pers.inventory[ITEM_INDEX(enemy_flag_item)] = 0;
804
805 ctfgame.last_flag_capture = level.time;
806 ctfgame.last_capture_team = ctf_team;
807 if (ctf_team == CTF_TEAM1)
808 ctfgame.team1++;
809 else
810 ctfgame.team2++;
811
812 gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagcap.wav"), 1, ATTN_NONE, 0);
813
814 // other gets another 10 frag bonus
815 other->client->resp.score += CTF_CAPTURE_BONUS;
816 if (other->client->resp.ghost)
817 other->client->resp.ghost->caps++;
818
819 // Ok, let's do the player loop, hand out the bonuses
820 for (i = 1; i <= maxclients->value; i++) {
821 player = &g_edicts[i];
822 if (!player->inuse)
823 continue;
824
825 if (player->client->resp.ctf_team != other->client->resp.ctf_team)
826 player->client->resp.ctf_lasthurtcarrier = -5;
827 else if (player->client->resp.ctf_team == other->client->resp.ctf_team) {
828 if (player != other)
829 player->client->resp.score += CTF_TEAM_BONUS;
830 // award extra points for capture assists
831 if (player->client->resp.ctf_lastreturnedflag + CTF_RETURN_FLAG_ASSIST_TIMEOUT > level.time) {
832 safe_bprintf(PRINT_HIGH, "%s gets an assist for returning the flag!\n", player->client->pers.netname);
833 player->client->resp.score += CTF_RETURN_FLAG_ASSIST_BONUS;
834 }
835 if (player->client->resp.ctf_lastfraggedcarrier + CTF_FRAG_CARRIER_ASSIST_TIMEOUT > level.time) {
836 safe_bprintf(PRINT_HIGH, "%s gets an assist for fragging the flag carrier!\n", player->client->pers.netname);
837 player->client->resp.score += CTF_FRAG_CARRIER_ASSIST_BONUS;
838 }
839 }
840 }
841
842 CTFResetFlags();
843 return false;
844 }
845 return false; // its at home base already
846 }
847 // hey, its not home. return it by teleporting it back
848 safe_bprintf(PRINT_HIGH, "%s returned the %s flag!\n",
849 other->client->pers.netname, CTFTeamName(ctf_team));
850 other->client->resp.score += CTF_RECOVERY_BONUS;
851 other->client->resp.ctf_lastreturnedflag = level.time;
852 gi.sound (ent, CHAN_RELIABLE+CHAN_NO_PHS_ADD+CHAN_VOICE, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
853 //CTFResetFlag will remove this entity! We must return false
854 CTFResetFlag(ctf_team);
855 return false;
856 }
857
858 // hey, its not our flag, pick it up
859 safe_bprintf(PRINT_HIGH, "%s got the %s flag!\n",
860 other->client->pers.netname, CTFTeamName(ctf_team));
861 other->client->resp.score += CTF_FLAG_BONUS;
862
863 other->client->pers.inventory[ITEM_INDEX(flag_item)] = 1;
864 other->client->resp.ctf_flagsince = level.time;
865
866 // pick up the flag
867 // if it's not a dropped flag, we just make is disappear
868 // if it's dropped, it will be removed by the pickup caller
869 if (!(ent->spawnflags & DROPPED_ITEM)) {
870 ent->flags |= FL_RESPAWN;
871 ent->svflags |= SVF_NOCLIENT;
872 ent->solid = SOLID_NOT;
873 }
874 return true;
875 }
876
CTFDropFlagTouch(edict_t * ent,edict_t * other,cplane_t * plane,csurface_t * surf)877 static void CTFDropFlagTouch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf)
878 {
879 //owner (who dropped us) can't touch for two secs
880 if (other == ent->owner &&
881 ent->nextthink - level.time > CTF_AUTO_FLAG_RETURN_TIMEOUT-2)
882 return;
883
884 Touch_Item (ent, other, plane, surf);
885 }
886
CTFDropFlagThink(edict_t * ent)887 static void CTFDropFlagThink(edict_t *ent)
888 {
889 // auto return the flag
890 // reset flag will remove ourselves
891 if (strcmp(ent->classname, "item_flag_team1") == 0) {
892 CTFResetFlag(CTF_TEAM1);
893 safe_bprintf(PRINT_HIGH, "The %s flag has returned!\n",
894 CTFTeamName(CTF_TEAM1));
895 } else if (strcmp(ent->classname, "item_flag_team2") == 0) {
896 CTFResetFlag(CTF_TEAM2);
897 safe_bprintf(PRINT_HIGH, "The %s flag has returned!\n",
898 CTFTeamName(CTF_TEAM2));
899 }
900 }
901
902 // Called from PlayerDie, to drop the flag from a dying player
CTFDeadDropFlag(edict_t * self)903 void CTFDeadDropFlag(edict_t *self)
904 {
905 edict_t *dropped = NULL;
906
907 if (self->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
908 dropped = Drop_Item(self, flag1_item);
909 self->client->pers.inventory[ITEM_INDEX(flag1_item)] = 0;
910 safe_bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
911 self->client->pers.netname, CTFTeamName(CTF_TEAM1));
912 } else if (self->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
913 dropped = Drop_Item(self, flag2_item);
914 self->client->pers.inventory[ITEM_INDEX(flag2_item)] = 0;
915 safe_bprintf(PRINT_HIGH, "%s lost the %s flag!\n",
916 self->client->pers.netname, CTFTeamName(CTF_TEAM2));
917 }
918
919 if (dropped) {
920 dropped->think = CTFDropFlagThink;
921 dropped->nextthink = level.time + CTF_AUTO_FLAG_RETURN_TIMEOUT;
922 dropped->touch = CTFDropFlagTouch;
923 }
924 }
925
CTFDrop_Flag(edict_t * ent,gitem_t * item)926 void CTFDrop_Flag(edict_t *ent, gitem_t *item) /* was qboolean */
927 {
928 if (rand() & 1)
929 safe_cprintf(ent, PRINT_HIGH, "Only lusers drop flags.\n");
930 else
931 safe_cprintf(ent, PRINT_HIGH, "Winners don't drop flags.\n");
932 /* return false; */
933 }
934
CTFFlagThink(edict_t * ent)935 static void CTFFlagThink(edict_t *ent)
936 {
937 if (ent->solid != SOLID_NOT)
938 ent->s.frame = 173 + (((ent->s.frame - 173) + 1) % 16);
939 ent->nextthink = level.time + FRAMETIME;
940 }
941
942
CTFFlagSetup(edict_t * ent)943 void CTFFlagSetup (edict_t *ent)
944 {
945 trace_t tr;
946 vec3_t dest;
947 float *v;
948
949 v = tv(-15,-15,-15);
950 VectorCopy (v, ent->mins);
951 v = tv(15,15,15);
952 VectorCopy (v, ent->maxs);
953
954 if (ent->model)
955 gi.setmodel (ent, ent->model);
956 else
957 gi.setmodel (ent, ent->item->world_model);
958 ent->solid = SOLID_TRIGGER;
959 ent->movetype = MOVETYPE_TOSS;
960 ent->touch = Touch_Item;
961
962 v = tv(0,0,-128);
963 VectorAdd (ent->s.origin, v, dest);
964
965 tr = gi.trace (ent->s.origin, ent->mins, ent->maxs, dest, ent, MASK_SOLID);
966 if (tr.startsolid)
967 {
968 gi.dprintf ("CTFFlagSetup: %s startsolid at %s\n", ent->classname, vtos(ent->s.origin));
969 G_FreeEdict (ent);
970 return;
971 }
972
973 VectorCopy (tr.endpos, ent->s.origin);
974
975 gi.linkentity (ent);
976
977 ent->nextthink = level.time + FRAMETIME;
978 ent->think = CTFFlagThink;
979 }
980
CTFEffects(edict_t * player)981 void CTFEffects(edict_t *player)
982 {
983 player->s.effects &= ~(EF_FLAG1 | EF_FLAG2);
984 if (player->health > 0) {
985 if (player->client->pers.inventory[ITEM_INDEX(flag1_item)]) {
986 player->s.effects |= EF_FLAG1;
987 }
988 if (player->client->pers.inventory[ITEM_INDEX(flag2_item)]) {
989 player->s.effects |= EF_FLAG2;
990 }
991 }
992
993 if (player->client->pers.inventory[ITEM_INDEX(flag1_item)])
994 player->s.modelindex3 = gi.modelindex("players/male/flag1.md2");
995 else if (player->client->pers.inventory[ITEM_INDEX(flag2_item)])
996 player->s.modelindex3 = gi.modelindex("players/male/flag2.md2");
997 else
998 player->s.modelindex3 = 0;
999 }
1000
1001 // called when we enter the intermission
CTFCalcScores(void)1002 void CTFCalcScores(void)
1003 {
1004 int i;
1005
1006 ctfgame.total1 = ctfgame.total2 = 0;
1007 for (i = 0; i < maxclients->value; i++) {
1008 if (!g_edicts[i+1].inuse)
1009 continue;
1010 if (game.clients[i].resp.ctf_team == CTF_TEAM1)
1011 ctfgame.total1 += game.clients[i].resp.score;
1012 else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
1013 ctfgame.total2 += game.clients[i].resp.score;
1014 }
1015 }
1016
CTFID_f(edict_t * ent)1017 void CTFID_f (edict_t *ent)
1018 {
1019 if (ent->client->resp.id_state) {
1020 safe_cprintf(ent, PRINT_HIGH, "Disabling player identication display.\n");
1021 ent->client->resp.id_state = false;
1022 } else {
1023 safe_cprintf(ent, PRINT_HIGH, "Activating player identication display.\n");
1024 ent->client->resp.id_state = true;
1025 }
1026 }
1027
CTFSetIDView(edict_t * ent)1028 static void CTFSetIDView(edict_t *ent)
1029 {
1030 vec3_t forward, dir;
1031 trace_t tr;
1032 edict_t *who, *best;
1033 float bd = 0, d;
1034 int i;
1035
1036 // only check every few frames
1037 if (level.time - ent->client->resp.lastidtime < 0.25)
1038 return;
1039 ent->client->resp.lastidtime = level.time;
1040
1041 ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
1042 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
1043
1044 AngleVectors(ent->client->v_angle, forward, NULL, NULL);
1045 VectorScale(forward, 1024, forward);
1046 VectorAdd(ent->s.origin, forward, forward);
1047 tr = gi.trace(ent->s.origin, NULL, NULL, forward, ent, MASK_SOLID);
1048 if (tr.fraction < 1 && tr.ent && tr.ent->client) {
1049 ent->client->ps.stats[STAT_CTF_ID_VIEW] =
1050 CS_GENERAL + (tr.ent - g_edicts - 1);
1051 if (tr.ent->client->resp.ctf_team == CTF_TEAM1)
1052 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
1053 else if (tr.ent->client->resp.ctf_team == CTF_TEAM2)
1054 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
1055 return;
1056 }
1057
1058 AngleVectors(ent->client->v_angle, forward, NULL, NULL);
1059 best = NULL;
1060 for (i = 1; i <= maxclients->value; i++) {
1061 who = g_edicts + i;
1062 if (!who->inuse || who->solid == SOLID_NOT)
1063 continue;
1064 VectorSubtract(who->s.origin, ent->s.origin, dir);
1065 VectorNormalize(dir);
1066 d = DotProduct(forward, dir);
1067 if (d > bd && loc_CanSee(ent, who)) {
1068 bd = d;
1069 best = who;
1070 }
1071 }
1072 if (bd > 0.90) {
1073 ent->client->ps.stats[STAT_CTF_ID_VIEW] =
1074 CS_GENERAL + (best - g_edicts - 1);
1075 if (best->client->resp.ctf_team == CTF_TEAM1)
1076 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf1;
1077 else if (best->client->resp.ctf_team == CTF_TEAM2)
1078 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = imageindex_sbfctf2;
1079 }
1080 }
1081
SetCTFStats(edict_t * ent)1082 void SetCTFStats(edict_t *ent)
1083 {
1084 gitem_t *tech;
1085 int i;
1086 int p1, p2;
1087 edict_t *e;
1088
1089 if (ctfgame.match > MATCH_NONE)
1090 ent->client->ps.stats[STAT_CTF_MATCH] = CONFIG_CTF_MATCH;
1091 else
1092 ent->client->ps.stats[STAT_CTF_MATCH] = 0;
1093
1094 if (ctfgame.warnactive)
1095 ent->client->ps.stats[STAT_CTF_TEAMINFO] = CONFIG_CTF_TEAMINFO;
1096 else
1097 ent->client->ps.stats[STAT_CTF_TEAMINFO] = 0;
1098
1099 //ghosting
1100 if (ent->client->resp.ghost) {
1101 ent->client->resp.ghost->score = ent->client->resp.score;
1102 strcpy(ent->client->resp.ghost->netname, ent->client->pers.netname);
1103 ent->client->resp.ghost->number = ent->s.number;
1104 }
1105
1106 // logo headers for the frag display
1107 ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = imageindex_ctfsb1;
1108 ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = imageindex_ctfsb2;
1109
1110 // if during intermission, we must blink the team header of the winning team
1111 if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
1112 // note that ctfgame.total[12] is set when we go to intermission
1113 if (ctfgame.team1 > ctfgame.team2)
1114 ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
1115 else if (ctfgame.team2 > ctfgame.team1)
1116 ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
1117 else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
1118 ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
1119 else if (ctfgame.total2 > ctfgame.total1)
1120 ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
1121 else { // tie game!
1122 ent->client->ps.stats[STAT_CTF_TEAM1_HEADER] = 0;
1123 ent->client->ps.stats[STAT_CTF_TEAM2_HEADER] = 0;
1124 }
1125 }
1126
1127 // tech icon
1128 i = 0;
1129 ent->client->ps.stats[STAT_CTF_TECH] = 0;
1130 while (tnames[i]) {
1131 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
1132 ent->client->pers.inventory[ITEM_INDEX(tech)]) {
1133 ent->client->ps.stats[STAT_CTF_TECH] = gi.imageindex(tech->icon);
1134 break;
1135 }
1136 i++;
1137 }
1138
1139 // figure out what icon to display for team logos
1140 // three states:
1141 // flag at base
1142 // flag taken
1143 // flag dropped
1144 p1 = imageindex_i_ctf1;
1145 e = G_Find(NULL, FOFS(classname), "item_flag_team1");
1146 if (e != NULL) {
1147 if (e->solid == SOLID_NOT) {
1148 int i;
1149
1150 // not at base
1151 // check if on player
1152 p1 = imageindex_i_ctf1d; // default to dropped
1153 for (i = 1; i <= maxclients->value; i++)
1154 if (g_edicts[i].inuse &&
1155 g_edicts[i].client->pers.inventory[ITEM_INDEX(flag1_item)]) {
1156 // enemy has it
1157 p1 = imageindex_i_ctf1t;
1158 break;
1159 }
1160 } else if (e->spawnflags & DROPPED_ITEM)
1161 p1 = imageindex_i_ctf1d; // must be dropped
1162 }
1163 p2 = imageindex_i_ctf2;
1164 e = G_Find(NULL, FOFS(classname), "item_flag_team2");
1165 if (e != NULL) {
1166 if (e->solid == SOLID_NOT) {
1167 int i;
1168
1169 // not at base
1170 // check if on player
1171 p2 = imageindex_i_ctf2d; // default to dropped
1172 for (i = 1; i <= maxclients->value; i++)
1173 if (g_edicts[i].inuse &&
1174 g_edicts[i].client->pers.inventory[ITEM_INDEX(flag2_item)]) {
1175 // enemy has it
1176 p2 = imageindex_i_ctf2t;
1177 break;
1178 }
1179 } else if (e->spawnflags & DROPPED_ITEM)
1180 p2 = imageindex_i_ctf2d; // must be dropped
1181 }
1182
1183
1184 ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
1185 ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
1186
1187 if (ctfgame.last_flag_capture && level.time - ctfgame.last_flag_capture < 5) {
1188 if (ctfgame.last_capture_team == CTF_TEAM1)
1189 if (level.framenum & 8)
1190 ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = p1;
1191 else
1192 ent->client->ps.stats[STAT_CTF_TEAM1_PIC] = 0;
1193 else
1194 if (level.framenum & 8)
1195 ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = p2;
1196 else
1197 ent->client->ps.stats[STAT_CTF_TEAM2_PIC] = 0;
1198 }
1199
1200 ent->client->ps.stats[STAT_CTF_TEAM1_CAPS] = ctfgame.team1;
1201 ent->client->ps.stats[STAT_CTF_TEAM2_CAPS] = ctfgame.team2;
1202
1203 ent->client->ps.stats[STAT_CTF_FLAG_PIC] = 0;
1204 if (ent->client->resp.ctf_team == CTF_TEAM1 &&
1205 ent->client->pers.inventory[ITEM_INDEX(flag2_item)] &&
1206 (level.framenum & 8))
1207 ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf2;
1208
1209 else if (ent->client->resp.ctf_team == CTF_TEAM2 &&
1210 ent->client->pers.inventory[ITEM_INDEX(flag1_item)] &&
1211 (level.framenum & 8))
1212 ent->client->ps.stats[STAT_CTF_FLAG_PIC] = imageindex_i_ctf1;
1213
1214 ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = 0;
1215 ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = 0;
1216 if (ent->client->resp.ctf_team == CTF_TEAM1)
1217 ent->client->ps.stats[STAT_CTF_JOINED_TEAM1_PIC] = imageindex_i_ctfj;
1218 else if (ent->client->resp.ctf_team == CTF_TEAM2)
1219 ent->client->ps.stats[STAT_CTF_JOINED_TEAM2_PIC] = imageindex_i_ctfj;
1220
1221 if (ent->client->resp.id_state)
1222 CTFSetIDView(ent);
1223 else {
1224 ent->client->ps.stats[STAT_CTF_ID_VIEW] = 0;
1225 ent->client->ps.stats[STAT_CTF_ID_VIEW_COLOR] = 0;
1226 }
1227 }
1228
1229 /*------------------------------------------------------------------------*/
1230
1231 /*QUAKED info_player_team1 (1 0 0) (-16 -16 -24) (16 16 32)
1232 potential team1 spawning position for ctf games
1233 */
SP_info_player_team1(edict_t * self)1234 void SP_info_player_team1(edict_t *self)
1235 {
1236 }
1237
1238 /*QUAKED info_player_team2 (0 0 1) (-16 -16 -24) (16 16 32)
1239 potential team2 spawning position for ctf games
1240 */
SP_info_player_team2(edict_t * self)1241 void SP_info_player_team2(edict_t *self)
1242 {
1243 }
1244
1245
1246 /*------------------------------------------------------------------------*/
1247 /* GRAPPLE */
1248 /*------------------------------------------------------------------------*/
1249
1250 // ent is player
CTFPlayerResetGrapple(edict_t * ent)1251 void CTFPlayerResetGrapple(edict_t *ent)
1252 {
1253 if (ent->client && ent->client->ctf_grapple)
1254 CTFResetGrapple(ent->client->ctf_grapple);
1255 }
1256
1257 // self is grapple, not player
CTFResetGrapple(edict_t * self)1258 void CTFResetGrapple(edict_t *self)
1259 {
1260 if (self->owner->client->ctf_grapple) {
1261 float volume = 1.0;
1262 gclient_t *cl;
1263
1264 if (self->owner->client->silencer_shots)
1265 volume = 0.2;
1266
1267 gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grreset.wav"), volume, ATTN_NORM, 0);
1268 cl = self->owner->client;
1269 cl->ctf_grapple = NULL;
1270 cl->ctf_grapplereleasetime = level.time;
1271 cl->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
1272 cl->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
1273 G_FreeEdict(self);
1274 }
1275 }
1276
CTFGrappleTouch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)1277 void CTFGrappleTouch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
1278 {
1279 float volume = 1.0;
1280
1281 if (other == self->owner)
1282 return;
1283
1284 if (self->owner->client->ctf_grapplestate != CTF_GRAPPLE_STATE_FLY)
1285 return;
1286
1287 if (surf && (surf->flags & SURF_SKY))
1288 {
1289 CTFResetGrapple(self);
1290 return;
1291 }
1292
1293 VectorCopy(vec3_origin, self->velocity);
1294
1295 PlayerNoise(self->owner, self->s.origin, PNOISE_IMPACT);
1296
1297 if (other->takedamage) {
1298 T_Damage (other, self, self->owner, self->velocity, self->s.origin, plane->normal, self->dmg, 1, 0, MOD_GRAPPLE);
1299 CTFResetGrapple(self);
1300 return;
1301 }
1302
1303 self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_PULL; // we're on hook
1304 self->enemy = other;
1305
1306 self->solid = SOLID_NOT;
1307
1308 if (self->owner->client->silencer_shots)
1309 volume = 0.2;
1310
1311 gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grpull.wav"), volume, ATTN_NORM, 0);
1312 gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhit.wav"), volume, ATTN_NORM, 0);
1313
1314 gi.WriteByte (svc_temp_entity);
1315 gi.WriteByte (TE_SPARKS);
1316 gi.WritePosition (self->s.origin);
1317 if (!plane)
1318 gi.WriteDir (vec3_origin);
1319 else
1320 gi.WriteDir (plane->normal);
1321 gi.multicast (self->s.origin, MULTICAST_PVS);
1322 }
1323
1324 // draw beam between grapple and self
CTFGrappleDrawCable(edict_t * self)1325 void CTFGrappleDrawCable(edict_t *self)
1326 {
1327 vec3_t offset, start, end, f, r;
1328 vec3_t dir;
1329 float distance;
1330
1331 AngleVectors (self->owner->client->v_angle, f, r, NULL);
1332 VectorSet(offset, 16, 16, self->owner->viewheight-8);
1333 P_ProjectSource (self->owner->client, self->owner->s.origin, offset, f, r, start);
1334
1335 VectorSubtract(start, self->owner->s.origin, offset);
1336
1337 VectorSubtract (start, self->s.origin, dir);
1338 distance = VectorLength(dir);
1339 // don't draw cable if close
1340 if (distance < 64)
1341 return;
1342
1343 #if 0
1344 if (distance > 256)
1345 return;
1346
1347 // check for min/max pitch
1348 vectoangles (dir, angles);
1349 if (angles[0] < -180)
1350 angles[0] += 360;
1351 if (fabs(angles[0]) > 45)
1352 return;
1353
1354 trace_t tr; //!!
1355
1356 tr = gi.trace (start, NULL, NULL, self->s.origin, self, MASK_SHOT);
1357 if (tr.ent != self) {
1358 CTFResetGrapple(self);
1359 return;
1360 }
1361 #endif
1362
1363 // adjust start for beam origin being in middle of a segment
1364 // VectorMA (start, 8, f, start);
1365
1366 VectorCopy (self->s.origin, end);
1367 // adjust end z for end spot since the monster is currently dead
1368 // end[2] = self->absmin[2] + self->size[2] / 2;
1369
1370 gi.WriteByte (svc_temp_entity);
1371 #if 1 //def USE_GRAPPLE_CABLE
1372 gi.WriteByte (TE_GRAPPLE_CABLE);
1373 gi.WriteShort (self->owner - g_edicts);
1374 gi.WritePosition (self->owner->s.origin);
1375 gi.WritePosition (end);
1376 gi.WritePosition (offset);
1377 #else
1378 gi.WriteByte (TE_MEDIC_CABLE_ATTACK);
1379 gi.WriteShort (self - g_edicts);
1380 gi.WritePosition (end);
1381 gi.WritePosition (start);
1382 #endif
1383 gi.multicast (self->s.origin, MULTICAST_PVS);
1384 }
1385
1386 void SV_AddGravity (edict_t *ent);
1387
1388 // pull the player toward the grapple
CTFGrapplePull(edict_t * self)1389 void CTFGrapplePull(edict_t *self)
1390 {
1391 vec3_t hookdir, v;
1392 float vlen;
1393
1394 if (strcmp(self->owner->client->pers.weapon->classname, "weapon_grapple") == 0 &&
1395 !self->owner->client->newweapon &&
1396 self->owner->client->weaponstate != WEAPON_FIRING &&
1397 self->owner->client->weaponstate != WEAPON_ACTIVATING) {
1398 CTFResetGrapple(self);
1399 return;
1400 }
1401
1402 if (self->enemy) {
1403 if (self->enemy->solid == SOLID_NOT) {
1404 CTFResetGrapple(self);
1405 return;
1406 }
1407 if (self->enemy->solid == SOLID_BBOX) {
1408 VectorScale(self->enemy->size, 0.5, v);
1409 VectorAdd(v, self->enemy->s.origin, v);
1410 VectorAdd(v, self->enemy->mins, self->s.origin);
1411 gi.linkentity (self);
1412 } else
1413 VectorCopy(self->enemy->velocity, self->velocity);
1414 if (self->enemy->takedamage &&
1415 !CheckTeamDamage (self->enemy, self->owner)) {
1416 float volume = 1.0;
1417
1418 if (self->owner->client->silencer_shots)
1419 volume = 0.2;
1420
1421 T_Damage (self->enemy, self, self->owner, self->velocity, self->s.origin, vec3_origin, 1, 1, 0, MOD_GRAPPLE);
1422 gi.sound (self, CHAN_WEAPON, gi.soundindex("weapons/grapple/grhurt.wav"), volume, ATTN_NORM, 0);
1423 }
1424 if (self->enemy->deadflag) { // he died
1425 CTFResetGrapple(self);
1426 return;
1427 }
1428 }
1429
1430 CTFGrappleDrawCable(self);
1431
1432 if (self->owner->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
1433 // pull player toward grapple
1434 // this causes icky stuff with prediction, we need to extend
1435 // the prediction layer to include two new fields in the player
1436 // move stuff: a point and a velocity. The client should add
1437 // that velociy in the direction of the point
1438 vec3_t forward, up;
1439
1440 AngleVectors (self->owner->client->v_angle, forward, NULL, up);
1441 VectorCopy(self->owner->s.origin, v);
1442 v[2] += self->owner->viewheight;
1443 VectorSubtract (self->s.origin, v, hookdir);
1444
1445 vlen = VectorLength(hookdir);
1446
1447 if (self->owner->client->ctf_grapplestate == CTF_GRAPPLE_STATE_PULL &&
1448 vlen < 64) {
1449 float volume = 1.0;
1450
1451 if (self->owner->client->silencer_shots)
1452 volume = 0.2;
1453
1454 self->owner->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
1455 gi.sound (self->owner, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grhang.wav"), volume, ATTN_NORM, 0);
1456 self->owner->client->ctf_grapplestate = CTF_GRAPPLE_STATE_HANG;
1457 }
1458
1459 VectorNormalize (hookdir);
1460 VectorScale(hookdir, CTF_GRAPPLE_PULL_SPEED, hookdir);
1461 VectorCopy(hookdir, self->owner->velocity);
1462 SV_AddGravity(self->owner);
1463 }
1464 }
1465
CTFFireGrapple(edict_t * self,vec3_t start,vec3_t dir,int damage,int speed,int effect)1466 void CTFFireGrapple (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, int effect)
1467 {
1468 edict_t *grapple;
1469 trace_t tr;
1470
1471 VectorNormalize (dir);
1472
1473 grapple = G_Spawn();
1474 VectorCopy (start, grapple->s.origin);
1475 VectorCopy (start, grapple->s.old_origin);
1476 vectoangles (dir, grapple->s.angles);
1477 VectorScale (dir, speed, grapple->velocity);
1478 grapple->movetype = MOVETYPE_FLYMISSILE;
1479 grapple->clipmask = MASK_SHOT;
1480 grapple->solid = SOLID_BBOX;
1481 grapple->s.effects |= effect;
1482 VectorClear (grapple->mins);
1483 VectorClear (grapple->maxs);
1484 grapple->s.modelindex = gi.modelindex ("models/weapons/grapple/hook/tris.md2");
1485 // grapple->s.sound = gi.soundindex ("misc/lasfly.wav");
1486 grapple->owner = self;
1487 grapple->touch = CTFGrappleTouch;
1488 // grapple->nextthink = level.time + FRAMETIME;
1489 // grapple->think = CTFGrappleThink;
1490 grapple->dmg = damage;
1491 self->client->ctf_grapple = grapple;
1492 self->client->ctf_grapplestate = CTF_GRAPPLE_STATE_FLY; // we're firing, not on hook
1493 gi.linkentity (grapple);
1494
1495 tr = gi.trace (self->s.origin, NULL, NULL, grapple->s.origin, grapple, MASK_SHOT);
1496 if (tr.fraction < 1.0)
1497 {
1498 VectorMA (grapple->s.origin, -10, dir, grapple->s.origin);
1499 grapple->touch (grapple, tr.ent, NULL, NULL);
1500 }
1501 }
1502
CTFGrappleFire(edict_t * ent,vec3_t g_offset,int damage,int effect)1503 void CTFGrappleFire (edict_t *ent, vec3_t g_offset, int damage, int effect)
1504 {
1505 vec3_t forward, right;
1506 vec3_t start;
1507 vec3_t offset;
1508 float volume = 1.0;
1509
1510 if (ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY)
1511 return; // it's already out
1512
1513 AngleVectors (ent->client->v_angle, forward, right, NULL);
1514 // VectorSet(offset, 24, 16, ent->viewheight-8+2);
1515 VectorSet(offset, 24, 8, ent->viewheight-8+2);
1516 VectorAdd (offset, g_offset, offset);
1517 P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
1518
1519 VectorScale (forward, -2, ent->client->kick_origin);
1520 ent->client->kick_angles[0] = -1;
1521
1522 if (ent->client->silencer_shots)
1523 volume = 0.2;
1524
1525 gi.sound (ent, CHAN_RELIABLE+CHAN_WEAPON, gi.soundindex("weapons/grapple/grfire.wav"), volume, ATTN_NORM, 0);
1526 CTFFireGrapple (ent, start, forward, damage, CTF_GRAPPLE_SPEED, effect);
1527
1528 #if 0
1529 // send muzzle flash
1530 gi.WriteByte (svc_muzzleflash);
1531 gi.WriteShort (ent-g_edicts);
1532 gi.WriteByte (MZ_BLASTER);
1533 gi.multicast (ent->s.origin, MULTICAST_PVS);
1534 #endif
1535
1536 PlayerNoise(ent, start, PNOISE_WEAPON);
1537 }
1538
1539
CTFWeapon_Grapple_Fire(edict_t * ent)1540 void CTFWeapon_Grapple_Fire (edict_t *ent)
1541 {
1542 int damage;
1543
1544 damage = 10;
1545 CTFGrappleFire (ent, vec3_origin, damage, 0);
1546 ent->client->ps.gunframe++;
1547 }
1548
CTFWeapon_Grapple(edict_t * ent)1549 void CTFWeapon_Grapple (edict_t *ent)
1550 {
1551 static int pause_frames[] = {10, 18, 27, 0};
1552 static int fire_frames[] = {6, 0};
1553 int prevstate;
1554
1555 // if the the attack button is still down, stay in the firing frame
1556 if ((ent->client->buttons & BUTTON_ATTACK) &&
1557 ent->client->weaponstate == WEAPON_FIRING &&
1558 ent->client->ctf_grapple)
1559 ent->client->ps.gunframe = 9;
1560
1561 if (!(ent->client->buttons & BUTTON_ATTACK) &&
1562 ent->client->ctf_grapple) {
1563 CTFResetGrapple(ent->client->ctf_grapple);
1564 if (ent->client->weaponstate == WEAPON_FIRING)
1565 ent->client->weaponstate = WEAPON_READY;
1566 }
1567
1568
1569 if (ent->client->newweapon &&
1570 ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY &&
1571 ent->client->weaponstate == WEAPON_FIRING) {
1572 // he wants to change weapons while grappled
1573 ent->client->weaponstate = WEAPON_DROPPING;
1574 ent->client->ps.gunframe = 32;
1575 }
1576
1577 prevstate = ent->client->weaponstate;
1578 Weapon_Generic (ent, 5, 9, 31, 36, pause_frames, fire_frames,
1579 CTFWeapon_Grapple_Fire);
1580
1581 // if we just switched back to grapple, immediately go to fire frame
1582 if (prevstate == WEAPON_ACTIVATING &&
1583 ent->client->weaponstate == WEAPON_READY &&
1584 ent->client->ctf_grapplestate > CTF_GRAPPLE_STATE_FLY) {
1585 if (!(ent->client->buttons & BUTTON_ATTACK))
1586 ent->client->ps.gunframe = 9;
1587 else
1588 ent->client->ps.gunframe = 5;
1589 ent->client->weaponstate = WEAPON_FIRING;
1590 }
1591 }
1592
CTFTeam_f(edict_t * ent)1593 void CTFTeam_f (edict_t *ent)
1594 {
1595 char *t, *s;
1596 int desired_team;
1597
1598 t = gi.args();
1599 if (!*t) {
1600 safe_cprintf(ent, PRINT_HIGH, "You are on the %s team.\n",
1601 CTFTeamName(ent->client->resp.ctf_team));
1602 return;
1603 }
1604
1605 if (ctfgame.match > MATCH_SETUP) {
1606 safe_cprintf(ent, PRINT_HIGH, "Can't change teams in a match.\n");
1607 return;
1608 }
1609
1610 if (Q_stricmp(t, "red") == 0)
1611 desired_team = CTF_TEAM1;
1612 else if (Q_stricmp(t, "blue") == 0)
1613 desired_team = CTF_TEAM2;
1614 else {
1615 safe_cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
1616 return;
1617 }
1618
1619 if (ent->client->resp.ctf_team == desired_team) {
1620 safe_cprintf(ent, PRINT_HIGH, "You are already on the %s team.\n",
1621 CTFTeamName(ent->client->resp.ctf_team));
1622 return;
1623 }
1624
1625 ////
1626 ent->svflags = 0;
1627 ent->flags &= ~FL_GODMODE;
1628 ent->client->resp.ctf_team = desired_team;
1629 ent->client->resp.ctf_state = 0;
1630 s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
1631 CTFAssignSkin(ent, s);
1632
1633 if (ent->solid == SOLID_NOT) { // spectator
1634 PutClientInServer (ent);
1635 // add a teleportation effect
1636 ent->s.event = EV_PLAYER_TELEPORT;
1637 // hold in place briefly
1638 ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
1639 ent->client->ps.pmove.pm_time = 14;
1640 safe_bprintf(PRINT_HIGH, "%s joined the %s team.\n",
1641 ent->client->pers.netname, CTFTeamName(desired_team));
1642 return;
1643 }
1644
1645 ent->health = 0;
1646 player_die (ent, ent, ent, 100000, vec3_origin);
1647 // don't even bother waiting for death frames
1648 ent->deadflag = DEAD_DEAD;
1649 respawn (ent);
1650
1651 ent->client->resp.score = 0;
1652
1653 safe_bprintf(PRINT_HIGH, "%s changed to the %s team.\n",
1654 ent->client->pers.netname, CTFTeamName(desired_team));
1655 }
1656
1657 /*
1658 ==================
1659 CTFScoreboardMessage
1660 ==================
1661 */
CTFScoreboardMessage(edict_t * ent,edict_t * killer)1662 void CTFScoreboardMessage (edict_t *ent, edict_t *killer)
1663 {
1664 char entry[1024];
1665 char string[1400];
1666 int len;
1667 int i, j, k, n;
1668 int sorted[2][MAX_CLIENTS];
1669 int sortedscores[2][MAX_CLIENTS];
1670 int score, total[2], totalscore[2];
1671 int last[2];
1672 gclient_t *cl;
1673 edict_t *cl_ent;
1674 int team;
1675 int maxsize = 1000;
1676
1677 // sort the clients by team and score
1678 total[0] = total[1] = 0;
1679 last[0] = last[1] = 0;
1680 totalscore[0] = totalscore[1] = 0;
1681 for (i=0 ; i<game.maxclients ; i++)
1682 {
1683 cl_ent = g_edicts + 1 + i;
1684 if (!cl_ent->inuse)
1685 continue;
1686 if (game.clients[i].resp.ctf_team == CTF_TEAM1)
1687 team = 0;
1688 else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
1689 team = 1;
1690 else
1691 continue; // unknown team?
1692
1693 score = game.clients[i].resp.score;
1694 for (j=0 ; j<total[team] ; j++)
1695 {
1696 if (score > sortedscores[team][j])
1697 break;
1698 }
1699 for (k=total[team] ; k>j ; k--)
1700 {
1701 sorted[team][k] = sorted[team][k-1];
1702 sortedscores[team][k] = sortedscores[team][k-1];
1703 }
1704 sorted[team][j] = i;
1705 sortedscores[team][j] = score;
1706 totalscore[team] += score;
1707 total[team]++;
1708 }
1709
1710 // print level name and exit rules
1711 // add the clients in sorted order
1712 *string = 0;
1713 len = 0;
1714
1715 // team one
1716 sprintf(string, "if 24 xv 8 yv 8 pic 24 endif "
1717 "xv 40 yv 28 string \"%4d/%-3d\" "
1718 "xv 98 yv 12 num 2 18 "
1719 "if 25 xv 168 yv 8 pic 25 endif "
1720 "xv 200 yv 28 string \"%4d/%-3d\" "
1721 "xv 256 yv 12 num 2 20 ",
1722 totalscore[0], total[0],
1723 totalscore[1], total[1]);
1724 len = strlen(string);
1725
1726 for (i=0 ; i<16 ; i++)
1727 {
1728 if (i >= total[0] && i >= total[1])
1729 break; // we're done
1730
1731 #if 0 //ndef NEW_SCORE
1732 // set up y
1733 sprintf(entry, "yv %d ", 42 + i * 8);
1734 if (maxsize - len > strlen(entry)) {
1735 strcat(string, entry);
1736 len = strlen(string);
1737 }
1738 #else
1739 *entry = 0;
1740 #endif
1741
1742 // left side
1743 if (i < total[0]) {
1744 cl = &game.clients[sorted[0][i]];
1745 cl_ent = g_edicts + 1 + sorted[0][i];
1746
1747 #if 0 //ndef NEW_SCORE
1748 sprintf(entry+strlen(entry),
1749 "xv 0 %s \"%3d %3d %-12.12s\" ",
1750 (cl_ent == ent) ? "string2" : "string",
1751 cl->resp.score,
1752 (cl->ping > 999) ? 999 : cl->ping,
1753 cl->pers.netname);
1754
1755 if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
1756 strcat(entry, "xv 56 picn sbfctf2 ");
1757 #else
1758 sprintf(entry+strlen(entry),
1759 "ctf 0 %d %d %d %d ",
1760 42 + i * 8,
1761 sorted[0][i],
1762 cl->resp.score,
1763 cl->ping > 999 ? 999 : cl->ping);
1764
1765 if (cl_ent->client->pers.inventory[ITEM_INDEX(flag2_item)])
1766 sprintf(entry + strlen(entry), "xv 56 yv %d picn sbfctf2 ",
1767 42 + i * 8);
1768 #endif
1769
1770 if (maxsize - len > strlen(entry)) {
1771 strcat(string, entry);
1772 len = strlen(string);
1773 last[0] = i;
1774 }
1775 }
1776
1777 *entry = 0; //jabot092
1778
1779 // right side
1780 if (i < total[1]) {
1781 cl = &game.clients[sorted[1][i]];
1782 cl_ent = g_edicts + 1 + sorted[1][i];
1783
1784 #if 0 //ndef NEW_SCORE
1785 sprintf(entry+strlen(entry),
1786 "xv 160 %s \"%3d %3d %-12.12s\" ",
1787 (cl_ent == ent) ? "string2" : "string",
1788 cl->resp.score,
1789 (cl->ping > 999) ? 999 : cl->ping,
1790 cl->pers.netname);
1791
1792 if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
1793 strcat(entry, "xv 216 picn sbfctf1 ");
1794
1795 #else
1796
1797 sprintf(entry+strlen(entry),
1798 "ctf 160 %d %d %d %d ",
1799 42 + i * 8,
1800 sorted[1][i],
1801 cl->resp.score,
1802 cl->ping > 999 ? 999 : cl->ping);
1803
1804 if (cl_ent->client->pers.inventory[ITEM_INDEX(flag1_item)])
1805 sprintf(entry + strlen(entry), "xv 216 yv %d picn sbfctf1 ",
1806 42 + i * 8);
1807 #endif
1808 if (maxsize - len > strlen(entry)) {
1809 strcat(string, entry);
1810 len = strlen(string);
1811 last[1] = i;
1812 }
1813 }
1814 }
1815
1816 // put in spectators if we have enough room
1817 if (last[0] > last[1])
1818 j = last[0];
1819 else
1820 j = last[1];
1821 j = (j + 2) * 8 + 42;
1822
1823 k = n = 0;
1824 if (maxsize - len > 50) {
1825 for (i = 0; i < maxclients->value; i++) {
1826 cl_ent = g_edicts + 1 + i;
1827 cl = &game.clients[i];
1828 if (!cl_ent->inuse ||
1829 cl_ent->solid != SOLID_NOT ||
1830 cl_ent->client->resp.ctf_team != CTF_NOTEAM)
1831 continue;
1832
1833 *entry = 0; //jabot092
1834
1835 if (!k) {
1836 k = 1;
1837 sprintf(entry, "xv 0 yv %d string2 \"Spectators\" ", j);
1838 strcat(string, entry);
1839 len = strlen(string);
1840 j += 8;
1841 }
1842
1843 sprintf(entry+strlen(entry),
1844 "ctf %d %d %d %d %d ",
1845 (n & 1) ? 160 : 0, // x
1846 j, // y
1847 i, // playernum
1848 cl->resp.score,
1849 cl->ping > 999 ? 999 : cl->ping);
1850 if (maxsize - len > strlen(entry)) {
1851 strcat(string, entry);
1852 len = strlen(string);
1853 }
1854
1855 if (n & 1)
1856 j += 8;
1857 n++;
1858 }
1859 }
1860
1861 if (total[0] - last[0] > 1) // couldn't fit everyone
1862 sprintf(string + strlen(string), "xv 8 yv %d string \"..and %d more\" ",
1863 42 + (last[0]+1)*8, total[0] - last[0] - 1);
1864 if (total[1] - last[1] > 1) // couldn't fit everyone
1865 sprintf(string + strlen(string), "xv 168 yv %d string \"..and %d more\" ",
1866 42 + (last[1]+1)*8, total[1] - last[1] - 1);
1867
1868 gi.WriteByte (svc_layout);
1869 gi.WriteString (string);
1870 }
1871
1872 /*------------------------------------------------------------------------*/
1873 /* TECH */
1874 /*------------------------------------------------------------------------*/
1875
CTFHasTech(edict_t * who)1876 void CTFHasTech(edict_t *who)
1877 {
1878 if (level.time - who->client->ctf_lasttechmsg > 2) {
1879 safe_centerprintf(who, "You already have a TECH powerup.");
1880 who->client->ctf_lasttechmsg = level.time;
1881 }
1882 }
1883
CTFWhat_Tech(edict_t * ent)1884 gitem_t *CTFWhat_Tech(edict_t *ent)
1885 {
1886 gitem_t *tech;
1887 int i;
1888
1889 i = 0;
1890 while (tnames[i]) {
1891 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
1892 ent->client->pers.inventory[ITEM_INDEX(tech)]) {
1893 return tech;
1894 }
1895 i++;
1896 }
1897 return NULL;
1898 }
1899
CTFPickup_Tech(edict_t * ent,edict_t * other)1900 qboolean CTFPickup_Tech (edict_t *ent, edict_t *other)
1901 {
1902 gitem_t *tech;
1903 int i;
1904
1905 i = 0;
1906 while (tnames[i]) {
1907 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
1908 other->client->pers.inventory[ITEM_INDEX(tech)]) {
1909 CTFHasTech(other);
1910 return false; // has this one
1911 }
1912 i++;
1913 }
1914
1915 // client only gets one tech
1916 other->client->pers.inventory[ITEM_INDEX(ent->item)]++;
1917 other->client->ctf_regentime = level.time;
1918 return true;
1919 }
1920
1921 static void SpawnTech(gitem_t *item, edict_t *spot);
1922
FindTechSpawn(void)1923 static edict_t *FindTechSpawn(void)
1924 {
1925 edict_t *spot = NULL;
1926 int i = rand() % 16;
1927
1928 while (i--)
1929 spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
1930 if (!spot)
1931 spot = G_Find (spot, FOFS(classname), "info_player_deathmatch");
1932 return spot;
1933 }
1934
TechThink(edict_t * tech)1935 static void TechThink(edict_t *tech)
1936 {
1937 edict_t *spot;
1938
1939 if ((spot = FindTechSpawn()) != NULL) {
1940 SpawnTech(tech->item, spot);
1941 G_FreeEdict(tech);
1942 } else {
1943 tech->nextthink = level.time + CTF_TECH_TIMEOUT;
1944 tech->think = TechThink;
1945 }
1946 }
1947
CTFDrop_Tech(edict_t * ent,gitem_t * item)1948 void CTFDrop_Tech(edict_t *ent, gitem_t *item)
1949 {
1950 edict_t *tech;
1951
1952 tech = Drop_Item(ent, item);
1953 tech->nextthink = level.time + CTF_TECH_TIMEOUT;
1954 tech->think = TechThink;
1955 ent->client->pers.inventory[ITEM_INDEX(item)] = 0;
1956 }
1957
CTFDeadDropTech(edict_t * ent)1958 void CTFDeadDropTech(edict_t *ent)
1959 {
1960 gitem_t *tech;
1961 edict_t *dropped;
1962 int i;
1963
1964 i = 0;
1965 while (tnames[i]) {
1966 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
1967 ent->client->pers.inventory[ITEM_INDEX(tech)]) {
1968 dropped = Drop_Item(ent, tech);
1969 // hack the velocity to make it bounce random
1970 dropped->velocity[0] = (rand() % 600) - 300;
1971 dropped->velocity[1] = (rand() % 600) - 300;
1972 dropped->nextthink = level.time + CTF_TECH_TIMEOUT;
1973 dropped->think = TechThink;
1974 dropped->owner = NULL;
1975 ent->client->pers.inventory[ITEM_INDEX(tech)] = 0;
1976 }
1977 i++;
1978 }
1979 }
1980
SpawnTech(gitem_t * item,edict_t * spot)1981 static void SpawnTech(gitem_t *item, edict_t *spot)
1982 {
1983 edict_t *ent;
1984 vec3_t forward, right;
1985 vec3_t angles;
1986
1987 ent = G_Spawn();
1988
1989 ent->classname = item->classname;
1990 ent->item = item;
1991 ent->spawnflags = DROPPED_ITEM;
1992 ent->s.effects = item->world_model_flags;
1993 ent->s.renderfx = RF_GLOW;
1994 VectorSet (ent->mins, -15, -15, -15);
1995 VectorSet (ent->maxs, 15, 15, 15);
1996 gi.setmodel (ent, ent->item->world_model);
1997 ent->solid = SOLID_TRIGGER;
1998 ent->movetype = MOVETYPE_TOSS;
1999 ent->touch = Touch_Item;
2000 ent->owner = ent;
2001
2002 angles[0] = 0;
2003 angles[1] = rand() % 360;
2004 angles[2] = 0;
2005
2006 AngleVectors (angles, forward, right, NULL);
2007 VectorCopy (spot->s.origin, ent->s.origin);
2008 ent->s.origin[2] += 16;
2009 VectorScale (forward, 100, ent->velocity);
2010 ent->velocity[2] = 300;
2011
2012 ent->nextthink = level.time + CTF_TECH_TIMEOUT;
2013 ent->think = TechThink;
2014
2015 gi.linkentity (ent);
2016 }
2017
SpawnTechs(edict_t * ent)2018 static void SpawnTechs(edict_t *ent)
2019 {
2020 gitem_t *tech;
2021 edict_t *spot;
2022 int i;
2023
2024 i = 0;
2025 while (tnames[i]) {
2026 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
2027 (spot = FindTechSpawn()) != NULL)
2028 SpawnTech(tech, spot);
2029 i++;
2030 }
2031 if (ent)
2032 G_FreeEdict(ent);
2033 }
2034
2035 // frees the passed edict!
CTFRespawnTech(edict_t * ent)2036 void CTFRespawnTech(edict_t *ent)
2037 {
2038 edict_t *spot;
2039
2040 if ((spot = FindTechSpawn()) != NULL)
2041 SpawnTech(ent->item, spot);
2042 G_FreeEdict(ent);
2043 }
2044
CTFSetupTechSpawn(void)2045 void CTFSetupTechSpawn(void)
2046 {
2047 edict_t *ent;
2048
2049 if (((int)dmflags->value & DF_CTF_NO_TECH))
2050 return;
2051
2052 ent = G_Spawn();
2053 ent->nextthink = level.time + 2;
2054 ent->think = SpawnTechs;
2055 }
2056
CTFResetTech(void)2057 void CTFResetTech(void)
2058 {
2059 edict_t *ent;
2060 int i;
2061
2062 for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
2063 if (ent->inuse)
2064 if (ent->item && (ent->item->flags & IT_TECH))
2065 G_FreeEdict(ent);
2066 }
2067 SpawnTechs(NULL);
2068 }
2069
CTFApplyResistance(edict_t * ent,int dmg)2070 int CTFApplyResistance(edict_t *ent, int dmg)
2071 {
2072 static gitem_t *tech = NULL;
2073 float volume = 1.0;
2074
2075 if (ent->client && ent->client->silencer_shots)
2076 volume = 0.2;
2077
2078 if (!tech)
2079 tech = FindItemByClassname("item_tech1");
2080 if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
2081 // make noise
2082 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech1.wav"), volume, ATTN_NORM, 0);
2083 return dmg / 2;
2084 }
2085 return dmg;
2086 }
2087
CTFApplyStrength(edict_t * ent,int dmg)2088 int CTFApplyStrength(edict_t *ent, int dmg)
2089 {
2090 static gitem_t *tech = NULL;
2091
2092 if (!tech)
2093 tech = FindItemByClassname("item_tech2");
2094 if (dmg && tech && ent->client && ent->client->pers.inventory[ITEM_INDEX(tech)]) {
2095 return dmg * 2;
2096 }
2097 return dmg;
2098 }
2099
CTFApplyStrengthSound(edict_t * ent)2100 qboolean CTFApplyStrengthSound(edict_t *ent)
2101 {
2102 static gitem_t *tech = NULL;
2103 float volume = 1.0;
2104
2105 if (ent->client && ent->client->silencer_shots)
2106 volume = 0.2;
2107
2108 if (!tech)
2109 tech = FindItemByClassname("item_tech2");
2110 if (tech && ent->client &&
2111 ent->client->pers.inventory[ITEM_INDEX(tech)]) {
2112 if (ent->client->ctf_techsndtime < level.time) {
2113 ent->client->ctf_techsndtime = level.time + 1;
2114 if (ent->client->quad_framenum > level.framenum)
2115 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2x.wav"), volume, ATTN_NORM, 0);
2116 else
2117 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech2.wav"), volume, ATTN_NORM, 0);
2118 }
2119 return true;
2120 }
2121 return false;
2122 }
2123
2124
CTFApplyHaste(edict_t * ent)2125 qboolean CTFApplyHaste(edict_t *ent)
2126 {
2127 static gitem_t *tech = NULL;
2128
2129 if (!tech)
2130 tech = FindItemByClassname("item_tech3");
2131 if (tech && ent->client &&
2132 ent->client->pers.inventory[ITEM_INDEX(tech)])
2133 return true;
2134 return false;
2135 }
2136
CTFApplyHasteSound(edict_t * ent)2137 void CTFApplyHasteSound(edict_t *ent)
2138 {
2139 static gitem_t *tech = NULL;
2140 float volume = 1.0;
2141
2142 if (ent->client && ent->client->silencer_shots)
2143 volume = 0.2;
2144
2145 if (!tech)
2146 tech = FindItemByClassname("item_tech3");
2147 if (tech && ent->client &&
2148 ent->client->pers.inventory[ITEM_INDEX(tech)] &&
2149 ent->client->ctf_techsndtime < level.time) {
2150 ent->client->ctf_techsndtime = level.time + 1;
2151 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech3.wav"), volume, ATTN_NORM, 0);
2152 }
2153 }
2154
CTFApplyRegeneration(edict_t * ent)2155 void CTFApplyRegeneration(edict_t *ent)
2156 {
2157 static gitem_t *tech = NULL;
2158 qboolean noise = false;
2159 gclient_t *client;
2160 int index;
2161 float volume = 1.0;
2162
2163 client = ent->client;
2164 if (!client)
2165 return;
2166
2167 if (ent->client->silencer_shots)
2168 volume = 0.2;
2169
2170 if (!tech)
2171 tech = FindItemByClassname("item_tech4");
2172 if (tech && client->pers.inventory[ITEM_INDEX(tech)]) {
2173 if (client->ctf_regentime < level.time) {
2174 client->ctf_regentime = level.time;
2175 if (ent->health < 150) {
2176 ent->health += 5;
2177 if (ent->health > 150)
2178 ent->health = 150;
2179 client->ctf_regentime += 0.5;
2180 noise = true;
2181 }
2182 index = ArmorIndex (ent);
2183 if (index && client->pers.inventory[index] < 150) {
2184 client->pers.inventory[index] += 5;
2185 if (client->pers.inventory[index] > 150)
2186 client->pers.inventory[index] = 150;
2187 client->ctf_regentime += 0.5;
2188 noise = true;
2189 }
2190 }
2191 if (noise && ent->client->ctf_techsndtime < level.time) {
2192 ent->client->ctf_techsndtime = level.time + 1;
2193 gi.sound(ent, CHAN_VOICE, gi.soundindex("ctf/tech4.wav"), volume, ATTN_NORM, 0);
2194 }
2195 }
2196 }
2197
CTFHasRegeneration(edict_t * ent)2198 qboolean CTFHasRegeneration(edict_t *ent)
2199 {
2200 static gitem_t *tech = NULL;
2201
2202 if (!tech)
2203 tech = FindItemByClassname("item_tech4");
2204 if (tech && ent->client &&
2205 ent->client->pers.inventory[ITEM_INDEX(tech)])
2206 return true;
2207 return false;
2208 }
2209
2210 /*
2211 ======================================================================
2212
2213 SAY_TEAM
2214
2215 ======================================================================
2216 */
2217
2218 // This array is in 'importance order', it indicates what items are
2219 // more important when reporting their names.
2220 struct {
2221 char *classname;
2222 int priority;
2223 } loc_names[] =
2224 {
2225 { "item_flag_team1", 1 },
2226 { "item_flag_team2", 1 },
2227 { "item_quad", 2 },
2228 { "item_invulnerability", 2 },
2229 { "weapon_bfg", 3 },
2230 { "weapon_railgun", 4 },
2231 { "weapon_rocketlauncher", 4 },
2232 { "weapon_hyperblaster", 4 },
2233 { "weapon_chaingun", 4 },
2234 { "weapon_grenadelauncher", 4 },
2235 { "weapon_machinegun", 4 },
2236 { "weapon_supershotgun", 4 },
2237 { "weapon_shotgun", 4 },
2238 { "item_power_screen", 5 },
2239 { "item_power_shield", 5 },
2240 { "item_armor_body", 6 },
2241 { "item_armor_combat", 6 },
2242 { "item_armor_jacket", 6 },
2243 { "item_silencer", 7 },
2244 { "item_breather", 7 },
2245 { "item_enviro", 7 },
2246 { "item_adrenaline", 7 },
2247 { "item_bandolier", 8 },
2248 { "item_pack", 8 },
2249 { NULL, 0 }
2250 };
2251
2252
CTFSay_Team_Location(edict_t * who,char * buf)2253 static void CTFSay_Team_Location(edict_t *who, char *buf)
2254 {
2255 edict_t *what = NULL;
2256 edict_t *hot = NULL;
2257 float hotdist = 999999, newdist;
2258 vec3_t v;
2259 int hotindex = 999;
2260 int i;
2261 gitem_t *item;
2262 int nearteam = -1;
2263 edict_t *flag1, *flag2;
2264 qboolean hotsee = false;
2265 qboolean cansee;
2266
2267 while ((what = loc_findradius(what, who->s.origin, 1024)) != NULL) {
2268 // find what in loc_classnames
2269 for (i = 0; loc_names[i].classname; i++)
2270 if (strcmp(what->classname, loc_names[i].classname) == 0)
2271 break;
2272 if (!loc_names[i].classname)
2273 continue;
2274 // something we can see get priority over something we can't
2275 cansee = loc_CanSee(what, who);
2276 if (cansee && !hotsee) {
2277 hotsee = true;
2278 hotindex = loc_names[i].priority;
2279 hot = what;
2280 VectorSubtract(what->s.origin, who->s.origin, v);
2281 hotdist = VectorLength(v);
2282 continue;
2283 }
2284 // if we can't see this, but we have something we can see, skip it
2285 if (hotsee && !cansee)
2286 continue;
2287 if (hotsee && hotindex < loc_names[i].priority)
2288 continue;
2289 VectorSubtract(what->s.origin, who->s.origin, v);
2290 newdist = VectorLength(v);
2291 if (newdist < hotdist ||
2292 (cansee && loc_names[i].priority < hotindex)) {
2293 hot = what;
2294 hotdist = newdist;
2295 hotindex = i;
2296 hotsee = loc_CanSee(hot, who);
2297 }
2298 }
2299
2300 if (!hot) {
2301 strcpy(buf, "nowhere");
2302 return;
2303 }
2304
2305 // we now have the closest item
2306 // see if there's more than one in the map, if so
2307 // we need to determine what team is closest
2308 what = NULL;
2309 while ((what = G_Find(what, FOFS(classname), hot->classname)) != NULL) {
2310 if (what == hot)
2311 continue;
2312 // if we are here, there is more than one, find out if hot
2313 // is closer to red flag or blue flag
2314 if ((flag1 = G_Find(NULL, FOFS(classname), "item_flag_team1")) != NULL &&
2315 (flag2 = G_Find(NULL, FOFS(classname), "item_flag_team2")) != NULL) {
2316 VectorSubtract(hot->s.origin, flag1->s.origin, v);
2317 hotdist = VectorLength(v);
2318 VectorSubtract(hot->s.origin, flag2->s.origin, v);
2319 newdist = VectorLength(v);
2320 if (hotdist < newdist)
2321 nearteam = CTF_TEAM1;
2322 else if (hotdist > newdist)
2323 nearteam = CTF_TEAM2;
2324 }
2325 break;
2326 }
2327
2328 if ((item = FindItemByClassname(hot->classname)) == NULL) {
2329 strcpy(buf, "nowhere");
2330 return;
2331 }
2332
2333 // in water?
2334 if (who->waterlevel)
2335 strcpy(buf, "in the water ");
2336 else
2337 *buf = 0;
2338
2339 // near or above
2340 VectorSubtract(who->s.origin, hot->s.origin, v);
2341 if (fabs(v[2]) > fabs(v[0]) && fabs(v[2]) > fabs(v[1]))
2342 if (v[2] > 0)
2343 strcat(buf, "above ");
2344 else
2345 strcat(buf, "below ");
2346 else
2347 strcat(buf, "near ");
2348
2349 if (nearteam == CTF_TEAM1)
2350 strcat(buf, "the red ");
2351 else if (nearteam == CTF_TEAM2)
2352 strcat(buf, "the blue ");
2353 else
2354 strcat(buf, "the ");
2355
2356 strcat(buf, item->pickup_name);
2357 }
2358
CTFSay_Team_Armor(edict_t * who,char * buf)2359 static void CTFSay_Team_Armor(edict_t *who, char *buf)
2360 {
2361 gitem_t *item;
2362 int index, cells;
2363 int power_armor_type;
2364
2365 *buf = 0;
2366
2367 power_armor_type = PowerArmorType (who);
2368 if (power_armor_type)
2369 {
2370 cells = who->client->pers.inventory[ITEM_INDEX(FindItem ("cells"))];
2371 if (cells)
2372 sprintf(buf+strlen(buf), "%s with %i cells ",
2373 (power_armor_type == POWER_ARMOR_SCREEN) ?
2374 "Power Screen" : "Power Shield", cells);
2375 }
2376
2377 index = ArmorIndex (who);
2378 if (index)
2379 {
2380 item = GetItemByIndex (index);
2381 if (item) {
2382 if (*buf)
2383 strcat(buf, "and ");
2384 sprintf(buf+strlen(buf), "%i units of %s",
2385 who->client->pers.inventory[index], item->pickup_name);
2386 }
2387 }
2388
2389 if (!*buf)
2390 strcpy(buf, "no armor");
2391 }
2392
CTFSay_Team_Health(edict_t * who,char * buf)2393 static void CTFSay_Team_Health(edict_t *who, char *buf)
2394 {
2395 if (who->health <= 0)
2396 strcpy(buf, "dead");
2397 else
2398 sprintf(buf, "%i health", who->health);
2399 }
2400
CTFSay_Team_Tech(edict_t * who,char * buf)2401 static void CTFSay_Team_Tech(edict_t *who, char *buf)
2402 {
2403 gitem_t *tech;
2404 int i;
2405
2406 // see if the player has a tech powerup
2407 i = 0;
2408 while (tnames[i]) {
2409 if ((tech = FindItemByClassname(tnames[i])) != NULL &&
2410 who->client->pers.inventory[ITEM_INDEX(tech)]) {
2411 sprintf(buf, "the %s", tech->pickup_name);
2412 return;
2413 }
2414 i++;
2415 }
2416 strcpy(buf, "no powerup");
2417 }
2418
CTFSay_Team_Weapon(edict_t * who,char * buf)2419 static void CTFSay_Team_Weapon(edict_t *who, char *buf)
2420 {
2421 if (who->client->pers.weapon)
2422 strcpy(buf, who->client->pers.weapon->pickup_name);
2423 else
2424 strcpy(buf, "none");
2425 }
2426
CTFSay_Team_Sight(edict_t * who,char * buf)2427 static void CTFSay_Team_Sight(edict_t *who, char *buf)
2428 {
2429 int i;
2430 edict_t *targ;
2431 int n = 0;
2432 char s[1024];
2433 char s2[1024];
2434
2435 *s = *s2 = 0;
2436 for (i = 1; i <= maxclients->value; i++) {
2437 targ = g_edicts + i;
2438 if (!targ->inuse ||
2439 targ == who ||
2440 !loc_CanSee(targ, who))
2441 continue;
2442 if (*s2) {
2443 if (strlen(s) + strlen(s2) + 3 < sizeof(s)) {
2444 if (n)
2445 strcat(s, ", ");
2446 strcat(s, s2);
2447 *s2 = 0;
2448 }
2449 n++;
2450 }
2451 strcpy(s2, targ->client->pers.netname);
2452 }
2453 if (*s2) {
2454 if (strlen(s) + strlen(s2) + 6 < sizeof(s)) {
2455 if (n)
2456 strcat(s, " and ");
2457 strcat(s, s2);
2458 }
2459 strcpy(buf, s);
2460 } else
2461 strcpy(buf, "no one");
2462 }
2463
CTFSay_Team(edict_t * who,char * msg)2464 void CTFSay_Team(edict_t *who, char *msg)
2465 {
2466 char outmsg[256];
2467 char buf[256];
2468 int i;
2469 char *p;
2470 edict_t *cl_ent;
2471
2472 if (CheckFlood(who))
2473 return;
2474
2475 outmsg[0] = 0;
2476
2477 if (*msg == '\"') {
2478 msg[strlen(msg) - 1] = 0;
2479 msg++;
2480 }
2481
2482 for (p = outmsg; *msg && (p - outmsg) < sizeof(outmsg) - 2; msg++) {
2483 if (*msg == '%') {
2484 switch (*++msg) {
2485 case 'l' :
2486 case 'L' :
2487 CTFSay_Team_Location(who, buf);
2488 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2489 strcpy(p, buf);
2490 p += strlen(buf);
2491 }
2492 break;
2493 case 'a' :
2494 case 'A' :
2495 CTFSay_Team_Armor(who, buf);
2496 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2497 strcpy(p, buf);
2498 p += strlen(buf);
2499 }
2500 break;
2501 case 'h' :
2502 case 'H' :
2503 CTFSay_Team_Health(who, buf);
2504 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2505 strcpy(p, buf);
2506 p += strlen(buf);
2507 }
2508 break;
2509 case 't' :
2510 case 'T' :
2511 CTFSay_Team_Tech(who, buf);
2512 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2513 strcpy(p, buf);
2514 p += strlen(buf);
2515 }
2516 break;
2517 case 'w' :
2518 case 'W' :
2519 CTFSay_Team_Weapon(who, buf);
2520 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2521 strcpy(p, buf);
2522 p += strlen(buf);
2523 }
2524 break;
2525
2526 case 'n' :
2527 case 'N' :
2528 CTFSay_Team_Sight(who, buf);
2529 if (strlen(buf) + (p - outmsg) < sizeof(outmsg) - 2) {
2530 strcpy(p, buf);
2531 p += strlen(buf);
2532 }
2533 break;
2534
2535 default :
2536 *p++ = *msg;
2537 }
2538 } else
2539 *p++ = *msg;
2540 }
2541 *p = 0;
2542
2543 for (i = 0; i < maxclients->value; i++) {
2544 cl_ent = g_edicts + 1 + i;
2545 if (!cl_ent->inuse)
2546 continue;
2547 if (cl_ent->client->resp.ctf_team == who->client->resp.ctf_team)
2548 safe_cprintf(cl_ent, PRINT_CHAT, "(%s): %s\n",
2549 who->client->pers.netname, outmsg);
2550 }
2551 }
2552
2553 /*-----------------------------------------------------------------------*/
2554 /*QUAKED misc_ctf_banner (1 .5 0) (-4 -64 0) (4 64 248) TEAM2
2555 The origin is the bottom of the banner.
2556 The banner is 248 tall.
2557 */
misc_ctf_banner_think(edict_t * ent)2558 static void misc_ctf_banner_think (edict_t *ent)
2559 {
2560 ent->s.frame = (ent->s.frame + 1) % 16;
2561 ent->nextthink = level.time + FRAMETIME;
2562 }
2563
SP_misc_ctf_banner(edict_t * ent)2564 void SP_misc_ctf_banner (edict_t *ent)
2565 {
2566 ent->movetype = MOVETYPE_NONE;
2567 ent->solid = SOLID_NOT;
2568 ent->s.modelindex = gi.modelindex ("models/ctf/banner/tris.md2");
2569 if (ent->spawnflags & 1) // team2
2570 ent->s.skinnum = 1;
2571
2572 ent->s.frame = rand() % 16;
2573 gi.linkentity (ent);
2574
2575 ent->think = misc_ctf_banner_think;
2576 ent->nextthink = level.time + FRAMETIME;
2577 }
2578
2579 /*QUAKED misc_ctf_small_banner (1 .5 0) (-4 -32 0) (4 32 124) TEAM2
2580 The origin is the bottom of the banner.
2581 The banner is 124 tall.
2582 */
SP_misc_ctf_small_banner(edict_t * ent)2583 void SP_misc_ctf_small_banner (edict_t *ent)
2584 {
2585 ent->movetype = MOVETYPE_NONE;
2586 ent->solid = SOLID_NOT;
2587 ent->s.modelindex = gi.modelindex ("models/ctf/banner/small.md2");
2588 if (ent->spawnflags & 1) // team2
2589 ent->s.skinnum = 1;
2590
2591 ent->s.frame = rand() % 16;
2592 gi.linkentity (ent);
2593
2594 ent->think = misc_ctf_banner_think;
2595 ent->nextthink = level.time + FRAMETIME;
2596 }
2597
2598 /*-----------------------------------------------------------------------*/
2599
SetLevelName(pmenu_t * p)2600 static void SetLevelName(pmenu_t *p)
2601 {
2602 static char levelname[33];
2603
2604 levelname[0] = '*';
2605 if (g_edicts[0].message)
2606 strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
2607 else
2608 strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
2609 levelname[sizeof(levelname) - 1] = 0;
2610 p->text = levelname;
2611 }
2612
2613
2614 /*-----------------------------------------------------------------------*/
2615
2616
2617 /* ELECTIONS */
2618
CTFBeginElection(edict_t * ent,elect_t type,char * msg)2619 qboolean CTFBeginElection(edict_t *ent, elect_t type, char *msg)
2620 {
2621 int i;
2622 int count;
2623 edict_t *e;
2624
2625 if (electpercentage->value == 0) {
2626 safe_cprintf(ent, PRINT_HIGH, "Elections are disabled, only an admin can process this action.\n");
2627 return false;
2628 }
2629
2630
2631 if (ctfgame.election != ELECT_NONE) {
2632 safe_cprintf(ent, PRINT_HIGH, "Election already in progress.\n");
2633 return false;
2634 }
2635
2636 // clear votes
2637 count = 0;
2638 for (i = 1; i <= maxclients->value; i++) {
2639 e = g_edicts + i;
2640 e->client->resp.voted = false;
2641 if (e->inuse)
2642 count++;
2643 }
2644
2645 if (count < 2) {
2646 safe_cprintf(ent, PRINT_HIGH, "Not enough players for election.\n");
2647 return false;
2648 }
2649
2650 ctfgame.etarget = ent;
2651 ctfgame.election = type;
2652 ctfgame.evotes = 0;
2653 ctfgame.needvotes = (count * electpercentage->value) / 100;
2654 ctfgame.electtime = level.time + 20; // twenty seconds for election
2655 strncpy(ctfgame.emsg, msg, sizeof(ctfgame.emsg) - 1);
2656
2657 // tell everyone
2658 safe_bprintf(PRINT_CHAT, "%s\n", ctfgame.emsg);
2659 safe_bprintf(PRINT_HIGH, "Type YES or NO to vote on this request.\n");
2660 safe_bprintf(PRINT_HIGH, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
2661 (int)(ctfgame.electtime - level.time));
2662
2663 return true;
2664 }
2665
2666 void DoRespawn (edict_t *ent);
2667
CTFResetAllPlayers(void)2668 void CTFResetAllPlayers(void)
2669 {
2670 int i;
2671 edict_t *ent;
2672
2673 for (i = 1; i <= maxclients->value; i++) {
2674 ent = g_edicts + i;
2675 if (!ent->inuse)
2676 continue;
2677
2678 if (ent->client->menu)
2679 PMenu_Close(ent);
2680
2681 CTFPlayerResetGrapple(ent);
2682 CTFDeadDropFlag(ent);
2683 CTFDeadDropTech(ent);
2684
2685 ent->client->resp.ctf_team = CTF_NOTEAM;
2686 ent->client->resp.ready = false;
2687
2688 ent->svflags = 0;
2689 ent->flags &= ~FL_GODMODE;
2690 PutClientInServer(ent);
2691 }
2692
2693 // reset the level
2694 CTFResetTech();
2695 CTFResetFlags();
2696
2697 for (ent = g_edicts + 1, i = 1; i < globals.num_edicts; i++, ent++) {
2698 if (ent->inuse && !ent->client) {
2699 if (ent->solid == SOLID_NOT && ent->think == DoRespawn &&
2700 ent->nextthink >= level.time) {
2701 ent->nextthink = 0;
2702 DoRespawn(ent);
2703 }
2704 }
2705 }
2706 if (ctfgame.match == MATCH_SETUP)
2707 ctfgame.matchtime = level.time + matchsetuptime->value * 60;
2708 }
2709
CTFAssignGhost(edict_t * ent)2710 void CTFAssignGhost(edict_t *ent)
2711 {
2712 int ghost, i;
2713
2714 for (ghost = 0; ghost < MAX_CLIENTS; ghost++)
2715 if (!ctfgame.ghosts[ghost].code)
2716 break;
2717 if (ghost == MAX_CLIENTS)
2718 return;
2719 ctfgame.ghosts[ghost].team = ent->client->resp.ctf_team;
2720 ctfgame.ghosts[ghost].score = 0;
2721 for (;;) {
2722 ctfgame.ghosts[ghost].code = 10000 + (rand() % 90000);
2723 for (i = 0; i < MAX_CLIENTS; i++)
2724 if (i != ghost && ctfgame.ghosts[i].code == ctfgame.ghosts[ghost].code)
2725 break;
2726 if (i == MAX_CLIENTS)
2727 break;
2728 }
2729 ctfgame.ghosts[ghost].ent = ent;
2730 strcpy(ctfgame.ghosts[ghost].netname, ent->client->pers.netname);
2731 ent->client->resp.ghost = ctfgame.ghosts + ghost;
2732 safe_cprintf(ent, PRINT_CHAT, "Your ghost code is **** %d ****\n", ctfgame.ghosts[ghost].code);
2733 safe_cprintf(ent, PRINT_HIGH, "If you lose connection, you can rejoin with your score "
2734 "intact by typing \"ghost %d\".\n", ctfgame.ghosts[ghost].code);
2735 }
2736
2737 // start a match
CTFStartMatch(void)2738 void CTFStartMatch(void)
2739 {
2740 int i;
2741 edict_t *ent;
2742 int ghost = 0;
2743
2744 ctfgame.match = MATCH_GAME;
2745 ctfgame.matchtime = level.time + matchtime->value * 60;
2746 ctfgame.countdown = false;
2747
2748 ctfgame.team1 = ctfgame.team2 = 0;
2749
2750 memset(ctfgame.ghosts, 0, sizeof(ctfgame.ghosts));
2751
2752 for (i = 1; i <= maxclients->value; i++) {
2753 ent = g_edicts + i;
2754 if (!ent->inuse)
2755 continue;
2756
2757 ent->client->resp.score = 0;
2758 ent->client->resp.ctf_state = 0;
2759 ent->client->resp.ghost = NULL;
2760
2761 safe_centerprintf(ent, "******************\n\nMATCH HAS STARTED!\n\n******************");
2762
2763 if (ent->client->resp.ctf_team != CTF_NOTEAM) {
2764 // make up a ghost code
2765 CTFAssignGhost(ent);
2766 CTFPlayerResetGrapple(ent);
2767 ent->svflags = SVF_NOCLIENT;
2768 ent->flags &= ~FL_GODMODE;
2769
2770 ent->client->respawn_time = level.time + 1.0 + ((rand()%30)/10.0);
2771 ent->client->ps.pmove.pm_type = PM_DEAD;
2772 ent->client->anim_priority = ANIM_DEATH;
2773 ent->s.frame = FRAME_death308-1;
2774 ent->client->anim_end = FRAME_death308;
2775 ent->deadflag = DEAD_DEAD;
2776 ent->movetype = MOVETYPE_NOCLIP;
2777 ent->client->ps.gunindex = 0;
2778 gi.linkentity (ent);
2779 }
2780 }
2781 }
2782
CTFEndMatch(void)2783 void CTFEndMatch(void)
2784 {
2785 ctfgame.match = MATCH_POST;
2786 safe_bprintf(PRINT_CHAT, "MATCH COMPLETED!\n");
2787
2788 CTFCalcScores();
2789
2790 safe_bprintf(PRINT_HIGH, "RED TEAM: %d captures, %d points\n",
2791 ctfgame.team1, ctfgame.total1);
2792 safe_bprintf(PRINT_HIGH, "BLUE TEAM: %d captures, %d points\n",
2793 ctfgame.team2, ctfgame.total2);
2794
2795 if (ctfgame.team1 > ctfgame.team2)
2796 safe_bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d CAPTURES!\n",
2797 ctfgame.team1 - ctfgame.team2);
2798 else if (ctfgame.team2 > ctfgame.team1)
2799 safe_bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d CAPTURES!\n",
2800 ctfgame.team2 - ctfgame.team1);
2801 else if (ctfgame.total1 > ctfgame.total2) // frag tie breaker
2802 safe_bprintf(PRINT_CHAT, "RED team won over the BLUE team by %d POINTS!\n",
2803 ctfgame.total1 - ctfgame.total2);
2804 else if (ctfgame.total2 > ctfgame.total1)
2805 safe_bprintf(PRINT_CHAT, "BLUE team won over the RED team by %d POINTS!\n",
2806 ctfgame.total2 - ctfgame.total1);
2807 else
2808 safe_bprintf(PRINT_CHAT, "TIE GAME!\n");
2809
2810 EndDMLevel();
2811 }
2812
CTFNextMap(void)2813 qboolean CTFNextMap(void)
2814 {
2815 if (ctfgame.match == MATCH_POST) {
2816 ctfgame.match = MATCH_SETUP;
2817 CTFResetAllPlayers();
2818 return true;
2819 }
2820 return false;
2821 }
2822
CTFWinElection(void)2823 void CTFWinElection(void)
2824 {
2825 switch (ctfgame.election) {
2826 case ELECT_MATCH :
2827 // reset into match mode
2828 if (competition->value < 3)
2829 gi.cvar_set("competition", "2");
2830 ctfgame.match = MATCH_SETUP;
2831 CTFResetAllPlayers();
2832 break;
2833
2834 case ELECT_ADMIN :
2835 ctfgame.etarget->client->resp.admin = true;
2836 safe_bprintf(PRINT_HIGH, "%s has become an admin.\n", ctfgame.etarget->client->pers.netname);
2837 safe_cprintf(ctfgame.etarget, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
2838 break;
2839
2840 case ELECT_MAP :
2841 safe_bprintf(PRINT_HIGH, "%s is warping to level %s.\n",
2842 ctfgame.etarget->client->pers.netname, ctfgame.elevel);
2843 strncpy(level.forcemap, ctfgame.elevel, sizeof(level.forcemap) - 1);
2844 EndDMLevel();
2845 break;
2846 }
2847 ctfgame.election = ELECT_NONE;
2848 }
2849
CTFVoteYes(edict_t * ent)2850 void CTFVoteYes(edict_t *ent)
2851 {
2852 if (ctfgame.election == ELECT_NONE) {
2853 safe_cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
2854 return;
2855 }
2856 if (ent->client->resp.voted) {
2857 safe_cprintf(ent, PRINT_HIGH, "You already voted.\n");
2858 return;
2859 }
2860 if (ctfgame.etarget == ent) {
2861 safe_cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
2862 return;
2863 }
2864
2865 ent->client->resp.voted = true;
2866
2867 ctfgame.evotes++;
2868 if (ctfgame.evotes == ctfgame.needvotes) {
2869 // the election has been won
2870 CTFWinElection();
2871 return;
2872 }
2873 safe_bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
2874 safe_bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
2875 (int)(ctfgame.electtime - level.time));
2876 }
2877
CTFVoteNo(edict_t * ent)2878 void CTFVoteNo(edict_t *ent)
2879 {
2880 if (ctfgame.election == ELECT_NONE) {
2881 safe_cprintf(ent, PRINT_HIGH, "No election is in progress.\n");
2882 return;
2883 }
2884 if (ent->client->resp.voted) {
2885 safe_cprintf(ent, PRINT_HIGH, "You already voted.\n");
2886 return;
2887 }
2888 if (ctfgame.etarget == ent) {
2889 safe_cprintf(ent, PRINT_HIGH, "You can't vote for yourself.\n");
2890 return;
2891 }
2892
2893 ent->client->resp.voted = true;
2894
2895 safe_bprintf(PRINT_HIGH, "%s\n", ctfgame.emsg);
2896 safe_bprintf(PRINT_CHAT, "Votes: %d Needed: %d Time left: %ds\n", ctfgame.evotes, ctfgame.needvotes,
2897 (int)(ctfgame.electtime - level.time));
2898 }
2899
CTFReady(edict_t * ent)2900 void CTFReady(edict_t *ent)
2901 {
2902 int i, j;
2903 edict_t *e;
2904 int t1, t2;
2905
2906 if (ent->client->resp.ctf_team == CTF_NOTEAM) {
2907 safe_cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
2908 return;
2909 }
2910
2911 if (ctfgame.match != MATCH_SETUP) {
2912 safe_cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
2913 return;
2914 }
2915
2916 if (ent->client->resp.ready) {
2917 safe_cprintf(ent, PRINT_HIGH, "You have already commited.\n");
2918 return;
2919 }
2920
2921 ent->client->resp.ready = true;
2922 safe_bprintf(PRINT_HIGH, "%s is ready.\n", ent->client->pers.netname);
2923
2924 t1 = t2 = 0;
2925 for (j = 0, i = 1; i <= maxclients->value; i++) {
2926 e = g_edicts + i;
2927 if (!e->inuse)
2928 continue;
2929 if (e->client->resp.ctf_team != CTF_NOTEAM && !e->client->resp.ready)
2930 j++;
2931 if (e->client->resp.ctf_team == CTF_TEAM1)
2932 t1++;
2933 else if (e->client->resp.ctf_team == CTF_TEAM2)
2934 t2++;
2935 }
2936 if (!j && t1 && t2) {
2937 // everyone has commited
2938 safe_bprintf(PRINT_CHAT, "All players have commited. Match starting\n");
2939 ctfgame.match = MATCH_PREGAME;
2940 ctfgame.matchtime = level.time + matchstarttime->value;
2941 ctfgame.countdown = false;
2942 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
2943 }
2944 }
2945
CTFNotReady(edict_t * ent)2946 void CTFNotReady(edict_t *ent)
2947 {
2948 if (ent->client->resp.ctf_team == CTF_NOTEAM) {
2949 safe_cprintf(ent, PRINT_HIGH, "Pick a team first (hit <TAB> for menu)\n");
2950 return;
2951 }
2952
2953 if (ctfgame.match != MATCH_SETUP && ctfgame.match != MATCH_PREGAME) {
2954 safe_cprintf(ent, PRINT_HIGH, "A match is not being setup.\n");
2955 return;
2956 }
2957
2958 if (!ent->client->resp.ready) {
2959 safe_cprintf(ent, PRINT_HIGH, "You haven't commited.\n");
2960 return;
2961 }
2962
2963 ent->client->resp.ready = false;
2964 safe_bprintf(PRINT_HIGH, "%s is no longer ready.\n", ent->client->pers.netname);
2965
2966 if (ctfgame.match == MATCH_PREGAME) {
2967 safe_bprintf(PRINT_CHAT, "Match halted.\n");
2968 ctfgame.match = MATCH_SETUP;
2969 ctfgame.matchtime = level.time + matchsetuptime->value * 60;
2970 }
2971 }
2972
CTFGhost(edict_t * ent)2973 void CTFGhost(edict_t *ent)
2974 {
2975 int i;
2976 int n;
2977
2978 if (gi.argc() < 2) {
2979 safe_cprintf(ent, PRINT_HIGH, "Usage: ghost <code>\n");
2980 return;
2981 }
2982
2983 if (ent->client->resp.ctf_team != CTF_NOTEAM) {
2984 safe_cprintf(ent, PRINT_HIGH, "You are already in the game.\n");
2985 return;
2986 }
2987 if (ctfgame.match != MATCH_GAME) {
2988 safe_cprintf(ent, PRINT_HIGH, "No match is in progress.\n");
2989 return;
2990 }
2991
2992 n = atoi(gi.argv(1));
2993
2994 for (i = 0; i < MAX_CLIENTS; i++) {
2995 if (ctfgame.ghosts[i].code && ctfgame.ghosts[i].code == n) {
2996 safe_cprintf(ent, PRINT_HIGH, "Ghost code accepted, your position has been reinstated.\n");
2997 ctfgame.ghosts[i].ent->client->resp.ghost = NULL;
2998 ent->client->resp.ctf_team = ctfgame.ghosts[i].team;
2999 ent->client->resp.ghost = ctfgame.ghosts + i;
3000 ent->client->resp.score = ctfgame.ghosts[i].score;
3001 ent->client->resp.ctf_state = 0;
3002 ctfgame.ghosts[i].ent = ent;
3003 ent->svflags = 0;
3004 ent->flags &= ~FL_GODMODE;
3005 PutClientInServer(ent);
3006 safe_bprintf(PRINT_HIGH, "%s has been reinstated to %s team.\n",
3007 ent->client->pers.netname, CTFTeamName(ent->client->resp.ctf_team));
3008 return;
3009 }
3010 }
3011 safe_cprintf(ent, PRINT_HIGH, "Invalid ghost code.\n");
3012 }
3013
CTFMatchSetup(void)3014 qboolean CTFMatchSetup(void)
3015 {
3016 if (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME)
3017 return true;
3018 return false;
3019 }
3020
CTFMatchOn(void)3021 qboolean CTFMatchOn(void)
3022 {
3023 if (ctfgame.match == MATCH_GAME)
3024 return true;
3025 return false;
3026 }
3027
3028
3029 /*-----------------------------------------------------------------------*/
3030
3031 void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p);
3032 void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p);
3033 void CTFCredits(edict_t *ent, pmenuhnd_t *p);
3034 void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p);
3035 void CTFChaseCam(edict_t *ent, pmenuhnd_t *p);
3036
3037 pmenu_t creditsmenu[] = {
3038 { "*Quake II", PMENU_ALIGN_CENTER, NULL },
3039 { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL },
3040 { NULL, PMENU_ALIGN_CENTER, NULL },
3041 { "*Programming", PMENU_ALIGN_CENTER, NULL },
3042 { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL },
3043 { "*Level Design", PMENU_ALIGN_CENTER, NULL },
3044 { "Christian Antkow", PMENU_ALIGN_CENTER, NULL },
3045 { "Tim Willits", PMENU_ALIGN_CENTER, NULL },
3046 { "Dave 'Zoid' Kirsch", PMENU_ALIGN_CENTER, NULL },
3047 { "*Art", PMENU_ALIGN_CENTER, NULL },
3048 { "Adrian Carmack Paul Steed", PMENU_ALIGN_CENTER, NULL },
3049 { "Kevin Cloud", PMENU_ALIGN_CENTER, NULL },
3050 { "*Sound", PMENU_ALIGN_CENTER, NULL },
3051 { "Tom 'Bjorn' Klok", PMENU_ALIGN_CENTER, NULL },
3052 { "*Original CTF Art Design", PMENU_ALIGN_CENTER, NULL },
3053 { "Brian 'Whaleboy' Cozzens", PMENU_ALIGN_CENTER, NULL },
3054 { NULL, PMENU_ALIGN_CENTER, NULL },
3055 { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
3056 };
3057
3058 static const int jmenu_level = 2;
3059 static const int jmenu_match = 3;
3060 static const int jmenu_red = 5;
3061 static const int jmenu_blue = 7;
3062 static const int jmenu_chase = 9;
3063 static const int jmenu_reqmatch = 11;
3064
3065 pmenu_t joinmenu[] = {
3066 { "*Quake II", PMENU_ALIGN_CENTER, NULL },
3067 { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL },
3068 { NULL, PMENU_ALIGN_CENTER, NULL },
3069 { NULL, PMENU_ALIGN_CENTER, NULL },
3070 { NULL, PMENU_ALIGN_CENTER, NULL },
3071 { "Join Red Team", PMENU_ALIGN_LEFT, CTFJoinTeam1 },
3072 { NULL, PMENU_ALIGN_LEFT, NULL },
3073 { "Join Blue Team", PMENU_ALIGN_LEFT, CTFJoinTeam2 },
3074 { NULL, PMENU_ALIGN_LEFT, NULL },
3075 { "Chase Camera", PMENU_ALIGN_LEFT, CTFChaseCam },
3076 { "Credits", PMENU_ALIGN_LEFT, CTFCredits },
3077 { NULL, PMENU_ALIGN_LEFT, NULL },
3078 { NULL, PMENU_ALIGN_LEFT, NULL },
3079 { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL },
3080 { "ENTER to select", PMENU_ALIGN_LEFT, NULL },
3081 { "ESC to Exit Menu", PMENU_ALIGN_LEFT, NULL },
3082 { "(TAB to Return)", PMENU_ALIGN_LEFT, NULL },
3083 { "v" CTF_STRING_VERSION, PMENU_ALIGN_RIGHT, NULL },
3084 };
3085
3086 pmenu_t nochasemenu[] = {
3087 { "*Quake II", PMENU_ALIGN_CENTER, NULL },
3088 { "*ThreeWave Capture the Flag", PMENU_ALIGN_CENTER, NULL },
3089 { NULL, PMENU_ALIGN_CENTER, NULL },
3090 { NULL, PMENU_ALIGN_CENTER, NULL },
3091 { "No one to chase", PMENU_ALIGN_LEFT, NULL },
3092 { NULL, PMENU_ALIGN_CENTER, NULL },
3093 { "Return to Main Menu", PMENU_ALIGN_LEFT, CTFReturnToMain }
3094 };
3095
CTFJoinTeam(edict_t * ent,int desired_team)3096 void CTFJoinTeam(edict_t *ent, int desired_team)
3097 {
3098 char *s;
3099
3100 PMenu_Close(ent);
3101
3102 ent->svflags &= ~SVF_NOCLIENT;
3103 ent->client->resp.ctf_team = desired_team;
3104 ent->client->resp.ctf_state = 0;
3105 s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
3106 CTFAssignSkin(ent, s);
3107
3108 // assign a ghost if we are in match mode
3109 if (ctfgame.match == MATCH_GAME) {
3110 if (ent->client->resp.ghost)
3111 ent->client->resp.ghost->code = 0;
3112 ent->client->resp.ghost = NULL;
3113 CTFAssignGhost(ent);
3114 }
3115
3116 PutClientInServer (ent);
3117 // add a teleportation effect
3118 ent->s.event = EV_PLAYER_TELEPORT;
3119 // hold in place briefly
3120 ent->client->ps.pmove.pm_flags = PMF_TIME_TELEPORT;
3121 ent->client->ps.pmove.pm_time = 14;
3122 safe_bprintf(PRINT_HIGH, "%s joined the %s team.\n",
3123 ent->client->pers.netname, CTFTeamName(desired_team));
3124
3125 if (ctfgame.match == MATCH_SETUP) {
3126 safe_centerprintf(ent, "***********************\n"
3127 "Type \"ready\" in console\n"
3128 "to ready up.\n"
3129 "***********************");
3130 }
3131 }
3132
CTFJoinTeam1(edict_t * ent,pmenuhnd_t * p)3133 void CTFJoinTeam1(edict_t *ent, pmenuhnd_t *p)
3134 {
3135 CTFJoinTeam(ent, CTF_TEAM1);
3136 }
3137
CTFJoinTeam2(edict_t * ent,pmenuhnd_t * p)3138 void CTFJoinTeam2(edict_t *ent, pmenuhnd_t *p)
3139 {
3140 CTFJoinTeam(ent, CTF_TEAM2);
3141 }
3142
CTFChaseCam(edict_t * ent,pmenuhnd_t * p)3143 void CTFChaseCam(edict_t *ent, pmenuhnd_t *p)
3144 {
3145 int i;
3146 edict_t *e;
3147
3148 if (ent->client->chase_target) {
3149 ent->client->chase_target = NULL;
3150 ent->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
3151 PMenu_Close(ent);
3152 return;
3153 }
3154
3155 for (i = 1; i <= maxclients->value; i++) {
3156 e = g_edicts + i;
3157 if (e->inuse && e->solid != SOLID_NOT) {
3158 ent->client->chase_target = e;
3159 PMenu_Close(ent);
3160 ent->client->update_chase = true;
3161 return;
3162 }
3163 }
3164
3165 SetLevelName(nochasemenu + jmenu_level);
3166
3167 PMenu_Close(ent);
3168 PMenu_Open(ent, nochasemenu, -1, sizeof(nochasemenu) / sizeof(pmenu_t), NULL);
3169 }
3170
CTFReturnToMain(edict_t * ent,pmenuhnd_t * p)3171 void CTFReturnToMain(edict_t *ent, pmenuhnd_t *p)
3172 {
3173 PMenu_Close(ent);
3174 CTFOpenJoinMenu(ent);
3175 }
3176
CTFRequestMatch(edict_t * ent,pmenuhnd_t * p)3177 void CTFRequestMatch(edict_t *ent, pmenuhnd_t *p)
3178 {
3179 char text[1024];
3180
3181 PMenu_Close(ent);
3182
3183 sprintf(text, "%s has requested to switch to competition mode.",
3184 ent->client->pers.netname);
3185 CTFBeginElection(ent, ELECT_MATCH, text);
3186 }
3187
3188 void DeathmatchScoreboard (edict_t *ent);
3189
CTFShowScores(edict_t * ent,pmenu_t * p)3190 void CTFShowScores(edict_t *ent, pmenu_t *p)
3191 {
3192 PMenu_Close(ent);
3193
3194 ent->client->showscores = true;
3195 ent->client->showinventory = false;
3196 DeathmatchScoreboard (ent);
3197 }
3198
CTFUpdateJoinMenu(edict_t * ent)3199 int CTFUpdateJoinMenu(edict_t *ent)
3200 {
3201 static char team1players[32];
3202 static char team2players[32];
3203 int num1, num2, i;
3204
3205 if (ctfgame.match >= MATCH_PREGAME && matchlock->value) {
3206 joinmenu[jmenu_red].text = "MATCH IS LOCKED";
3207 joinmenu[jmenu_red].SelectFunc = NULL;
3208 joinmenu[jmenu_blue].text = " (entry is not permitted)";
3209 joinmenu[jmenu_blue].SelectFunc = NULL;
3210 } else {
3211 if (ctfgame.match >= MATCH_PREGAME) {
3212 joinmenu[jmenu_red].text = "Join Red MATCH Team";
3213 joinmenu[jmenu_blue].text = "Join Blue MATCH Team";
3214 } else {
3215 joinmenu[jmenu_red].text = "Join Red Team";
3216 joinmenu[jmenu_blue].text = "Join Blue Team";
3217 }
3218 joinmenu[jmenu_red].SelectFunc = CTFJoinTeam1;
3219 joinmenu[jmenu_blue].SelectFunc = CTFJoinTeam2;
3220 }
3221
3222 if (ctf_forcejoin->string && *ctf_forcejoin->string) {
3223 if (stricmp(ctf_forcejoin->string, "red") == 0) {
3224 joinmenu[jmenu_blue].text = NULL;
3225 joinmenu[jmenu_blue].SelectFunc = NULL;
3226 } else if (stricmp(ctf_forcejoin->string, "blue") == 0) {
3227 joinmenu[jmenu_red].text = NULL;
3228 joinmenu[jmenu_red].SelectFunc = NULL;
3229 }
3230 }
3231
3232 if (ent->client->chase_target)
3233 joinmenu[jmenu_chase].text = "Leave Chase Camera";
3234 else
3235 joinmenu[jmenu_chase].text = "Chase Camera";
3236
3237 SetLevelName(joinmenu + jmenu_level);
3238
3239 num1 = num2 = 0;
3240 for (i = 0; i < maxclients->value; i++) {
3241 if (!g_edicts[i+1].inuse)
3242 continue;
3243 if (game.clients[i].resp.ctf_team == CTF_TEAM1)
3244 num1++;
3245 else if (game.clients[i].resp.ctf_team == CTF_TEAM2)
3246 num2++;
3247 }
3248
3249 sprintf(team1players, " (%d players)", num1);
3250 sprintf(team2players, " (%d players)", num2);
3251
3252 switch (ctfgame.match) {
3253 case MATCH_NONE :
3254 joinmenu[jmenu_match].text = NULL;
3255 break;
3256
3257 case MATCH_SETUP :
3258 joinmenu[jmenu_match].text = "*MATCH SETUP IN PROGRESS";
3259 break;
3260
3261 case MATCH_PREGAME :
3262 joinmenu[jmenu_match].text = "*MATCH STARTING";
3263 break;
3264
3265 case MATCH_GAME :
3266 joinmenu[jmenu_match].text = "*MATCH IN PROGRESS";
3267 break;
3268 }
3269
3270 if (joinmenu[jmenu_red].text)
3271 joinmenu[jmenu_red+1].text = team1players;
3272 else
3273 joinmenu[jmenu_red+1].text = NULL;
3274 if (joinmenu[jmenu_blue].text)
3275 joinmenu[jmenu_blue+1].text = team2players;
3276 else
3277 joinmenu[jmenu_blue+1].text = NULL;
3278
3279 joinmenu[jmenu_reqmatch].text = NULL;
3280 joinmenu[jmenu_reqmatch].SelectFunc = NULL;
3281 if (competition->value && ctfgame.match < MATCH_SETUP) {
3282 joinmenu[jmenu_reqmatch].text = "Request Match";
3283 joinmenu[jmenu_reqmatch].SelectFunc = CTFRequestMatch;
3284 }
3285
3286 if (num1 > num2)
3287 return CTF_TEAM1;
3288 else if (num2 > num1)
3289 return CTF_TEAM2;
3290 return (rand() & 1) ? CTF_TEAM1 : CTF_TEAM2;
3291 }
3292
CTFOpenJoinMenu(edict_t * ent)3293 void CTFOpenJoinMenu(edict_t *ent)
3294 {
3295 int team;
3296
3297 team = CTFUpdateJoinMenu(ent);
3298 if (ent->client->chase_target)
3299 team = 8;
3300 else if (team == CTF_TEAM1)
3301 team = 4;
3302 else
3303 team = 6;
3304 PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t), NULL);
3305 }
3306
CTFCredits(edict_t * ent,pmenuhnd_t * p)3307 void CTFCredits(edict_t *ent, pmenuhnd_t *p)
3308 {
3309 PMenu_Close(ent);
3310 PMenu_Open(ent, creditsmenu, -1, sizeof(creditsmenu) / sizeof(pmenu_t), NULL);
3311 }
3312
CTFStartClient(edict_t * ent)3313 qboolean CTFStartClient(edict_t *ent)
3314 {
3315 if (ent->client->resp.ctf_team != CTF_NOTEAM)
3316 return false;
3317
3318 if (!((int)dmflags->value & DF_CTF_FORCEJOIN) || ctfgame.match >= MATCH_SETUP) {
3319 // start as 'observer'
3320 ent->movetype = MOVETYPE_NOCLIP;
3321 ent->solid = SOLID_NOT;
3322 ent->svflags |= SVF_NOCLIENT;
3323 ent->client->resp.ctf_team = CTF_NOTEAM;
3324 ent->client->ps.gunindex = 0;
3325 gi.linkentity (ent);
3326
3327 CTFOpenJoinMenu(ent);
3328 return true;
3329 }
3330 return false;
3331 }
3332
CTFObserver(edict_t * ent)3333 void CTFObserver(edict_t *ent)
3334 {
3335 char userinfo[MAX_INFO_STRING];
3336
3337 // start as 'observer'
3338 if (ent->movetype == MOVETYPE_NOCLIP)
3339
3340 CTFPlayerResetGrapple(ent);
3341 CTFDeadDropFlag(ent);
3342 CTFDeadDropTech(ent);
3343
3344 ent->deadflag = DEAD_NO;
3345 ent->movetype = MOVETYPE_NOCLIP;
3346 ent->solid = SOLID_NOT;
3347 ent->svflags |= SVF_NOCLIENT;
3348 ent->client->resp.ctf_team = CTF_NOTEAM;
3349 ent->client->ps.gunindex = 0;
3350 ent->client->resp.score = 0;
3351 memcpy (userinfo, ent->client->pers.userinfo, sizeof(userinfo));
3352 InitClientPersistant(ent->client);
3353 ClientUserinfoChanged (ent, userinfo);
3354 gi.linkentity (ent);
3355 CTFOpenJoinMenu(ent);
3356 }
3357
CTFInMatch(void)3358 qboolean CTFInMatch(void)
3359 {
3360 if (ctfgame.match > MATCH_NONE)
3361 return true;
3362 return false;
3363 }
3364
CTFCheckRules(void)3365 qboolean CTFCheckRules(void)
3366 {
3367 int t;
3368 int i, j;
3369 char text[64];
3370 edict_t *ent;
3371
3372 if (ctfgame.election != ELECT_NONE && ctfgame.electtime <= level.time) {
3373 safe_bprintf(PRINT_CHAT, "Election timed out and has been cancelled.\n");
3374 ctfgame.election = ELECT_NONE;
3375 }
3376
3377 if (ctfgame.match != MATCH_NONE) {
3378 t = ctfgame.matchtime - level.time;
3379
3380 // no team warnings in match mode
3381 ctfgame.warnactive = 0;
3382
3383 if (t <= 0) { // time ended on something
3384 switch (ctfgame.match) {
3385 case MATCH_SETUP :
3386 // go back to normal mode
3387 if (competition->value < 3) {
3388 ctfgame.match = MATCH_NONE;
3389 gi.cvar_set("competition", "1");
3390 CTFResetAllPlayers();
3391 } else {
3392 // reset the time
3393 ctfgame.matchtime = level.time + matchsetuptime->value * 60;
3394 }
3395 return false;
3396
3397 case MATCH_PREGAME :
3398 // match started!
3399 CTFStartMatch();
3400 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/tele_up.wav"), 1, ATTN_NONE, 0);
3401 return false;
3402
3403 case MATCH_GAME :
3404 // match ended!
3405 CTFEndMatch();
3406 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/bigtele.wav"), 1, ATTN_NONE, 0);
3407 return false;
3408 }
3409 }
3410
3411 if (t == ctfgame.lasttime)
3412 return false;
3413
3414 ctfgame.lasttime = t;
3415
3416 switch (ctfgame.match) {
3417 case MATCH_SETUP :
3418 for (j = 0, i = 1; i <= maxclients->value; i++) {
3419 ent = g_edicts + i;
3420 if (!ent->inuse)
3421 continue;
3422 if (ent->client->resp.ctf_team != CTF_NOTEAM &&
3423 !ent->client->resp.ready)
3424 j++;
3425 }
3426
3427 if (competition->value < 3)
3428 sprintf(text, "%02d:%02d SETUP: %d not ready",
3429 t / 60, t % 60, j);
3430 else
3431 sprintf(text, "SETUP: %d not ready", j);
3432
3433 gi.configstring (CONFIG_CTF_MATCH, text);
3434 break;
3435
3436
3437 case MATCH_PREGAME :
3438 sprintf(text, "%02d:%02d UNTIL START",
3439 t / 60, t % 60);
3440 gi.configstring (CONFIG_CTF_MATCH, text);
3441
3442 if (t <= 10 && !ctfgame.countdown) {
3443 ctfgame.countdown = true;
3444 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
3445 }
3446 break;
3447
3448 case MATCH_GAME:
3449 sprintf(text, "%02d:%02d MATCH",
3450 t / 60, t % 60);
3451 gi.configstring (CONFIG_CTF_MATCH, text);
3452 if (t <= 10 && !ctfgame.countdown) {
3453 ctfgame.countdown = true;
3454 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("world/10_0.wav"), 1, ATTN_NONE, 0);
3455 }
3456 break;
3457 }
3458 return false;
3459
3460 } else {
3461 int team1 = 0, team2 = 0;
3462
3463 if (level.time == ctfgame.lasttime)
3464 return false;
3465 ctfgame.lasttime = level.time;
3466 // this is only done in non-match (public) mode
3467
3468 if (warn_unbalanced->value) {
3469 // count up the team totals
3470 for (i = 1; i <= maxclients->value; i++) {
3471 ent = g_edicts + i;
3472 if (!ent->inuse)
3473 continue;
3474 if (ent->client->resp.ctf_team == CTF_TEAM1)
3475 team1++;
3476 else if (ent->client->resp.ctf_team == CTF_TEAM2)
3477 team2++;
3478 }
3479
3480 if (team1 - team2 >= 2 && team2 >= 2) {
3481 if (ctfgame.warnactive != CTF_TEAM1) {
3482 ctfgame.warnactive = CTF_TEAM1;
3483 gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Red has too many players");
3484 }
3485 } else if (team2 - team1 >= 2 && team1 >= 2) {
3486 if (ctfgame.warnactive != CTF_TEAM2) {
3487 ctfgame.warnactive = CTF_TEAM2;
3488 gi.configstring (CONFIG_CTF_TEAMINFO, "WARNING: Blue has too many players");
3489 }
3490 } else
3491 ctfgame.warnactive = 0;
3492 } else
3493 ctfgame.warnactive = 0;
3494
3495 }
3496
3497
3498
3499 if (capturelimit->value &&
3500 (ctfgame.team1 >= capturelimit->value ||
3501 ctfgame.team2 >= capturelimit->value)) {
3502 safe_bprintf (PRINT_HIGH, "Capturelimit hit.\n");
3503 return true;
3504 }
3505 return false;
3506 }
3507
3508 /*--------------------------------------------------------------------------
3509 * just here to help old map conversions
3510 *--------------------------------------------------------------------------*/
3511
old_teleporter_touch(edict_t * self,edict_t * other,cplane_t * plane,csurface_t * surf)3512 static void old_teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
3513 {
3514 edict_t *dest;
3515 int i;
3516 vec3_t forward;
3517
3518 if (!other->client)
3519 return;
3520 dest = G_Find (NULL, FOFS(targetname), self->target);
3521 if (!dest)
3522 {
3523 gi.dprintf ("Couldn't find destination\n");
3524 return;
3525 }
3526
3527 //ZOID
3528 CTFPlayerResetGrapple(other);
3529 //ZOID
3530
3531 // unlink to make sure it can't possibly interfere with KillBox
3532 gi.unlinkentity (other);
3533
3534 VectorCopy (dest->s.origin, other->s.origin);
3535 VectorCopy (dest->s.origin, other->s.old_origin);
3536 // other->s.origin[2] += 10;
3537
3538 // clear the velocity and hold them in place briefly
3539 VectorClear (other->velocity);
3540 other->client->ps.pmove.pm_time = 160>>3; // hold time
3541 other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
3542
3543 // draw the teleport splash at source and on the player
3544 self->enemy->s.event = EV_PLAYER_TELEPORT;
3545 other->s.event = EV_PLAYER_TELEPORT;
3546
3547 // set angles
3548 for (i=0 ; i<3 ; i++)
3549 other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
3550
3551 other->s.angles[PITCH] = 0;
3552 other->s.angles[YAW] = dest->s.angles[YAW];
3553 other->s.angles[ROLL] = 0;
3554 VectorCopy (dest->s.angles, other->client->ps.viewangles);
3555 VectorCopy (dest->s.angles, other->client->v_angle);
3556
3557 // give a little forward velocity
3558 AngleVectors (other->client->v_angle, forward, NULL, NULL);
3559 VectorScale(forward, 200, other->velocity);
3560
3561 // kill anything at the destination
3562 if (!KillBox (other))
3563 {
3564 }
3565
3566 gi.linkentity (other);
3567 }
3568
3569 /*QUAKED trigger_teleport (0.5 0.5 0.5) ?
3570 Players touching this will be teleported
3571 */
SP_trigger_teleport(edict_t * ent)3572 void SP_trigger_teleport (edict_t *ent)
3573 {
3574 edict_t *s;
3575 int i;
3576
3577 if (!ent->target)
3578 {
3579 gi.dprintf ("teleporter without a target.\n");
3580 G_FreeEdict (ent);
3581 return;
3582 }
3583
3584 ent->svflags |= SVF_NOCLIENT;
3585 ent->solid = SOLID_TRIGGER;
3586 ent->touch = old_teleporter_touch;
3587 gi.setmodel (ent, ent->model);
3588 gi.linkentity (ent);
3589
3590 // noise maker and splash effect dude
3591 s = G_Spawn();
3592 ent->enemy = s;
3593 for (i = 0; i < 3; i++)
3594 s->s.origin[i] = ent->mins[i] + (ent->maxs[i] - ent->mins[i])/2;
3595 s->s.sound = gi.soundindex ("world/hum1.wav");
3596 gi.linkentity(s);
3597
3598 }
3599
3600 /*QUAKED info_teleport_destination (0.5 0.5 0.5) (-16 -16 -24) (16 16 32)
3601 Point trigger_teleports at these.
3602 */
SP_info_teleport_destination(edict_t * ent)3603 void SP_info_teleport_destination (edict_t *ent)
3604 {
3605 ent->s.origin[2] += 16;
3606 }
3607
3608 /*----------------------------------------------------------------------------------*/
3609 /* ADMIN */
3610
3611 typedef struct admin_settings_s {
3612 int matchlen;
3613 int matchsetuplen;
3614 int matchstartlen;
3615 qboolean weaponsstay;
3616 qboolean instantitems;
3617 qboolean quaddrop;
3618 qboolean instantweap;
3619 qboolean matchlock;
3620 } admin_settings_t;
3621
3622 #define SETMENU_SIZE (7 + 5)
3623
3624 void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu);
3625 void CTFOpenAdminMenu(edict_t *ent);
3626
CTFAdmin_SettingsApply(edict_t * ent,pmenuhnd_t * p)3627 void CTFAdmin_SettingsApply(edict_t *ent, pmenuhnd_t *p)
3628 {
3629 admin_settings_t *settings = p->arg;
3630 char st[80];
3631 int i;
3632
3633 if (settings->matchlen != matchtime->value) {
3634 safe_bprintf(PRINT_HIGH, "%s changed the match length to %d minutes.\n",
3635 ent->client->pers.netname, settings->matchlen);
3636 if (ctfgame.match == MATCH_GAME) {
3637 // in the middle of a match, change it on the fly
3638 ctfgame.matchtime = (ctfgame.matchtime - matchtime->value*60) + settings->matchlen*60;
3639 }
3640 sprintf(st, "%d", settings->matchlen);
3641 gi.cvar_set("matchtime", st);
3642 }
3643
3644 if (settings->matchsetuplen != matchsetuptime->value) {
3645 safe_bprintf(PRINT_HIGH, "%s changed the match setup time to %d minutes.\n",
3646 ent->client->pers.netname, settings->matchsetuplen);
3647 if (ctfgame.match == MATCH_SETUP) {
3648 // in the middle of a match, change it on the fly
3649 ctfgame.matchtime = (ctfgame.matchtime - matchsetuptime->value*60) + settings->matchsetuplen*60;
3650 }
3651 sprintf(st, "%d", settings->matchsetuplen);
3652 gi.cvar_set("matchsetuptime", st);
3653 }
3654
3655 if (settings->matchstartlen != matchstarttime->value) {
3656 safe_bprintf(PRINT_HIGH, "%s changed the match start time to %d seconds.\n",
3657 ent->client->pers.netname, settings->matchstartlen);
3658 if (ctfgame.match == MATCH_PREGAME) {
3659 // in the middle of a match, change it on the fly
3660 ctfgame.matchtime = (ctfgame.matchtime - matchstarttime->value) + settings->matchstartlen;
3661 }
3662 sprintf(st, "%d", settings->matchstartlen);
3663 gi.cvar_set("matchstarttime", st);
3664 }
3665
3666 if (settings->weaponsstay != !!((int)dmflags->value & DF_WEAPONS_STAY)) {
3667 safe_bprintf(PRINT_HIGH, "%s turned %s weapons stay.\n",
3668 ent->client->pers.netname, settings->weaponsstay ? "on" : "off");
3669 i = (int)dmflags->value;
3670 if (settings->weaponsstay)
3671 i |= DF_WEAPONS_STAY;
3672 else
3673 i &= ~DF_WEAPONS_STAY;
3674 sprintf(st, "%d", i);
3675 gi.cvar_set("dmflags", st);
3676 }
3677
3678 if (settings->instantitems != !!((int)dmflags->value & DF_INSTANT_ITEMS)) {
3679 safe_bprintf(PRINT_HIGH, "%s turned %s instant items.\n",
3680 ent->client->pers.netname, settings->instantitems ? "on" : "off");
3681 i = (int)dmflags->value;
3682 if (settings->instantitems)
3683 i |= DF_INSTANT_ITEMS;
3684 else
3685 i &= ~DF_INSTANT_ITEMS;
3686 sprintf(st, "%d", i);
3687 gi.cvar_set("dmflags", st);
3688 }
3689
3690 if (settings->quaddrop != !!((int)dmflags->value & DF_QUAD_DROP)) {
3691 safe_bprintf(PRINT_HIGH, "%s turned %s quad drop.\n",
3692 ent->client->pers.netname, settings->quaddrop ? "on" : "off");
3693 i = (int)dmflags->value;
3694 if (settings->quaddrop)
3695 i |= DF_QUAD_DROP;
3696 else
3697 i &= ~DF_QUAD_DROP;
3698 sprintf(st, "%d", i);
3699 gi.cvar_set("dmflags", st);
3700 }
3701
3702 if (settings->instantweap != !!((int)instantweap->value)) {
3703 safe_bprintf(PRINT_HIGH, "%s turned %s instant weapons.\n",
3704 ent->client->pers.netname, settings->instantweap ? "on" : "off");
3705 sprintf(st, "%d", (int)settings->instantweap);
3706 gi.cvar_set("instantweap", st);
3707 }
3708
3709 if (settings->matchlock != !!((int)matchlock->value)) {
3710 safe_bprintf(PRINT_HIGH, "%s turned %s match lock.\n",
3711 ent->client->pers.netname, settings->matchlock ? "on" : "off");
3712 sprintf(st, "%d", (int)settings->matchlock);
3713 gi.cvar_set("matchlock", st);
3714 }
3715
3716 PMenu_Close(ent);
3717 CTFOpenAdminMenu(ent);
3718 }
3719
CTFAdmin_SettingsCancel(edict_t * ent,pmenuhnd_t * p)3720 void CTFAdmin_SettingsCancel(edict_t *ent, pmenuhnd_t *p)
3721 {
3722 admin_settings_t *settings = p->arg;
3723
3724 PMenu_Close(ent);
3725 CTFOpenAdminMenu(ent);
3726 }
3727
CTFAdmin_ChangeMatchLen(edict_t * ent,pmenuhnd_t * p)3728 void CTFAdmin_ChangeMatchLen(edict_t *ent, pmenuhnd_t *p)
3729 {
3730 admin_settings_t *settings = p->arg;
3731
3732 settings->matchlen = (settings->matchlen % 60) + 5;
3733 if (settings->matchlen < 5)
3734 settings->matchlen = 5;
3735
3736 CTFAdmin_UpdateSettings(ent, p);
3737 }
3738
CTFAdmin_ChangeMatchSetupLen(edict_t * ent,pmenuhnd_t * p)3739 void CTFAdmin_ChangeMatchSetupLen(edict_t *ent, pmenuhnd_t *p)
3740 {
3741 admin_settings_t *settings = p->arg;
3742
3743 settings->matchsetuplen = (settings->matchsetuplen % 60) + 5;
3744 if (settings->matchsetuplen < 5)
3745 settings->matchsetuplen = 5;
3746
3747 CTFAdmin_UpdateSettings(ent, p);
3748 }
3749
CTFAdmin_ChangeMatchStartLen(edict_t * ent,pmenuhnd_t * p)3750 void CTFAdmin_ChangeMatchStartLen(edict_t *ent, pmenuhnd_t *p)
3751 {
3752 admin_settings_t *settings = p->arg;
3753
3754 settings->matchstartlen = (settings->matchstartlen % 600) + 10;
3755 if (settings->matchstartlen < 20)
3756 settings->matchstartlen = 20;
3757
3758 CTFAdmin_UpdateSettings(ent, p);
3759 }
3760
CTFAdmin_ChangeWeapStay(edict_t * ent,pmenuhnd_t * p)3761 void CTFAdmin_ChangeWeapStay(edict_t *ent, pmenuhnd_t *p)
3762 {
3763 admin_settings_t *settings = p->arg;
3764
3765 settings->weaponsstay = !settings->weaponsstay;
3766 CTFAdmin_UpdateSettings(ent, p);
3767 }
3768
CTFAdmin_ChangeInstantItems(edict_t * ent,pmenuhnd_t * p)3769 void CTFAdmin_ChangeInstantItems(edict_t *ent, pmenuhnd_t *p)
3770 {
3771 admin_settings_t *settings = p->arg;
3772
3773 settings->instantitems = !settings->instantitems;
3774 CTFAdmin_UpdateSettings(ent, p);
3775 }
3776
CTFAdmin_ChangeQuadDrop(edict_t * ent,pmenuhnd_t * p)3777 void CTFAdmin_ChangeQuadDrop(edict_t *ent, pmenuhnd_t *p)
3778 {
3779 admin_settings_t *settings = p->arg;
3780
3781 settings->quaddrop = !settings->quaddrop;
3782 CTFAdmin_UpdateSettings(ent, p);
3783 }
3784
CTFAdmin_ChangeInstantWeap(edict_t * ent,pmenuhnd_t * p)3785 void CTFAdmin_ChangeInstantWeap(edict_t *ent, pmenuhnd_t *p)
3786 {
3787 admin_settings_t *settings = p->arg;
3788
3789 settings->instantweap = !settings->instantweap;
3790 CTFAdmin_UpdateSettings(ent, p);
3791 }
3792
CTFAdmin_ChangeMatchLock(edict_t * ent,pmenuhnd_t * p)3793 void CTFAdmin_ChangeMatchLock(edict_t *ent, pmenuhnd_t *p)
3794 {
3795 admin_settings_t *settings = p->arg;
3796
3797 settings->matchlock = !settings->matchlock;
3798 CTFAdmin_UpdateSettings(ent, p);
3799 }
3800
CTFAdmin_UpdateSettings(edict_t * ent,pmenuhnd_t * setmenu)3801 void CTFAdmin_UpdateSettings(edict_t *ent, pmenuhnd_t *setmenu)
3802 {
3803 int i = 2;
3804 char text[64];
3805 admin_settings_t *settings = setmenu->arg;
3806
3807 sprintf(text, "Match Len: %2d mins", settings->matchlen);
3808 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLen);
3809 i++;
3810
3811 sprintf(text, "Match Setup Len: %2d mins", settings->matchsetuplen);
3812 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchSetupLen);
3813 i++;
3814
3815 sprintf(text, "Match Start Len: %2d secs", settings->matchstartlen);
3816 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchStartLen);
3817 i++;
3818
3819 sprintf(text, "Weapons Stay: %s", settings->weaponsstay ? "Yes" : "No");
3820 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeWeapStay);
3821 i++;
3822
3823 sprintf(text, "Instant Items: %s", settings->instantitems ? "Yes" : "No");
3824 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantItems);
3825 i++;
3826
3827 sprintf(text, "Quad Drop: %s", settings->quaddrop ? "Yes" : "No");
3828 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeQuadDrop);
3829 i++;
3830
3831 sprintf(text, "Instant Weapons: %s", settings->instantweap ? "Yes" : "No");
3832 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeInstantWeap);
3833 i++;
3834
3835 sprintf(text, "Match Lock: %s", settings->matchlock ? "Yes" : "No");
3836 PMenu_UpdateEntry(setmenu->entries + i, text, PMENU_ALIGN_LEFT, CTFAdmin_ChangeMatchLock);
3837 i++;
3838
3839 PMenu_Update(ent);
3840 }
3841
3842 pmenu_t def_setmenu[] = {
3843 { "*Settings Menu", PMENU_ALIGN_CENTER, NULL },
3844 { NULL, PMENU_ALIGN_CENTER, NULL },
3845 { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchlen;
3846 { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchsetuplen;
3847 { NULL, PMENU_ALIGN_LEFT, NULL }, //int matchstartlen;
3848 { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean weaponsstay;
3849 { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantitems;
3850 { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean quaddrop;
3851 { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean instantweap;
3852 { NULL, PMENU_ALIGN_LEFT, NULL }, //qboolean matchlock;
3853 { NULL, PMENU_ALIGN_LEFT, NULL },
3854 { "Apply", PMENU_ALIGN_LEFT, CTFAdmin_SettingsApply },
3855 { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_SettingsCancel }
3856 };
3857
CTFAdmin_Settings(edict_t * ent,pmenuhnd_t * p)3858 void CTFAdmin_Settings(edict_t *ent, pmenuhnd_t *p)
3859 {
3860 admin_settings_t *settings;
3861 pmenuhnd_t *menu;
3862
3863 PMenu_Close(ent);
3864
3865 settings = malloc(sizeof(*settings));
3866
3867 settings->matchlen = matchtime->value;
3868 settings->matchsetuplen = matchsetuptime->value;
3869 settings->matchstartlen = matchstarttime->value;
3870 settings->weaponsstay = !!((int)dmflags->value & DF_WEAPONS_STAY);
3871 settings->instantitems = !!((int)dmflags->value & DF_INSTANT_ITEMS);
3872 settings->quaddrop = !!((int)dmflags->value & DF_QUAD_DROP);
3873 settings->instantweap = instantweap->value != 0;
3874 settings->matchlock = matchlock->value != 0;
3875
3876 menu = PMenu_Open(ent, def_setmenu, -1, sizeof(def_setmenu) / sizeof(pmenu_t), settings);
3877 CTFAdmin_UpdateSettings(ent, menu);
3878 }
3879
CTFAdmin_MatchSet(edict_t * ent,pmenuhnd_t * p)3880 void CTFAdmin_MatchSet(edict_t *ent, pmenuhnd_t *p)
3881 {
3882 PMenu_Close(ent);
3883
3884 if (ctfgame.match == MATCH_SETUP) {
3885 safe_bprintf(PRINT_CHAT, "Match has been forced to start.\n");
3886 ctfgame.match = MATCH_PREGAME;
3887 ctfgame.matchtime = level.time + matchstarttime->value;
3888 gi.positioned_sound (world->s.origin, world, CHAN_AUTO | CHAN_RELIABLE, gi.soundindex("misc/talk1.wav"), 1, ATTN_NONE, 0);
3889 ctfgame.countdown = false;
3890 } else if (ctfgame.match == MATCH_GAME) {
3891 safe_bprintf(PRINT_CHAT, "Match has been forced to terminate.\n");
3892 ctfgame.match = MATCH_SETUP;
3893 ctfgame.matchtime = level.time + matchsetuptime->value * 60;
3894 CTFResetAllPlayers();
3895 }
3896 }
3897
CTFAdmin_MatchMode(edict_t * ent,pmenuhnd_t * p)3898 void CTFAdmin_MatchMode(edict_t *ent, pmenuhnd_t *p)
3899 {
3900 PMenu_Close(ent);
3901
3902 if (ctfgame.match != MATCH_SETUP) {
3903 if (competition->value < 3)
3904 gi.cvar_set("competition", "2");
3905 ctfgame.match = MATCH_SETUP;
3906 CTFResetAllPlayers();
3907 }
3908 }
3909
CTFAdmin_Reset(edict_t * ent,pmenuhnd_t * p)3910 void CTFAdmin_Reset(edict_t *ent, pmenuhnd_t *p)
3911 {
3912 PMenu_Close(ent);
3913
3914 // go back to normal mode
3915 safe_bprintf(PRINT_CHAT, "Match mode has been terminated, reseting to normal game.\n");
3916 ctfgame.match = MATCH_NONE;
3917 gi.cvar_set("competition", "1");
3918 CTFResetAllPlayers();
3919 }
3920
CTFAdmin_Cancel(edict_t * ent,pmenuhnd_t * p)3921 void CTFAdmin_Cancel(edict_t *ent, pmenuhnd_t *p)
3922 {
3923 PMenu_Close(ent);
3924 }
3925
3926
3927 pmenu_t adminmenu[] = {
3928 { "*Administration Menu", PMENU_ALIGN_CENTER, NULL },
3929 { NULL, PMENU_ALIGN_CENTER, NULL }, // blank
3930 { "Settings", PMENU_ALIGN_LEFT, CTFAdmin_Settings },
3931 { NULL, PMENU_ALIGN_LEFT, NULL },
3932 { NULL, PMENU_ALIGN_LEFT, NULL },
3933 { "Cancel", PMENU_ALIGN_LEFT, CTFAdmin_Cancel },
3934 { NULL, PMENU_ALIGN_CENTER, NULL },
3935 };
3936
CTFOpenAdminMenu(edict_t * ent)3937 void CTFOpenAdminMenu(edict_t *ent)
3938 {
3939 adminmenu[3].text = NULL;
3940 adminmenu[3].SelectFunc = NULL;
3941 adminmenu[4].text = NULL;
3942 adminmenu[4].SelectFunc = NULL;
3943 if (ctfgame.match == MATCH_SETUP) {
3944 adminmenu[3].text = "Force start match";
3945 adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
3946 adminmenu[4].text = "Reset to pickup mode";
3947 adminmenu[4].SelectFunc = CTFAdmin_Reset;
3948 } else if (ctfgame.match == MATCH_GAME || ctfgame.match == MATCH_PREGAME) {
3949 adminmenu[3].text = "Cancel match";
3950 adminmenu[3].SelectFunc = CTFAdmin_MatchSet;
3951 } else if (ctfgame.match == MATCH_NONE && competition->value) {
3952 adminmenu[3].text = "Switch to match mode";
3953 adminmenu[3].SelectFunc = CTFAdmin_MatchMode;
3954 }
3955
3956
3957 // if (ent->client->menu)
3958 // PMenu_Close(ent->client->menu);
3959
3960 PMenu_Open(ent, adminmenu, -1, sizeof(adminmenu) / sizeof(pmenu_t), NULL);
3961 }
3962
CTFAdmin(edict_t * ent)3963 void CTFAdmin(edict_t *ent)
3964 {
3965 char text[1024];
3966
3967 if (!allow_admin->value) {
3968 safe_cprintf(ent, PRINT_HIGH, "Administration is disabled\n");
3969 return;
3970 }
3971
3972 if (gi.argc() > 1 && admin_password->string && *admin_password->string &&
3973 !ent->client->resp.admin && strcmp(admin_password->string, gi.argv(1)) == 0) {
3974 ent->client->resp.admin = true;
3975 safe_bprintf(PRINT_HIGH, "%s has become an admin.\n", ent->client->pers.netname);
3976 safe_cprintf(ent, PRINT_HIGH, "Type 'admin' to access the adminstration menu.\n");
3977 }
3978
3979 if (!ent->client->resp.admin) {
3980 sprintf(text, "%s has requested admin rights.",
3981 ent->client->pers.netname);
3982 CTFBeginElection(ent, ELECT_ADMIN, text);
3983 return;
3984 }
3985
3986 if (ent->client->menu)
3987 PMenu_Close(ent);
3988
3989 CTFOpenAdminMenu(ent);
3990 }
3991
3992 /*----------------------------------------------------------------*/
3993
CTFStats(edict_t * ent)3994 void CTFStats(edict_t *ent)
3995 {
3996 int i, e;
3997 ghost_t *g;
3998 char st[80];
3999 char text[1024];
4000 edict_t *e2;
4001
4002 *text = 0;
4003 if (ctfgame.match == MATCH_SETUP) {
4004 for (i = 1; i <= maxclients->value; i++) {
4005 e2 = g_edicts + i;
4006 if (!e2->inuse)
4007 continue;
4008 if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
4009 sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
4010 if (strlen(text) + strlen(st) < sizeof(text) - 50)
4011 strcat(text, st);
4012 }
4013 }
4014 }
4015
4016 for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++)
4017 if (g->ent)
4018 break;
4019
4020 if (i == MAX_CLIENTS) {
4021 if (*text)
4022 safe_cprintf(ent, PRINT_HIGH, "%s", text);
4023 safe_cprintf(ent, PRINT_HIGH, "No statistics available.\n");
4024 return;
4025 }
4026
4027 strcat(text, " #|Name |Score|Kills|Death|BasDf|CarDf|Effcy|\n");
4028
4029 for (i = 0, g = ctfgame.ghosts; i < MAX_CLIENTS; i++, g++) {
4030 if (!*g->netname)
4031 continue;
4032
4033 if (g->deaths + g->kills == 0)
4034 e = 50;
4035 else
4036 e = g->kills * 100 / (g->kills + g->deaths);
4037 sprintf(st, "%3d|%-16.16s|%5d|%5d|%5d|%5d|%5d|%4d%%|\n",
4038 g->number,
4039 g->netname,
4040 g->score,
4041 g->kills,
4042 g->deaths,
4043 g->basedef,
4044 g->carrierdef,
4045 e);
4046 if (strlen(text) + strlen(st) > sizeof(text) - 50) {
4047 sprintf(text+strlen(text), "And more...\n");
4048 safe_cprintf(ent, PRINT_HIGH, "%s", text);
4049 return;
4050 }
4051 strcat(text, st);
4052 }
4053 safe_cprintf(ent, PRINT_HIGH, "%s", text);
4054 }
4055
CTFPlayerList(edict_t * ent)4056 void CTFPlayerList(edict_t *ent)
4057 {
4058 int i;
4059 char st[80];
4060 char text[1400];
4061 edict_t *e2;
4062
4063 #if 0
4064 *text = 0;
4065 if (ctfgame.match == MATCH_SETUP) {
4066 for (i = 1; i <= maxclients->value; i++) {
4067 e2 = g_edicts + i;
4068 if (!e2->inuse)
4069 continue;
4070 if (!e2->client->resp.ready && e2->client->resp.ctf_team != CTF_NOTEAM) {
4071 sprintf(st, "%s is not ready.\n", e2->client->pers.netname);
4072 if (strlen(text) + strlen(st) < sizeof(text) - 50)
4073 strcat(text, st);
4074 }
4075 }
4076 }
4077 #endif
4078
4079 // number, name, connect time, ping, score, admin
4080
4081 *text = 0;
4082 for (i = 1; i <= maxclients->value; i++) {
4083 e2 = g_edicts + i;
4084 if (!e2->inuse)
4085 continue;
4086
4087 Com_sprintf(st, sizeof(st), "%3d %-16.16s %02d:%02d %4d %3d%s%s\n",
4088 i,
4089 e2->client->pers.netname,
4090 (level.framenum - e2->client->resp.enterframe) / 600,
4091 ((level.framenum - e2->client->resp.enterframe) % 600)/10,
4092 e2->client->ping,
4093 e2->client->resp.score,
4094 (ctfgame.match == MATCH_SETUP || ctfgame.match == MATCH_PREGAME) ?
4095 (e2->client->resp.ready ? " (ready)" : " (notready)") : "",
4096 e2->client->resp.admin ? " (admin)" : "");
4097
4098 if (strlen(text) + strlen(st) > sizeof(text) - 50) {
4099 sprintf(text+strlen(text), "And more...\n");
4100 safe_cprintf(ent, PRINT_HIGH, "%s", text);
4101 return;
4102 }
4103 strcat(text, st);
4104 }
4105 safe_cprintf(ent, PRINT_HIGH, "%s", text);
4106 }
4107
4108
CTFWarp(edict_t * ent)4109 void CTFWarp(edict_t *ent)
4110 {
4111 char text[1024];
4112 char *mlist, *token;
4113 static const char *seps = " \t\n\r";
4114
4115 if (gi.argc() < 2) {
4116 safe_cprintf(ent, PRINT_HIGH, "Where do you want to warp to?\n");
4117 safe_cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
4118 return;
4119 }
4120
4121 mlist = strdup(warp_list->string);
4122
4123 token = strtok(mlist, seps);
4124 while (token != NULL) {
4125 if (Q_stricmp(token, gi.argv(1)) == 0)
4126 break;
4127 token = strtok(NULL, seps);
4128 }
4129
4130 if (token == NULL) {
4131 safe_cprintf(ent, PRINT_HIGH, "Unknown CTF level.\n");
4132 safe_cprintf(ent, PRINT_HIGH, "Available levels are: %s\n", warp_list->string);
4133 free(mlist);
4134 return;
4135 }
4136
4137 free(mlist);
4138
4139
4140 if (ent->client->resp.admin) {
4141 safe_bprintf(PRINT_HIGH, "%s is warping to level %s.\n",
4142 ent->client->pers.netname, gi.argv(1));
4143 strncpy(level.forcemap, gi.argv(1), sizeof(level.forcemap) - 1);
4144 EndDMLevel();
4145 return;
4146 }
4147
4148 sprintf(text, "%s has requested warping to level %s.",
4149 ent->client->pers.netname, gi.argv(1));
4150 if (CTFBeginElection(ent, ELECT_MAP, text))
4151 strncpy(ctfgame.elevel, gi.argv(1), sizeof(ctfgame.elevel) - 1);
4152 }
4153
CTFBoot(edict_t * ent)4154 void CTFBoot(edict_t *ent)
4155 {
4156 int i;
4157 edict_t *targ;
4158 char text[80];
4159
4160 if (!ent->client->resp.admin) {
4161 safe_cprintf(ent, PRINT_HIGH, "You are not an admin.\n");
4162 return;
4163 }
4164
4165 if (gi.argc() < 2) {
4166 safe_cprintf(ent, PRINT_HIGH, "Who do you want to kick?\n");
4167 return;
4168 }
4169
4170 if (*gi.argv(1) < '0' && *gi.argv(1) > '9') {
4171 safe_cprintf(ent, PRINT_HIGH, "Specify the player number to kick.\n");
4172 return;
4173 }
4174
4175 i = atoi(gi.argv(1));
4176 if (i < 1 || i > maxclients->value) {
4177 safe_cprintf(ent, PRINT_HIGH, "Invalid player number.\n");
4178 return;
4179 }
4180
4181 targ = g_edicts + i;
4182 if (!targ->inuse) {
4183 safe_cprintf(ent, PRINT_HIGH, "That player number is not connected.\n");
4184 return;
4185 }
4186
4187 sprintf(text, "kick %d\n", i - 1);
4188 gi.AddCommandString(text);
4189 }
4190
4191
CTFSetPowerUpEffect(edict_t * ent,int def)4192 void CTFSetPowerUpEffect(edict_t *ent, int def)
4193 {
4194 if (ent->client->resp.ctf_team == CTF_TEAM1)
4195 ent->s.effects |= EF_PENT; // red
4196 else if (ent->client->resp.ctf_team == CTF_TEAM2)
4197 ent->s.effects |= EF_QUAD; // red
4198 else
4199 ent->s.effects |= def;
4200 }
4201
4202