Annotation of quake2/ctf/g_ctf.c, revision 1.1.1.2

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

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.