]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_ctf.qc
Merge remote-tracking branch 'origin/master' into samual/notification_rewrite
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
index eb1bed36674f93e5b7d0cde04581d0aec0026cb7..fbe57ec9361b84ae737da334df5dd3ca5390eeea 100644 (file)
@@ -20,36 +20,39 @@ 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, refername;
-       
-       if((autocvar_g_ctf_captimerecord_always) || (player_count - currentbots)) 
-       {
-               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; }
+       entity tmp_entity;
+       float notification, success;
+       float cap_record = ctf_captimerecord;
+       float cap_time = (time - flag.ctf_pickuptime);
+       float f1, f2 = NO_FL_ARG;
+       string s1, s2 = NO_STR_ARG;
+       string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
+       
+       // figure shit out
+       if(!ctf_captimerecord) 
+               { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_TIME_); s1 = player.netname; f1 = (cap_time * 100); success = TRUE; }
+       else if(cap_time < cap_record) 
+               { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_BROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = TRUE; }
+       else
+               { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_UNBROKEN_); s1 = player.netname; s2 = refername; f1 = (cap_time * 100); f2 = (cap_record * 100); success = FALSE; }
 
-               if(success) 
-               {
-                       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); 
-               } 
+       // notify about shit
+       FOR_EACH_REALCLIENT(tmp_entity)
+       {
+               if not(tmp_entity.CAPTURE_VERBOSE) { notification = APP_TEAM_ENT_2(flag, INFO_CTF_CAPTURE_); s2 = NO_STR_ARG; f1 = f2 = NO_FL_ARG; }
+               Send_Notification_Legacy_Wrapper(tmp_entity, MSG_ONE, MSG_INFO, notification, s1, s2, f1, f2, NO_FL_ARG);
        }
-       
-       return cap_message;
+
+       // write that shit in the database
+       if(success) 
+       {
+               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)
@@ -70,30 +73,24 @@ void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnra
        vector targpos;
        if(current_height) // make sure we can actually do this arcing path
        {
-               vector arc_targpos = (to + ('0 0 1' * current_height));
-               WarpZone_TraceLine(flag.origin, arc_targpos, MOVE_NOMONSTERS, flag);
+               targpos = (to + ('0 0 1' * current_height));
+               WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
                if(trace_fraction < 1)
                {
                        //print("normal arc line failed, trying to find new pos...");
-                       WarpZone_TraceLine(to, arc_targpos, MOVE_NOMONSTERS, flag);
-                       arc_targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
-
-                       WarpZone_TraceLine(flag.origin, arc_targpos, MOVE_NOMONSTERS, flag);
+                       WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
+                       targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
+                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
                        if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
-                       else { targpos = arc_targpos; /* print(" ^3SUCCESS^7, using new arc line.\n"); */ }
+                       /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
                }
-               else { targpos = arc_targpos; }
        }
        else { targpos = to; }
 
        //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 
        vector desired_direction = normalize(targpos - from);
-       if(turnrate)
-       {
-               vector current_direction = normalize(flag.velocity);
-               flag.velocity = (normalize(current_direction + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); 
-       }
+       if(turnrate) { flag.velocity = (normalize(normalize(flag.velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
        else { flag.velocity = (desired_direction * autocvar_g_ctf_pass_velocity); }
 }
 
@@ -172,9 +169,9 @@ void ctf_CaptureShield_Update(entity player, float wanted_status)
        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);
+                       Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                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_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_FREE, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                        
                player.ctf_captureshielded = updated_status;
        }
@@ -197,7 +194,7 @@ void ctf_CaptureShield_Touch()
        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_Legacy_Wrapper(other, MSG_ONE, MSG_CENTER, CENTER_CTF_CAPTURESHIELD_SHIELDED, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
 }
 
 void ctf_CaptureShield_Spawn(entity flag)
@@ -240,7 +237,7 @@ 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);
+       Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
        sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
@@ -293,11 +290,11 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        FOR_EACH_REALPLAYER(tmp_player)
        {
                if(tmp_player == sender)
-                       centerprint(tmp_player, strcat("You passed the ", flag.netname, " to ", player.netname));
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_SENT_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                else if(tmp_player == player)
-                       centerprint(tmp_player, strcat("You received the ", flag.netname, " from ", sender.netname));
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                else if(!IsDifferentTeam(tmp_player, sender))
-                       centerprint(tmp_player, strcat(sender.netname, " passed the ", flag.netname, " to ", player.netname));
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
        }
        
        // create new waypoint
@@ -410,7 +407,7 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
        if not(player) { return; } // without someone to give the reward to, we can't possibly cap
        
        // messages and sounds
-       Send_KillNotification(player.netname, enemy_flag.netname, ctf_CaptureRecord(enemy_flag, player), INFO_CAPTUREFLAG, MSG_INFO);
+       ctf_CaptureRecord(enemy_flag, player);
        sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTN_NONE);
        
        switch(capturetype)
@@ -451,8 +448,8 @@ 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);
+       Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_RETURN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
+       Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
        sound(player, CH_TRIGGER, flag.snd_flag_returned, VOL_BASE, ATTN_NONE);
        ctf_EventLog("return", flag.team, player);
 
@@ -477,7 +474,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
@@ -501,18 +497,20 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        }
 
        // messages and sounds
-       Send_KillNotification (player.netname, flag.netname, "", INFO_GOTFLAG, MSG_INFO);
+       Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
        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), ") ") : "");
-       
+
        FOR_EACH_REALPLAYER(tmp_player)
        {
                if(tmp_player == player)
-                       centerprint(tmp_player, strcat("You got the ", flag.netname, "!"));
-               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!"));
+               {
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
+                       if(ctf_stalemate) { Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); }
+               }
+               else if(!IsDifferentTeam(tmp_player, player) && tmp_player != player)
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, (tmp_player.PICKUP_TEAM_VERBOSE ? CENTER_CTF_PICKUP_TEAM_VERBOSE : CENTER_CTF_PICKUP_TEAM), Team_ColorCode(player.team), (tmp_player.PICKUP_TEAM_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
+               else if(IsDifferentTeam(tmp_player, player))
+                       Send_Notification_Legacy_Wrapper(tmp_player, MSG_ONE, MSG_CENTER, (tmp_player.PICKUP_ENEMY_VERBOSE ? CENTER_CTF_PICKUP_ENEMY_VERBOSE : CENTER_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), (tmp_player.PICKUP_ENEMY_VERBOSE ? player.netname : NO_STR_ARG), NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
        }
        
        // scoring
@@ -571,14 +569,14 @@ 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_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DROPPED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
+                               case RETURN_DAMAGE: Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_DAMAGED_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
+                               case RETURN_SPEEDRUN: Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_), NO_STR_ARG, NO_STR_ARG, ctf_captimerecord, NO_FL_ARG, NO_FL_ARG); break;
+                               case RETURN_NEEDKILL: Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_NEEDKILL_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break;
                                
                                default:
                                case RETURN_TIMEOUT:
-                                       { bprint("The ", flag.netname, " has returned to base\n"); break; }
+                                       { Send_Notification_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG); break; }
                        }
                        sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTN_NONE);
                        ctf_EventLog("returned", flag.team, world);
@@ -590,34 +588,36 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
 void ctf_CheckStalemate(void)
 {
        // declarations
-       float stale_red_flags, stale_blue_flags;
+       float stale_red_flags = 0, stale_blue_flags = 0;
        entity tmp_entity;
 
-       entity ctf_staleflaglist; // reset the list, we need to build the list each time this function runs
+       entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
 
        // build list of stale flags
        for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
        {
-               if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+               if(autocvar_g_ctf_stalemate)
                if(tmp_entity.ctf_status != FLAG_BASE)
-               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
                {
                        tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
                        ctf_staleflaglist = tmp_entity;
                        
                        switch(tmp_entity.team)
                        {
-                               case COLOR_TEAM1: ++stale_red_flags; break;
-                               case COLOR_TEAM2: ++stale_blue_flags; break;
+                               case FL_TEAM_1: ++stale_red_flags; break;
+                               case FL_TEAM_2: ++stale_blue_flags; break;
                        }
                }
        }
 
        if(stale_red_flags && stale_blue_flags)
                ctf_stalemate = TRUE;
-       else if(!stale_red_flags && !stale_blue_flags)
-               ctf_stalemate = FALSE;
-       
+       else if((!stale_red_flags && !stale_blue_flags) && 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)
+               { ctf_stalemate = FALSE; wpforenemy_announced = FALSE; }
+               
        // if sufficient stalemate, then set up the waypointsprite and announce the stalemate if necessary
        if(ctf_stalemate)
        {
@@ -631,9 +631,9 @@ void ctf_CheckStalemate(void)
                {
                        FOR_EACH_REALPLAYER(tmp_entity)
                                if(tmp_entity.flagcarried)
-                                       centerprint(tmp_entity, "Stalemate! Enemies can now see you on radar!");
+                                       Send_Notification_Legacy_Wrapper(tmp_entity, MSG_ONE, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                                else
-                                       centerprint(tmp_entity, "Stalemate! Flag carriers can now be seen by enemies on radar!");
+                                       Send_Notification_Legacy_Wrapper(tmp_entity, MSG_ONE, MSG_CENTER, CENTER_CTF_STALEMATE_OTHER, NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                        
                        wpforenemy_announced = TRUE;
                }
@@ -697,8 +697,9 @@ void ctf_FlagThink()
                        {
                                for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
                                        if(tmp_entity.ctf_status == FLAG_DROPPED)
-                                               if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
-                                                       ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
+                                       if(vlen(self.origin - tmp_entity.origin) < autocvar_g_ctf_dropped_capture_radius)
+                                       if(time > tmp_entity.ctf_droptime + autocvar_g_ctf_dropped_capture_delay)
+                                               ctf_Handle_Capture(self, tmp_entity, CAPTURE_DROPPED);
                        }
                        return;
                }
@@ -750,7 +751,7 @@ void ctf_FlagThink()
                                ImpulseCommands();
                                self = tmp_entity;
                        }
-                       if(autocvar_g_ctf_flagcarrier_waypointforenemy_stalemate)
+                       if(autocvar_g_ctf_stalemate)
                        {
                                if(time >= wpforenemy_nextthink)
                                {
@@ -912,8 +913,6 @@ void ctf_RespawnFlag(entity flag)
        flag.ctf_dropper = world;
        flag.ctf_pickuptime = 0;
        flag.ctf_droptime = 0;
-
-       wpforenemy_announced = FALSE;
 }
 
 void ctf_Reset()
@@ -933,7 +932,7 @@ 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));
+       WaypointSprite_SpawnFixed(((self.team == FL_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));
 
        // captureshield setup
@@ -950,10 +949,10 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        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.netname = ((teamnumber) ? "^1REPLACETHIS^7" : "^4REPLACETHIS^7"); // ((teamnumber) ? "^1RED^7 flag" : "^4BLUE^7 flag");
+       flag.team = ((teamnumber) ? FL_TEAM_1 : FL_TEAM_2); // FL_TEAM_1: color 4 team (red) - FL_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.classname = "item_flag_team";
        flag.target = "###item###"; // wut?
@@ -1758,8 +1757,12 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
        }
        else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && IsDifferentTeam(frag_target, frag_attacker)) // if the target is a flagcarrier
        {
-               if(autocvar_g_ctf_flagcarrier_auto_helpme_when_damaged > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent)))
+               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)
+               {
+                       frag_target.wps_helpme_time = time;
                        WaypointSprite_HelpMePing(frag_target.wps_flagcarrier);
+               }
        }
        return FALSE;
 }
@@ -1821,7 +1824,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                // pass the flag to a team mate
                if(autocvar_g_ctf_pass)
                {
-                       entity head, closest_target;
+                       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
@@ -1831,8 +1834,8 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                if(!head.speedrunning && !head.vehicle)
                                {
                                        // 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, PLAYER_CENTER(head));
-                                       vector passer_center = PLAYER_CENTER(player);
+                                       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))
                                        {
@@ -1840,13 +1843,13 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                                { 
                                                        if(clienttype(head) == CLIENTTYPE_BOT)
                                                        {
-                                                               centerprint(player, strcat("Requesting ", head.netname, " to pass you the ", head.flagcarried.netname)); 
+                                                               Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                                                                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_Legacy_Wrapper(head, MSG_ONE, MSG_CENTER, CENTER_CTF_PASS_REQUESTED, player.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
+                                                               Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_PASS_REQUESTING, head.netname, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
                                                        }
                                                        player.throw_antispam = time + autocvar_g_ctf_pass_wait; 
                                                        return TRUE; 
@@ -1855,7 +1858,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                                                {
                                                        if(closest_target)
                                                        {
-                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, PLAYER_CENTER(closest_target));
+                                                               vector closest_target_center = WarpZone_UnTransformOrigin(closest_target, CENTER_OR_VIEWOFS(closest_target));
                                                                if(vlen(passer_center - head_center) < vlen(passer_center - closest_target_center))
                                                                        { closest_target = head; }
                                                        }
@@ -1871,7 +1874,33 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerUseKey)
                
                // throw the flag in front of you
                if(autocvar_g_ctf_throw && player.flagcarried)
-                       { ctf_Handle_Throw(player, world, DROP_THROW); return TRUE; }
+               {
+                       if(player.throw_count == -1)
+                       {
+                               if(time > player.throw_prevtime + autocvar_g_ctf_throw_punish_delay)
+                               {
+                                       player.throw_prevtime = time;
+                                       player.throw_count = 1;
+                                       ctf_Handle_Throw(player, world, DROP_THROW);
+                                       return TRUE;
+                               }
+                               else
+                               {
+                                       Send_Notification_Legacy_Wrapper(player, MSG_ONE, MSG_CENTER, CENTER_CTF_FLAG_THROW_PUNISH, NO_STR_ARG, NO_STR_ARG, rint((player.throw_prevtime + autocvar_g_ctf_throw_punish_delay) - time), NO_FL_ARG, NO_FL_ARG);
+                                       return FALSE;
+                               }
+                       }
+                       else
+                       {
+                               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;
@@ -1881,6 +1910,7 @@ MUTATOR_HOOKFUNCTION(ctf_HelpMePing)
 {
        if(self.wps_flagcarrier) // update the flagcarrier waypointsprite with "NEEDING HELP" notification
        {
+               self.wps_helpme_time = time;
                WaypointSprite_HelpMePing(self.wps_flagcarrier);
        } 
        else // create a normal help me waypointsprite
@@ -1896,7 +1926,7 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
 {
        if(vh_player.flagcarried)
        {
-               if(!autocvar_g_ctf_allow_vehicle_carry)
+               if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
                {
                        ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
                }
@@ -1931,8 +1961,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_Legacy_Wrapper(world, MSG_BROADCAST, MSG_INFO, APP_TEAM_ENT_2(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_), NO_STR_ARG, NO_STR_ARG, NO_FL_ARG, NO_FL_ARG, NO_FL_ARG);
+               ctf_RespawnFlag(self.flagcarried);
                return TRUE;
        }
        
@@ -1956,7 +1986,7 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
                                flag.solid = SOLID_NOT;
                                flag.nextthink = FALSE; // stop thinking
                                
-                               print("stopping the ", flag.netname, " from moving.\n");
+                               //dprint("stopping the ", flag.netname, " from moving.\n");
                                break;
                        }
                        
@@ -1979,6 +2009,14 @@ 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;
+}
+
 
 // ==========
 // Spawnfuncs
@@ -1991,7 +2029,7 @@ void spawnfunc_info_player_team1()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM1; // red
+       self.team = FL_TEAM_1; // red
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2003,7 +2041,7 @@ void spawnfunc_info_player_team2()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM2; // blue
+       self.team = FL_TEAM_2; // blue
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2014,7 +2052,7 @@ void spawnfunc_info_player_team3()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM3; // yellow
+       self.team = FL_TEAM_3; // yellow
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2026,7 +2064,7 @@ void spawnfunc_info_player_team4()
 {
        if(g_assault) { remove(self); return; }
        
-       self.team = COLOR_TEAM4; // purple
+       self.team = FL_TEAM_4; // purple
        spawnfunc_info_player_deathmatch();
 }
 
@@ -2128,8 +2166,8 @@ void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to
        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", FL_TEAM_1 - 1);
+               ctf_SpawnTeam("Blue", FL_TEAM_2 - 1);
        }
        
        ctf_ScoreRules();
@@ -2163,19 +2201,26 @@ 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_ONADD
        {
                if(time > 1) // game loads at time 1
                        error("This is a game type and it cannot be added at runtime.");
-               g_ctf = 1;
                ctf_Initialize();
        }
 
+       MUTATOR_ONROLLBACK_OR_REMOVE
+       {
+               // we actually cannot roll back ctf_Initialize here
+               // BUT: we don't need to! If this gets called, adding always
+               // succeeds.
+       }
+
        MUTATOR_ONREMOVE
        {
-               g_ctf = 0;
-               error("This is a game type and it cannot be removed at runtime.");
+               print("This is a game type and it cannot be removed at runtime.");
+               return -1;
        }
 
        return 0;