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