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, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_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))
{
//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"); } */
set_movetype(flag, MOVETYPE_TOSS);
flag.takedamage = DAMAGE_YES;
flag.angles = '0 0 0';
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ SetResourceAmount(flag, RESOURCE_HEALTH, flag.max_flag_health);
flag.ctf_droptime = time;
flag.ctf_dropper = player;
flag.ctf_status = FLAG_DROPPED;
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
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;
}
{
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;
}
}
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, RESOURCE_HEALTH, flag.max_flag_health); break; // reset health/return timelimit
default: break;
}
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);
this.ctf_flagdamaged_byworld = true;
else
{
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ SetResourceAmount(this, RESOURCE_HEALTH, 0);
ctf_CheckFlagReturn(this, RETURN_NEEDKILL);
}
return;
{
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); }
{
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, RESOURCE_HEALTH, 0);
ctf_CheckFlagReturn(this, RETURN_DROPPED);
return;
}
{
if(this.speedrunning && ctf_captimerecord && (time >= this.ctf_pickuptime + ctf_captimerecord))
{
- SetResourceAmountExplicit(this, RESOURCE_HEALTH, 0);
+ SetResourceAmount(this, RESOURCE_HEALTH, 0);
ctf_CheckFlagReturn(this, RETURN_SPEEDRUN);
CS(this.owner).impulse = CHIMPULSE_SPEEDRUN.impulse; // move the player back to the waypoint they set
{
if(!autocvar_g_ctf_flag_return_damage_delay)
{
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, 0);
+ SetResourceAmount(flag, RESOURCE_HEALTH, 0);
ctf_CheckFlagReturn(flag, RETURN_NEEDKILL);
}
if(!flag.ctf_flagdamaged_byworld) { return; }
set_movetype(flag, ((flag.noalign) ? MOVETYPE_NONE : MOVETYPE_TOSS));
flag.takedamage = DAMAGE_NO;
- SetResourceAmountExplicit(flag, RESOURCE_HEALTH, flag.max_flag_health);
+ SetResourceAmount(flag, RESOURCE_HEALTH, flag.max_flag_health);
flag.solid = SOLID_TRIGGER;
flag.velocity = '0 0 0';
flag.angles = flag.mangle;
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, RESOURCE_HEALTH, flag.max_flag_health);
flag.event_damage = ctf_FlagDamage;
flag.pushable = true;
flag.teleportable = TELEPORT_NORMAL;
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, RESOURCE_HEALTH) + GetResourceAmount(head, RESOURCE_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);
}
}
-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;
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
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)
{
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)
{
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;
{
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);
}
}
- // 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;
{
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);
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);
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);
}
}
}
});
+ // 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);
// 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, RESOURCE_HEALTH), GetResourceAmount(player, RESOURCE_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
}
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, RESOURCE_HEALTH), GetResourceAmount(frag_target, RESOURCE_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);