]> 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 1de05661bed63d57a275f61c8d0709d2fb8c7232..3b2a0624ca9c3cc2bbe68aef142c891c0b0194ee 100644 (file)
@@ -32,11 +32,11 @@ void ctf_CaptureRecord(entity flag, entity player)
        {
                if(tmp_entity.CAPTURE_VERBOSE)
                {
-                       if(!ctf_captimerecord) { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_2(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_2(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_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+                       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_2(flag, INFO_CTF_CAPTURE_), player.netname); }
+               else { Send_Notification(NOTIF_ONE_ONLY, tmp_entity, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_), player.netname); }
        }
 
        // the previous notification broadcast is only sent to real clients, this will notify server log too
@@ -44,11 +44,11 @@ void ctf_CaptureRecord(entity flag, entity player)
        {
                if(autocvar_notification_ctf_capture_verbose)
                {
-                       if(!ctf_captimerecord) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
-                       else if(cap_time < cap_record) { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-                       else { Local_Notification(MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+                       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_2(flag, INFO_CTF_CAPTURE_), player.netname); }
+               else { Local_Notification(MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_CAPTURE_), player.netname); }
        }
        
        // write that shit in the database
@@ -131,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 
@@ -138,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;
@@ -154,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;
        }
@@ -182,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;
 }
@@ -190,7 +225,7 @@ 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;
@@ -239,7 +274,7 @@ void ctf_Handle_Drop(entity flag, entity player, float droptype)
        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, APP_TEAM_ENT_4(flag, INFO_CTF_LOST_), player.netname);
        sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
@@ -292,11 +327,11 @@ void ctf_Handle_Retrieve(entity flag, entity 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, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_), 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, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
                else if(!IsDifferentTeam(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, APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
        }
        
        // create new waypoint
@@ -407,11 +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_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
+       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, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
+       sound(player, CH_TRIGGER, ((IsDifferentTeam(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture), VOL_BASE, ATTN_NONE);
        
        switch(capturetype)
        {
@@ -451,8 +487,8 @@ 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);
+       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, ATTN_NONE);
        ctf_EventLog("return", flag.team, player);
 
@@ -500,24 +536,24 @@ 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_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_), player.netname);
        sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTN_NONE);
 
        FOR_EACH_REALPLAYER(tmp_player)
        {
                if(tmp_player == player)
                {
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
+                       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, CENTER_CTF_PICKUP_TEAM_VERBOSE, Team_ColorCode(player.team), player.netname);
+                               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, CENTER_CTF_PICKUP_TEAM, Team_ColorCode(player.team));
+                               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))
+               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);
@@ -582,14 +618,14 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
                {
                        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, 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:
-                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); 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);
                        ctf_EventLog("returned", flag.team, world);
@@ -601,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
@@ -620,15 +656,19 @@ void ctf_CheckStalemate(void)
                        {
                                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
@@ -637,7 +677,10 @@ 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)
@@ -769,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;
                }
                
@@ -780,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))
@@ -841,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
@@ -942,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 == NUM_TEAM_1) ? "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
@@ -950,9 +1009,8 @@ 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
@@ -961,9 +1019,8 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a 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 = 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;
@@ -986,19 +1043,19 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        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 = 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 = ((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"); }
+       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_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 = ((teamnumber) ? "ctf/red_dropped.wav" : "ctf/blue_dropped.wav"); }
+       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
        
@@ -1021,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
@@ -1082,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;
        }
@@ -1095,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;
        }
@@ -1128,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;
        }
@@ -1142,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;
        }
@@ -1158,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;
        }
@@ -1714,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);
+                                 | 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;
                        }
                }
@@ -1766,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)
@@ -1973,7 +2055,7 @@ 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, APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_));
                ctf_RespawnFlag(self.flagcarried);
                return TRUE;
        }
@@ -2029,6 +2111,12 @@ MUTATOR_HOOKFUNCTION(ctf_GetCvars)
        return TRUE;
 }
 
+MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
+{
+       ret_float = ctf_teams;
+       return 0;
+}
+
 
 // ==========
 // Spawnfuncs
@@ -2095,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)
@@ -2113,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)
@@ -2144,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);
@@ -2174,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", 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()
@@ -2214,6 +2361,7 @@ MUTATOR_DEFINITION(gamemode_ctf)
        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
        {