]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_ctf.qc
Add player's team to the log print
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
index 62320f37f3232589f2e089798cf2bfd63a2a5049..afa466d36267a81029ea31fd101c40d3d53fc0f6 100644 (file)
@@ -17,7 +17,8 @@ void ctf_FakeTimeLimit(entity e, float t)
 void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
 {
        if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
+               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
 }
 
 void ctf_CaptureRecord(entity flag, entity player)
@@ -25,20 +26,22 @@ void ctf_CaptureRecord(entity flag, entity player)
        float cap_record = ctf_captimerecord;
        float cap_time = (time - flag.ctf_pickuptime);
        string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-       
+
        // notify about shit
-       if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
-       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-       
+       if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
+       else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
+       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+
        // write that shit in the database
+       if(!ctf_oneflag) // but not in 1-flag mode
        if((!ctf_captimerecord) || (cap_time < cap_record))
        {
                ctf_captimerecord = cap_time;
                db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time"), ftos(cap_time));
                db_put(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"), player.netname);
-               write_recordmarker(player, (time - cap_time), cap_time); 
-       } 
+               write_recordmarker(player, (time - cap_time), cap_time);
+       }
 }
 
 void ctf_FlagcarrierWaypoints(entity player)
@@ -92,7 +95,7 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
                float ang; // angle between shotdir and h
                float h; // hypotenuse, which is the distance between attacker to head
                float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
-               
+
                h = vlen(head_center - passer_center);
                ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
                a = h * cos(ang);
@@ -113,20 +116,26 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
 
 
 // =======================
-// CaptureShield Functions 
+// CaptureShield Functions
 // =======================
 
-float ctf_CaptureShield_CheckStatus(entity p) 
+float ctf_CaptureShield_CheckStatus(entity p)
 {
-       float s, se;
+       float s, s2, s3, s4, se, se2, se3, se4, sr, ser;
        entity e;
        float players_worseeq, players_total;
 
        if(ctf_captureshield_max_ratio <= 0)
                return FALSE;
 
-       s = PlayerScore_Add(p, SP_SCORE, 0);
-       if(s >= -ctf_captureshield_min_negscore)
+       s = PlayerScore_Add(p, SP_CTF_CAPS, 0);
+       s2 = PlayerScore_Add(p, SP_CTF_PICKUPS, 0);
+       s3 = PlayerScore_Add(p, SP_CTF_RETURNS, 0);
+       s4 = PlayerScore_Add(p, SP_CTF_FCKILLS, 0);
+
+       sr = ((s - s2) + (s3 + s4));
+
+       if(sr >= -ctf_captureshield_min_negscore)
                return FALSE;
 
        players_total = players_worseeq = 0;
@@ -134,15 +143,21 @@ float ctf_CaptureShield_CheckStatus(entity p)
        {
                if(DIFF_TEAM(e, p))
                        continue;
-               se = PlayerScore_Add(e, SP_SCORE, 0);
-               if(se <= s)
+               se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
+               se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
+               se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
+               se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
+
+               ser = ((se - se2) + (se3 + se4));
+
+               if(ser <= sr)
                        ++players_worseeq;
                ++players_total;
        }
 
        // player is in the worse half, if >= half the players are better than him, or consequently, if < half of the players are worse
        // use this rule here
-       
+
        if(players_worseeq >= players_total * ctf_captureshield_max_ratio)
                return FALSE;
 
@@ -161,28 +176,40 @@ void ctf_CaptureShield_Update(entity player, float wanted_status)
 
 float ctf_CaptureShield_Customize()
 {
+       if(self.enemy.active != ACTIVE_ACTIVE) { return TRUE; }
        if(!other.ctf_captureshielded) { return FALSE; }
-       if(SAME_TEAM(self, other)) { return FALSE; }
-       
+       if(CTF_SAMETEAM(self, other)) { return FALSE; }
+
        return TRUE;
 }
 
 void ctf_CaptureShield_Touch()
 {
+       if(self.enemy.active != ACTIVE_ACTIVE)
+       {
+               vector mymid = (self.absmin + self.absmax) * 0.5;
+               vector othermid = (other.absmin + other.absmax) * 0.5;
+
+               Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
+               if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_INACTIVE); }
+
+               return;
+       }
+
        if(!other.ctf_captureshielded) { return; }
-       if(SAME_TEAM(self, other)) { return; }
-       
+       if(CTF_SAMETEAM(self, other)) { return; }
+
        vector mymid = (self.absmin + self.absmax) * 0.5;
        vector othermid = (other.absmin + other.absmax) * 0.5;
 
        Damage(other, self, self, 0, DEATH_HURTTRIGGER, mymid, normalize(othermid - mymid) * ctf_captureshield_force);
-       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
+       if(IS_REAL_CLIENT(other)) { Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED); }
 }
 
 void ctf_CaptureShield_Spawn(entity flag)
 {
        entity shield = spawn();
-       
+
        shield.enemy = self;
        shield.team = self.team;
        shield.touch = ctf_CaptureShield_Touch;
@@ -193,7 +220,7 @@ void ctf_CaptureShield_Spawn(entity flag)
        shield.solid = SOLID_TRIGGER;
        shield.avelocity = '7 0 11';
        shield.scale = 0.5;
-       
+
        setorigin(shield, self.origin);
        setmodel(shield, "models/ctf/shield.md3");
        setsize(shield, shield.scale * shield.mins, shield.scale * shield.maxs);
@@ -217,16 +244,16 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype)
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
        flag.ctf_status = FLAG_DROPPED;
-       
+
        // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
        sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
        // scoring
-       PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);   
+       PlayerTeamScore_AddScore(player, -autocvar_g_ctf_score_penalty_drop);
        PlayerScore_Add(player, SP_CTF_DROPS, 1);
-       
+
        // waypoints
        if(autocvar_g_ctf_flag_dropped_waypoint)
                WaypointSprite_Spawn("flagdropped", 0, 0, flag, FLAG_WAYPOINT_OFFSET, world, ((autocvar_g_ctf_flag_dropped_waypoint == 2) ? 0 : player.team), flag, wps_flagdropped, TRUE, RADARICON_FLAG, WPCOLOR_DROPPEDFLAG(flag.team));
@@ -236,9 +263,9 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype)
                WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
                WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health);
        }
-       
+
        player.throw_antispam = time + autocvar_g_ctf_pass_wait;
-       
+
        if(droptype == DROP_PASS)
        {
                flag.pass_distance = 0;
@@ -251,14 +278,23 @@ void ctf_Handle_Retrieve(entity flag, entity player)
 {
        entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
        entity sender = flag.pass_sender;
-       
+
        // transfer flag to player
        flag.owner = player;
        flag.owner.flagcarried = flag;
-       
+
        // reset flag
-       setattachment(flag, player, "");
-       setorigin(flag, FLAG_CARRY_OFFSET);
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
        flag.movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
@@ -268,20 +304,20 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        // messages and sounds
        sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTEN_NORM);
        ctf_EventLog("receive", flag.team, player);
-       
+
        FOR_EACH_REALPLAYER(tmp_player)
        {
                if(tmp_player == sender)
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname);
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
                else if(tmp_player == player)
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
                else if(SAME_TEAM(tmp_player, sender))
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
        }
-       
+
        // create new waypoint
        ctf_FlagcarrierWaypoints(player);
-       
+
        sender.throw_antispam = time + autocvar_g_ctf_pass_wait;
        player.throw_antispam = sender.throw_antispam;
 
@@ -294,12 +330,12 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
 {
        entity flag = player.flagcarried;
        vector targ_origin, flag_velocity;
-       
+
        if(!flag) { return; }
        if((droptype == DROP_PASS) && !receiver) { return; }
-       
+
        if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
-       
+
        // reset the flag
        setattachment(flag, world, "");
        setorigin(flag, player.origin + FLAG_DROP_OFFSET);
@@ -308,9 +344,9 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
-       
+
        flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-       
+
        switch(droptype)
        {
                case DROP_PASS:
@@ -331,30 +367,30 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                        flag.pass_sender = player;
                        flag.pass_target = receiver;
                        flag.ctf_status = FLAG_PASSING;
-                       
+
                        // other
                        sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTEN_NORM);
                        WarpZone_TrailParticles(world, particleeffectnum(flag.passeffect), player.origin, targ_origin);
                        ctf_EventLog("pass", flag.team, player);
                        break;
                }
-               
+
                case DROP_THROW:
                {
                        makevectors((player.v_angle_y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle_x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
-                               
+
                        flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
                        flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, FALSE);
                        ctf_Handle_Drop(flag, player, droptype);
                        break;
                }
-               
+
                case DROP_RESET:
                {
                        flag.velocity = '0 0 0'; // do nothing
                        break;
                }
-               
+
                default:
                case DROP_NORMAL:
                {
@@ -367,10 +403,10 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
        // kill old waypointsprite
        WaypointSprite_Ping(player.wps_flagcarrier);
        WaypointSprite_Kill(player.wps_flagcarrier);
-       
+
        if(player.wps_enemyflagcarrier)
                WaypointSprite_Kill(player.wps_enemyflagcarrier);
-       
+
        // captureshield
        ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
 }
@@ -384,22 +420,39 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
 {
        entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
        entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
-       float old_time, new_time; 
-       
-       if not(player) { return; } // without someone to give the reward to, we can't possibly cap
+       entity player_team_flag = world, tmp_entity;
+       float old_time, new_time;
+
+       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(ctf_oneflag) 
+       {
+               if(CTF_SAMETEAM(player, flag)) { return; }
+       }
+       else if(CTF_DIFFTEAM(player, flag)) { return; }
        
+       if(ctf_oneflag)
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       if(SAME_TEAM(tmp_entity, player))
+       {
+               player_team_flag = tmp_entity;
+               break;
+       }
+
+       player.throw_prevtime = time;
+       player.throw_count = 0;
+
        // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
+       Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
        ctf_CaptureRecord(enemy_flag, player);
-       sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
-       
+       sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
+
        switch(capturetype)
        {
                case CAPTURE_NORMAL: ctf_EventLog("capture", enemy_flag.team, player); break;
                case CAPTURE_DROPPED: ctf_EventLog("droppedcapture", enemy_flag.team, player); break;
                default: break;
        }
-       
+
        // scoring
        PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_capture);
        PlayerTeamScore_Add(player, SP_CTF_CAPS, ST_CTF_CAPS, 1);
@@ -418,11 +471,11 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
        {
                WaypointSprite_Kill(player.wps_flagcarrier);
                if(flag.speedrunning) { ctf_FakeTimeLimit(player, -1); }
-               
+
                if((enemy_flag.ctf_dropper) && (player != enemy_flag.ctf_dropper))
                        { PlayerTeamScore_AddScore(enemy_flag.ctf_dropper, autocvar_g_ctf_score_capture_assist); }
        }
-       
+
        // reset the flag
        player.next_take_time = time + autocvar_g_ctf_flag_collect_delay;
        ctf_RespawnFlag(enemy_flag);
@@ -431,24 +484,38 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
 void ctf_Handle_Return(entity flag, entity player)
 {
        // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_));
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname);
+       if(player.flags & FL_MONSTER)
+       {
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
+       }
+       else if(flag.team)
+       {
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_RETURN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_), player.netname);
+       }
        sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("return", flag.team, player);
 
        // scoring
-       PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
-       PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+       if(IS_PLAYER(player))
+       {
+               PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_return); // reward for return
+               PlayerScore_Add(player, SP_CTF_RETURNS, 1); // add to count of returns
+       }
 
        TeamScore_AddToTeam(flag.team, ST_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the team who was last carrying it
-       
-       if(flag.ctf_dropper) 
+
+       if(flag.ctf_dropper)
        {
                PlayerScore_Add(flag.ctf_dropper, SP_SCORE, -autocvar_g_ctf_score_penalty_returned); // punish the player who dropped the flag
-               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag 
+               ctf_CaptureShield_Update(flag.ctf_dropper, 0); // shield player from picking up flag
                flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
        }
-       
+
+       // other
+       if(player.flagcarried == flag)
+               WaypointSprite_Kill(player.wps_flagcarrier);
+
        // reset the flag
        ctf_RespawnFlag(flag);
 }
@@ -457,20 +524,30 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
 {
        // declarations
        float pickup_dropped_score; // used to calculate dropped pickup score
-       
+       entity tmp_entity; // temporary entity
+
        // attach the flag to the player
        flag.owner = player;
        player.flagcarried = flag;
-       setattachment(flag, player, "");
-       setorigin(flag, FLAG_CARRY_OFFSET);
-       
+       if(player.vehicle)
+       {
+               setattachment(flag, player.vehicle, "");
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
+               flag.scale = VEHICLE_FLAG_SCALE;
+       }
+       else
+       {
+               setattachment(flag, player, "");
+               setorigin(flag, FLAG_CARRY_OFFSET);
+       }
+
        // flag setup
        flag.movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
        flag.angles = '0 0 0';
        flag.ctf_status = FLAG_CARRY;
-       
+
        switch(pickuptype)
        {
                case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
@@ -479,26 +556,42 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        }
 
        // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);  
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
        if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
-       
-       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
-       Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
-       
+       if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
+       else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
+       else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
+
+       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
+       
+       if(!flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(DIFF_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
+       
+       if(flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(CTF_SAMETEAM(flag, tmp_entity))
+       if(SAME_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
+       else
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
+
        sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
-       
+
        // scoring
        PlayerScore_Add(player, SP_CTF_PICKUPS, 1);
        switch(pickuptype)
-       {               
+       {
                case PICKUP_BASE:
                {
                        PlayerTeamScore_AddScore(player, autocvar_g_ctf_score_pickup_base);
                        ctf_EventLog("steal", flag.team, player);
                        break;
                }
-               
+
                case PICKUP_DROPPED:
                {
                        pickup_dropped_score = (autocvar_g_ctf_flag_return_time ? bound(0, ((flag.ctf_droptime + autocvar_g_ctf_flag_return_time) - time) / autocvar_g_ctf_flag_return_time, 1) : 1);
@@ -508,10 +601,10 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
                        ctf_EventLog("pickup", flag.team, player);
                        break;
                }
-               
+
                default: break;
        }
-       
+
        // speedrunning
        if(pickuptype == PICKUP_BASE)
        {
@@ -519,11 +612,11 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
                if((player.speedrunning) && (ctf_captimerecord))
                        ctf_FakeTimeLimit(player, time + ctf_captimerecord);
        }
-               
+
        // effects
        pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
-       
-       // waypoints 
+
+       // waypoints
        if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
        ctf_FlagcarrierWaypoints(player);
        WaypointSprite_Ping(player.wps_flagcarrier);
@@ -539,19 +632,19 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
        if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
        {
                if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, flag.health); }
-               
+
                if((flag.health <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
                {
                        switch(returntype)
                        {
-                               case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
-                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
-                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
-                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
-                               
+                               case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
+                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
+                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
+                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
+
                                default:
                                case RETURN_TIMEOUT:
-                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
+                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
                        }
                        sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
                        ctf_EventLog("returned", flag.team, world);
@@ -560,10 +653,25 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
        }
 }
 
+float ctf_Stalemate_Customize()
+{
+       // make spectators see what the player would see
+       entity e, wp_owner;
+       e = WaypointSprite_getviewentity(other);
+       wp_owner = self.owner;
+
+       // team waypoints
+       if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return FALSE; }
+       if(SAME_TEAM(wp_owner, e)) { return FALSE; }
+       if(!IS_PLAYER(e)) { return FALSE; }
+
+       return TRUE;
+}
+
 void ctf_CheckStalemate(void)
 {
        // declarations
-       float stale_red_flags = 0, stale_blue_flags = 0;
+       float stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
        entity tmp_entity;
 
        entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
@@ -577,36 +685,49 @@ void ctf_CheckStalemate(void)
                {
                        tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
                        ctf_staleflaglist = tmp_entity;
-                       
+
                        switch(tmp_entity.team)
                        {
                                case NUM_TEAM_1: ++stale_red_flags; break;
                                case NUM_TEAM_2: ++stale_blue_flags; break;
+                               case NUM_TEAM_3: ++stale_yellow_flags; break;
+                               case NUM_TEAM_4: ++stale_pink_flags; break;
+                               default: ++stale_neutral_flags; break;
                        }
                }
        }
 
-       if(stale_red_flags && stale_blue_flags)
+       if(ctf_oneflag)
+               stale_flags = (stale_neutral_flags >= 1);
+       else
+               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
+
+       if(ctf_oneflag && stale_flags == 1)
+               ctf_stalemate = TRUE;
+       else if(stale_flags == ctf_teams)
                ctf_stalemate = TRUE;
-       else if((!stale_red_flags && !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 2)
+       else if(stale_flags == 0 && autocvar_g_ctf_stalemate_endcondition == 2)
                { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
-       else if((!stale_red_flags || !stale_blue_flags) && autocvar_g_ctf_stalemate_endcondition == 1)
+       else if(stale_flags < ctf_teams && autocvar_g_ctf_stalemate_endcondition == 1)
                { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
-               
+
        // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
        if(ctf_stalemate)
        {
                for(tmp_entity = ctf_staleflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_staleflagnext)
                {
                        if((tmp_entity.owner) && (!tmp_entity.owner.wps_enemyflagcarrier))
-                               WaypointSprite_Spawn("enemyflagcarrier", 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, tmp_entity.team, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
+                       {
+                               WaypointSprite_Spawn(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, TRUE, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
+                               tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
+                       }
                }
-               
-               if not(wpforenemy_announced)
+
+               if (!wpforenemy_announced)
                {
                        FOR_EACH_REALPLAYER(tmp_entity)
                                Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
-                       
+
                        wpforenemy_announced = TRUE;
                }
        }
@@ -616,12 +737,18 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float death
 {
        if(ITEM_DAMAGE_NEEDKILL(deathtype))
        {
-               // automatically kill the flag and return it
-               self.health = 0;
-               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               if(autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.ctf_flagdamaged = TRUE;
+               }
+               else
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
                return;
        }
-       if(autocvar_g_ctf_flag_return_damage) 
+       if(autocvar_g_ctf_flag_return_damage)
        {
                // reduce health and check if it should be returned
                self.health = self.health - damage;
@@ -646,9 +773,9 @@ void ctf_FlagThink()
        if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
                dprint("wtf the flag got squashed?\n");
                tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
-               if(!trace_startsolid) // can we resize it without getting stuck?
+               if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
-                       
+
        switch(self.ctf_status) // reset flag angles in case warpzones adjust it
        {
                case FLAG_DROPPED:
@@ -656,13 +783,13 @@ void ctf_FlagThink()
                        self.angles = '0 0 0';
                        break;
                }
-               
+
                default: break;
        }
 
        // main think method
        switch(self.ctf_status)
-       {       
+       {
                case FLAG_BASE:
                {
                        if(autocvar_g_ctf_dropped_capture_radius)
@@ -675,7 +802,7 @@ void ctf_FlagThink()
                        }
                        return;
                }
-               
+
                case FLAG_DROPPED:
                {
                        if(autocvar_g_ctf_flag_dropped_floatinwater)
@@ -684,7 +811,7 @@ void ctf_FlagThink()
                                if(pointcontents(midpoint) == CONTENT_WATER)
                                {
                                        self.velocity = self.velocity * 0.5;
-                                       
+
                                        if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
                                                { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
                                        else
@@ -701,18 +828,24 @@ void ctf_FlagThink()
                                        return;
                                }
                        }
-                       if(autocvar_g_ctf_flag_return_time)
+                       if(self.ctf_flagdamaged)
+                       {
+                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
+                               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+                               return;
+                       }
+                       else if(autocvar_g_ctf_flag_return_time)
                        {
                                self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
                                ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
                                return;
-                       } 
+                       }
                        return;
                }
-                       
+
                case FLAG_CARRY:
                {
-                       if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord)) 
+                       if(self.speedrunning && ctf_captimerecord && (time >= self.ctf_pickuptime + ctf_captimerecord))
                        {
                                self.health = 0;
                                ctf_CheckFlagReturn(self, RETURN_SPEEDRUN);
@@ -731,17 +864,25 @@ void ctf_FlagThink()
                                        wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
                                }
                        }
+                       if(CTF_SAMETEAM(self, self.owner) && self.team)
+                       {
+                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+                                       ctf_Handle_Throw(self.owner, world, DROP_THROW);
+                               else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
+                                       ctf_Handle_Return(self, self.owner);
+                       }
                        return;
                }
-               
+
                case FLAG_PASSING:
                {
                        vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
                        targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
                        WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
-                       
+
                        if((self.pass_target == world)
                                || (self.pass_target.deadflag != DEAD_NO)
+                               || (self.pass_target.flagcarried)
                                || (vlen(self.origin - targ_origin) > autocvar_g_ctf_pass_radius)
                                || ((trace_fraction < 1) && (trace_ent != self.pass_target))
                                || (time > self.ctf_droptime + autocvar_g_ctf_pass_timelimit))
@@ -768,26 +909,38 @@ void ctf_FlagThink()
 void ctf_FlagTouch()
 {
        if(gameover) { return; }
-       
-       entity toucher = other;
-       
+       if(self.active != ACTIVE_ACTIVE) { return; }
+
+       entity toucher = other, tmp_entity;
+       float is_not_monster = (!(toucher.flags & FL_MONSTER)), num_perteam = 0;
+
        // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
        if(ITEM_TOUCH_NEEDKILL())
        {
-               self.health = 0;
-               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               if(!autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
                return;
        }
-       
+
+       FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
+
        // special touch behaviors
        if(toucher.vehicle_flags & VHF_ISVEHICLE)
        {
-               if(autocvar_g_ctf_allow_vehicle_touch)
+               if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
                        toucher = toucher.owner; // the player is actually the vehicle owner, not other
                else
                        return; // do nothing
        }
-       else if not(IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
+       else if(toucher.flags & FL_MONSTER)
+       {
+               if(!autocvar_g_ctf_allow_monster_touch)
+                       return; // do nothing
+       }
+       else if (!IS_PLAYER(toucher)) // The flag just touched an object, most likely the world
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
@@ -799,32 +952,39 @@ void ctf_FlagTouch()
        }
        else if(toucher.deadflag != DEAD_NO) { return; }
 
-       switch(self.ctf_status) 
-       {       
+       switch(self.ctf_status)
+       {
                case FLAG_BASE:
                {
-                       if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self))
+                       if(ctf_oneflag)
+                       {
+                               if(CTF_DIFFTEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+                                       ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+                               else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                                       ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+                       }
+                       else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
                                ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
+                       else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
                                ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
                        break;
                }
-               
+
                case FLAG_DROPPED:
                {
-                       if(SAME_TEAM(toucher, self))
+                       if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
                                ctf_Handle_Return(self, toucher); // toucher just returned his own flag
-                       else if((!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
+                       else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
                                ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
                        break;
                }
-                       
+
                case FLAG_CARRY:
                {
                        dprint("Someone touched a flag even though it was being carried?\n");
                        break;
                }
-               
+
                case FLAG_PASSING:
                {
                        if((IS_PLAYER(toucher)) && (toucher.deadflag == DEAD_NO) && (toucher != self.pass_sender))
@@ -847,28 +1007,29 @@ void ctf_RespawnFlag(entity flag)
                { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
 
        flag.last_respawn = time;
-       
+
        // reset the player (if there is one)
        if((flag.owner) && (flag.owner.flagcarried == flag))
        {
-               if(flag.owner.wps_enemyflagcarrier)
-                       WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
-                       
+               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
                WaypointSprite_Kill(flag.wps_flagcarrier);
-               
+
                flag.owner.flagcarried = world;
 
                if(flag.speedrunning)
                        ctf_FakeTimeLimit(flag.owner, -1);
        }
 
-       if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
+       if((flag.owner) && (flag.owner.vehicle))
+               flag.scale = FLAG_SCALE;
+
+       if(flag.ctf_status == FLAG_DROPPED)
                { WaypointSprite_Kill(flag.wps_flagdropped); }
 
        // reset the flag
        setattachment(flag, world, "");
        setorigin(flag, flag.ctf_spawnorigin);
-       
+
        flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_NO;
        flag.health = flag.max_flag_health;
@@ -876,7 +1037,7 @@ void ctf_RespawnFlag(entity flag)
        flag.velocity = '0 0 0';
        flag.angles = flag.mangle;
        flag.flags = FL_ITEM | FL_NOTARGET;
-       
+
        flag.ctf_status = FLAG_BASE;
        flag.owner = world;
        flag.pass_distance = 0;
@@ -885,6 +1046,9 @@ void ctf_RespawnFlag(entity flag)
        flag.ctf_dropper = world;
        flag.ctf_pickuptime = 0;
        flag.ctf_droptime = 0;
+       flag.ctf_flagdamaged = 0;
+       
+       ctf_CheckStalemate();
 }
 
 void ctf_Reset()
@@ -892,10 +1056,44 @@ void ctf_Reset()
        if(self.owner)
                if(IS_PLAYER(self.owner))
                        ctf_Handle_Throw(self.owner, world, DROP_RESET);
-                       
+
        ctf_RespawnFlag(self);
 }
 
+void ctf_Use()
+{
+       if(self.ctf_status != FLAG_BASE) { return; }
+       
+       entity flag;
+       float flag_cnt = 0;
+       
+       for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
+       {
+               if(flag != self)
+               if(SAME_TEAM(flag, self))
+               if(flag.active == ACTIVE_ACTIVE)
+                       ++flag_cnt;
+       }
+
+       if(self.active == ACTIVE_ACTIVE)
+       if(flag_cnt < 1)
+       {
+               dprint("ctf_Use: Unable to deactivate flag (not enough active flags on the map)\n");
+               return;
+       }
+
+       self.active = ((self.active) ? ACTIVE_NOT : ACTIVE_ACTIVE);
+       
+       if(self.active == ACTIVE_ACTIVE)
+               WaypointSprite_Ping(self.wps_flagbase);
+}
+
+float ctf_FlagWaypoint_Customize()
+{
+       if(self.owner.active != ACTIVE_ACTIVE) { return FALSE; }
+       return TRUE;
+}
+
 void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf_FlagSetup()
 {
        // bot waypoints
@@ -904,34 +1102,44 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf
        self.bot_basewaypoint = self.nearestwaypoint;
 
        // waypointsprites
-       WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
-       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, FALSE));
+       string basename = "base";
+
+       switch(self.team)
+       {
+               case NUM_TEAM_1: basename = "redbase"; break;
+               case NUM_TEAM_2: basename = "bluebase"; break;
+               case NUM_TEAM_3: basename = "yellowbase"; break;
+               case NUM_TEAM_4: basename = "pinkbase"; break;
+               default: basename = "neutralbase"; break;
+       }
+
+       WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
+       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, FALSE) : '1 1 1'));
+       self.wps_flagbase.customizeentityforclient = ctf_FlagWaypoint_Customize;
 
        // captureshield setup
        ctf_CaptureShield_Spawn(self);
 }
 
 void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc 
-{      
+{
        // declarations
-       teamnumber = fabs(teamnumber - bound(0, autocvar_g_ctf_reverse, 1)); // if we were originally 1, this will become 0. If we were originally 0, this will become 1. 
        self = flag; // for later usage with droptofloor()
-       
+
        // main setup
        flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
        ctf_worldflaglist = flag;
 
        setattachment(flag, world, "");
 
-       flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag"); // Primarily only used for debugging or when showing nearby item name
-       flag.team = ((teamnumber) ? NUM_TEAM_1 : NUM_TEAM_2); // NUM_TEAM_1: color 4 team (red) - NUM_TEAM_2: color 13 team (blue)
-       flag.items = ((teamnumber) ? IT_KEY2 : IT_KEY1); // IT_KEY2: gold key (redish enough) - IT_KEY1: silver key (bluish enough)
+       flag.netname = sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber));
+       flag.team = teamnumber;
        flag.classname = "item_flag_team";
        flag.target = "###item###"; // wut?
        flag.flags = FL_ITEM | FL_NOTARGET;
        flag.solid = SOLID_TRIGGER;
        flag.takedamage = DAMAGE_NO;
-       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;   
+       flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
        flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
        flag.health = flag.max_flag_health;
        flag.event_damage = ctf_FlagDamage;
@@ -942,28 +1150,30 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        flag.velocity = '0 0 0';
        flag.mangle = flag.angles;
        flag.reset = ctf_Reset;
+       flag.use = ctf_Use;
        flag.touch = ctf_FlagTouch;
        flag.think = ctf_FlagThink;
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
 
        // appearence
-       if(flag.model == "")       { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_model); }
+       if(flag.model == "")       { flag.model = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_model : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_model : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_model : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_model : autocvar_g_ctf_flag_neutral_model)))); }
        if(!flag.scale)            { flag.scale = FLAG_SCALE; }
-       if(!flag.skin)             { flag.skin = ((teamnumber) ? autocvar_g_ctf_flag_red_skin : autocvar_g_ctf_flag_blue_skin); }
-       if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber) ? "redflag_touch" : "blueflag_touch"); }
-       if(flag.passeffect == "")  { flag.passeffect = ((teamnumber) ? "red_pass" : "blue_pass"); }
-       if(flag.capeffect == "")   { flag.capeffect = ((teamnumber) ? "red_cap" : "blue_cap"); }
-       
-       // sound 
-       if(flag.snd_flag_taken == "")    { flag.snd_flag_taken  = ((teamnumber) ? "ctf/red_taken.wav" : "ctf/blue_taken.wav"); }
-       if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber) ? "ctf/red_returned.wav" : "ctf/blue_returned.wav"); }
-       if(flag.snd_flag_capture == "")  { flag.snd_flag_capture = ((teamnumber) ? "ctf/red_capture.wav" : "ctf/blue_capture.wav"); } // blue team scores by capturing the red flag
+       if(!flag.skin)             { flag.skin = ((teamnumber == NUM_TEAM_1) ? autocvar_g_ctf_flag_red_skin : ((teamnumber == NUM_TEAM_2) ? autocvar_g_ctf_flag_blue_skin : ((teamnumber == NUM_TEAM_3) ? autocvar_g_ctf_flag_yellow_skin : ((teamnumber == NUM_TEAM_4) ? autocvar_g_ctf_flag_pink_skin : autocvar_g_ctf_flag_neutral_skin)))); }
+       if(flag.toucheffect == "") { flag.toucheffect = ((teamnumber == NUM_TEAM_1) ? "redflag_touch" : ((teamnumber == NUM_TEAM_2) ? "blueflag_touch" : ((teamnumber == NUM_TEAM_3) ? "yellowflag_touch" : ((teamnumber == NUM_TEAM_4) ? "pinkflag_touch" : "neutralflag_touch")))); }
+       if(flag.passeffect == "")  { flag.passeffect = ((teamnumber == NUM_TEAM_1) ? "red_pass" : ((teamnumber == NUM_TEAM_2) ? "blue_pass" : ((teamnumber == NUM_TEAM_3) ? "yellow_pass" : ((teamnumber == NUM_TEAM_4) ? "pink_pass" : "neutral_pass")))); }
+       if(flag.capeffect == "")   { flag.capeffect = ((teamnumber == NUM_TEAM_1) ? "red_cap" : ((teamnumber == NUM_TEAM_2) ? "blue_cap" : ((teamnumber == NUM_TEAM_3) ? "yellow_cap" : ((teamnumber == NUM_TEAM_4) ? "pink_cap" : "")))); } // neutral flag cant be capped itself
+       if(!flag.active)           { flag.active = ACTIVE_ACTIVE; }
+
+       // sound
+       if(flag.snd_flag_taken == "")    { flag.snd_flag_taken = ((teamnumber == NUM_TEAM_1) ? "ctf/red_taken.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_taken.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_taken.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_taken.wav" : "ctf/neutral_taken.wav")))); }
+       if(flag.snd_flag_returned == "") { flag.snd_flag_returned = ((teamnumber == NUM_TEAM_1) ? "ctf/red_returned.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_returned.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_returned.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_returned.wav" : "")))); } // neutral flag can't be returned by players
+       if(flag.snd_flag_capture == "")  { flag.snd_flag_capture = ((teamnumber == NUM_TEAM_1) ? "ctf/red_capture.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_capture.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_capture.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_capture.wav" : "")))); } // again can't be captured
        if(flag.snd_flag_respawn == "")  { flag.snd_flag_respawn = "ctf/flag_respawn.wav"; } // if there is ever a team-based sound for this, update the code to match.
-       if(flag.snd_flag_dropped == "")  { flag.snd_flag_dropped = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
+       if(flag.snd_flag_dropped == "")  { flag.snd_flag_dropped = ((teamnumber == NUM_TEAM_1) ? "ctf/red_dropped.wav" : ((teamnumber == NUM_TEAM_2) ? "ctf/blue_dropped.wav" : ((teamnumber == NUM_TEAM_3) ? "ctf/yellow_dropped.wav" : ((teamnumber == NUM_TEAM_4) ? "ctf/pink_dropped.wav" : "ctf/neutral_dropped.wav")))); }
        if(flag.snd_flag_touch == "")    { flag.snd_flag_touch = "ctf/touch.wav"; } // again has no team-based sound
        if(flag.snd_flag_pass == "")     { flag.snd_flag_pass = "ctf/pass.wav"; } // same story here
-       
+
        // precache
        precache_sound(flag.snd_flag_taken);
        precache_sound(flag.snd_flag_returned);
@@ -980,33 +1190,43 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        setmodel(flag, flag.model); // precision set below
        setsize(flag, FLAG_MIN, FLAG_MAX);
        setorigin(flag, (flag.origin + FLAG_SPAWN_OFFSET));
-       
+
        if(autocvar_g_ctf_flag_glowtrails)
        {
-               flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
+               flag.glow_color = ((teamnumber == NUM_TEAM_1) ? 251 : ((teamnumber == NUM_TEAM_2) ? 210 : ((teamnumber == NUM_TEAM_3) ? 110 : ((teamnumber == NUM_TEAM_4) ? 145 : 254))));
                flag.glow_size = 25;
                flag.glow_trail = 1;
        }
-       
+
        flag.effects |= EF_LOWPRECISION;
        if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
-       if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
+       if(autocvar_g_ctf_dynamiclights)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
+                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
+                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
+                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
+                       default: flag.effects |= EF_DIMLIGHT; break;
+               }
+       }
        
        // flag placement
        if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
-       {       
-               flag.dropped_origin = flag.origin; 
+       {
+               flag.dropped_origin = flag.origin;
                flag.noalign = TRUE;
                flag.movetype = MOVETYPE_NONE;
        }
        else // drop to floor, automatically find a platform and set that as spawn origin
-       { 
+       {
                flag.noalign = FALSE;
                self = flag;
                droptofloor();
-               flag.movetype = MOVETYPE_TOSS; 
-       }       
-       
+               flag.movetype = MOVETYPE_TOSS;
+       }
+
        InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
 }
 
@@ -1044,7 +1264,7 @@ entity havocbot_ctf_find_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team == f.team)
+               if (CTF_SAMETEAM(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
@@ -1057,7 +1277,20 @@ entity havocbot_ctf_find_enemy_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team != f.team)
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(bot, f))
+                       {
+                               if(f.team)
+                               {
+                                       if(bot.flagcarried)
+                                               return f;
+                               }
+                               else if(!bot.flagcarried)
+                                       return f;
+                       }
+               }
+               else if (CTF_DIFFTEAM(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
@@ -1066,7 +1299,7 @@ entity havocbot_ctf_find_enemy_flag(entity bot)
 
 float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
 {
-       if not(teamplay)
+       if (!teamplay)
                return 0;
 
        float c = 0;
@@ -1074,7 +1307,7 @@ float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
 
        FOR_EACH_PLAYER(head)
        {
-               if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
+               if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
                        continue;
 
                if(vlen(head.origin - org) < tc_radius)
@@ -1090,7 +1323,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (CTF_SAMETEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1104,11 +1337,11 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (CTF_SAMETEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
-       if not(head)
+       if (!head)
                return;
 
        navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
@@ -1120,7 +1353,20 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team != head.team)
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(self, head))
+                       {
+                               if(head.team)
+                               {
+                                       if(self.flagcarried)
+                                               break;
+                               }
+                               else if(!self.flagcarried)
+                                       break;
+                       }
+               }
+               else if(CTF_DIFFTEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1130,7 +1376,7 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale)
 
 void havocbot_goalrating_ctf_enemybase(float ratingscale)
 {
-       if not(bot_waypoints_for_items)
+       if (!bot_waypoints_for_items)
        {
                havocbot_goalrating_ctf_enemyflag(ratingscale);
                return;
@@ -1140,7 +1386,7 @@ void havocbot_goalrating_ctf_enemybase(float ratingscale)
 
        head = havocbot_ctf_find_enemy_flag(self);
 
-       if not(head)
+       if (!head)
                return;
 
        navigation_routerating(head.bot_basewaypoint, ratingscale, 10000);
@@ -1242,7 +1488,7 @@ void havocbot_ctf_reset_role(entity bot)
        // if there is only me on the team switch to offense
        c = 0;
        FOR_EACH_PLAYER(head)
-       if(head.team==bot.team)
+       if(SAME_TEAM(head, bot))
                ++c;
 
        if(c==1)
@@ -1288,7 +1534,10 @@ void havocbot_role_ctf_carrier()
                self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
 
                navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourbase(50000);
+               if(ctf_oneflag)
+                       havocbot_goalrating_ctf_enemybase(50000);
+               else
+                       havocbot_goalrating_ctf_ourbase(50000);
 
                if(self.health<100)
                        havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
@@ -1608,7 +1857,7 @@ void havocbot_role_ctf_defense()
                }
 
                if(closestplayer)
-               if(closestplayer.team!=self.team)
+               if(DIFF_TEAM(closestplayer, self))
                if(vlen(org - self.origin)>1000)
                if(checkpvs(self.origin,closestplayer)||random()<0.5)
                        havocbot_goalrating_ctf_ourbase(30000);
@@ -1675,41 +1924,53 @@ void havocbot_role_ctf_setrole(entity bot, float role)
 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
 {
        entity flag;
-       
-       // initially clear items so they can be set as necessary later.
-       self.items &= ~(IT_RED_FLAG_CARRYING | IT_RED_FLAG_TAKEN | IT_RED_FLAG_LOST
-               | IT_BLUE_FLAG_CARRYING | IT_BLUE_FLAG_TAKEN | IT_BLUE_FLAG_LOST | IT_CTF_SHIELDED);
 
-       // scan through all the flags and notify the client about them 
+       float t = 0, t2 = 0, t3 = 0;
+
+       // initially clear items so they can be set as necessary later.
+       self.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST 
+                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
+                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
+                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
+                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
+                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
+
+       // scan through all the flags and notify the client about them
        for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
+               if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
+
                switch(flag.ctf_status)
                {
                        case FLAG_PASSING:
                        case FLAG_CARRY:
                        {
                                if((flag.owner == self) || (flag.pass_sender == self))
-                                       self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
+                                       self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
                                else 
-                                       self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
+                                       self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
                                break;
                        }
                        case FLAG_DROPPED:
                        {
-                               self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
+                               self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
                                break;
                        }
                }
        }
-       
+
        // item for stopping players from capturing the flag too often
        if(self.ctf_captureshielded)
-               self.items |= IT_CTF_SHIELDED;
-       
+               self.ctf_flagstatus |= CTF_SHIELDED;
+
        // update the health of the flag carrier waypointsprite
-       if(self.wps_flagcarrier) 
+       if(self.wps_flagcarrier)
                WaypointSprite_UpdateHealth(self.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(self.health, self.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON));
-       
+
        return FALSE;
 }
 
@@ -1728,7 +1989,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
                        frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
                }
        }
-       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
+       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
        {
                if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
                if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
@@ -1748,10 +2009,14 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
                PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
                PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
        }
-                               
+
        if(frag_target.flagcarried)
-               { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
-               
+       {
+               entity tmp_entity = frag_target.flagcarried;
+               ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
+               tmp_entity.ctf_dropper = world;
+       }
+
        return FALSE;
 }
 
@@ -1764,23 +2029,23 @@ MUTATOR_HOOKFUNCTION(ctf_GiveFragsForKill)
 MUTATOR_HOOKFUNCTION(ctf_RemovePlayer)
 {
        entity flag; // temporary entity for the search method
-       
+
        if(self.flagcarried)
                { ctf_Handle_Throw(self, world, DROP_NORMAL); }
-       
+
        for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
                if(flag.pass_sender == self) { flag.pass_sender = world; }
                if(flag.pass_target == self) { flag.pass_target = world; }
                if(flag.ctf_dropper == self) { flag.ctf_dropper = world; }
        }
-               
+
        return FALSE;
 }
 
 MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
 {
-       if(self.flagcarried) 
+       if(self.flagcarried)
        if(!autocvar_g_ctf_portalteleport)
                { ctf_Handle_Throw(self, world, DROP_NORMAL); }
 
@@ -1790,7 +2055,7 @@ MUTATOR_HOOKFUNCTION(ctf_PortalTeleport)
 MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
 {
        if(MUTATOR_RETURNVALUE || gameover) { return FALSE; }
-       
+
        entity player = self;
 
        if((time > player.throw_antispam) && (player.deadflag == DEAD_NO) && !player.speedrunning && (!player.vehicle || autocvar_g_ctf_allow_vehicle_touch))
@@ -1800,21 +2065,21 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                {
                        entity head, closest_target = world;
                        head = WarpZone_FindRadius(player.origin, autocvar_g_ctf_pass_radius, TRUE);
-                       
+
                        while(head) // find the closest acceptable target to pass to
                        {
                                if(IS_PLAYER(head) && head.deadflag == DEAD_NO)
                                if(head != player && SAME_TEAM(head, player))
                                if(!head.speedrunning && !head.vehicle)
                                {
-                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc) 
+                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
                                        vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
                                        vector passer_center = CENTER_OR_VIEWOFS(player);
-                                       
+
                                        if(ctf_CheckPassDirection(head_center, passer_center, player.v_angle, head.WarpZone_findradius_nearest))
                                        {
-                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried) 
-                                               { 
+                                               if(autocvar_g_ctf_pass_request && !player.flagcarried && head.flagcarried)
+                                               {
                                                        if(IS_BOT_CLIENT(head))
                                                        {
                                                                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
@@ -1825,8 +2090,8 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                                                Send_Notification(NOTIF_ONE, head, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname);
                                                                Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
                                                        }
-                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
-                                                       return TRUE; 
+                                                       player.throw_antispam = time + autocvar_g_ctf_pass_wait;
+                                                       return TRUE;
                                                }
                                                else if(player.flagcarried)
                                                {
@@ -1842,10 +2107,10 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                }
                                head = head.chain;
                        }
-                       
+
                        if(closest_target) { ctf_Handle_Throw(player, closest_target, DROP_PASS); return TRUE; }
                }
-               
+
                // throw the flag in front of you
                if(autocvar_g_ctf_throw && player.flagcarried)
                {
@@ -1869,14 +2134,14 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_time) { player.throw_count = 1; }
                                else { player.throw_count += 1; }
                                if(player.throw_count >= autocvar_g_ctf_throw_punish_count) { player.throw_count = -1; }
-                                       
+
                                player.throw_prevtime = time;
                                ctf_Handle_Throw(player, world, DROP_THROW);
                                return TRUE;
                        }
                }
        }
-               
+
        return FALSE;
 }
 
@@ -1886,7 +2151,7 @@ MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
        {
                self.wps_helpme_time = time;
                WaypointSprite_HelpMePing(self.wps_flagcarrier);
-       } 
+       }
        else // create a normal help me waypointsprite
        {
                WaypointSprite_Spawn("helpme", waypointsprite_deployed_lifetime, waypointsprite_limitedrange, self, FLAG_WAYPOINT_OFFSET, world, self.team, self, wps_helpme, FALSE, RADARICON_HELPME, '1 0.5 0');
@@ -1905,15 +2170,15 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
                        ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
                }
                else
-               {            
-                       setattachment(vh_player.flagcarried, vh_vehicle, ""); 
+               {
+                       setattachment(vh_player.flagcarried, vh_vehicle, "");
                        setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
                        vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //vh_player.flagcarried.angles = '0 0 0';       
+                       //vh_player.flagcarried.angles = '0 0 0';
                }
                return TRUE;
        }
-               
+
        return FALSE;
 }
 
@@ -1921,7 +2186,7 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
 {
        if(vh_player.flagcarried)
        {
-               setattachment(vh_player.flagcarried, vh_player, ""); 
+               setattachment(vh_player.flagcarried, vh_player, "");
                setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
                vh_player.flagcarried.scale = FLAG_SCALE;
                vh_player.flagcarried.angles = '0 0 0';
@@ -1935,18 +2200,18 @@ MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
 {
        if(self.flagcarried)
        {
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
                ctf_RespawnFlag(self.flagcarried);
                return TRUE;
        }
-       
+
        return FALSE;
 }
 
 MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
 {
        entity flag; // temporary entity for the search method
-       
+
        for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
                switch(flag.ctf_status)
@@ -1959,11 +2224,11 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
                                flag.takedamage = DAMAGE_NO;
                                flag.solid = SOLID_NOT;
                                flag.nextthink = FALSE; // stop thinking
-                               
+
                                //dprint("stopping the ", flag.netname, " from moving.\n");
                                break;
                        }
-                       
+
                        default:
                        case FLAG_BASE:
                        case FLAG_CARRY:
@@ -1973,7 +2238,7 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
                        }
                }
        }
-       
+
        return FALSE;
 }
 
@@ -1983,6 +2248,19 @@ MUTATOR_HOOKFUNCTION(ctf_BotRoles)
        return TRUE;
 }
 
+MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
+{
+       //ret_float = ctf_teams;
+       ret_string = "ctf_team";
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
+{
+       self.ctf_flagstatus = other.ctf_flagstatus;
+       return FALSE;
+}
+
 
 // ==========
 // Spawnfuncs
@@ -1994,7 +2272,7 @@ Keys: "angle" viewing angle when spawning. */
 void spawnfunc_info_player_team1()
 {
        if(g_assault) { remove(self); return; }
-       
+
        self.team = NUM_TEAM_1; // red
        spawnfunc_info_player_deathmatch();
 }
@@ -2006,7 +2284,7 @@ Keys: "angle" viewing angle when spawning. */
 void spawnfunc_info_player_team2()
 {
        if(g_assault) { remove(self); return; }
-       
+
        self.team = NUM_TEAM_2; // blue
        spawnfunc_info_player_deathmatch();
 }
@@ -2017,7 +2295,7 @@ Keys: "angle" viewing angle when spawning. */
 void spawnfunc_info_player_team3()
 {
        if(g_assault) { remove(self); return; }
-       
+
        self.team = NUM_TEAM_3; // yellow
        spawnfunc_info_player_deathmatch();
 }
@@ -2029,45 +2307,99 @@ Keys: "angle" viewing angle when spawning. */
 void spawnfunc_info_player_team4()
 {
        if(g_assault) { remove(self); return; }
-       
+
        self.team = NUM_TEAM_4; // purple
        spawnfunc_info_player_deathmatch();
 }
 
 /*QUAKED spawnfunc_item_flag_team1 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
 CTF flag for team one (Red).
-Keys: 
-"angle" Angle the flag will point (minus 90 degrees)... 
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
 "model" model to use, note this needs red and blue as skins 0 and 1...
 "noise" sound played when flag is picked up...
 "noise1" sound played when flag is returned by a teammate...
 "noise2" sound played when flag is captured...
-"noise3" sound played when flag is lost in the field and respawns itself... 
+"noise3" sound played when flag is lost in the field and respawns itself...
 "noise4" sound played when flag is dropped by a player...
 "noise5" sound played when flag touches the ground... */
 void spawnfunc_item_flag_team1()
 {
        if(!g_ctf) { remove(self); return; }
 
-       ctf_FlagSetup(1, self); // 1 = red
+       ctf_FlagSetup(NUM_TEAM_1, self);
 }
 
 /*QUAKED spawnfunc_item_flag_team2 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
 CTF flag for team two (Blue).
+Keys:
+"angle" Angle the flag will point (minus 90 degrees)...
+"model" model to use, note this needs red and blue as skins 0 and 1...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself...
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_team2()
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_2, self);
+}
+
+/*QUAKED spawnfunc_item_flag_team3 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team three (Yellow).
 Keys: 
 "angle" Angle the flag will point (minus 90 degrees)... 
-"model" model to use, note this needs red and blue as skins 0 and 1...
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
 "noise" sound played when flag is picked up...
 "noise1" sound played when flag is returned by a teammate...
 "noise2" sound played when flag is captured...
 "noise3" sound played when flag is lost in the field and respawns itself... 
 "noise4" sound played when flag is dropped by a player...
 "noise5" sound played when flag touches the ground... */
-void spawnfunc_item_flag_team2()
+void spawnfunc_item_flag_team3()
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_3, self);
+}
+
+/*QUAKED spawnfunc_item_flag_team4 (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag for team four (Pink).
+Keys: 
+"angle" Angle the flag will point (minus 90 degrees)... 
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself... 
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_team4()
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_4, self);
+}
+
+/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag (Neutral).
+Keys: 
+"angle" Angle the flag will point (minus 90 degrees)... 
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself... 
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_neutral()
 {
        if(!g_ctf) { remove(self); return; }
 
-       ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
+       ctf_FlagSetup(0, self);
 }
 
 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
@@ -2079,7 +2411,7 @@ Keys:
 void spawnfunc_ctf_team()
 {
        if(!g_ctf) { remove(self); return; }
-       
+
        self.classname = "ctf_team";
        self.team = self.cnt + 1;
 }
@@ -2098,9 +2430,10 @@ void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
 // ==============
 
 // scoreboard setup
-void ctf_ScoreRules()
+void ctf_ScoreRules(float teams)
 {
-       ScoreRules_basics(2, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
+       CheckAllowedTeams(world);
+       ScoreRules_basics(teams, SFL_SORT_PRIO_PRIMARY, 0, TRUE);
        ScoreInfo_SetLabel_TeamScore  (ST_CTF_CAPS,     "caps",      SFL_SORT_PRIO_PRIMARY);
        ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPS,     "caps",      SFL_SORT_PRIO_SECONDARY);
        ScoreInfo_SetLabel_PlayerScore(SP_CTF_CAPTIME,  "captime",   SFL_LOWER_IS_BETTER | SFL_TIME);
@@ -2128,15 +2461,31 @@ void ctf_SpawnTeam (string teamname, float teamcolor)
 
 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
 {
+       ctf_teams = 2;
+
+       entity tmp_entity;
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+               if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+               if(tmp_entity.team == 0) { ctf_oneflag = TRUE; }
+       }
+
+       ctf_teams = bound(2, ctf_teams, 4);
+
        // if no teams are found, spawn defaults
        if(find(world, classname, "ctf_team") == world)
        {
                print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
                ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
                ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
+               if(ctf_teams >= 3)
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
+               if(ctf_teams >= 4)
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
        }
-       
-       ctf_ScoreRules();
+
+       ctf_ScoreRules(ctf_teams);
 }
 
 void ctf_Initialize()
@@ -2147,6 +2496,8 @@ void ctf_Initialize()
        ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
        ctf_captureshield_force = autocvar_g_ctf_shield_force;
        
+       addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
+
        InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
 }
 
@@ -2166,8 +2517,10 @@ MUTATOR_DEFINITION(gamemode_ctf)
        MUTATOR_HOOK(VehicleEnter, ctf_VehicleEnter, CBC_ORDER_ANY);
        MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
        MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
-       MUTATOR_HOOK(HavocBot_ChooseRule, ctf_BotRoles, CBC_ORDER_ANY);
-       
+       MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
+
        MUTATOR_ONADD
        {
                if(time > 1) // game loads at time 1