]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc
Replace team-coloured CTF flag textures with a single neutral texture, add support...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / ctf / sv_ctf.qc
index efbbf94e72aceee1716ad7d50f9ad49edc6a66b7..5fbb2b2a68ea985daa18c8200f533973b7c8a433 100644 (file)
@@ -1,7 +1,18 @@
 #include "sv_ctf.qh"
 
 #include <common/effects/all.qh>
+#include <common/mapobjects/teleporters.qh>
+#include <common/mapobjects/triggers.qh>
+#include <common/mutators/mutator/powerups/_mod.qh>
 #include <common/vehicles/all.qh>
+#include <server/command/vote.qh>
+#include <server/client.qh>
+#include <server/gamelog.qh>
+#include <server/intermission.qh>
+#include <server/damage.qh>
+#include <server/world.qh>
+#include <server/items/items.qh>
+#include <server/race.qh>
 #include <server/teamplay.qh>
 
 #include <lib/warpzone/common.qh>
@@ -48,6 +59,8 @@ bool autocvar_g_ctf_flag_return_when_unreachable;
 float autocvar_g_ctf_flag_return_damage;
 float autocvar_g_ctf_flag_return_damage_delay;
 float autocvar_g_ctf_flag_return_dropped;
+bool autocvar_g_ctf_flag_waypoint = true;
+float autocvar_g_ctf_flag_waypoint_maxdistance;
 float autocvar_g_ctf_flagcarrier_auto_helpme_damage;
 float autocvar_g_ctf_flagcarrier_auto_helpme_time;
 float autocvar_g_ctf_flagcarrier_selfdamagefactor;
@@ -144,8 +157,8 @@ bool ctf_Return_Customize(entity this, entity client)
 void ctf_FlagcarrierWaypoints(entity player)
 {
        WaypointSprite_Spawn(WP_FlagCarrier, 0, 0, player, FLAG_WAYPOINT_OFFSET, NULL, player.team, player, wps_flagcarrier, true, RADARICON_FLAG);
-       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id) * 2);
-       WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+       WaypointSprite_UpdateMaxHealth(player.wps_flagcarrier, 2 * healtharmor_maxdamage(start_health, start_armorvalue, autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
+       WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
        WaypointSprite_UpdateTeamRadar(player.wps_flagcarrier, RADARICON_FLAGCARRIER, WPCOLOR_FLAGCARRIER(player.team));
 
        if(player.flagcarried && CTF_SAMETEAM(player, player.flagcarried))
@@ -341,7 +354,7 @@ void ctf_Handle_Drop(entity flag, entity player, int droptype)
        set_movetype(flag, MOVETYPE_TOSS);
        flag.takedamage = DAMAGE_YES;
        flag.angles = '0 0 0';
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
        flag.ctf_status = FLAG_DROPPED;
@@ -363,8 +376,8 @@ void ctf_Handle_Drop(entity flag, entity player, int droptype)
 
        if(autocvar_g_ctf_flag_return_time || (autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health))
        {
-               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_flag_health);
-               WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH));
+               WaypointSprite_UpdateMaxHealth(flag.wps_flagdropped, flag.max_health);
+               WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH));
        }
 
        player.throw_antispam = time + autocvar_g_ctf_pass_wait;
@@ -436,11 +449,24 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
        if(!flag) { return; }
        if((droptype == DROP_PASS) && !receiver) { return; }
 
-       if(flag.speedrunning) { ctf_RespawnFlag(flag); return; }
+       if(flag.speedrunning)
+       {
+               // ensure old waypoints are removed before resetting the flag
+               WaypointSprite_Kill(player.wps_flagcarrier);
+
+               if(player.wps_enemyflagcarrier)
+                       WaypointSprite_Kill(player.wps_enemyflagcarrier);
+
+               if(player.wps_flagreturn)
+                       WaypointSprite_Kill(player.wps_flagreturn);
+               ctf_RespawnFlag(flag);
+               return;
+       }
 
        // reset the flag
        setattachment(flag, NULL, "");
-       setorigin(flag, player.origin + FLAG_DROP_OFFSET);
+       tracebox(player.origin - FLAG_DROP_OFFSET, flag.m_mins, flag.m_maxs, player.origin + FLAG_DROP_OFFSET, MOVE_NOMONSTERS, flag);
+       setorigin(flag, trace_endpos);
        flag.owner.flagcarried = NULL;
        GameRules_scoring_vip(flag.owner, false);
        flag.owner = NULL;
@@ -482,7 +508,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int 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 & ITEM_Strength.m_itemid) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
+                       flag_velocity = (('0 0 1' * autocvar_g_ctf_throw_velocity_up) + ((v_forward * autocvar_g_ctf_throw_velocity_forward) * ((StatusEffects_active(STATUSEFFECT_Strength, player)) ? autocvar_g_ctf_throw_strengthmultiplier : 1)));
                        flag.velocity = W_CalculateProjectileVelocity(player, player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
                        navigation_dynamicgoal_set(flag, player);
@@ -519,10 +545,12 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
        ctf_CaptureShield_Update(player, 0); // shield player from picking up flag
 }
 
+#if 0
 void shockwave_spawn(string m, vector org, float sz, float t1, float t2)
 {
        return modeleffect_spawn(m, 0, 0, org, '0 0 0', '0 0 0', '0 0 0', 0, sz, 1, t1, t2);
 }
+#endif
 
 // ==============
 // Event Handlers
@@ -586,7 +614,9 @@ void ctf_Handle_Capture(entity flag, entity toucher, int capturetype)
 
        // effects
        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);
+#if 0
+       shockwave_spawn("models/ctf/shockwavetransring.md3", flag.origin - '0 0 15', -0.8, 0, 1);
+#endif
 
        // other
        if(capturetype == CAPTURE_NORMAL)
@@ -679,7 +709,7 @@ void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
        switch(pickuptype)
        {
                case PICKUP_BASE: flag.ctf_pickuptime = time; break; // used for timing runs
-               case PICKUP_DROPPED: SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
+               case PICKUP_DROPPED: SetResourceExplicit(flag, RES_HEALTH, flag.max_health); break; // reset health/return timelimit
                default: break;
        }
 
@@ -702,10 +732,12 @@ void ctf_Handle_Pickup(entity flag, entity player, int pickuptype)
        if(flag.team)
                FOREACH_CLIENT(IS_PLAYER(it) && it != player, {
                        if(CTF_SAMETEAM(flag, it))
-                       if(SAME_TEAM(player, it))
-                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
-                       else
-                               Send_Notification(NOTIF_ONE, it, MSG_CHOICE, ((SAME_TEAM(flag, player)) ? CHOICE_CTF_PICKUP_ENEMY_TEAM : CHOICE_CTF_PICKUP_ENEMY), Team_ColorCode(player.team), player.netname);
+                       {
+                               if(SAME_TEAM(player, it))
+                                       Send_Notification(NOTIF_ONE, it, MSG_CHOICE, APP_TEAM_NUM(flag.team, CHOICE_CTF_PICKUP_TEAM), Team_ColorCode(player.team), player.netname);
+                               else
+                                       Send_Notification(NOTIF_ONE, it, 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);
@@ -761,9 +793,9 @@ void ctf_CheckFlagReturn(entity flag, int returntype)
 {
        if((flag.ctf_status == FLAG_DROPPED) || (flag.ctf_status == FLAG_PASSING))
        {
-               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RESOURCE_HEALTH)); }
+               if(flag.wps_flagdropped) { WaypointSprite_UpdateHealth(flag.wps_flagdropped, GetResource(flag, RES_HEALTH)); }
 
-               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               if((GetResource(flag, RES_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
                {
                        switch(returntype)
                        {
@@ -874,7 +906,7 @@ void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage
                        this.ctf_flagdamaged_byworld = true;
                else
                {
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                       SetResourceExplicit(this, RES_HEALTH, 0);
                        ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
                }
                return;
@@ -882,7 +914,7 @@ void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage
        if(autocvar_g_ctf_flag_return_damage)
        {
                // reduce health and check if it should be returned
-               TakeResource(this, RESOURCE_HEALTH, damage);
+               TakeResource(this, RES_HEALTH, damage);
                ctf_CheckFlagReturn(this, RETURN_DAMAGE);
                return;
        }
@@ -945,20 +977,20 @@ void ctf_FlagThink(entity this)
                        {
                                if((vdist(this.origin - this.ctf_spawnorigin, <=, autocvar_g_ctf_flag_return_dropped)) || (autocvar_g_ctf_flag_return_dropped == -1))
                                {
-                                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                                       SetResourceExplicit(this, RES_HEALTH, 0);
                                        ctf_CheckFlagReturn(this, RETURN_DROPPED);
                                        return;
                                }
                        }
                        if(this.ctf_flagdamaged_byworld)
                        {
-                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE));
+                               TakeResource(this, RES_HEALTH, (this.max_health / autocvar_g_ctf_flag_return_damage_delay) * FLAG_THINKRATE);
                                ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
                                return;
                        }
                        else if(autocvar_g_ctf_flag_return_time)
                        {
-                               TakeResource(this, RESOURCE_HEALTH, ((this.max_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
+                               TakeResource(this, RES_HEALTH, (this.max_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE);
                                ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
                                return;
                        }
@@ -969,7 +1001,7 @@ void ctf_FlagThink(entity this)
                {
                        if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
                        {
-                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                               SetResourceExplicit(this, RES_HEALTH, 0);
                                ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
 
                                CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
@@ -1038,7 +1070,7 @@ METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
        {
                if(!autocvar_g_ctf_flag_return_damage_delay)
                {
-                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+                       SetResourceExplicit(flag, RES_HEALTH, 0);
                        ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
                }
                if(!flag.ctf_flagdamaged_byworld) { return; }
@@ -1130,6 +1162,7 @@ METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
 .float last_respawn;
 void ctf_RespawnFlag(entity flag)
 {
+       flag.watertype = CONTENT_EMPTY; // TODO: it is unclear why this workaround is needed, likely many other potential breakage points!!
        // check for flag respawn being called twice in a row
        if(flag.last_respawn > time - 0.5)
                { backtrace("flag respawn called twice quickly! please notify Samual about this..."); }
@@ -1160,9 +1193,10 @@ void ctf_RespawnFlag(entity flag)
        setattachment(flag, NULL, "");
        setorigin(flag, flag.ctf_spawnorigin);
 
-       set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
+       //set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS)); // would be desired, except maps that want floating flags have it set to fall!
+       set_movetype(flag, MOVETYPE_NONE); // match the initial setup handling (flag doesn't move when spawned)
        flag.takedamage = DAMAGE_NO;
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
        flag.solid = SOLID_TRIGGER;
        flag.velocity = '0 0 0';
        flag.angles = flag.mangle;
@@ -1220,10 +1254,14 @@ void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map
                default: basename = WP_FlagBaseNeutral; break;
        }
 
-       entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
-       wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
-       WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
-       setcefc(wp, ctf_FlagBase_Customize);
+       if(autocvar_g_ctf_flag_waypoint)
+       {
+               entity wp = WaypointSprite_SpawnFixed(basename, this.origin + FLAG_WAYPOINT_OFFSET, this, wps_flagbase, RADARICON_FLAG);
+               wp.colormod = ((this.team) ? Team_ColorRGB(this.team) : '1 1 1');
+               wp.fade_rate = autocvar_g_ctf_flag_waypoint_maxdistance;
+               WaypointSprite_UpdateTeamRadar(this.wps_flagbase, RADARICON_FLAG, ((this.team) ? colormapPaletteColor(this.team - 1, false) : '1 1 1'));
+               setcefc(wp, ctf_FlagBase_Customize);
+       }
 
        // captureshield setup
        ctf_CaptureShield_Spawn(this);
@@ -1231,7 +1269,7 @@ void ctf_DelayedFlagSetup(entity this) // called after a flag is placed on a map
 
 .bool pushable;
 
-void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag entity on the map as a spawnfunc
+void ctf_FlagSetup(int teamnum, entity flag) // called when spawning a flag entity on the map as a spawnfunc
 {
        // main setup
        flag.ctf_worldflagnext = ctf_worldflaglist; // link flag into ctf_worldflaglist
@@ -1239,8 +1277,8 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
 
        setattachment(flag, NULL, "");
 
-       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnumber), Team_ColorName_Upper(teamnumber)));
-       flag.team = teamnumber;
+       flag.netname = strzone(sprintf("%s%s^7 flag", Team_ColorCode(teamnum), Team_ColorName_Upper(teamnum)));
+       flag.team = teamnum;
        flag.classname = "item_flag_team";
        flag.target = "###item###"; // for finding the nearest item using findnearest
        flag.flags = FL_ITEM | FL_NOTARGET;
@@ -1248,8 +1286,8 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        flag.solid = SOLID_TRIGGER;
        flag.takedamage = DAMAGE_NO;
        flag.damageforcescale = autocvar_g_ctf_flag_damageforcescale;
-       flag.max_flag_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       flag.max_health = ((autocvar_g_ctf_flag_return_damage && autocvar_g_ctf_flag_health) ? autocvar_g_ctf_flag_health : 100);
+       SetResourceExplicit(flag, RES_HEALTH, flag.max_health);
        flag.event_damage = ctf_FlagDamage;
        flag.pushable = true;
        flag.teleportable = TELEPORT_NORMAL;
@@ -1266,28 +1304,33 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        flag.nextthink = time + FLAG_THINKRATE;
        flag.ctf_status = FLAG_BASE;
 
+       // set correct team colors
+       flag.glowmod = Team_ColorRGB(teamnum);
+       flag.colormap = (teamnum) ? (teamnum - 1) * 0x11 : 0x00;
+       flag.colormap |= BIT(10); // RENDER_COLORMAPPED
+
        // crudely force them all to 0
        if(autocvar_g_ctf_score_ignore_fields)
                flag.cnt = flag.score_assist = flag.score_team_capture = flag.score_capture = flag.score_drop = flag.score_pickup = flag.score_return = 0;
 
-       string teamname = Static_Team_ColorName_Lower(teamnumber);
+       string teamname = Static_Team_ColorName_Lower(teamnum);
        // appearence
        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)); }
-       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnumber).eent_eff_name; }
-       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnumber).eent_eff_name; }
-       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnumber).eent_eff_name; }
+       if (flag.toucheffect == "") { flag.toucheffect = EFFECT_FLAG_TOUCH(teamnum).eent_eff_name; }
+       if (flag.passeffect == "")      { flag.passeffect = EFFECT_PASS(teamnum).eent_eff_name; }
+       if (flag.capeffect == "")       { flag.capeffect = EFFECT_CAP(teamnum).eent_eff_name; }
 
        // sounds
 #define X(s,b) \
                if(flag.s == "") flag.s = b; \
                precache_sound(flag.s);
 
-       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnumber))))
-       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnumber))))
-       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnumber))))
-       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnumber))))
+       X(snd_flag_taken,               strzone(SND(CTF_TAKEN(teamnum))))
+       X(snd_flag_returned,    strzone(SND(CTF_RETURNED(teamnum))))
+       X(snd_flag_capture,     strzone(SND(CTF_CAPTURE(teamnum))))
+       X(snd_flag_dropped,     strzone(SND(CTF_DROPPED(teamnum))))
        X(snd_flag_respawn,     strzone(SND(CTF_RESPAWN)))
        X(snd_flag_touch,               strzone(SND(CTF_TOUCH)))
        X(snd_flag_pass,                strzone(SND(CTF_PASS)))
@@ -1305,7 +1348,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
 
        if(autocvar_g_ctf_flag_glowtrails)
        {
-               switch(teamnumber)
+               switch(teamnum)
                {
                        case NUM_TEAM_1: flag.glow_color = 251; break;
                        case NUM_TEAM_2: flag.glow_color = 210; break;
@@ -1321,7 +1364,7 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        if(autocvar_g_ctf_fullbrightflags) { flag.effects |= EF_FULLBRIGHT; }
        if(autocvar_g_ctf_dynamiclights)
        {
-               switch(teamnumber)
+               switch(teamnum)
                {
                        case NUM_TEAM_1: flag.effects |= EF_RED; break;
                        case NUM_TEAM_2: flag.effects |= EF_BLUE; break;
@@ -1383,7 +1426,7 @@ void havocbot_ctf_calculate_middlepoint()
                // for symmetrical editing of waypoints
                entity f1 = ctf_worldflaglist;
                entity f2 = f1.ctf_worldflagnext;
-               float m = -(f1.origin.y - f2.origin.y) / (f1.origin.x - f2.origin.x);
+               float m = -(f1.origin.y - f2.origin.y) / (max(f1.origin.x - f2.origin.x, FLOAT_EPSILON));
                float q = havocbot_middlepoint.y - m * havocbot_middlepoint.x;
                havocbot_symmetry_axis_m = m;
                havocbot_symmetry_axis_q = q;
@@ -1519,7 +1562,7 @@ void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
                {
                        // adjust rating of our flag carrier depending on his health
                        head = head.tag_entity;
-                       float f = bound(0, (head.health + head.armorvalue) / 100, 2) - 1;
+                       float f = bound(0, (GetResource(head, RES_HEALTH) + GetResource(head, RES_ARMOR)) / 100, 2) - 1;
                        ratingscale += ratingscale * f * 0.1;
                }
                navigation_routerating(this, head, ratingscale, 10000);
@@ -2122,7 +2165,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
        bool b1 = false, b2 = false, b3 = false, b4 = false, b5 = false; // TODO: kill this, we WANT to show the other flags, somehow! (note: also means you don't see if you're FC)
 
        // initially clear items so they can be set as necessary later.
-       STAT(CTF_FLAGSTATUS, player) &= ~(CTF_RED_FLAG_CARRYING         | CTF_RED_FLAG_TAKEN            | CTF_RED_FLAG_LOST
+       STAT(OBJECTIVE_STATUS, player) &= ~(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
@@ -2136,7 +2179,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
                if(flag.team == NUM_TEAM_2 && !b2) { b2 = true; t = CTF_BLUE_FLAG_CARRYING;             t2 = CTF_BLUE_FLAG_TAKEN;               t3 = CTF_BLUE_FLAG_LOST; }
                if(flag.team == NUM_TEAM_3 && !b3) { b3 = true; t = CTF_YELLOW_FLAG_CARRYING;   t2 = CTF_YELLOW_FLAG_TAKEN;             t3 = CTF_YELLOW_FLAG_LOST; }
                if(flag.team == NUM_TEAM_4 && !b4) { b4 = true; t = CTF_PINK_FLAG_CARRYING;             t2 = CTF_PINK_FLAG_TAKEN;               t3 = CTF_PINK_FLAG_LOST; }
-               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(CTF_FLAGSTATUS, player) |= CTF_FLAG_NEUTRAL; }
+               if(flag.team == 0 && !b5)                  { b5 = true; t = CTF_NEUTRAL_FLAG_CARRYING;  t2 = CTF_NEUTRAL_FLAG_TAKEN;    t3 = CTF_NEUTRAL_FLAG_LOST; STAT(OBJECTIVE_STATUS, player) |= CTF_FLAG_NEUTRAL; }
 
                switch(flag.ctf_status)
                {
@@ -2144,14 +2187,14 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
                        case FLAG_CARRY:
                        {
                                if((flag.owner == player) || (flag.pass_sender == player))
-                                       STAT(CTF_FLAGSTATUS, player) |= t; // carrying: player is currently carrying the flag
+                                       STAT(OBJECTIVE_STATUS, player) |= t; // carrying: player is currently carrying the flag
                                else
-                                       STAT(CTF_FLAGSTATUS, player) |= t2; // taken: someone else is carrying the flag
+                                       STAT(OBJECTIVE_STATUS, player) |= t2; // taken: someone else is carrying the flag
                                break;
                        }
                        case FLAG_DROPPED:
                        {
-                               STAT(CTF_FLAGSTATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
+                               STAT(OBJECTIVE_STATUS, player) |= t3; // lost: the flag is dropped somewhere on the map
                                break;
                        }
                }
@@ -2159,17 +2202,17 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
 
        // item for stopping players from capturing the flag too often
        if(player.ctf_captureshielded)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_SHIELDED;
+               STAT(OBJECTIVE_STATUS, player) |= CTF_SHIELDED;
 
        if(ctf_stalemate)
-               STAT(CTF_FLAGSTATUS, player) |= CTF_STALEMATE;
+               STAT(OBJECTIVE_STATUS, player) |= CTF_STALEMATE;
 
        // update the health of the flag carrier waypointsprite
        if(player.wps_flagcarrier)
-               WaypointSprite_UpdateHealth(player.wps_flagcarrier, '1 0 0' * healtharmor_maxdamage(GetResourceAmount(player, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id));
+               WaypointSprite_UpdateHealth(player.wps_flagcarrier, healtharmor_maxdamage(GetResource(player, RES_HEALTH), GetResource(player, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x);
 }
 
-MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in g_damage.qc
+MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force values that are applied to players in damage.qc
 {
        entity frag_attacker = M_ARGV(1, entity);
        entity frag_target = M_ARGV(2, entity);
@@ -2194,8 +2237,8 @@ MUTATOR_HOOKFUNCTION(ctf, Damage_Calculate) // for changing damage and force val
        }
        else if(frag_target.flagcarried && !IS_DEAD(frag_target) && 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(GetResourceAmount(frag_target, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id)))
-               if(time > frag_target.wps_helpme_time + autocvar_g_ctf_flagcarrier_auto_helpme_time)
+               if(autocvar_g_ctf_flagcarrier_auto_helpme_damage > healtharmor_maxdamage(GetResource(frag_target, RES_HEALTH), GetResource(frag_target, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x
+                       && 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);
@@ -2263,15 +2306,7 @@ MUTATOR_HOOKFUNCTION(ctf, ClientConnect)
 
        entity player = M_ARGV(0, entity);
 
-       if(IS_REAL_CLIENT(player))
-       {
-               int m = min(RANKINGS_CNT, autocvar_g_cts_send_rankings_cnt);
-               race_send_rankings_cnt(MSG_ONE);
-               for (int i = 1; i <= m; ++i)
-               {
-                       race_SendRankings(i, 0, 0, MSG_ONE);
-               }
-       }
+       race_SendAll(player, true);
 }
 
 MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
@@ -2281,16 +2316,7 @@ MUTATOR_HOOKFUNCTION(ctf, GetPressedKeys)
 
        entity player = M_ARGV(0, entity);
 
-       if(CS(player).cvar_cl_allow_uidtracking == 1 && CS(player).cvar_cl_allow_uid2name == 1)
-       {
-               if (!player.stored_netname)
-                       player.stored_netname = strzone(uid2name(player.crypto_idfp));
-               if(player.stored_netname != player.netname)
-               {
-                       db_put(ServerProgsDB, strcat("/uid2name/", player.crypto_idfp), player.netname);
-                       strcpy(player.stored_netname, player.netname);
-               }
-       }
+       race_checkAndWriteName(player);
 }
 
 MUTATOR_HOOKFUNCTION(ctf, PortalTeleport)
@@ -2322,7 +2348,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerUseKey)
                                if(head != player && SAME_TEAM(head, player))
                                if(!head.speedrunning && !head.vehicle)
                                {
-                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in g_damage.qc)
+                                       // if it's a player, use the view origin as reference (stolen from RadiusDamage functions in damage.qc)
                                        vector head_center = WarpZone_UnTransformOrigin(head, CENTER_OR_VIEWOFS(head));
                                        vector passer_center = CENTER_OR_VIEWOFS(player);
 
@@ -2506,14 +2532,6 @@ MUTATOR_HOOKFUNCTION(ctf, TeamBalance_CheckAllowedTeams)
        M_ARGV(1, string) = "ctf_team";
 }
 
-MUTATOR_HOOKFUNCTION(ctf, SpectateCopy)
-{
-       entity spectatee = M_ARGV(0, entity);
-       entity client = M_ARGV(1, entity);
-
-       STAT(CTF_FLAGSTATUS, client) = STAT(CTF_FLAGSTATUS, spectatee);
-}
-
 MUTATOR_HOOKFUNCTION(ctf, GetRecords)
 {
        int record_page = M_ARGV(0, int);
@@ -2590,6 +2608,13 @@ MUTATOR_HOOKFUNCTION(ctf, DropSpecialItems)
                ctf_Handle_Throw(frag_target, NULL, DROP_THROW);
 }
 
+MUTATOR_HOOKFUNCTION(ctf, LogDeath_AppendItemCodes)
+{
+       entity player = M_ARGV(0, entity);
+       if(player.flagcarried)
+               M_ARGV(1, string) = strcat(M_ARGV(1, string), "F"); // item codes
+}
+
 
 // ==========
 // Spawnfuncs
@@ -2696,7 +2721,6 @@ spawnfunc(ctf_team)
 {
        if(!g_ctf) { delete(this); return; }
 
-       this.classname = "ctf_team";
        this.team = this.cnt + 1;
 }
 
@@ -2800,6 +2824,8 @@ void ctf_DelayedInit(entity this) // Do this check with a delay so we can wait f
 
 void ctf_Initialize()
 {
+       CTF_FLAG = NEW(Flag);
+       record_type = CTF_RECORD;
        ctf_captimerecord = stof(db_get(ServerProgsDB, strcat(GetMapname(), "/captimerecord/time")));
 
        ctf_captureshield_min_negscore = autocvar_g_ctf_shield_min_negscore;