[Bots] Fix taunting bots positioning (#4754)

* [Bots] Fix taunting bots positioning

- Fixes taunting bots liking to hug their target on certain models or chosen positions.
- Makes bots have a more realistic combat range in comparison to players.
- Removed unnecessary rules and checks for melee distance.

* Update ruletypes.h
This commit is contained in:
nytmyr 2025-03-06 16:07:38 -06:00 committed by GitHub
parent 1d4ba082ad
commit d6a21be25e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 96 additions and 128 deletions

View File

@ -850,17 +850,15 @@ RULE_BOOL(Bots, BotArcheryConsumesAmmo, true, "Set to false to disable Archery A
RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption") RULE_BOOL(Bots, BotThrowingConsumesAmmo, true, "Set to false to disable Throwing Ammo Consumption")
RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).") RULE_INT(Bots, StackSizeMin, 20, "20 Default. -1 to disable and use default max stack size. Minimum stack size to give a bot (Arrows/Throwing).")
RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.") RULE_INT(Bots, HasOrMayGetAggroThreshold, 90, "90 Default. Percent threshold of total hate where bots will stop casting spells that generate hate if they are set to try to not pull aggro via spells.")
RULE_BOOL(Bots, UseFlatNormalMeleeRange, false, "False Default. If true, bots melee distance will be a flat distance set by Bots:NormalMeleeRangeDistance.") RULE_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
RULE_REAL(Bots, NormalMeleeRangeDistance, 0.75, "If UseFlatNormalMeleeRange is enabled, multiplier of the max melee range at which a bot will stand in melee combat. 0.75 Recommended, max melee for all abilities to land.") RULE_REAL(Bots, LowerTauntingMeleeDistanceMultiplier, 0.25, "Closest % of the hit box a taunting melee bot will get to the target. Default 0.25")
RULE_REAL(Bots, PercentMinMeleeDistance, 0.75, "Multiplier of the their melee range - Minimum distance from target a bot will stand while in melee combat before trying to adjust. 0.60 Recommended.") RULE_REAL(Bots, LowerMaxMeleeRangeDistanceMultiplier, 0.80, "Closest % of the hit box a max melee range melee bot will get to the target. Default 0.80")
RULE_REAL(Bots, MaxDistanceForMelee, 20, "Maximum distance bots will stand for melee. Default 20 to allow all special attacks to land.") RULE_REAL(Bots, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
RULE_REAL(Bots, TauntNormalMeleeRangeDistance, 0.50, "Multiplier of the max melee range at which a taunting bot will stand in melee combat. 0.50 Recommended, closer than others .") RULE_REAL(Bots, UpperTauntingMeleeDistanceMultiplier, 0.45, "Furthest % of the hit box a taunting melee bot will get from the target. Default 0.45")
RULE_REAL(Bots, PercentTauntMinMeleeDistance, 0.40, "Multiplier of their melee range - Minimum distance from target a taunting bot will stand while in melee combat before trying to adjust. 0.25 Recommended.") RULE_REAL(Bots, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_REAL(Bots, PercentMaxMeleeRangeDistance, 0.95, "Multiplier of the max melee range at which a bot will stand in melee combat. 0.95 Recommended, max melee while disabling special attacks/taunt.") RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
RULE_REAL(Bots, PercentMinMaxMeleeRangeDistance, 0.75, "Multiplier of the closest max melee range at which a bot will stand in melee combat before trying to adjust. 0.75 Recommended, max melee while disabling special attacks/taunt.")
RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.") RULE_BOOL(Bots, TauntingBotsFollowTopHate, true, "True Default. If true, bots that are taunting will attempt to stick with whoever currently is top hate.")
RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.") RULE_INT(Bots, DistanceTauntingBotsStickMainHate, 10, "If TauntingBotsFollowTopHate is enabled, this is the distance bots will try to stick to whoever currently is Top Hate.")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "True Default. If true, when bots are at max melee distance, special abilities including taunt will be disabled.")
RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.") RULE_INT(Bots, MinJitterTimer, 500, "Minimum ms between bot movement jitter checks.")
RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.") RULE_INT(Bots, MaxJitterTimer, 2500, "Maximum ms between bot movement jitter checks. Set to 0 to disable timer checks.")
RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.") RULE_BOOL(Bots, PreventBotCampOnFD, true, "True Default. If true, players will not be able to camp bots while feign death.")

View File

@ -2256,7 +2256,7 @@ void Bot::AI_Process()
SetAttackingFlag(false); SetAttackingFlag(false);
} }
float tar_distance = DistanceSquared(m_Position, tar->GetPosition()); float tar_distance = DistanceSquaredNoZ(m_Position, tar->GetPosition());
// TARGET VALIDATION // TARGET VALIDATION
if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) { if (!IsValidTarget(bot_owner, leash_owner, lo_distance, leash_distance, tar, tar_distance)) {
@ -2296,7 +2296,6 @@ void Bot::AI_Process()
CombatRangeInput input = { CombatRangeInput input = {
.target = tar, .target = tar,
.target_distance = tar_distance, .target_distance = tar_distance,
.behind_mob = behind_mob,
.stop_melee_level = stop_melee_level, .stop_melee_level = stop_melee_level,
.p_item = p_item, .p_item = p_item,
.s_item = s_item .s_item = s_item
@ -2368,6 +2367,16 @@ void Bot::AI_Process()
return; return;
} }
if (
HasTargetReflection() &&
!IsTaunting() &&
!tar->IsFleeing() &&
!tar->IsFeared() &&
TryEvade(tar)
) {
return;
}
// ENGAGED AT COMBAT RANGE // ENGAGED AT COMBAT RANGE
// We can fight // We can fight
@ -2434,7 +2443,11 @@ void Bot::AI_Process()
ranged_timer.Start(); ranged_timer.Start();
} }
else if (!IsBotRanged() && GetLevel() < stop_melee_level) { else if (!IsBotRanged() && GetLevel() < stop_melee_level) {
if (!GetMaxMeleeRange() || !RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)) { if (
IsTaunting() ||
!GetMaxMeleeRange() ||
!RuleB(Bots, DisableSpecialAbilitiesAtMaxMelee)
) {
DoClassAttacks(tar); DoClassAttacks(tar);
} }
@ -3065,7 +3078,7 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
// For races with a fixed size // For races with a fixed size
if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) { if (GetRace() == Race::LavaDragon || GetRace() == Race::Wurm || GetRace() == Race::GhostDragon) {
// size_mod = 60.0f; size_mod = 60.0f;
} }
else if (size_mod < 6.0f) { else if (size_mod < 6.0f) {
size_mod = 8.0f; size_mod = 8.0f;
@ -3110,91 +3123,39 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
size_mod = (size_mod / 7.0f); size_mod = (size_mod / 7.0f);
} }
o.melee_distance_max = size_mod; o.melee_distance_max = sqrt(size_mod);
if (!RuleB(Bots, UseFlatNormalMeleeRange)) { bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon();
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield();
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level;
bool is_two_hander = input.p_item && input.p_item->GetItem()->IsType2HWeapon(); if (IsTaunting()) { // Taunting bots
bool is_shield = input.s_item && input.s_item->GetItem()->IsTypeShield(); o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier);
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage(); o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier);
switch (GetClass()) {
case Class::Warrior:
case Class::Paladin:
case Class::ShadowKnight:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.45f
: is_shield ? o.melee_distance_max * 0.35f
: o.melee_distance_max * 0.40f
);
break;
case Class::Necromancer:
case Class::Wizard:
case Class::Magician:
case Class::Enchanter:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.95f
: o.melee_distance_max * 0.75f
);
break;
case Class::Rogue:
o.melee_distance = (
input.behind_mob && is_backstab_weapon
? o.melee_distance_max * 0.35f
: o.melee_distance_max * 0.50f
);
break;
default:
o.melee_distance = (
is_two_hander ? o.melee_distance_max * 0.70f
: o.melee_distance_max * 0.50f
);
break;
}
o.melee_distance = sqrt(o.melee_distance);
o.melee_distance_max = sqrt(o.melee_distance_max);
} }
else { else if (IsBotRanged()) { // Archers/Throwers
o.melee_distance_max = sqrt(o.melee_distance_max); float min_distance = RuleI(Combat, MinRangedAttackDist);
o.melee_distance = o.melee_distance_max * RuleR(Bots, NormalMeleeRangeDistance); float max_distance = GetBotRangedValue();
float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
} }
else if (is_stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged();
if (o.melee_distance > RuleR(Bots, MaxDistanceForMelee)) { o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2));
o.melee_distance = RuleR(Bots, MaxDistanceForMelee); o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
} }
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentMinMeleeDistance); o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier);
if (IsTaunting()) {
o.melee_distance_min = o.melee_distance * RuleR(Bots, PercentTauntMinMeleeDistance);
o.melee_distance = o.melee_distance * RuleR(Bots, TauntNormalMeleeRangeDistance);
} }
else { // Regular melee
bool is_stop_melee_level = GetLevel() >= input.stop_melee_level; o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier);
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier);
if (!IsTaunting() && !IsBotRanged() && !is_stop_melee_level && GetMaxMeleeRange()) {
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, PercentMinMaxMeleeRangeDistance);
o.melee_distance = o.melee_distance_max * RuleR(Bots, PercentMaxMeleeRangeDistance);
}
if (is_stop_melee_level && !IsBotRanged()) {
float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance, (desired_range / 2));
o.melee_distance = std::max((o.melee_distance + 1), desired_range);
}
if (IsBotRanged()) {
float min_distance = RuleI(Combat, MinRangedAttackDist);
float max_distance = GetBotRangedValue();
float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged if set to ranged even if items/ammo aren't correct
o.melee_distance_min = std::max(min_distance, (desired_range / 2));
o.melee_distance = std::min(max_distance, desired_range);
} }
o.at_combat_range = (input.target_distance <= o.melee_distance); o.at_combat_range = (input.target_distance <= o.melee_distance);
@ -11846,21 +11807,19 @@ void Bot::DoCombatPositioning(
bool front_mob bool front_mob
) { ) {
if (HasTargetReflection()) { if (HasTargetReflection()) {
if (!IsTaunting() && !tar->IsFeared() && !tar->IsStunned()) { if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range
if (TryEvade(tar)) {
return;
}
}
else if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay
if (tar_distance <= melee_distance_max) { if (tar_distance <= melee_distance_max) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), GetBehindMob(), false)) { if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), false)) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
} }
} }
} }
else if (tar_distance < melee_distance_min || (!front_mob && IsTaunting())) { // Back up any bots that are too close else if (
tar_distance < melee_distance_min ||
(!front_mob && IsTaunting())
) { // Back up any bots that are too close or if they're taunting and not in front of the mob
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) { if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
@ -11870,46 +11829,58 @@ void Bot::DoCombatPositioning(
} }
else { else {
if (!tar->IsFeared()) { if (!tar->IsFeared()) {
if (IsTaunting()) { // Taunting adjustments if (
Mob* mob_tar = tar->GetTarget(); tar_distance < melee_distance_min ||
(GetBehindMob() && !behind_mob) ||
if (!mob_tar) { (IsTaunting() && !front_mob) ||
DoFaceCheckNoJitter(tar); !HasRequiredLoSForPositioning(tar)
) { // Regular adjustment
return;
}
if (RuleB(Bots, TauntingBotsFollowTopHate)) { // If enabled, taunting bots will stick to top hate
if (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate)) {
Goal = mob_tar->GetPosition();
RunToGoalWithJitter(Goal);
return;
}
}
else { // Otherwise, stick to any other bots that are taunting
if (mob_tar->IsBot() && mob_tar->CastToBot()->IsTaunting() && (Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))) {
Goal = mob_tar->GetPosition();
RunToGoalWithJitter(Goal);
return;
}
}
}
else if (tar_distance < melee_distance_min || (GetBehindMob() && !behind_mob) || (IsTaunting() && !front_mob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) { if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
} }
} }
else if (tar->IsEnraged() && !IsTaunting() && !stop_melee_level && !behind_mob) { // Move non-taunting melee bots behind target during enrage else if (
tar->IsEnraged() &&
!IsTaunting() &&
!stop_melee_level &&
!behind_mob
) { // Move non-taunting melee bots behind target during enrage
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) { if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) {
RunToGoalWithJitter(Goal); RunToGoalWithJitter(Goal);
return; return;
} }
} }
if (IsTaunting()) { // Taunting adjustments
Mob* mob_tar = tar->GetTarget();
if (mob_tar) {
if (
RuleB(Bots, TauntingBotsFollowTopHate) &&
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
) { // If enabled, taunting bots will stick to top hate
Goal = mob_tar->GetPosition();
RunToGoalWithJitter(Goal);
return;
}
else { // Otherwise, stick to any other bots that are taunting
if (
mob_tar->IsBot() &&
mob_tar->CastToBot()->IsTaunting() &&
(Distance(m_Position, mob_tar->GetPosition()) > RuleI(Bots, DistanceTauntingBotsStickMainHate))
) {
Goal = mob_tar->GetPosition();
RunToGoalWithJitter(Goal);
return;
}
}
}
}
} }
} }

View File

@ -235,7 +235,6 @@ static std::map<uint16, std::string> botSubType_names = {
struct CombatRangeInput { struct CombatRangeInput {
Mob* target; Mob* target;
float target_distance; float target_distance;
bool behind_mob;
uint8 stop_melee_level; uint8 stop_melee_level;
const EQ::ItemInstance* p_item; const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item; const EQ::ItemInstance* s_item;