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

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

unix.superglobalmegacorp.com

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