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