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