]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/server/mutators/gamemode_ctf.qc
Merge branch 'master' into Mario/vehicles
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / mutators / gamemode_ctf.qc
index 4c94e49070179adfcc7dc98d673d2eb20e9c8cfb..31d21116a483e1a1a0b0d6dabfd340427cef08df 100644 (file)
@@ -1,4 +1,14 @@
-#include "../../common/movetypes/movetypes.qh"
+#include "gamemode_ctf.qh"
+#include "../_all.qh"
+
+#include "gamemode.qh"
+
+#ifdef SVQC
+#include "../../common/vehicles/sv_vehicles.qh"
+#endif
+
+#include "../../warpzonelib/common.qh"
+#include "../../warpzonelib/mathlib.qh"
 
 void ctf_FakeTimeLimit(entity e, float t)
 {
@@ -11,10 +21,11 @@ void ctf_FakeTimeLimit(entity e, float t)
                WriteCoord(MSG_ONE, (t + 1) / 60);
 }
 
-void ctf_EventLog(string mode, float flagteam, entity actor) // use an alias for easy changing and quick editing later
+void ctf_EventLog(string mode, int flagteam, entity actor) // use an alias for easy changing and quick editing later
 {
        if(autocvar_sv_eventlog)
-               GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
+               GameLogEcho(sprintf(":ctf:%s:%d:%d:%s", mode, flagteam, actor.team, ((actor != world) ? ftos(actor.playerid) : "")));
+               //GameLogEcho(strcat(":ctf:", mode, ":", ftos(flagteam), ((actor != world) ? (strcat(":", ftos(actor.playerid))) : "")));
 }
 
 void ctf_CaptureRecord(entity flag, entity player)
@@ -24,11 +35,13 @@ void ctf_CaptureRecord(entity flag, entity player)
        string refername = db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/netname"));
 
        // notify about shit
-       if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
-       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
-       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_2(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+       if(ctf_oneflag) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_CTF_CAPTURE_NEUTRAL, player.netname); }
+       else if(!ctf_captimerecord) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_TIME_), player.netname, (cap_time * 100)); }
+       else if(cap_time < cap_record) { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_BROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
+       else { Send_Notification(NOTIF_ALL, world, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_CAPTURE_UNBROKEN_), player.netname, refername, (cap_time * 100), (cap_record * 100)); }
 
        // write that shit in the database
+       if(!ctf_oneflag) // but not in 1-flag mode
        if((!ctf_captimerecord) || (cap_time < cap_record))
        {
                ctf_captimerecord = cap_time;
@@ -57,27 +70,27 @@ void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnra
        if(current_height) // make sure we can actually do this arcing path
        {
                targpos = (to + ('0 0 1' * current_height));
-               WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
+               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, targpos, MOVE_NOMONSTERS, flag);
                        targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
-                       WarpZone_TraceLine(flag.move_origin, targpos, MOVE_NOMONSTERS, flag);
+                       WarpZone_TraceLine(flag.origin, targpos, MOVE_NOMONSTERS, flag);
                        if(trace_fraction < 1) { targpos = to; /* print(" ^1FAILURE^7, reverting to original direction.\n"); */ }
                        /*else { print(" ^3SUCCESS^7, using new arc line.\n"); } */
                }
        }
        else { targpos = to; }
 
-       //flag.move_angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
+       //flag.angles = normalize(('0 1 0' * to_y) - ('0 1 0' * from_y));
 
        vector desired_direction = normalize(targpos - from);
-       if(turnrate) { flag.move_velocity = (normalize(normalize(flag.move_velocity) + (desired_direction * autocvar_g_ctf_pass_turnrate)) * autocvar_g_ctf_pass_velocity); }
-       else { flag.move_velocity = (desired_direction * 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); }
 }
 
-float ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
+bool ctf_CheckPassDirection(vector head_center, vector passer_center, vector passer_angle, vector nearest_to_passer)
 {
        if(autocvar_g_ctf_pass_directional_max || autocvar_g_ctf_pass_directional_min)
        {
@@ -86,13 +99,11 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
                makevectors(passer_angle);
 
                // find the closest point on the enemy to the center of the attack
-               float ang; // angle between shotdir and h
                float h; // hypotenuse, which is the distance between attacker to head
                float a; // adjacent side, which is the distance between attacker and the point on w_shotdir that is closest to head.origin
 
                h = vlen(head_center - passer_center);
-               ang = acos(dotproduct(normalize(head_center - passer_center), v_forward));
-               a = h * cos(ang);
+               a = h * (normalize(head_center - passer_center) * v_forward);
 
                vector nearest_on_line = (passer_center + a * v_forward);
                float distance_from_line = vlen(nearest_to_passer - nearest_on_line);
@@ -113,17 +124,23 @@ float ctf_CheckPassDirection(vector head_center, vector passer_center, vector pa
 // CaptureShield Functions
 // =======================
 
-float ctf_CaptureShield_CheckStatus(entity p)
+bool ctf_CaptureShield_CheckStatus(entity p)
 {
-       float s, se;
+       int s, s2, s3, s4, se, se2, se3, se4, sr, ser;
        entity e;
-       float players_worseeq, players_total;
+       int 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;
@@ -131,8 +148,14 @@ float ctf_CaptureShield_CheckStatus(entity p)
        {
                if(DIFF_TEAM(e, p))
                        continue;
-               se = PlayerScore_Add(e, SP_SCORE, 0);
-               if(se <= s)
+               se = PlayerScore_Add(e, SP_CTF_CAPS, 0);
+               se2 = PlayerScore_Add(e, SP_CTF_PICKUPS, 0);
+               se3 = PlayerScore_Add(e, SP_CTF_RETURNS, 0);
+               se4 = PlayerScore_Add(e, SP_CTF_FCKILLS, 0);
+
+               ser = ((se - se2) + (se3 + se4));
+
+               if(ser <= sr)
                        ++players_worseeq;
                ++players_total;
        }
@@ -146,9 +169,9 @@ float ctf_CaptureShield_CheckStatus(entity p)
        return true;
 }
 
-void ctf_CaptureShield_Update(entity player, float wanted_status)
+void ctf_CaptureShield_Update(entity player, bool wanted_status)
 {
-       float updated_status = ctf_CaptureShield_CheckStatus(player);
+       bool updated_status = ctf_CaptureShield_CheckStatus(player);
        if((wanted_status == player.ctf_captureshielded) && (updated_status != wanted_status)) // 0: shield only, 1: unshield only
        {
                Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((updated_status) ? CENTER_CTF_CAPTURESHIELD_SHIELDED : CENTER_CTF_CAPTURESHIELD_FREE));
@@ -156,10 +179,10 @@ void ctf_CaptureShield_Update(entity player, float wanted_status)
        }
 }
 
-float ctf_CaptureShield_Customize()
+bool ctf_CaptureShield_Customize()
 {
        if(!other.ctf_captureshielded) { return false; }
-       if(SAME_TEAM(self, other)) { return false; }
+       if(CTF_SAMETEAM(self, other)) { return false; }
 
        return true;
 }
@@ -167,7 +190,7 @@ float ctf_CaptureShield_Customize()
 void ctf_CaptureShield_Touch()
 {
        if(!other.ctf_captureshielded) { return; }
-       if(SAME_TEAM(self, other)) { return; }
+       if(CTF_SAMETEAM(self, other)) { return; }
 
        vector mymid = (self.absmin + self.absmax) * 0.5;
        vector othermid = (other.absmin + other.absmax) * 0.5;
@@ -201,22 +224,22 @@ void ctf_CaptureShield_Spawn(entity flag)
 // Drop/Pass/Throw Code
 // ====================
 
-void ctf_Handle_Drop(entity flag, entity player, float droptype)
+void ctf_Handle_Drop(entity flag, entity player, int droptype)
 {
        // declarations
        player = (player ? player : flag.pass_sender);
 
        // main
-       flag.move_movetype = MOVETYPE_TOSS;
+       flag.movetype = MOVETYPE_TOSS;
        flag.takedamage = DAMAGE_YES;
-       flag.move_angles = '0 0 0';
+       flag.angles = '0 0 0';
        flag.health = flag.max_flag_health;
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
        flag.ctf_status = FLAG_DROPPED;
 
        // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_LOST_), player.netname);
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_LOST_) : INFO_CTF_LOST_NEUTRAL), player.netname);
        sound(flag, CH_TRIGGER, flag.snd_flag_dropped, VOL_BASE, ATTEN_NONE);
        ctf_EventLog("dropped", player.team, player);
 
@@ -257,18 +280,18 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        if(player.vehicle)
        {
                setattachment(flag, player.vehicle, "");
-               flag.move_origin = VEHICLE_FLAG_OFFSET;
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
                flag.scale = VEHICLE_FLAG_SCALE;
        }
        else
        {
                setattachment(flag, player, "");
-               flag.move_origin = VEHICLE_FLAG_OFFSET;
+               setorigin(flag, FLAG_CARRY_OFFSET);
        }
-       flag.move_movetype = MOVETYPE_NONE;
+       flag.movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
-       flag.move_angles = '0 0 0';
+       flag.angles = '0 0 0';
        flag.ctf_status = FLAG_CARRY;
 
        // messages and sounds
@@ -278,11 +301,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, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_SENT_) : CENTER_CTF_PASS_SENT_NEUTRAL), player.netname);
                else if(tmp_player == player)
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_RECEIVED_), sender.netname);
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_RECEIVED_) : CENTER_CTF_PASS_RECEIVED_NEUTRAL), sender.netname);
                else if(SAME_TEAM(tmp_player, sender))
-                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PASS_OTHER_), sender.netname, player.netname);
+                       Send_Notification(NOTIF_ONE, tmp_player, MSG_CENTER, ((flag.team) ? APP_TEAM_ENT_4(flag, CENTER_CTF_PASS_OTHER_) : CENTER_CTF_PASS_OTHER_NEUTRAL), sender.netname, player.netname);
        }
 
        // create new waypoint
@@ -296,7 +319,7 @@ void ctf_Handle_Retrieve(entity flag, entity player)
        flag.pass_target = world;
 }
 
-void ctf_Handle_Throw(entity player, entity receiver, float droptype)
+void ctf_Handle_Throw(entity player, entity receiver, int droptype)
 {
        entity flag = player.flagcarried;
        vector targ_origin, flag_velocity;
@@ -308,15 +331,14 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
 
        // reset the flag
        setattachment(flag, world, "");
-       flag.move_origin = player.origin + FLAG_DROP_OFFSET;
+       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
        flag.owner.flagcarried = world;
        flag.owner = world;
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
 
-       flag.move_flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
-       flag.flags = flag.move_flags;
+       flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
 
        switch(droptype)
        {
@@ -333,7 +355,7 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                        ctf_CalculatePassVelocity(flag, targ_origin, player.origin, false);
 
                        // main
-                       flag.move_movetype = MOVETYPE_FLY;
+                       flag.movetype = MOVETYPE_FLY;
                        flag.takedamage = DAMAGE_NO;
                        flag.pass_sender = player;
                        flag.pass_target = receiver;
@@ -350,22 +372,22 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
                {
                        makevectors((player.v_angle.y * '0 1 0') + (bound(autocvar_g_ctf_throw_angle_min, player.v_angle.x, autocvar_g_ctf_throw_angle_max) * '1 0 0'));
 
-                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & IT_STRENGTH) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
-                       flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
+                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((player.items & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
                        break;
                }
 
                case DROP_RESET:
                {
-                       flag.move_velocity = '0 0 0'; // do nothing
+                       flag.velocity = '0 0 0'; // do nothing
                        break;
                }
 
                default:
                case DROP_NORMAL:
                {
-                       flag.move_velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
+                       flag.velocity = W_CalculateProjectileVelocity(player.velocity, (('0 0 1' * autocvar_g_ctf_drop_velocity_up) + ((('0 1 0' * crandom()) + ('1 0 0' * crandom())) * autocvar_g_ctf_drop_velocity_side)), false);
                        ctf_Handle_Drop(flag, player, droptype);
                        break;
                }
@@ -387,20 +409,33 @@ void ctf_Handle_Throw(entity player, entity receiver, float droptype)
 // Event Handlers
 // ==============
 
-void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
+void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
 {
        entity enemy_flag = ((capturetype == CAPTURE_NORMAL) ? toucher.flagcarried : toucher);
        entity player = ((capturetype == CAPTURE_NORMAL) ? toucher : enemy_flag.ctf_dropper);
+       entity player_team_flag = world, tmp_entity;
        float old_time, new_time;
 
-       if (!player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(!player) { return; } // without someone to give the reward to, we can't possibly cap
+       if(CTF_DIFFTEAM(player, flag)) { return; }
+       
+       if(ctf_oneflag)
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       if(SAME_TEAM(tmp_entity, player))
+       {
+               player_team_flag = tmp_entity;
+               break;
+       }
 
        nades_GiveBonus(player, autocvar_g_nades_bonus_score_high );
 
+       player.throw_prevtime = time;
+       player.throw_count = 0;
+
        // messages and sounds
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(enemy_flag, CENTER_CTF_CAPTURE_));
+       Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((enemy_flag.team) ? APP_TEAM_ENT_4(enemy_flag, CENTER_CTF_CAPTURE_) : CENTER_CTF_CAPTURE_NEUTRAL));
        ctf_CaptureRecord(enemy_flag, player);
-       sound(player, CH_TRIGGER, flag.snd_flag_capture, VOL_BASE, ATTEN_NONE);
+       sound(player, CH_TRIGGER, ((ctf_oneflag) ? player_team_flag.snd_flag_capture : ((DIFF_TEAM(player, flag)) ? enemy_flag.snd_flag_capture : flag.snd_flag_capture)), VOL_BASE, ATTEN_NONE);
 
        switch(capturetype)
        {
@@ -419,8 +454,8 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
                PlayerScore_Add(player, SP_CTF_CAPTIME, new_time - old_time);
 
        // effects
-       pointparticles(particleeffectnum(flag.capeffect), flag.move_origin, '0 0 0', 1);
-       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.move_origin - '0 0 15', -0.8, 0, 1);
+       Send_Effect(flag.capeffect, flag.origin, '0 0 0', 1);
+       //shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
 
        // other
        if(capturetype == CAPTURE_NORMAL)
@@ -440,14 +475,14 @@ void ctf_Handle_Capture(entity flag, entity toucher, float capturetype)
 void ctf_Handle_Return(entity flag, entity player)
 {
        // messages and sounds
-       if(player.flags & FL_MONSTER)
+       if(IS_MONSTER(player))
        {
-               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
+               Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_4(flag, INFO_CTF_RETURN_MONSTER_), player.monster_name);
        }
-       else
+       else if(flag.team)
        {
-               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, ATTEN_NONE);
        ctf_EventLog("return", flag.team, player);
@@ -470,14 +505,19 @@ void ctf_Handle_Return(entity flag, entity player)
                flag.ctf_dropper.next_take_time = time + autocvar_g_ctf_flag_collect_delay; // set next take time
        }
 
+       // other
+       if(player.flagcarried == flag)
+               WaypointSprite_Kill(player.wps_flagcarrier);
+
        // reset the flag
        ctf_RespawnFlag(flag);
 }
 
-void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
+void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
 {
        // declarations
        float pickup_dropped_score; // used to calculate dropped pickup score
+       entity tmp_entity; // temporary entity
 
        // attach the flag to the player
        flag.owner = player;
@@ -485,20 +525,20 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        if(player.vehicle)
        {
                setattachment(flag, player.vehicle, "");
-               flag.move_origin = VEHICLE_FLAG_OFFSET;
+               setorigin(flag, VEHICLE_FLAG_OFFSET);
                flag.scale = VEHICLE_FLAG_SCALE;
        }
        else
        {
                setattachment(flag, player, "");
-               flag.move_origin = FLAG_CARRY_OFFSET;
+               setorigin(flag, FLAG_CARRY_OFFSET);
        }
 
        // flag setup
-       flag.move_movetype = MOVETYPE_NONE;
+       flag.movetype = MOVETYPE_NONE;
        flag.takedamage = DAMAGE_NO;
        flag.solid = SOLID_NOT;
-       flag.move_angles = '0 0 0';
+       flag.angles = '0 0 0';
        flag.ctf_status = FLAG_CARRY;
 
        switch(pickuptype)
@@ -509,12 +549,28 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        }
 
        // messages and sounds
-       Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_PICKUP_), player.netname);
-       Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_2(flag, CENTER_CTF_PICKUP_));
+       Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_PICKUP_) : INFO_CTF_PICKUP_NEUTRAL), player.netname);
        if(ctf_stalemate) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_STALEMATE_CARRIER); }
-
-       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, CHOICE_CTF_PICKUP_TEAM, Team_ColorCode(player.team), player.netname);
-       Send_Notification(NOTIF_TEAM, flag, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY, Team_ColorCode(player.team), player.netname);
+       if(!flag.team) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, CENTER_CTF_PICKUP_NEUTRAL); }
+       else if(CTF_DIFFTEAM(player, flag)) { Send_Notification(NOTIF_ONE, player, MSG_CENTER, APP_TEAM_ENT_4(flag, CENTER_CTF_PICKUP_)); }
+       else { Send_Notification(NOTIF_ONE, player, MSG_CENTER, ((SAME_TEAM(player, flag)) ? CENTER_CTF_PICKUP_TEAM : CENTER_CTF_PICKUP_TEAM_ENEMY), Team_ColorCode(flag.team)); }
+
+       Send_Notification(NOTIF_TEAM_EXCEPT, player, MSG_CHOICE, ((flag.team) ? APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_) : CHOICE_CTF_PICKUP_TEAM_NEUTRAL), Team_ColorCode(player.team), player.netname);
+       
+       if(!flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(DIFF_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, CHOICE_CTF_PICKUP_ENEMY_NEUTRAL, Team_ColorCode(player.team), player.netname);
+       
+       if(flag.team)
+       FOR_EACH_PLAYER(tmp_entity)
+       if(tmp_entity != player)
+       if(CTF_SAMETEAM(flag, tmp_entity))
+       if(SAME_TEAM(player, tmp_entity))
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, APP_TEAM_ENT_4(flag, CHOICE_CTF_PICKUP_TEAM_), Team_ColorCode(player.team), player.netname);
+       else
+               Send_Notification(NOTIF_ONE, tmp_entity, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
 
        sound(player, CH_TRIGGER, flag.snd_flag_taken, VOL_BASE, ATTEN_NONE);
 
@@ -552,7 +608,7 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
        }
 
        // effects
-       pointparticles(particleeffectnum(flag.toucheffect), player.origin, '0 0 0', 1);
+       Send_Effect(flag.toucheffect, player.origin, '0 0 0', 1);
 
        // waypoints
        if(pickuptype == PICKUP_DROPPED) { WaypointSprite_Kill(flag.wps_flagdropped); }
@@ -565,7 +621,7 @@ void ctf_Handle_Pickup(entity flag, entity player, float pickuptype)
 // Main Flag Functions
 // ===================
 
-void ctf_CheckFlagReturn(entity flag, float returntype)
+void ctf_CheckFlagReturn(entity flag, int returntype)
 {
        if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
        {
@@ -575,14 +631,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, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DROPPED_) : INFO_CTF_FLAGRETURN_DROPPED_NEUTRAL)); break;
+                               case RETURN_DAMAGE: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_DAMAGED_) : INFO_CTF_FLAGRETURN_DAMAGED_NEUTRAL)); break;
+                               case RETURN_SPEEDRUN: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_SPEEDRUN_) : INFO_CTF_FLAGRETURN_SPEEDRUN_NEUTRAL), ctf_captimerecord); break;
+                               case RETURN_NEEDKILL: Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_NEEDKILL_) : INFO_CTF_FLAGRETURN_NEEDKILL_NEUTRAL)); break;
 
                                default:
                                case RETURN_TIMEOUT:
-                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_ENT_2(flag, INFO_CTF_FLAGRETURN_TIMEOUT_)); break; }
+                                       { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((flag.team) ? APP_TEAM_ENT_4(flag, INFO_CTF_FLAGRETURN_TIMEOUT_) : INFO_CTF_FLAGRETURN_TIMEOUT_NEUTRAL)); break; }
                        }
                        sound(flag, CH_TRIGGER, flag.snd_flag_respawn, VOL_BASE, ATTEN_NONE);
                        ctf_EventLog("returned", flag.team, world);
@@ -591,10 +647,25 @@ void ctf_CheckFlagReturn(entity flag, float returntype)
        }
 }
 
+bool ctf_Stalemate_Customize()
+{
+       // make spectators see what the player would see
+       entity e, wp_owner;
+       e = WaypointSprite_getviewentity(other);
+       wp_owner = self.owner;
+
+       // team waypoints
+       if(CTF_SAMETEAM(wp_owner.flagcarried, wp_owner)) { return false; }
+       if(SAME_TEAM(wp_owner, e)) { return false; }
+       if(!IS_PLAYER(e)) { return false; }
+
+       return true;
+}
+
 void ctf_CheckStalemate(void)
 {
        // declarations
-       float stale_red_flags = 0, stale_blue_flags = 0;
+       int stale_flags = 0, stale_red_flags = 0, stale_blue_flags = 0, stale_yellow_flags = 0, stale_pink_flags = 0, stale_neutral_flags = 0;
        entity tmp_entity;
 
        entity ctf_staleflaglist = world; // reset the list, we need to build the list each time this function runs
@@ -604,7 +675,7 @@ void ctf_CheckStalemate(void)
        {
                if(autocvar_g_ctf_stalemate)
                if(tmp_entity.ctf_status != FLAG_BASE)
-               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time)
+               if(time >= tmp_entity.ctf_pickuptime + autocvar_g_ctf_stalemate_time || !tmp_entity.team) // instant stalemate in oneflag
                {
                        tmp_entity.ctf_staleflagnext = ctf_staleflaglist; // link flag into staleflaglist
                        ctf_staleflaglist = tmp_entity;
@@ -613,15 +684,25 @@ 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;
+                               default: ++stale_neutral_flags; break;
                        }
                }
        }
 
-       if(stale_red_flags && stale_blue_flags)
+       if(ctf_oneflag)
+               stale_flags = (stale_neutral_flags >= 1);
+       else
+               stale_flags = (stale_red_flags >= 1) + (stale_blue_flags >= 1) + (stale_yellow_flags >= 1) + (stale_pink_flags >= 1);
+
+       if(ctf_oneflag && stale_flags == 1)
+               ctf_stalemate = true;
+       else if(stale_flags >= 2)
                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 < 2 && 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
@@ -630,7 +711,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(((ctf_oneflag) ? "flagcarrier" : "enemyflagcarrier"), 0, 0, tmp_entity.owner, FLAG_WAYPOINT_OFFSET, world, 0, tmp_entity.owner, wps_enemyflagcarrier, true, RADARICON_FLAG, WPCOLOR_ENEMYFC(tmp_entity.owner.team));
+                               tmp_entity.owner.wps_enemyflagcarrier.customizeentityforclient = ctf_Stalemate_Customize;
+                       }
                }
 
                if (!wpforenemy_announced)
@@ -643,14 +727,19 @@ void ctf_CheckStalemate(void)
        }
 }
 
-void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+void ctf_FlagDamage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
 {
-       self.move_velocity = self.velocity;
        if(ITEM_DAMAGE_NEEDKILL(deathtype))
        {
-               // automatically kill the flag and return it
-               self.health = 0;
-               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               if(autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.ctf_flagdamaged = true;
+               }
+               else
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
                return;
        }
        if(autocvar_g_ctf_flag_return_damage)
@@ -662,11 +751,13 @@ void ctf_FlagDamage(entity inflictor, entity attacker, float damage, float death
        }
 }
 
-void ctf_FlagUpdate()
+void ctf_FlagThink()
 {
        // declarations
        entity tmp_entity;
 
+       self.nextthink = time + FLAG_THINKRATE; // only 5 fps, more is unnecessary.
+
        // captureshield
        if(self == ctf_worldflaglist) // only for the first flag
                FOR_EACH_CLIENT(tmp_entity)
@@ -675,7 +766,7 @@ void ctf_FlagUpdate()
        // sanity checks
        if(self.mins != FLAG_MIN || self.maxs != FLAG_MAX) { // reset the flag boundaries in case it got squished
                dprint("wtf the flag got squashed?\n");
-               tracebox(self.move_origin, FLAG_MIN, FLAG_MAX, self.move_origin, MOVE_NOMONSTERS, self);
+               tracebox(self.origin, FLAG_MIN, FLAG_MAX, self.origin, MOVE_NOMONSTERS, self);
                if(!trace_startsolid || self.noalign) // can we resize it without getting stuck?
                        setsize(self, FLAG_MIN, FLAG_MAX); }
 
@@ -683,7 +774,7 @@ void ctf_FlagUpdate()
        {
                case FLAG_DROPPED:
                {
-                       self.move_angles = '0 0 0';
+                       self.angles = '0 0 0';
                        break;
                }
 
@@ -699,7 +790,7 @@ void ctf_FlagUpdate()
                        {
                                for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
                                        if(tmp_entity.ctf_status == FLAG_DROPPED)
-                                       if(vlen(self.move_origin - tmp_entity.move_origin) < autocvar_g_ctf_dropped_capture_radius)
+                                       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);
                        }
@@ -713,25 +804,31 @@ void ctf_FlagUpdate()
                                vector midpoint = ((self.absmin + self.absmax) * 0.5);
                                if(pointcontents(midpoint) == CONTENT_WATER)
                                {
-                                       self.move_velocity = self.move_velocity * 0.5;
+                                       self.velocity = self.velocity * 0.5;
 
                                        if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
-                                               { self.move_velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
+                                               { self.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
                                        else
-                                               { self.move_movetype = MOVETYPE_FLY; }
+                                               { self.movetype = MOVETYPE_FLY; }
                                }
-                               else if(self.move_movetype == MOVETYPE_FLY) { self.move_movetype = MOVETYPE_TOSS; }
+                               else if(self.movetype == MOVETYPE_FLY) { self.movetype = MOVETYPE_TOSS; }
                        }
                        if(autocvar_g_ctf_flag_return_dropped)
                        {
-                               if((vlen(self.move_origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
+                               if((vlen(self.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_dropped) || (autocvar_g_ctf_flag_return_dropped == -1))
                                {
                                        self.health = 0;
                                        ctf_CheckFlagReturn(self, RETURN_DROPPED);
                                        return;
                                }
                        }
-                       if(autocvar_g_ctf_flag_return_time)
+                       if(self.ctf_flagdamaged)
+                       {
+                               self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
+                               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+                               return;
+                       }
+                       else if(autocvar_g_ctf_flag_return_time)
                        {
                                self.health -= ((self.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
                                ctf_CheckFlagReturn(self, RETURN_TIMEOUT);
@@ -761,6 +858,13 @@ void ctf_FlagUpdate()
                                        wpforenemy_nextthink = time + WPFE_THINKRATE; // waypoint for enemy think rate (to reduce unnecessary spam of this check)
                                }
                        }
+                       if(CTF_SAMETEAM(self, self.owner) && self.team)
+                       {
+                               if(autocvar_g_ctf_flag_return) // drop the flag if reverse status has changed
+                                       ctf_Handle_Throw(self.owner, world, DROP_THROW);
+                               else if(vlen(self.owner.origin - self.ctf_spawnorigin) <= autocvar_g_ctf_flag_return_carried_radius)
+                                       ctf_Handle_Return(self, self.owner);
+                       }
                        return;
                }
 
@@ -768,12 +872,12 @@ void ctf_FlagUpdate()
                {
                        vector targ_origin = ((self.pass_target.absmin + self.pass_target.absmax) * 0.5);
                        targ_origin = WarpZone_RefSys_TransformOrigin(self.pass_target, self, targ_origin); // origin of target as seen by the flag (us)
-                       WarpZone_TraceLine(self.move_origin, targ_origin, MOVE_NOMONSTERS, self);
+                       WarpZone_TraceLine(self.origin, targ_origin, MOVE_NOMONSTERS, self);
 
                        if((self.pass_target == world)
                                || (self.pass_target.deadflag != DEAD_NO)
                                || (self.pass_target.flagcarried)
-                               || (vlen(self.move_origin - targ_origin) > autocvar_g_ctf_pass_radius)
+                               || (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))
                        {
@@ -783,7 +887,7 @@ void ctf_FlagUpdate()
                        else
                        {
                                // still a viable target, go for it
-                               ctf_CalculatePassVelocity(self, targ_origin, self.move_origin, true);
+                               ctf_CalculatePassVelocity(self, targ_origin, self.origin, true);
                        }
                        return;
                }
@@ -796,46 +900,37 @@ void ctf_FlagUpdate()
        }
 }
 
-void ctf_FlagThink()
-{
-       self.nextthink = time;
-
-       if(time >= self.ctf_thinkrate)
-       {
-               self.ctf_thinkrate = time + FLAG_THINKRATE;
-               ctf_FlagUpdate();
-       }
-
-       //Movetype_Physics_NoMatchServer();
-       Movetype_Physics_MatchTicrate(sys_frametime, 0);
-}
-
 void ctf_FlagTouch()
 {
        if(gameover) { return; }
        if(trace_dphitcontents & (DPCONTENTS_PLAYERCLIP | DPCONTENTS_MONSTERCLIP)) { return; }
 
-       entity toucher = other;
-       float is_not_monster = (!(toucher.flags & FL_MONSTER));
+       entity toucher = other, tmp_entity;
+       bool is_not_monster = (!IS_MONSTER(toucher)), num_perteam = 0;
 
        // automatically kill the flag and return it if it touched lava/slime/nodrop surfaces
        if(ITEM_TOUCH_NEEDKILL())
        {
-               self.health = 0;
-               ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
-               return;
+               if(!autocvar_g_ctf_flag_return_damage_delay)
+               {
+                       self.health = 0;
+                       ctf_CheckFlagReturn(self, RETURN_NEEDKILL);
+               }
+               if(!self.ctf_flagdamaged) { return; }
        }
 
+       FOR_EACH_PLAYER(tmp_entity) if(SAME_TEAM(toucher, tmp_entity)) { ++num_perteam; }
+
        // special touch behaviors
        if(toucher.frozen) { return; }
-       else if(toucher.vehicle_flags & VHF_ISVEHICLE)
+       else if(IS_VEHICLE(toucher))
        {
                if(autocvar_g_ctf_allow_vehicle_touch && toucher.owner)
                        toucher = toucher.owner; // the player is actually the vehicle owner, not other
                else
                        return; // do nothing
        }
-       else if(toucher.flags & FL_MONSTER)
+       else if(IS_MONSTER(toucher))
        {
                if(!autocvar_g_ctf_allow_monster_touch)
                        return; // do nothing
@@ -844,7 +939,7 @@ void ctf_FlagTouch()
        {
                if(time > self.wait) // if we haven't in a while, play a sound/effect
                {
-                       pointparticles(particleeffectnum(self.toucheffect), self.move_origin, '0 0 0', 1);
+                       Send_Effect(self.toucheffect, self.origin, '0 0 0', 1);
                        sound(self, CH_TRIGGER, self.snd_flag_touch, VOL_BASE, ATTEN_NORM);
                        self.wait = time + FLAG_TOUCHRATE;
                }
@@ -856,16 +951,23 @@ void ctf_FlagTouch()
        {
                case FLAG_BASE:
                {
-                       if(SAME_TEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
+                       if(ctf_oneflag)
+                       {
+                               if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && !toucher.flagcarried.team && is_not_monster)
+                                       ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the neutral flag to enemy base
+                               else if(!self.team && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                                       ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the neutral flag
+                       }
+                       else if(CTF_SAMETEAM(toucher, self) && (toucher.flagcarried) && DIFF_TEAM(toucher.flagcarried, self) && is_not_monster)
                                ctf_Handle_Capture(self, toucher, CAPTURE_NORMAL); // toucher just captured the enemies flag to his base
-                       else if(DIFF_TEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
+                       else if(CTF_DIFFTEAM(toucher, self) && (!toucher.flagcarried) && (!toucher.ctf_captureshielded) && (time > toucher.next_take_time) && is_not_monster)
                                ctf_Handle_Pickup(self, toucher, PICKUP_BASE); // toucher just stole the enemies flag
                        break;
                }
 
                case FLAG_DROPPED:
                {
-                       if(SAME_TEAM(toucher, self))
+                       if(CTF_SAMETEAM(toucher, self) && (autocvar_g_ctf_flag_return || num_perteam <= 1) && self.team) // automatically return if there's only 1 player on the team
                                ctf_Handle_Return(self, toucher); // toucher just returned his own flag
                        else if(is_not_monster && (!toucher.flagcarried) && ((toucher != self.ctf_dropper) || (time > self.ctf_droptime + autocvar_g_ctf_flag_collect_delay)))
                                ctf_Handle_Pickup(self, toucher, PICKUP_DROPPED); // toucher just picked up a dropped enemy flag
@@ -904,9 +1006,7 @@ void ctf_RespawnFlag(entity flag)
        // reset the player (if there is one)
        if((flag.owner) && (flag.owner.flagcarried == flag))
        {
-               if(flag.owner.wps_enemyflagcarrier)
-                       WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
-
+               WaypointSprite_Kill(flag.owner.wps_enemyflagcarrier);
                WaypointSprite_Kill(flag.wps_flagcarrier);
 
                flag.owner.flagcarried = world;
@@ -918,21 +1018,20 @@ void ctf_RespawnFlag(entity flag)
        if((flag.owner) && (flag.owner.vehicle))
                flag.scale = FLAG_SCALE;
 
-       if((flag.ctf_status == FLAG_DROPPED) && (flag.wps_flagdropped))
+       if(flag.ctf_status == FLAG_DROPPED)
                { WaypointSprite_Kill(flag.wps_flagdropped); }
 
        // reset the flag
        setattachment(flag, world, "");
-       flag.move_origin = flag.ctf_spawnorigin;
+       setorigin(flag, flag.ctf_spawnorigin);
 
-       flag.move_movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
+       flag.movetype = ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_NO;
        flag.health = flag.max_flag_health;
        flag.solid = SOLID_TRIGGER;
-       flag.move_velocity = '0 0 0';
-       flag.move_angles = flag.mangle;
-       flag.move_flags = FL_ITEM | FL_NOTARGET;
-       flag.flags = flag.move_flags;
+       flag.velocity = '0 0 0';
+       flag.angles = flag.mangle;
+       flag.flags = FL_ITEM | FL_NOTARGET;
 
        flag.ctf_status = FLAG_BASE;
        flag.owner = world;
@@ -942,6 +1041,7 @@ void ctf_RespawnFlag(entity flag)
        flag.ctf_dropper = world;
        flag.ctf_pickuptime = 0;
        flag.ctf_droptime = 0;
+       flag.ctf_flagdamaged = 0;
 
        ctf_CheckStalemate();
 }
@@ -963,22 +1063,34 @@ void ctf_DelayedFlagSetup(void) // called after a flag is placed on a map by ctf
        self.bot_basewaypoint = self.nearestwaypoint;
 
        // waypointsprites
-       // move_origin isnt accessible just yet
-       WaypointSprite_SpawnFixed(((self.team == NUM_TEAM_1) ? "redbase" : "bluebase"), self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
-       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, colormapPaletteColor(self.team - 1, false));
+       string basename = "base";
+
+       switch(self.team)
+       {
+               case NUM_TEAM_1: basename = "redbase"; break;
+               case NUM_TEAM_2: basename = "bluebase"; break;
+               case NUM_TEAM_3: basename = "yellowbase"; break;
+               case NUM_TEAM_4: basename = "pinkbase"; break;
+               default: basename = "neutralbase"; break;
+       }
+
+       WaypointSprite_SpawnFixed(basename, self.origin + FLAG_WAYPOINT_OFFSET, self, wps_flagbase, RADARICON_FLAG, ((self.team) ? Team_ColorRGB(self.team) : '1 1 1'));
+       WaypointSprite_UpdateTeamRadar(self.wps_flagbase, RADARICON_FLAG, ((self.team) ? colormapPaletteColor(self.team - 1, false) : '1 1 1'));
 
        // captureshield setup
        ctf_CaptureShield_Spawn(self);
+}
 
-       self.move_origin = self.origin;
-       self.move_angles = self.angles;
-       self.move_velocity = self.velocity;
+void set_flag_string(entity flag, .string field, string value, string teamname)
+{
+       if(flag.field == "")
+               flag.field = strzone(sprintf(value,teamname));
 }
 
-void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+void ctf_FlagSetup(int 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.
+       string teamname = Static_Team_ColorName_Lower(teamnumber);
        self = flag; // for later usage with droptofloor()
 
        // main setup
@@ -987,13 +1099,11 @@ 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 = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
+       flag.team = teamnumber;
        flag.classname = "item_flag_team";
        flag.target = "###item###"; // wut?
-       flag.move_flags = FL_ITEM | FL_NOTARGET;
-       flag.flags = flag.move_flags;
+       flag.flags = FL_ITEM | FL_NOTARGET;
        flag.solid = SOLID_TRIGGER;
        flag.takedamage = DAMAGE_NO;
        flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
@@ -1002,34 +1112,33 @@ void ctf_FlagSetup(float teamnumber, entity flag) // called when spawning a flag
        flag.event_damage = ctf_FlagDamage;
        flag.pushable = true;
        flag.teleportable = TELEPORT_NORMAL;
+       flag.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_PLAYERCLIP | DPCONTENTS_BOTCLIP;
        flag.damagedbytriggers = autocvar_g_ctf_flag_return_when_unreachable;
        flag.damagedbycontents = autocvar_g_ctf_flag_return_when_unreachable;
-       flag.move_velocity = '0 0 0';
+       flag.velocity = '0 0 0';
        flag.mangle = flag.angles;
        flag.reset = ctf_Reset;
        flag.touch = ctf_FlagTouch;
-       flag.move_touch = flag.touch;
        flag.think = ctf_FlagThink;
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
-       flag.move_time = time;
 
        // appearence
-       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"); }
-
-       // 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.scale)                         { flag.scale = FLAG_SCALE; }
+       if(flag.skin == 0)                      { flag.skin = cvar(sprintf("g_ctf_flag_%s_skin", teamname)); }
+       if(flag.model == "")            { flag.model = cvar_string(sprintf("g_ctf_flag_%s_model", teamname)); }
+       set_flag_string(flag, toucheffect,      "%sflag_touch", teamname);
+       set_flag_string(flag, passeffect,       "%s_pass",              teamname);
+       set_flag_string(flag, capeffect,        "%s_cap",               teamname);
+
+       // sounds
+       set_flag_string(flag, snd_flag_taken,           "ctf/%s_taken.wav",     teamname);
+       set_flag_string(flag, snd_flag_returned,        "ctf/%s_returned.wav",  teamname);
+       set_flag_string(flag, snd_flag_capture,         "ctf/%s_capture.wav",   teamname);
+       set_flag_string(flag, snd_flag_dropped,         "ctf/%s_dropped.wav",   teamname);
+       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_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);
@@ -1050,28 +1159,45 @@ 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;
+                       case NUM_TEAM_2: flag.glow_color = 210; break;
+                       case NUM_TEAM_3: flag.glow_color = 110; break;
+                       case NUM_TEAM_4: flag.glow_color = 145; break;
+                       default:                 flag.glow_color = 254; break;
+               }
                flag.glow_size = 25;
                flag.glow_trail = 1;
        }
 
        flag.effects |= EF_LOWPRECISION;
        if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
-       if(autocvar_g_ctf_dynamiclights)   { flag.effects |= ((teamnumber) ? EF_RED : EF_BLUE); }
-
+       if(autocvar_g_ctf_dynamiclights)
+       {
+               switch(teamnumber)
+               {
+                       case NUM_TEAM_1: flag.effects |= EF_RED; break;
+                       case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
+                       case NUM_TEAM_3: flag.effects |= EF_DIMLIGHT; break;
+                       case NUM_TEAM_4: flag.effects |= EF_RED; break;
+                       default:                 flag.effects |= EF_DIMLIGHT; break;
+               }
+       }
+       
        // flag placement
        if((flag.spawnflags & 1) || flag.noalign) // don't drop to floor, just stay at fixed location
        {
                flag.dropped_origin = flag.origin;
                flag.noalign = true;
-               flag.move_movetype = MOVETYPE_NONE;
+               flag.movetype = MOVETYPE_NONE;
        }
        else // drop to floor, automatically find a platform and set that as spawn origin
        {
                flag.noalign = false;
                self = flag;
                droptofloor();
-               flag.move_movetype = MOVETYPE_TOSS;
+               flag.movetype = MOVETYPE_TOSS;
        }
 
        InitializeEntity(flag, ctf_DelayedFlagSetup, INITPRIO_SETLOCATION);
@@ -1094,7 +1220,7 @@ void havocbot_calculate_middlepoint()
        f = ctf_worldflaglist;
        while (f)
        {
-               fo = f.move_origin;
+               fo = f.origin;
                s = s + fo;
                f = f.ctf_worldflagnext;
        }
@@ -1111,7 +1237,7 @@ entity havocbot_ctf_find_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team == f.team)
+               if (CTF_SAMETEAM(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
@@ -1124,24 +1250,37 @@ entity havocbot_ctf_find_enemy_flag(entity bot)
        f = ctf_worldflaglist;
        while (f)
        {
-               if (bot.team != f.team)
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(bot, f))
+                       {
+                               if(f.team)
+                               {
+                                       if(bot.flagcarried)
+                                               return f;
+                               }
+                               else if(!bot.flagcarried)
+                                       return f;
+                       }
+               }
+               else if (CTF_DIFFTEAM(bot, f))
                        return f;
                f = f.ctf_worldflagnext;
        }
        return world;
 }
 
-float havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
+int havocbot_ctf_teamcount(entity bot, vector org, float tc_radius)
 {
        if (!teamplay)
                return 0;
 
-       float c = 0;
+       int c = 0;
        entity head;
 
        FOR_EACH_PLAYER(head)
        {
-               if(head.team!=bot.team || head.deadflag != DEAD_NO || head == bot)
+               if(DIFF_TEAM(head, bot) || head.deadflag != DEAD_NO || head == bot)
                        continue;
 
                if(vlen(head.origin - org) < tc_radius)
@@ -1157,7 +1296,7 @@ void havocbot_goalrating_ctf_ourflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (CTF_SAMETEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1171,7 +1310,7 @@ void havocbot_goalrating_ctf_ourbase(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team == head.team)
+               if (CTF_SAMETEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1187,7 +1326,20 @@ void havocbot_goalrating_ctf_enemyflag(float ratingscale)
        head = ctf_worldflaglist;
        while (head)
        {
-               if (self.team != head.team)
+               if(ctf_oneflag)
+               {
+                       if(CTF_DIFFTEAM(self, head))
+                       {
+                               if(head.team)
+                               {
+                                       if(self.flagcarried)
+                                               break;
+                               }
+                               else if(!self.flagcarried)
+                                       break;
+                       }
+               }
+               else if(CTF_DIFFTEAM(self, head))
                        break;
                head = head.ctf_worldflagnext;
        }
@@ -1238,7 +1390,7 @@ void havocbot_goalrating_ctf_droppedflags(float ratingscale, vector org, float d
                {
                        if(df_radius)
                        {
-                               if(vlen(org-head.move_origin)<df_radius)
+                               if(vlen(org-head.origin)<df_radius)
                                        navigation_routerating(head, ratingscale, 10000);
                        }
                        else
@@ -1355,7 +1507,10 @@ void havocbot_role_ctf_carrier()
                self.bot_strategytime = time + autocvar_bot_ai_strategyinterval;
 
                navigation_goalrating_start();
-               havocbot_goalrating_ctf_ourbase(50000);
+               if(ctf_oneflag)
+                       havocbot_goalrating_ctf_enemybase(50000);
+               else
+                       havocbot_goalrating_ctf_ourbase(50000);
 
                if(self.health<100)
                        havocbot_goalrating_ctf_carrieritems(1000, self.origin, 1000);
@@ -1402,7 +1557,7 @@ void havocbot_role_ctf_escort()
        // If the flag carrier reached the base switch to defense
        mf = havocbot_ctf_find_flag(self);
        if(mf.ctf_status!=FLAG_BASE)
-       if(vlen(ef.move_origin - mf.dropped_origin) < 300)
+       if(vlen(ef.origin - mf.dropped_origin) < 300)
        {
                havocbot_role_ctf_setrole(self, HAVOCBOT_CTF_ROLE_DEFENSE);
                return;
@@ -1461,7 +1616,7 @@ void havocbot_role_ctf_offense()
                if(mf.tag_entity)
                        pos = mf.tag_entity.origin;
                else
-                       pos = mf.move_origin;
+                       pos = mf.origin;
 
                // Try to get it if closer than the enemy base
                if(vlen(self.origin-ef.dropped_origin)>vlen(self.origin-pos))
@@ -1477,7 +1632,7 @@ void havocbot_role_ctf_offense()
                if(ef.tag_entity)
                        pos = ef.tag_entity.origin;
                else
-                       pos = ef.move_origin;
+                       pos = ef.origin;
 
                if(vlen(pos-mf.dropped_origin)>700)
                {
@@ -1689,7 +1844,7 @@ void havocbot_role_ctf_defense()
        }
 }
 
-void havocbot_role_ctf_setrole(entity bot, float role)
+void havocbot_role_ctf_setrole(entity bot, int role)
 {
        dprint(strcat(bot.netname," switched to "));
        switch(role)
@@ -1742,28 +1897,39 @@ void havocbot_role_ctf_setrole(entity bot, float role)
 MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
 {
        entity flag;
+       int 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.ctf_flagstatus &= ~(CTF_RED_FLAG_CARRYING          | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST 
+                                                  | CTF_BLUE_FLAG_CARRYING             | CTF_BLUE_FLAG_TAKEN           | CTF_BLUE_FLAG_LOST
+                                                  | CTF_YELLOW_FLAG_CARRYING   | CTF_YELLOW_FLAG_TAKEN         | CTF_YELLOW_FLAG_LOST
+                                                  | CTF_PINK_FLAG_CARRYING     | CTF_PINK_FLAG_TAKEN           | CTF_PINK_FLAG_LOST
+                                                  | CTF_NEUTRAL_FLAG_CARRYING  | CTF_NEUTRAL_FLAG_TAKEN        | CTF_NEUTRAL_FLAG_LOST
+                                                  | CTF_FLAG_NEUTRAL | CTF_SHIELDED);
 
        // scan through all the flags and notify the client about them
        for(flag = ctf_worldflaglist; flag; flag = flag.ctf_worldflagnext)
        {
+               if(flag.team == NUM_TEAM_1) { t = CTF_RED_FLAG_CARRYING;                t2 = CTF_RED_FLAG_TAKEN;                t3 = CTF_RED_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_2) { t = CTF_BLUE_FLAG_CARRYING;               t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_3) { t = CTF_YELLOW_FLAG_CARRYING;     t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
+               if(flag.team == NUM_TEAM_4) { t = CTF_PINK_FLAG_CARRYING;               t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
+               if(flag.team == 0)                      { t = CTF_NEUTRAL_FLAG_CARRYING;        t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; self.ctf_flagstatus |= CTF_FLAG_NEUTRAL; }
+
                switch(flag.ctf_status)
                {
                        case FLAG_PASSING:
                        case FLAG_CARRY:
                        {
                                if((flag.owner == self) || (flag.pass_sender == self))
-                                       self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_CARRYING : IT_BLUE_FLAG_CARRYING); // carrying: self is currently carrying the flag
-                               else
-                                       self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_TAKEN : IT_BLUE_FLAG_TAKEN); // taken: someone on self's team is carrying the flag
+                                       self.ctf_flagstatus |= t; // carrying: self is currently carrying the flag
+                               else 
+                                       self.ctf_flagstatus |= t2; // taken: someone else is carrying the flag
                                break;
                        }
                        case FLAG_DROPPED:
                        {
-                               self.items |= ((flag.items & IT_KEY2) ? IT_RED_FLAG_LOST : IT_BLUE_FLAG_LOST); // lost: the flag is dropped somewhere on the map
+                               self.ctf_flagstatus |= t3; // lost: the flag is dropped somewhere on the map
                                break;
                        }
                }
@@ -1771,7 +1937,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerPreThink)
 
        // item for stopping players from capturing the flag too often
        if(self.ctf_captureshielded)
-               self.items |= IT_CTF_SHIELDED;
+               self.ctf_flagstatus |= CTF_SHIELDED;
 
        // update the health of the flag carrier waypointsprite
        if(self.wps_flagcarrier)
@@ -1795,7 +1961,7 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDamage) // for changing damage and force values t
                        frag_force *= autocvar_g_ctf_flagcarrier_forcefactor;
                }
        }
-       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && DIFF_TEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
+       else if(frag_target.flagcarried && (frag_target.deadflag == DEAD_NO) && CTF_DIFFTEAM(frag_target, frag_attacker)) // if the target is a flagcarrier
        {
                if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > ('1 0 0' * healtharmor_maxdamage(frag_target.health, frag_target.armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON)))
                if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
@@ -1817,7 +1983,11 @@ MUTATOR_HOOKFUNCTION(ctf_PlayerDies)
        }
 
        if(frag_target.flagcarried)
-               { ctf_Handle_Throw(frag_target, world, DROP_NORMAL); }
+       {
+               entity tmp_entity = frag_target.flagcarried;
+               ctf_Handle_Throw(frag_target, world, DROP_NORMAL);
+               tmp_entity.ctf_dropper = world;
+       }
 
        return false;
 }
@@ -1967,6 +2137,8 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
 {
        if(vh_player.flagcarried)
        {
+               vh_player.flagcarried.nodrawtoclient = vh_player; // hide the flag from the driver
+
                if(!autocvar_g_ctf_allow_vehicle_carry && !autocvar_g_ctf_allow_vehicle_touch)
                {
                        ctf_Handle_Throw(vh_player, world, DROP_NORMAL);
@@ -1974,9 +2146,9 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleEnter)
                else
                {
                        setattachment(vh_player.flagcarried, vh_vehicle, "");
-                       vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
+                       setorigin(vh_player.flagcarried, VEHICLE_FLAG_OFFSET);
                        vh_player.flagcarried.scale = VEHICLE_FLAG_SCALE;
-                       //vh_player.flagcarried.move_angles = '0 0 0';
+                       //vh_player.flagcarried.angles = '0 0 0';
                }
                return true;
        }
@@ -1989,9 +2161,10 @@ MUTATOR_HOOKFUNCTION(ctf_VehicleExit)
        if(vh_player.flagcarried)
        {
                setattachment(vh_player.flagcarried, vh_player, "");
-               vh_player.flagcarried.move_origin = VEHICLE_FLAG_OFFSET;
+               setorigin(vh_player.flagcarried, FLAG_CARRY_OFFSET);
                vh_player.flagcarried.scale = FLAG_SCALE;
-               vh_player.flagcarried.move_angles = '0 0 0';
+               vh_player.flagcarried.angles = '0 0 0';
+               vh_player.flagcarried.nodrawtoclient = world;
                return true;
        }
 
@@ -2002,7 +2175,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, ((self.flagcarried.team) ? APP_TEAM_ENT_4(self.flagcarried, INFO_CTF_FLAGRETURN_ABORTRUN_) : INFO_CTF_FLAGRETURN_ABORTRUN_NEUTRAL));
                ctf_RespawnFlag(self.flagcarried);
                return true;
        }
@@ -2022,7 +2195,7 @@ MUTATOR_HOOKFUNCTION(ctf_MatchEnd)
                        case FLAG_PASSING:
                        {
                                // lock the flag, game is over
-                               flag.move_movetype = MOVETYPE_NONE;
+                               flag.movetype = MOVETYPE_NONE;
                                flag.takedamage = DAMAGE_NO;
                                flag.solid = SOLID_NOT;
                                flag.nextthink = false; // stop thinking
@@ -2050,6 +2223,19 @@ MUTATOR_HOOKFUNCTION(ctf_BotRoles)
        return true;
 }
 
+MUTATOR_HOOKFUNCTION(ctf_GetTeamCount)
+{
+       //ret_float = ctf_teams;
+       ret_string = "ctf_team";
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ctf_SpectateCopy)
+{
+       self.ctf_flagstatus = other.ctf_flagstatus;
+       return false;
+}
+
 
 // ==========
 // Spawnfuncs
@@ -2116,7 +2302,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)
@@ -2134,7 +2320,62 @@ 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 four (Pink).
+Keys: 
+"angle" Angle the flag will point (minus 90 degrees)... 
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself... 
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_team4()
+{
+       if(!g_ctf) { remove(self); return; }
+
+       ctf_FlagSetup(NUM_TEAM_4, self);
+}
+
+/*QUAKED spawnfunc_item_flag_neutral (0 0.5 0.8) (-48 -48 -37) (48 48 37)
+CTF flag (Neutral).
+Keys: 
+"angle" Angle the flag will point (minus 90 degrees)... 
+"model" model to use, note this needs red, blue yellow and pink as skins 0, 1, 2 and 3...
+"noise" sound played when flag is picked up...
+"noise1" sound played when flag is returned by a teammate...
+"noise2" sound played when flag is captured...
+"noise3" sound played when flag is lost in the field and respawns itself... 
+"noise4" sound played when flag is dropped by a player...
+"noise5" sound played when flag touches the ground... */
+void spawnfunc_item_flag_neutral()
+{
+       if(!g_ctf) { remove(self); return; }
+       if(!cvar("g_ctf_oneflag")) { remove(self); return; }
+
+       ctf_FlagSetup(0, self);
 }
 
 /*QUAKED spawnfunc_ctf_team (0 .5 .8) (-16 -16 -24) (16 16 32)
@@ -2159,15 +2400,19 @@ void spawnfunc_team_CTF_blueplayer() { spawnfunc_info_player_team2();  }
 void spawnfunc_team_CTF_redspawn()   { spawnfunc_info_player_team1();  }
 void spawnfunc_team_CTF_bluespawn()  { spawnfunc_info_player_team2();  }
 
+void team_CTF_neutralflag()                     { spawnfunc_item_flag_neutral();  }
+void team_neutralobelisk()                      { spawnfunc_item_flag_neutral();  }
+
 
 // ==============
 // Initialization
 // ==============
 
 // scoreboard setup
-void ctf_ScoreRules()
+void ctf_ScoreRules(int 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);
@@ -2179,7 +2424,7 @@ void ctf_ScoreRules()
 }
 
 // code from here on is just to support maps that don't have flag and team entities
-void ctf_SpawnTeam (string teamname, float teamcolor)
+void ctf_SpawnTeam (string teamname, int teamcolor)
 {
        entity oldself;
        oldself = self;
@@ -2195,15 +2440,31 @@ void ctf_SpawnTeam (string teamname, float teamcolor)
 
 void ctf_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
 {
+       ctf_teams = 2;
+
+       entity tmp_entity;
+       for(tmp_entity = ctf_worldflaglist; tmp_entity; tmp_entity = tmp_entity.ctf_worldflagnext)
+       {
+               if(tmp_entity.team == NUM_TEAM_3) { ctf_teams = max(3, ctf_teams); }
+               if(tmp_entity.team == NUM_TEAM_4) { ctf_teams = max(4, ctf_teams); }
+               if(tmp_entity.team == 0) { ctf_oneflag = true; }
+       }
+
+       ctf_teams = bound(2, ctf_teams, 4);
+
        // if no teams are found, spawn defaults
        if(find(world, classname, "ctf_team") == world)
        {
                print("No ""ctf_team"" entities found on this map, creating them anyway.\n");
                ctf_SpawnTeam("Red", NUM_TEAM_1 - 1);
                ctf_SpawnTeam("Blue", NUM_TEAM_2 - 1);
+               if(ctf_teams >= 3)
+                       ctf_SpawnTeam("Yellow", NUM_TEAM_3 - 1);
+               if(ctf_teams >= 4)
+                       ctf_SpawnTeam("Pink", NUM_TEAM_4 - 1);
        }
 
-       ctf_ScoreRules();
+       ctf_ScoreRules(ctf_teams);
 }
 
 void ctf_Initialize()
@@ -2214,6 +2475,8 @@ void ctf_Initialize()
        ctf_captureshield_max_ratio = autocvar_g_ctf_shield_max_ratio;
        ctf_captureshield_force = autocvar_g_ctf_shield_force;
 
+       addstat(STAT_CTF_FLAGSTATUS, AS_INT, ctf_flagstatus);
+
        InitializeEntity(world, ctf_DelayedInit, INITPRIO_GAMETYPE);
 }
 
@@ -2234,6 +2497,8 @@ MUTATOR_DEFINITION(gamemode_ctf)
        MUTATOR_HOOK(VehicleExit, ctf_VehicleExit, CBC_ORDER_ANY);
        MUTATOR_HOOK(AbortSpeedrun, ctf_AbortSpeedrun, CBC_ORDER_ANY);
        MUTATOR_HOOK(HavocBot_ChooseRole, ctf_BotRoles, CBC_ORDER_ANY);
+       MUTATOR_HOOK(GetTeamCount, ctf_GetTeamCount, CBC_ORDER_ANY);
+       MUTATOR_HOOK(SpectateCopy, ctf_SpectateCopy, CBC_ORDER_ANY);
 
        MUTATOR_ONADD
        {