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