]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_ctf.qc
Merge branch 'master' into divVerent/4team_ctf
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
index 5ca20ec7b55a0177981f03a4cbcddaa2909ea675..7d4339343507ba29b47b37bbc7cd75dd04196e66 100644 (file)
@@ -20,36 +20,45 @@ void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for
                GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
 }
 
-string ctf_CaptureRecord(entity flag, entity player)
+void ctf_CaptureRecord(entity flag, entity player)
 {
-       float cap_time, cap_record, success;
-       string cap_message = string_null, refername;
+       entity tmp_entity;
+       float cap_record = ctf_captimerecord;
+       float cap_time = (time - flag.ctf_pickuptime);
+       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
        
-       if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) 
+       // notify about shit
+       FOR_EACH_REALCLIENT(tmp_entity)
        {
-               cap_record = ctf_captimerecord;
-               cap_time = (time - flag.ctf_pickuptime);
-
-               refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
-               refername = ((refername == player.netname) ? "their" : strcat(refername, "^7's"));
-
-               if(!ctf_captimerecord) 
-                       { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds"); success = TRUE; }
-               else if(cap_time < cap_record) 
-                       { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, breaking ", refername, " previous record of ", ftos_decimals(cap_record, 2), " seconds"); success = TRUE; }
-               else
-                       { cap_message = strcat(" in ", ftos_decimals(cap_time, 2), " seconds, failing to break ", refername, " record of ", ftos_decimals(cap_record, 2), " seconds"); success = FALSE; }
+               if(tmp_entity.CAPTURE_VERBOSE)
+               {
+                       if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
+                       else if(cap_time < cap_record) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+                       else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+               }
+               else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_), player.netname); }
+       }
 
-               if(success) 
+       // the previous notification broadcast is only sent to real clients, this will notify server log too
+       if(server_is_dedicated)
+       {
+               if(autocvar_notification_ctf_capture_verbose)
                {
-                       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); 
-               } 
+                       if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
+                       else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+                       else { Local_Notification(MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+               }
+               else { Local_Notification(MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_), player.netname); }
        }
        
-       return cap_message;
+       // write that shit in the database
+       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); 
+       } 
 }
 
 void ctf_FlagcarrierWaypoints(entity player)
@@ -122,6 +131,29 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
        else { return TRUE; }
 }
 
+float ctf_IsDifferentTeam(entity a, entity b)
+{
+       float f = IsDifferentTeam(a, b);
+       return (autocvar_g_ctf_reverse) ? !f : f;
+}
+
+float ctf_Stalemate_waypointsprite_visible_for_player(entity e)
+{
+       // personal waypoints
+       if(self.enemy)
+               if(self.enemy != e)
+                       return FALSE;
+                       
+       // team waypoints
+       if(self.rule == SPRITERULE_DEFAULT)
+       if(ctf_IsDifferentTeam(self.owner.flagcarried, self.owner))
+       if(ctf_IsDifferentTeam(self.owner.flagcarried, e))
+       if(!IsDifferentTeam(self.owner, e) || !IS_PLAYER(e))
+               return FALSE;
+
+       return TRUE;
+}
+
 
 // =======================
 // CaptureShield Functions 
@@ -129,15 +161,21 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
 
 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;
@@ -145,8 +183,14 @@ float ctf_CaptureShield_CheckStatus(entity p)
        {
                if(IsDifferentTeam(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;
        }
@@ -165,11 +209,7 @@ void ctf_CaptureShield_Update(entity player, float wanted_status)
        float updated_status = ctf_CaptureShield_CheckStatus(player);
        if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
        {
-               if(updated_status) // TODO csqc notifier for this // Samual: How?
-                       Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Make some defensive scores before trying again.", 5, 0);
-               else
-                       Send_CSQC_Centerprint_Generic(player, CPID_CTF_CAPTURESHIELD, "^3You are now free.\n\n^3Feel free to ^1try to capture^3 the flag again\n^3if you think you will succeed.", 5, 0);
-                       
+               Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
                player.ctf_captureshielded = updated_status;
        }
 }
@@ -177,7 +217,7 @@ void ctf_CaptureShield_Update(entity player, float wanted_status)
 float ctf_CaptureShield_Customize()
 {
        if(!other.ctf_captureshielded) { return FALSE; }
-       if(!IsDifferentTeam(self, other)) { return FALSE; }
+       if(!ctf_IsDifferentTeam(self, other)) { return FALSE; }
        
        return TRUE;
 }
@@ -185,13 +225,13 @@ float ctf_CaptureShield_Customize()
 void ctf_CaptureShield_Touch()
 {
        if(!other.ctf_captureshielded) { return; }
-       if(!IsDifferentTeam(self, other)) { return; }
+       if(!ctf_IsDifferentTeam(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_CSQC_Centerprint_Generic(other, CPID_CTF_CAPTURESHIELD, "^3You are ^4shielded^3 from the flag\n^3for ^1too many unsuccessful attempts^3 to capture.\n\n^3Get some defensive scores before trying again.", 5, 0);
+       Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED);
 }
 
 void ctf_CaptureShield_Spawn(entity flag)
@@ -234,8 +274,8 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype)
        flag.ctf_status = FLAG_DROPPED;
        
        // messages and sounds
-       Send_KillNotification(player.netname, flag.netname, "", INFO_LOSTFLAG, MSG_INFO);
-       sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_LOST_), player.netname);
+       sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
        // scoring
@@ -281,17 +321,17 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        flag.ctf_status = FLAG_CARRY;
 
        // messages and sounds
-       sound(player, CH_TRIGGER, flag.snd_flag_pass, VOL_BASE, ATTN_NORM);
+       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)
-                       centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_), player.netname);
                else if(tmp_player == player)
-                       centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
                else if(!IsDifferentTeam(tmp_player, sender))
-                       centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
        }
        
        // create new waypoint
@@ -348,7 +388,7 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                        flag.ctf_status = FLAG_PASSING;
                        
                        // other
-                       sound(player, CH_TRIGGER, flag.snd_flag_touch, VOL_BASE, ATTN_NORM);
+                       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;
@@ -402,10 +442,12 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
        float old_time, new_time; 
        
        if not(player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(ctf_IsDifferentTeam(player, flag)) { return; }
        
        // messages and sounds
-       Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
-       sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
+       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_));
+       ctf_CaptureRecord(enemy_flag, player);
+       sound(player, CH_TRIGGER, ((IsDifferentTeam(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture), VOL_BASE, ATTEN_NONE);
        
        switch(capturetype)
        {
@@ -445,9 +487,9 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
 void ctf_Handle_Return(entity flag, entity player)
 {
        // messages and sounds
-       //centerprint(player, strcat("You returned the ", flag.netname));
-       Send_KillNotification(player.netname, flag.netname, "", INFO_RETURNFLAG, MSG_INFO);
-       sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
+       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
@@ -471,7 +513,6 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
 {
        // declarations
        entity tmp_player; // temporary entity which the FOR_EACH_PLAYER loop uses to scan players
-       string verbosename; // holds the name of the player OR no name at all for printing in the centerprints
        float pickup_dropped_score; // used to calculate dropped pickup score
        
        // attach the flag to the player
@@ -495,21 +536,30 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        }
 
        // messages and sounds
-       Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
-       sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
-       verbosename = ((autocvar_g_ctf_flag_pickup_verbosename) ? strcat(Team_ColorCode(player.team), "(^7", player.netname, Team_ColorCode(player.team), ") ") : "");
-       
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_), player.netname);
+       sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
+
        FOR_EACH_REALPLAYER(tmp_player)
        {
                if(tmp_player == player)
                {
-                       centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
-                       //if(ctf_stalemate) { centerprint(tmp_player, "Stalemate! Enemies can see you on radar!"); }
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_));
+                       if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
+               }
+               else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
+               {
+                       if(tmp_player.PICKUP_TEAM_VERBOSE)
+                               Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_TEAM_VERBOSE_), Team_ColorCode(player.team), player.netname);
+                       else
+                               Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_TEAM_), Team_ColorCode(player.team));
+               }
+               else if(IsDifferentTeam(tmp_player, player) && !ctf_IsDifferentTeam(flag, tmp_player))
+               {
+                       if(tmp_player.PICKUP_ENEMY_VERBOSE)
+                               Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY_VERBOSE, Team_ColorCode(player.team), player.netname);
+                       else
+                               Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, CENTER_CTF_PICKUP_ENEMY, Team_ColorCode(player.team));
                }
-               //else if(!IsDifferentTeam(tmp_player, player))
-               //      centerprint(tmp_player, strcat("Your ", Team_ColorCode(player.team), "team mate ", verbosename, "^7got the flag! Protect them!"));
-               else if(!IsDifferentTeam(tmp_player, flag))
-                       centerprint(tmp_player, strcat("The ", Team_ColorCode(player.team), "enemy ", verbosename, "^7got your flag! Retrieve it!"));
        }
        
        // scoring
@@ -568,16 +618,16 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
                {
                        switch(returntype)
                        {
-                               case RETURN_DROPPED: bprint("The ", flag.netname, " was dropped in the base and returned itself\n"); break;
-                               case RETURN_DAMAGE: bprint("The ", flag.netname, " was destroyed and returned to base\n"); break;
-                               case RETURN_SPEEDRUN: bprint("The ", flag.netname, " became impatient after ", ftos_decimals(ctf_captimerecord, 2), " seconds and returned itself\n"); break;
-                               case RETURN_NEEDKILL: bprint("The ", flag.netname, " fell somewhere it couldn't be reached and returned to base\n"); break;
+                               case RETURN_DROPPED: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_)); break;
+                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_)); break;
+                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), ctf_captimerecord); break;
+                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_)); break;
                                
                                default:
                                case RETURN_TIMEOUT:
-                                       { bprint("The ", flag.netname, " has returned to base\n"); break; }
+                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
                        }
-                       sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
+                       sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
                        ctf_EventLog("returned", flag.team, world);
                        ctf_RespawnFlag(flag);
                }
@@ -587,7 +637,7 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
 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;
        entity tmp_entity;
 
        entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
@@ -604,17 +654,21 @@ void ctf_CheckStalemate(void)
                        
                        switch(tmp_entity.team)
                        {
-                               case COLOR_TEAM1: ++stale_red_flags; break;
-                               case COLOR_TEAM2: ++stale_blue_flags; break;
+                               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;
                        }
                }
        }
-
-       if(stale_red_flags && stale_blue_flags)
+       
+       stale_flags = (stale_red_flags > 0) + (stale_blue_flags > 0) + (stale_yellow_flags > 0) + (stale_pink_flags > 0);
+       
+       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 && stale_flags > 0 && 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
@@ -623,16 +677,16 @@ void ctf_CheckStalemate(void)
                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("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.waypointsprite_visible_for_player = ctf_Stalemate_waypointsprite_visible_for_player;
+                       }
                }
                
                if not(wpforenemy_announced)
                {
                        FOR_EACH_REALPLAYER(tmp_entity)
-                               if(tmp_entity.flagcarried)
-                                       centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
-                               else
-                                       centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
+                               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CENTER, ((tmp_entity.flagcarried) ? CENTER_CTF_STALEMATE_CARRIER : CENTER_CTF_STALEMATE_OTHER));
                        
                        wpforenemy_announced = TRUE;
                }
@@ -758,6 +812,11 @@ void ctf_FlagThink()
                                        wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
                                }
                        }
+                       if(!ctf_IsDifferentTeam(self, self.owner))
+                       {
+                               // drop the flag if reverse status has changed
+                               ctf_Handle_Throw(self.owner, world, DROP_THROW);
+                       }
                        return;
                }
                
@@ -769,6 +828,7 @@ void ctf_FlagThink()
                        
                        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))
@@ -819,7 +879,7 @@ void ctf_FlagTouch()
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
                        pointparticles(particleeffectnum(self.toucheffect), self.origin, '0 0 0', 1);
-                       sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTN_NORM);
+                       sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
                        self.wait = time + FLAG_TOUCHRATE;
                }
                return;
@@ -830,16 +890,16 @@ void ctf_FlagTouch()
        {       
                case FLAG_BASE:
                {
-                       if(!IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
+                       if(!ctf_IsDifferentTeam(toucher, self) && (toucher.flagcarried) && IsDifferentTeam(toucher.flagcarried, self))
                                ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
+                       else if(ctf_IsDifferentTeam(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time))
                                ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
                        break;
                }
                
                case FLAG_DROPPED:
                {
-                       if(!IsDifferentTeam(toucher, self))
+                       if(!ctf_IsDifferentTeam(toucher, self))
                                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)))
                                ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
@@ -931,7 +991,17 @@ 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 == COLOR_TEAM1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, 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;
+       }
+       
+       WaypointSprite_SpawnFixed(basename, 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));
 
        // captureshield setup
@@ -939,20 +1009,18 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf
 }
 
 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, ""); 
+       setattachment(flag, world, "");
 
-       flag.netname = ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
-       flag.team = ((teamnumber) ? COLOR_TEAM1 : COLOR_TEAM2); // COLOR_TEAM1: color 4 team (red) - COLOR_TEAM2: 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 = strcat(Static_Team_ColorName_Upper(teamnumber), "^7 flag");
+       flag.team = teamnumber;
        flag.classname = "item_flag_team";
        flag.target = "###item###"; // wut?
        flag.flags = FL_ITEM | FL_NOTARGET;
@@ -973,22 +1041,23 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        flag.think = ctf_FlagThink;
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
-       
-       if(!flag.model) { flag.model = ((teamnumber) ? autocvar_g_ctf_flag_red_model : autocvar_g_ctf_flag_blue_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"); }
+
+       // appearence
+       if(flag.model == "")       { flag.model = strzone(cvar_string(strcat("g_ctf_flag_", Static_Team_ColorName_Lower(teamnumber), "_model"))); }
+       if(!flag.scale)            { flag.scale = FLAG_SCALE; }
+       if(!flag.skin)             { flag.skin = cvar(strcat("g_ctf_flag_", Static_Team_ColorName_Lower(teamnumber), "_skin")); }
+       if(flag.toucheffect == "") { flag.toucheffect = strzone(strcat(Static_Team_ColorName_Lower(teamnumber), "_touch")); }
+       if(flag.passeffect == "")  { flag.passeffect = strzone(strcat(Static_Team_ColorName_Lower(teamnumber), "_pass")); }
+       if(flag.capeffect == "")   { flag.capeffect = strzone(strcat(Static_Team_ColorName_Lower(teamnumber), "_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.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_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
+       if(flag.snd_flag_taken == "")    { flag.snd_flag_taken  = strzone(strcat("ctf/", Static_Team_ColorName_Lower(teamnumber), "_taken.wav")); }
+       if(flag.snd_flag_returned == "") { flag.snd_flag_returned = strzone(strcat("ctf/", Static_Team_ColorName_Lower(teamnumber), "_returned.wav")); }
+       if(flag.snd_flag_capture == "")  { flag.snd_flag_capture = strzone(strcat("ctf/", Static_Team_ColorName_Lower(teamnumber), "_capture.wav")); } // blue team scores by capturing the red flag
+       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 = strzone(strcat("ctf/", Static_Team_ColorName_Lower(teamnumber), "_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);
@@ -1009,14 +1078,29 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        
        if(autocvar_g_ctf_flag_glowtrails)
        {
-               flag.glow_color = ((teamnumber) ? 251 : 210); // 251: red - 210: blue
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.glow_color = 251; break; // red
+                       case NUM_TEAM_2: flag.glow_color = 210; break; // blue
+                       case NUM_TEAM_3: flag.glow_color = 110; break; // yellow
+                       case NUM_TEAM_4: flag.glow_color = 145; break; // pink
+               }
                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;
+               }
+       }
        
        // flag placement
        if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
@@ -1070,7 +1154,7 @@ entity havocbot_ctf_find_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team == f.team)
+               if (!ctf_IsDifferentTeam(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
@@ -1083,7 +1167,7 @@ entity havocbot_ctf_find_enemy_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team != f.team)
+               if (ctf_IsDifferentTeam(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
@@ -1116,7 +1200,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (!ctf_IsDifferentTeam(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1130,7 +1214,7 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (!ctf_IsDifferentTeam(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1146,7 +1230,7 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team != head.team)
+               if (ctf_IsDifferentTeam(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1702,27 +1786,37 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
 {
        entity flag;
        
+       float t = 0, t2 = 0, t3 = 0;
+       
        // 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);
+       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_YELLOW_FLAG_CARRYING | IT_YELLOW_FLAG_TAKEN      | IT_YELLOW_FLAG_LOST
+                                 | IT_PINK_FLAG_CARRYING       | IT_PINK_FLAG_TAKEN    | IT_PINK_FLAG_LOST
+                                 | IT_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 = IT_RED_FLAG_CARRYING;         t2 = IT_RED_FLAG_TAKEN;         t3 = IT_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2) { t = IT_BLUE_FLAG_CARRYING;        t2 = IT_BLUE_FLAG_TAKEN;        t3 = IT_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3) { t = IT_YELLOW_FLAG_CARRYING;      t2 = IT_YELLOW_FLAG_TAKEN;      t3 = IT_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4) { t = IT_PINK_FLAG_CARRYING;        t2 = IT_PINK_FLAG_TAKEN;        t3 = IT_PINK_FLAG_LOST; }
+       
                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.items |= 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.items |= 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.items |= t3; // lost: the flag is dropped somewhere on the map
                                break;
                        }
                }
@@ -1754,7 +1848,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) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
+       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && ctf_IsDifferentTeam(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)))
                if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
@@ -1762,13 +1856,14 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
                        frag_target.wps_helpme_time = time;
                        WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
                }
+               // todo: add notification for when flag carrier needs help?
        }
        return FALSE;
 }
 
 MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
 {
-       if((frag_attacker != frag_target) && (IS_PLAYER(frag_target)) && (frag_target.flagcarried))
+       if((frag_attacker != frag_target) && (IS_PLAYER(frag_attacker)) && (frag_target.flagcarried))
        {
                PlayerTeamScore_AddScore(frag_attacker, autocvar_g_ctf_score_kill);
                PlayerScore_Add(frag_attacker, SP_CTF_FCKILLS, 1);
@@ -1842,13 +1937,13 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                                { 
                                                        if(IS_BOT_CLIENT(head))
                                                        {
-                                                               centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
+                                                               Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname);
                                                                ctf_Handle_Throw(head, player, DROP_PASS);
                                                        }
                                                        else
                                                        {
-                                                               centerprint(head, strcat(player.netname, " requests you to pass the ", head.flagcarried.netname)); 
-                                                               centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
+                                                               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; 
@@ -1885,7 +1980,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                }
                                else
                                {
-                                       centerprint(player, strcat("Too many flag throws, throwing disabled for ", ftos(rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time)), " seconds."));
+                                       Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time));
                                        return FALSE;
                                }
                        }
@@ -1960,8 +2055,8 @@ MUTATOR_HOOKFUNCTION(ctf_AbortSpeedrun)
 {
        if(self.flagcarried)
        {
-               bprint("The ", self.flagcarried.netname, " was returned to base by its carrier\n");
-               ctf_RespawnFlag(self);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
+               ctf_RespawnFlag(self.flagcarried);
                return TRUE;
        }
        
@@ -2008,6 +2103,20 @@ MUTATOR_HOOKFUNCTION(ctf_BotRoles)
        return TRUE;
 }
 
+MUTATOR_HOOKFUNCTION(ctf_GetCvars)
+{
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, CAPTURE_VERBOSE, "notification_ctf_capture_verbose");
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_TEAM_VERBOSE, "notification_ctf_pickup_team_verbose");
+       GetCvars_handleFloat(get_cvars_s, get_cvars_f, PICKUP_ENEMY_VERBOSE, "notification_ctf_pickup_enemy_verbose");
+       return TRUE;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
+{
+       ret_float = ctf_teams;
+       return 0;
+}
+
 
 // ==========
 // Spawnfuncs
@@ -2020,7 +2129,7 @@ void spawnfunc_info_player_team1()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM1; // red
+       self.team = NUM_TEAM_1; // red
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2032,7 +2141,7 @@ void spawnfunc_info_player_team2()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM2; // blue
+       self.team = NUM_TEAM_2; // blue
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2043,7 +2152,7 @@ void spawnfunc_info_player_team3()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM3; // yellow
+       self.team = NUM_TEAM_3; // yellow
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2055,7 +2164,7 @@ void spawnfunc_info_player_team4()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM4; // purple
+       self.team = NUM_TEAM_4; // purple
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2074,7 +2183,7 @@ 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)
@@ -2092,7 +2201,43 @@ void spawnfunc_item_flag_team2()
 {
        if(!g_ctf) { remove(self); return; }
 
-       ctf_FlagSetup(0, self); // the 0 is misleading, but -- 0 = blue.
+       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, 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_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 two (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_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
@@ -2123,9 +2268,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);
@@ -2153,15 +2299,37 @@ 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;
+               break; // found 1 flag for this team
+       }
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       if(tmp_entity.team == NUM_TEAM_4)
+       {
+               ++ctf_teams;
+               break; // found 1 flag for this team
+       }
+       
+       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", COLOR_TEAM1 - 1);
-               ctf_SpawnTeam("Blue", COLOR_TEAM2 - 1);
+               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()
@@ -2192,6 +2360,8 @@ MUTATOR_DEFINITION(gamemode_ctf)
        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(GetCvars, ctf_GetCvars, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
        
        MUTATOR_ONADD
        {