PageRenderTime 36ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/ctf/g_ctf.c

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