]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blobdiff - qcsrc/common/gamemodes/gamemode/ctf/sv_ctf.qc
Reduce name space of resource constants and variables (RESOURCE_* --> RES_*, resour...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / ctf / sv_ctf.qc
index d77b7208759c699beb5f5c1fe820798a05b4048f..84fcadda3a929e07e271f5426f9c6341343e6da1 100644 (file)
@@ -144,8 +144,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(GetResourceAmount(player, RES_HEALTH), GetResourceAmount(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))
@@ -186,7 +186,7 @@ void ctf_CalculatePassVelocity(entity flag, vector to, vector from, float turnra
                {
                        //print("normal arc line failed, trying to find new pos...");
                        WarpZone_TraceLine(to, targpos, MOVE_NOMONSTERS, flag);
-                       targpos = (trace_endpos + FLAG_PASS_ARC_OFFSET);
+                       targpos = (trace_endpos + eZ * FLAG_PASS_ARC_OFFSET_Z);
                        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"); } */
@@ -341,7 +341,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);
+       SetResourceAmount(flag, RES_HEALTH, flag.max_flag_health);
        flag.ctf_droptime = time;
        flag.ctf_dropper = player;
        flag.ctf_status = FLAG_DROPPED;
@@ -364,7 +364,7 @@ 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_UpdateHealth(flag.wps_flagdropped, GetResourceAmount(flag, RES_HEALTH));
        }
 
        player.throw_antispam = time + autocvar_g_ctf_pass_wait;
@@ -447,7 +447,6 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
        flag.solid = SOLID_TRIGGER;
        flag.ctf_dropper = player;
        flag.ctf_droptime = time;
-       navigation_dynamicgoal_set(flag);
 
        flag.flags = FL_ITEM | FL_NOTARGET; // clear FL_ONGROUND for MOVETYPE_TOSS
 
@@ -486,6 +485,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
                        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, player.velocity, flag_velocity, false);
                        ctf_Handle_Drop(flag, player, droptype);
+                       navigation_dynamicgoal_set(flag, player);
                        break;
                }
 
@@ -500,6 +500,7 @@ void ctf_Handle_Throw(entity player, entity receiver, int droptype)
                {
                        flag.velocity = W_CalculateProjectileVelocity(player, 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);
+                       navigation_dynamicgoal_set(flag, player);
                        break;
                }
        }
@@ -678,7 +679,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: SetResourceAmount(flag, RES_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
                default: break;
        }
 
@@ -701,10 +702,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);
@@ -760,9 +763,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, GetResourceAmount(flag, RES_HEALTH)); }
 
-               if((GetResourceAmount(flag, RESOURCE_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
+               if((GetResourceAmount(flag, RES_HEALTH) <= 0) || (time >= flag.ctf_droptime + autocvar_g_ctf_flag_return_time))
                {
                        switch(returntype)
                        {
@@ -873,7 +876,7 @@ void ctf_FlagDamage(entity this, entity inflictor, entity attacker, float damage
                        this.ctf_flagdamaged_byworld = true;
                else
                {
-                       SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                       SetResourceAmount(this, RES_HEALTH, 0);
                        ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
                }
                return;
@@ -881,7 +884,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;
        }
@@ -933,7 +936,7 @@ void ctf_FlagThink(entity this)
                                {
                                        this.velocity = this.velocity * 0.5;
 
-                                       if(pointcontents(midpoint + FLAG_FLOAT_OFFSET) == CONTENT_WATER)
+                                       if (pointcontents(midpoint + eZ * FLAG_FLOAT_OFFSET_Z) == CONTENT_WATER)
                                                { this.velocity_z = autocvar_g_ctf_flag_dropped_floatinwater; }
                                        else
                                                { set_movetype(this, MOVETYPE_FLY); }
@@ -944,20 +947,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);
+                                       SetResourceAmount(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_flag_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_flag_health / autocvar_g_ctf_flag_return_time) * FLAG_THINKRATE));
                                ctf_CheckFlagReturn(this, RETURN_TIMEOUT);
                                return;
                        }
@@ -968,7 +971,7 @@ void ctf_FlagThink(entity this)
                {
                        if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
                        {
-                               SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+                               SetResourceAmount(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
@@ -1037,7 +1040,7 @@ METHOD(Flag, giveTo, bool(Flag this, entity flag, entity toucher))
        {
                if(!autocvar_g_ctf_flag_return_damage_delay)
                {
-                       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+                       SetResourceAmount(flag, RES_HEALTH, 0);
                        ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
                }
                if(!flag.ctf_flagdamaged_byworld) { return; }
@@ -1161,7 +1164,7 @@ void ctf_RespawnFlag(entity flag)
 
        set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
        flag.takedamage = DAMAGE_NO;
-       SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+       SetResourceAmount(flag, RES_HEALTH, flag.max_flag_health);
        flag.solid = SOLID_TRIGGER;
        flag.velocity = '0 0 0';
        flag.angles = flag.mangle;
@@ -1241,14 +1244,14 @@ void ctf_FlagSetup(int teamnumber, entity flag) // called when spawning a flag e
        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.target = "###item###"; // for finding the nearest item using findnearest
        flag.flags = FL_ITEM | FL_NOTARGET;
        IL_PUSH(g_items, flag);
        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);
+       SetResourceAmount(flag, RES_HEALTH, flag.max_flag_health);
        flag.event_damage = ctf_FlagDamage;
        flag.pushable = true;
        flag.teleportable = TELEPORT_NORMAL;
@@ -1513,17 +1516,28 @@ void havocbot_goalrating_ctf_enemyflag(entity this, float ratingscale)
                head = head.ctf_worldflagnext;
        }
        if (head)
+       {
+               if (head.ctf_status == FLAG_CARRY)
+               {
+                       // adjust rating of our flag carrier depending on his health
+                       head = head.tag_entity;
+                       float f = bound(0, (GetResourceAmount(head, RES_HEALTH) + GetResourceAmount(head, RES_ARMOR)) / 100, 2) - 1;
+                       ratingscale += ratingscale * f * 0.1;
+               }
                navigation_routerating(this, head, ratingscale, 10000);
+       }
 }
 
 void havocbot_goalrating_ctf_enemybase(entity this, float ratingscale)
 {
+       // disabled because we always spawn waypoints for flags with waypoint_spawnforitem_force
+       /*
        if (!bot_waypoints_for_items)
        {
                havocbot_goalrating_ctf_enemyflag(this, ratingscale);
                return;
        }
-
+       */
        entity head;
 
        head = havocbot_ctf_find_enemy_flag(this);
@@ -1570,28 +1584,10 @@ void havocbot_goalrating_ctf_droppedflags(entity this, float ratingscale, vector
        }
 }
 
-void havocbot_goalrating_ctf_carrieritems(entity this, float ratingscale, vector org, float sradius)
-{
-       IL_EACH(g_items, it.bot_pickup,
-       {
-               // gather health and armor only
-               if (it.solid)
-               if (GetResourceAmount(it, RESOURCE_HEALTH) || GetResourceAmount(it, RESOURCE_ARMOR))
-               if (vdist(it.origin - org, <, sradius))
-               {
-                       // get the value of the item
-                       float t = it.bot_pickupevalfunc(this, it) * 0.0001;
-                       if (t > 0)
-                               navigation_routerating(this, it, t * ratingscale, 500);
-               }
-       });
-}
-
 void havocbot_ctf_reset_role(entity this)
 {
        float cdefense, cmiddle, coffense;
        entity mf, ef;
-       float c;
 
        if(IS_DEAD(this))
                return;
@@ -1620,15 +1616,30 @@ void havocbot_ctf_reset_role(entity this)
                return;
        }
 
-       // if there is only me on the team switch to offense
-       c = 0;
-       FOREACH_CLIENT(IS_PLAYER(it) && SAME_TEAM(it, this), { ++c; });
+       // if there is no one else on the team switch to offense
+       int count = 0;
+       // don't check if this bot is a player since it isn't true when the bot is added to the server
+       FOREACH_CLIENT(it != this && IS_PLAYER(it) && SAME_TEAM(it, this), { ++count; });
 
-       if(c==1)
+       if (count == 0)
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
                return;
        }
+       else if (time < CS(this).jointime + 1)
+       {
+               // if bots spawn all at once set good default roles
+               if (count == 1)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
+                       return;
+               }
+               else if (count == 2)
+               {
+                       havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+                       return;
+               }
+       }
 
        // Evaluate best position to take
        // Count mates on middle position
@@ -1646,6 +1657,25 @@ void havocbot_ctf_reset_role(entity this)
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_OFFENSE);
        else
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
+
+       // if bots spawn all at once assign them a more appropriated role after a while
+       if (time < CS(this).jointime + 1 && count > 2)
+               this.havocbot_role_timeout = time + 10 + random() * 10;
+}
+
+bool havocbot_ctf_is_basewaypoint(entity item)
+{
+       if (item.classname != "waypoint")
+               return false;
+
+       entity head = ctf_worldflaglist;
+       while (head)
+       {
+               if (item == head.bot_basewaypoint)
+                       return true;
+               head = head.ctf_worldflagnext;
+       }
+       return false;
 }
 
 void havocbot_role_ctf_carrier(entity this)
@@ -1666,30 +1696,30 @@ void havocbot_role_ctf_carrier(entity this)
        {
                navigation_goalrating_start(this);
 
+               // role: carrier
+               entity mf = havocbot_ctf_find_flag(this);
+               vector base_org = mf.dropped_origin;
+               float base_rating = (mf.ctf_status == FLAG_BASE) ? 10000 : (vdist(this.origin - base_org, >, 100) ? 2000 : 1000);
                if(ctf_oneflag)
-                       havocbot_goalrating_ctf_enemybase(this, 50000);
+                       havocbot_goalrating_ctf_enemybase(this, base_rating);
                else
-                       havocbot_goalrating_ctf_ourbase(this, 50000);
+                       havocbot_goalrating_ctf_ourbase(this, base_rating);
+
+               // start collecting items very close to the bot but only inside of own base radius
+               if (vdist(this.origin - base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 15000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
 
-               if(GetResourceAmount(this, RESOURCE_HEALTH) < 100)
-                       havocbot_goalrating_ctf_carrieritems(this, 1000, this.origin, 1000);
+               havocbot_goalrating_items(this, 10000, base_org, havocbot_middlepoint_radius * 0.5);
 
                navigation_goalrating_end(this);
 
                navigation_goalrating_timeout_set(this);
 
-               entity head = ctf_worldflaglist;
-               while (head)
-               {
-                       if (this.goalentity == head.bot_basewaypoint)
-                       {
-                               this.goalentity_lock_timeout = time + 5;
-                               break;
-                       }
-                       head = head.ctf_worldflagnext;
-               }
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + ((this.bot_aimtarg) ? 2 : 3);
 
-               if (this.goalentity)
+               if (goal)
                        this.havocbot_cantfindflag = time + 10;
                else if (time > this.havocbot_cantfindflag)
                {
@@ -1725,11 +1755,15 @@ void havocbot_role_ctf_escort(entity this)
                this.havocbot_role_timeout = 0;
                return;
        }
+       if (ef.ctf_status == FLAG_DROPPED)
+       {
+               navigation_goalrating_timeout_expire(this, 1);
+               return;
+       }
 
        // If the flag carrier reached the base switch to defense
        mf = havocbot_ctf_find_flag(this);
-       if(mf.ctf_status!=FLAG_BASE)
-       if(vdist(ef.origin - mf.dropped_origin, <, 300))
+       if (mf.ctf_status != FLAG_BASE && vdist(ef.origin - mf.dropped_origin, <, 900))
        {
                havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_DEFENSE);
                return;
@@ -1754,9 +1788,10 @@ void havocbot_role_ctf_escort(entity this)
        {
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_enemyflag(this, 30000);
-               havocbot_goalrating_ctf_ourstolenflag(this, 40000);
-               havocbot_goalrating_items(this, 10000, this.origin, 10000);
+               // role: escort
+               havocbot_goalrating_ctf_enemyflag(this, 10000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 6000);
+               havocbot_goalrating_items(this, 21000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
@@ -1816,13 +1851,6 @@ void havocbot_role_ctf_offense(entity this)
                }
        }
 
-       // About to fail, switch to middlefield
-       if(GetResourceAmount(this, RESOURCE_HEALTH) < 50)
-       {
-               havocbot_role_ctf_setrole(this, HAVOCBOT_CTF_ROLE_MIDDLE);
-               return;
-       }
-
        // Set the role timeout if necessary
        if (!this.havocbot_role_timeout)
                this.havocbot_role_timeout = time + 120;
@@ -1837,10 +1865,10 @@ void havocbot_role_ctf_offense(entity this)
        {
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_enemybase(this, 20000);
-               havocbot_goalrating_items(this, 5000, this.origin, 1000);
-               havocbot_goalrating_items(this, 1000, this.origin, 10000);
+               // role: offense
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 10000);
+               havocbot_goalrating_items(this, 22000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
@@ -1886,15 +1914,20 @@ void havocbot_role_ctf_retriever(entity this)
 
        if (navigation_goalrating_timeout(this))
        {
-               float rt_radius;
-               rt_radius = 10000;
+               const float RT_RADIUS = 10000;
 
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 40000, this.origin, rt_radius);
-               havocbot_goalrating_ctf_enemybase(this, 30000);
-               havocbot_goalrating_items(this, 500, this.origin, rt_radius);
+               // role: retriever
+               havocbot_goalrating_ctf_ourstolenflag(this, 10000);
+               havocbot_goalrating_ctf_droppedflags(this, 12000, this.origin, RT_RADIUS);
+               havocbot_goalrating_ctf_enemybase(this, 8000);
+               entity ef = havocbot_ctf_find_enemy_flag(this);
+               vector enemy_base_org = ef.dropped_origin;
+               // start collecting items very close to the bot but only inside of enemy base radius
+               if (vdist(this.origin - enemy_base_org, <, havocbot_middlepoint_radius))
+                       havocbot_goalrating_items(this, 27000, this.origin, min(500, havocbot_middlepoint_radius * 0.5));
+               havocbot_goalrating_items(this, 18000, this.origin, havocbot_middlepoint_radius);
 
                navigation_goalrating_end(this);
 
@@ -1943,15 +1976,20 @@ void havocbot_role_ctf_middle(entity this)
 
                navigation_goalrating_start(this);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 50000);
-               havocbot_goalrating_ctf_droppedflags(this, 30000, this.origin, 10000);
-               havocbot_goalrating_enemyplayers(this, 10000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 5000, org, havocbot_middlepoint_radius * 0.5);
-               havocbot_goalrating_items(this, 2500, this.origin, 10000);
-               havocbot_goalrating_ctf_enemybase(this, 2500);
+               // role: middle
+               havocbot_goalrating_ctf_ourstolenflag(this, 8000);
+               havocbot_goalrating_ctf_droppedflags(this, 9000, this.origin, 10000);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius * 0.5);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
+               havocbot_goalrating_ctf_enemybase(this, 3000);
 
                navigation_goalrating_end(this);
 
+               entity goal = this.goalentity;
+               if (havocbot_ctf_is_basewaypoint(goal) && vdist(goal.origin - this.origin, <, 100))
+                       this.goalentity_lock_timeout = time + 2;
+
                navigation_goalrating_timeout_set(this);
        }
 }
@@ -2006,17 +2044,18 @@ void havocbot_role_ctf_defense(entity this)
                        }
                });
 
+               // role: defense
                if(closestplayer)
                if(DIFF_TEAM(closestplayer, this))
                if(vdist(org - this.origin, >, 1000))
                if(checkpvs(this.origin,closestplayer)||random()<0.5)
-                       havocbot_goalrating_ctf_ourbase(this, 30000);
+                       havocbot_goalrating_ctf_ourbase(this, 10000);
 
-               havocbot_goalrating_ctf_ourstolenflag(this, 20000);
-               havocbot_goalrating_ctf_droppedflags(this, 20000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_enemyplayers(this, 15000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 10000, org, havocbot_middlepoint_radius);
-               havocbot_goalrating_items(this, 5000, this.origin, 10000);
+               havocbot_goalrating_ctf_ourstolenflag(this, 5000);
+               havocbot_goalrating_ctf_droppedflags(this, 6000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_enemyplayers(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 25000, org, havocbot_middlepoint_radius);
+               havocbot_goalrating_items(this, 18000, this.origin, 10000);
 
                navigation_goalrating_end(this);
 
@@ -2129,7 +2168,7 @@ MUTATOR_HOOKFUNCTION(ctf, PlayerPreThink)
 
        // 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(GetResourceAmount(player, RES_HEALTH), GetResourceAmount(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
@@ -2157,8 +2196,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(GetResourceAmount(frag_target, RES_HEALTH), GetResourceAmount(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);