Annotation of quake2/ctf/g_ctf.c, revision 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.