[Bots] Positioning rewrite (#4856)

* [Bots] Positioning rewrite

- Fixes positioning issues where bots could run back and forth or stay at the target's feet.
- Adds checks to ensure bots don't spam positioning updates and complete their initial positioning.
- Cleans up logic and makes it easier to read.

* Cleanup
This commit is contained in:
nytmyr 2025-04-09 22:39:59 -05:00 committed by GitHub
parent 08bb9de437
commit ff71cfbd5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 193 additions and 243 deletions

View File

@ -858,12 +858,6 @@ 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_REAL(Bots, LowerMeleeDistanceMultiplier, 0.35, "Closest % of the hit box a melee bot will get to the target. Default 0.35")
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, 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, UpperMeleeDistanceMultiplier, 0.55, "Furthest % of the hit box a melee bot will get from the target. Default 0.55")
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, UpperMaxMeleeRangeDistanceMultiplier, 0.95, "Furthest % of the hit box a max melee range melee bot will get from the target. Default 0.95")
RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.") RULE_BOOL(Bots, DisableSpecialAbilitiesAtMaxMelee, true, "If true, when bots are at max melee distance, special abilities including taunt will be disabled. Default True.")
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.")

View File

@ -245,6 +245,8 @@ Bot::Bot(
EquipBot(); EquipBot();
m_combat_jitter_timer.Start();
if (GetClass() == Class::Rogue) { if (GetClass() == Class::Rogue) {
m_rogue_evade_timer.Start(); m_rogue_evade_timer.Start();
} }
@ -2186,8 +2188,7 @@ void Bot::AI_Process()
} }
if (HOLDING || (raid && r_group == RAID_GROUPLESS)) { if (HOLDING || (raid && r_group == RAID_GROUPLESS)) {
glm::vec3 Goal(0, 0, 0); TryNonCombatMovementChecks(bot_owner, follow_mob);
TryNonCombatMovementChecks(bot_owner, follow_mob, Goal);
return; return;
} }
@ -2217,8 +2218,6 @@ void Bot::AI_Process()
} }
//ALT COMBAT (ACQUIRE HATE) //ALT COMBAT (ACQUIRE HATE)
glm::vec3 Goal(0, 0, 0);
// We have aggro to choose from // We have aggro to choose from
if (IsEngaged()) { if (IsEngaged()) {
if (rest_timer.Enabled()) { if (rest_timer.Enabled()) {
@ -2293,7 +2292,7 @@ void Bot::AI_Process()
const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary); const EQ::ItemInstance* p_item = GetBotItem(EQ::invslot::slotPrimary);
const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary); const EQ::ItemInstance* s_item = GetBotItem(EQ::invslot::slotSecondary);
CombatRangeInput input = { CombatRangeInput i = {
.target = tar, .target = tar,
.target_distance = tar_distance, .target_distance = tar_distance,
.stop_melee_level = stop_melee_level, .stop_melee_level = stop_melee_level,
@ -2301,7 +2300,7 @@ void Bot::AI_Process()
.s_item = s_item .s_item = s_item
}; };
CombatRangeOutput o = EvaluateCombatRange(input); CombatRangeOutput o = EvaluateCombatRange(i);
// Combat range variables // Combat range variables
bool at_combat_range = o.at_combat_range; bool at_combat_range = o.at_combat_range;
@ -2362,7 +2361,7 @@ void Bot::AI_Process()
} }
} }
TryPursueTarget(leash_distance, Goal); TryPursueTarget(leash_distance);
return; return;
} }
@ -2385,52 +2384,30 @@ void Bot::AI_Process()
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT); (bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
if (!other_bot_pulling && at_combat_range) { if (!other_bot_pulling && at_combat_range) {
bool jitter_cooldown = false; CombatPositioningInput cpi {
.tar = tar,
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) { .stop_melee_level = stop_melee_level,
jitter_cooldown = true; .tar_distance = tar_distance,
} .melee_distance_min = melee_distance_min,
.melee_distance = melee_distance,
if ( .melee_distance_max = melee_distance_max,
IsMoving() || .behind_mob = behind_mob,
GetCombatJitterFlag() || .front_mob = front_mob
GetCombatOutOfRangeJitterFlag() };
) {
if (
!GetCombatJitterFlag() ||
!IsMoving() ||
GetCombatOutOfRangeJitterFlag()
) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
}
if (DoCombatPositioning(cpi) && IsMoving()) {
return; return;
} }
if (
!jitter_cooldown &&
AI_movement_timer->Check() &&
(!spellend_timer.Enabled() || GetClass() == Class::Bard)
) {
DoCombatPositioning(tar, Goal, stop_melee_level, tar_distance, melee_distance_min, melee_distance, melee_distance_max, behind_mob, front_mob);
return;
}
else {
if (!IsSitting() && !IsFacingMob(tar)) { if (!IsSitting() && !IsFacingMob(tar)) {
FaceTarget(tar); FaceTarget(tar);
return; return;
} }
}
if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) { if (!IsBotNonSpellFighter() && AI_HasSpells() && AI_EngagedCastCheck()) {
return; return;
} }
if (IsMoving()) {
StopMoving(CalculateHeadingToTarget(tar->GetX(), tar->GetY()));
return;
}
if ( if (
!tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) && !tar->GetSpecialAbility(SpecialAbility::RangedAttackImmunity) &&
IsBotRanged() && IsBotRanged() &&
@ -2479,7 +2456,7 @@ void Bot::AI_Process()
// ENGAGED NOT AT COMBAT RANGE // ENGAGED NOT AT COMBAT RANGE
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) { else if (!other_bot_pulling && !TryPursueTarget(leash_distance)) {
return; return;
} }
@ -2492,7 +2469,7 @@ void Bot::AI_Process()
TryMeditate(); TryMeditate();
} }
else { // Out-of-combat behavior else { // Out-of-combat behavior
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance); DoOutOfCombatChecks(bot_owner, follow_mob, leash_distance, fm_distance);
} }
} }
@ -2509,8 +2486,10 @@ bool Bot::TryBardMovementCasts() {// Basically, bard bots get a chance to cast i
return false; return false;
} }
bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal) {// Non-engaged movement checks bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob) {// Non-engaged movement checks
if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) { if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == Class::Bard)) {
glm::vec3 Goal(0, 0, 0);
if (GUARDING) { if (GUARDING) {
Goal = GetGuardPoint(); Goal = GetGuardPoint();
} }
@ -2564,7 +2543,7 @@ bool Bot::TryIdleChecks(float fm_distance) {
return false; return false;
} }
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) { void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance) {
SetAttackFlag(false); SetAttackFlag(false);
SetCombatRoundForAlerts(false); SetCombatRoundForAlerts(false);
SetAttackingFlag(false); SetAttackingFlag(false);
@ -2597,7 +2576,7 @@ void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goa
} }
// Ok to idle // Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) { if (TryNonCombatMovementChecks(bot_owner, follow_mob)) {
return; return;
} }
@ -2808,15 +2787,14 @@ bool Bot::TryMeditate() {
} }
// This code actually gets processed when we are too far away from target and have not engaged yet // This code actually gets processed when we are too far away from target and have not engaged yet
bool Bot::TryPursueTarget(float leash_distance, glm::vec3& Goal) { bool Bot::TryPursueTarget(float leash_distance) {
if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) { if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == Class::Bard)) {
if (GetTarget() && !IsRooted()) { if (GetTarget() && !IsRooted()) {
LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName()); LogAIDetail("Pursuing [{}] while engaged", GetTarget()->GetCleanName());
Goal = GetTarget()->GetPosition(); glm::vec3 Goal = GetTarget()->GetPosition();
if (DistanceSquared(m_Position, Goal) <= leash_distance) { if (DistanceSquared(m_Position, Goal) <= leash_distance) {
RunTo(Goal.x, Goal.y, Goal.z); RunTo(Goal.x, Goal.y, Goal.z);
SetCombatOutOfRangeJitter();
} else { } else {
WipeHateList(); WipeHateList();
SetTarget(nullptr); SetTarget(nullptr);
@ -3130,8 +3108,8 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage(); bool is_backstab_weapon = input.p_item && input.p_item->GetItemBackstabDamage();
if (IsTaunting()) { // Taunting bots if (IsTaunting()) { // Taunting bots
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerTauntingMeleeDistanceMultiplier); o.melee_distance_min = o.melee_distance_max * 0.25f;
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperTauntingMeleeDistanceMultiplier); o.melee_distance = o.melee_distance_max * 0.45f;
} }
else if (IsBotRanged()) { // Archers/Throwers else if (IsBotRanged()) { // Archers/Throwers
float min_distance = RuleI(Combat, MinRangedAttackDist); float min_distance = RuleI(Combat, MinRangedAttackDist);
@ -3139,22 +3117,22 @@ CombatRangeOutput Bot::EvaluateCombatRange(const CombatRangeInput& input) {
float desired_range = GetBotDistanceRanged(); float desired_range = GetBotDistanceRanged();
max_distance = (max_distance == 0 ? desired_range : max_distance); // stay ranged even if items/ammo aren't correct 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_min = std::max(min_distance, (desired_range * 0.75f));
o.melee_distance = std::min(max_distance, desired_range); o.melee_distance = std::min(max_distance, desired_range);
} }
else if (input.stop_melee_level) { // Casters else if (input.stop_melee_level) { // Casters
float desired_range = GetBotDistanceRanged(); float desired_range = GetBotDistanceRanged();
o.melee_distance_min = std::max(o.melee_distance_max, (desired_range / 2)); o.melee_distance_min = std::max(o.melee_distance_max, (desired_range * 0.75f));
o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range); o.melee_distance = std::max((o.melee_distance_max * 1.25f), desired_range);
} }
else if (GetMaxMeleeRange()) { // Melee bots set to max melee range else if (GetMaxMeleeRange()) { // Melee bots set to max melee range
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMaxMeleeRangeDistanceMultiplier); o.melee_distance_min = o.melee_distance_max * 0.80f;
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMaxMeleeRangeDistanceMultiplier); o.melee_distance = o.melee_distance_max * 0.95f;
} }
else { // Regular melee else { // Regular melee
o.melee_distance_min = o.melee_distance_max * RuleR(Bots, LowerMeleeDistanceMultiplier); o.melee_distance_min = o.melee_distance_max * 0.30f;
o.melee_distance = o.melee_distance_max * RuleR(Bots, UpperMeleeDistanceMultiplier); o.melee_distance = o.melee_distance_max * 0.65f;
} }
o.at_combat_range = (input.target_distance <= o.melee_distance); o.at_combat_range = (input.target_distance <= o.melee_distance);
@ -11966,193 +11944,171 @@ void Bot::SetCastedSpellType(uint16 spell_type) {
_castedSpellType = spell_type; _castedSpellType = spell_type;
} }
void Bot::DoFaceCheckWithJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
SetCombatJitter();
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::DoFaceCheckNoJitter(Mob* tar) {
if (!tar) {
return;
}
if (IsMoving()) {
return;
}
if (!IsFacingMob(tar)) {
FaceTarget(tar);
return;
}
return;
}
void Bot::RunToGoalWithJitter(glm::vec3 Goal) { void Bot::RunToGoalWithJitter(glm::vec3 Goal) {
RunTo(Goal.x, Goal.y, Goal.z); RunTo(Goal.x, Goal.y, Goal.z);
SetCombatJitter(); SetCombatJitter();
} }
void Bot::SetCombatOutOfRangeJitter() {
SetCombatOutOfRangeJitterFlag();
if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
}
}
void Bot::SetCombatJitter() { void Bot::SetCombatJitter() {
SetCombatJitterFlag();
if (RuleI(Bots, MaxJitterTimer) > 0) { if (RuleI(Bots, MaxJitterTimer) > 0) {
m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true); m_combat_jitter_timer.Start(zone->random.Int(RuleI(Bots, MinJitterTimer), RuleI(Bots, MaxJitterTimer)), true);
} }
} }
void Bot::DoCombatPositioning( bool Bot::DoCombatPositioning(const CombatPositioningInput& input)
Mob* tar, {
glm::vec3 Goal, bool adjustment_needed = false;
bool stop_melee_level, bool is_too_close = input.tar_distance < input.melee_distance_min;
float tar_distance, bool los_adjust = !HasRequiredLoSForPositioning(input.tar);
float melee_distance_min, bool behind_mob_set = !input.stop_melee_level &&
float melee_distance, !IsBotRanged() &&
float melee_distance_max, GetBehindMob(); // Don't want casters or ranged to find positions behind the target.
bool behind_mob, bool adjustment_allowed = !IsMoving() &&
bool front_mob m_combat_jitter_timer.Check() &&
) { (!spellend_timer.Enabled() || GetClass() == Class::Bard);
if (!tar->IsFeared()) {
bool is_too_close = tar_distance < melee_distance_min;
bool los_adjust = !HasRequiredLoSForPositioning(tar);
if (tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
bool rooted_adjust = tar_distance <= melee_distance_max && HasTargetReflection();
if (rooted_adjust) { if (!IsMoving() && !IsSitting() && !IsFacingMob(input.tar)) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 1.25f), GetBehindMob(), !GetBehindMob())) { FaceTarget(input.tar);
RunToGoalWithJitter(Goal);
return;
} }
}
}
else {
if (IsTaunting()) { // Taunting adjustments
bool taunting_adjust = (!front_mob || is_too_close || los_adjust);
if (taunting_adjust) { FindPositionInput find_position_input = {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, true)) { .tar = input.tar,
RunToGoalWithJitter(Goal); .distance_min = input.melee_distance_min,
.distance_max = input.melee_distance_max,
.behind_only = behind_mob_set,
.front_only = IsTaunting(),
.bypass_los = false,
};
return; bool is_melee = (!input.stop_melee_level && !IsBotRanged());
}
}
}
else {
if (tar->IsEnraged() && !stop_melee_level && !IsBotRanged()) { // Move non-taunting melee bots behind target during enrage
bool enraged_adjust = !behind_mob || is_too_close || los_adjust;
if (enraged_adjust) { if (input.tar->IsRooted() && !IsTaunting()) { // Move non-taunting melee out of range
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, true)) { adjustment_needed =
RunToGoalWithJitter(Goal); (input.tar_distance <= input.melee_distance_max) &&
HasTargetReflection();
return; if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_max + 1;
find_position_input.distance_max = input.melee_distance_max * 1.25f;
PlotBotPositionAroundTarget(find_position_input);
}
} else {
if (input.tar->IsFeared()) {
adjustment_needed = los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
} }
} }
} else if (IsTaunting() || HasTargetReflection()) { // Taunting/Aggro adjustments
else { // Regular adjustments adjustment_needed =
bool regular_adjust =
is_too_close || is_too_close ||
los_adjust || los_adjust ||
(!GetBehindMob() && !front_mob) || (is_melee && !input.front_mob);
(GetBehindMob() && !behind_mob);
if (regular_adjust) { if (adjustment_needed && adjustment_allowed) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), !GetBehindMob())) { find_position_input.distance_min = input.melee_distance_min;
RunToGoalWithJitter(Goal); find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = false;
find_position_input.front_only = true;
return; PlotBotPositionAroundTarget(find_position_input);
} }
} else {
if (input.tar->IsEnraged() && is_melee) { // Move non-taunting melee bots behind target during enrage
adjustment_needed =
!behind_mob_set ||
is_too_close ||
los_adjust;
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
find_position_input.behind_only = true;
find_position_input.front_only = false;
PlotBotPositionAroundTarget(find_position_input);
} }
} else { // Regular adjustments
adjustment_needed =
is_too_close ||
los_adjust ||
(behind_mob_set && !input.behind_mob);
if (adjustment_needed && adjustment_allowed) {
find_position_input.distance_min = input.melee_distance_min;
find_position_input.distance_max = input.melee_distance;
PlotBotPositionAroundTarget(find_position_input);
} }
} }
} }
} }
DoFaceCheckNoJitter(tar); if (!adjustment_needed && IsMoving()) {
StopMoving();
} }
bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only, bool front_only, bool bypass_los) { return adjustment_needed;
}
bool Bot::PlotBotPositionAroundTarget(const FindPositionInput& input) {
bool Result = false; bool Result = false;
if (target) { if (input.tar) {
glm::vec3 temp_goal(0, 0, input.tar->GetZ());
glm::vec3 tar_position(input.tar->GetX(), input.tar->GetY(), input.tar->GetZ());
float look_heading = 0; float look_heading = 0;
min_distance = min_distance;
max_distance = max_distance;
float temp_x = 0;
float temp_y = 0;
float temp_z = target->GetZ();
float best_z = 0; float best_z = 0;
auto offset = GetZOffset();
const float tar_x = target->GetX();
const float tar_y = target->GetY();
float tar_distance = 0; float tar_distance = 0;
float desired_angle = 0;
glm::vec3 temp_z_Position; const float offset = GetZOffset();
glm::vec4 temp_m_Position;
const uint16 max_iterations_allowed = 50; const uint16 max_iterations_allowed = 50;
uint16 counter = 0; uint16 counter = 0;
while (counter < max_iterations_allowed) { while (counter < max_iterations_allowed) {
temp_x = tar_x + zone->random.Real(-max_distance, max_distance); temp_goal.x = tar_position.x + zone->random.Real(-input.distance_max, input.distance_max);
temp_y = tar_y + zone->random.Real(-max_distance, max_distance); temp_goal.y = tar_position.y + zone->random.Real(-input.distance_max, input.distance_max);
best_z = GetFixedZ(temp_goal);
temp_z_Position.x = temp_x;
temp_z_Position.y = temp_y;
temp_z_Position.z = temp_z;
best_z = GetFixedZ(temp_z_Position);
if (best_z != BEST_Z_INVALID) { if (best_z != BEST_Z_INVALID) {
temp_z = best_z; temp_goal.z = best_z;
} }
else { else {
counter++; counter++;
continue; continue;
} }
temp_m_Position.x = temp_x; tar_distance = Distance(input.tar->GetPosition(), temp_goal);
temp_m_Position.y = temp_y;
temp_m_Position.z = temp_z;
tar_distance = Distance(target->GetPosition(), temp_m_Position); if (tar_distance > input.distance_max || tar_distance < std::max(input.distance_min, (input.distance_max * 0.75f))) {
if (tar_distance > max_distance || tar_distance < min_distance) {
counter++; counter++;
continue; continue;
} }
if (front_only && !InFrontMob(target, temp_x, temp_y)) { if (input.front_only && !InFrontMob(input.tar, temp_goal.x, temp_goal.y)) {
counter++; counter++;
continue; continue;
} }
else if (behind_only && !BehindMob(target, temp_x, temp_y)) { else if (input.behind_only && !BehindMob(input.tar, temp_goal.x, temp_goal.y)) {
counter++; counter++;
continue; continue;
} }
if (!bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(target, temp_x, temp_y, temp_z)) { if (!input.bypass_los && CastToBot()->RequiresLoSForPositioning() && !CheckPositioningLosFN(input.tar, temp_goal.x, temp_goal.y, temp_goal.z)) {
counter++; counter++;
continue; continue;
} }
@ -12161,9 +12117,7 @@ bool Bot::PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest,
} }
if (Result) { if (Result) {
x_dest = temp_x; RunToGoalWithJitter(temp_goal);
y_dest = temp_y;
z_dest = temp_z;
} }
} }

View File

@ -39,9 +39,6 @@
constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds constexpr uint32 BOT_KEEP_ALIVE_INTERVAL = 5000; // 5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MIN = 1500; // 1.5 seconds
constexpr uint32 BOT_COMBAT_JITTER_INTERVAL_MAX = 3000; // 3 seconds
constexpr uint32 MAG_EPIC_1_0 = 28034; constexpr uint32 MAG_EPIC_1_0 = 28034;
extern WorldServer worldserver; extern WorldServer worldserver;
@ -232,21 +229,6 @@ static std::map<uint16, std::string> botSubType_names = {
{ CommandedSubTypes::Selo, "Selo" } { CommandedSubTypes::Selo, "Selo" }
}; };
struct CombatRangeInput {
Mob* target;
float target_distance;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
class Bot : public NPC { class Bot : public NPC {
friend class Mob; friend class Mob;
public: public:
@ -577,7 +559,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type); uint16 GetPetBotSpellType(uint16 spell_type);
// Movement checks // Movement checks
bool PlotBotPositionAroundTarget(Mob* target, float& x_dest, float& y_dest, float& z_dest, float min_distance, float max_distance, bool behind_only = false, bool front_only = false, bool bypass_los = false); bool PlotBotPositionAroundTarget(const FindPositionInput& input);
std::vector<Mob*> GetSpellTargetList(bool entire_raid = false); std::vector<Mob*> GetSpellTargetList(bool entire_raid = false);
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; } void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; } std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
@ -1102,15 +1084,8 @@ public:
bool CheckIfCasting(float fm_distance); bool CheckIfCasting(float fm_distance);
void HealRotationChecks(); void HealRotationChecks();
bool GetCombatJitterFlag() { return m_combat_jitter_flag; }
void SetCombatJitterFlag(bool flag = true) { m_combat_jitter_flag = flag; }
bool GetCombatOutOfRangeJitterFlag() { return m_combat_out_of_range_jitter_flag; }
void SetCombatOutOfRangeJitterFlag(bool flag = true) { m_combat_out_of_range_jitter_flag = flag; }
void SetCombatJitter(); void SetCombatJitter();
void SetCombatOutOfRangeJitter(); bool DoCombatPositioning(const CombatPositioningInput& input);
void DoCombatPositioning(Mob* tar, glm::vec3 Goal, bool stop_melee_level, float tar_distance, float melee_distance_min, float melee_distance, float melee_distance_max, bool behind_mob, bool front_mob);
void DoFaceCheckWithJitter(Mob* tar);
void DoFaceCheckNoJitter(Mob* tar);
void RunToGoalWithJitter(glm::vec3 Goal); void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning(); bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar); bool HasRequiredLoSForPositioning(Mob* tar);
@ -1118,12 +1093,12 @@ public:
// Try Combat Methods // Try Combat Methods
bool TryEvade(Mob* tar); bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar); bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal); bool TryPursueTarget(float leash_distance);
bool TryMeditate(); bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance); bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance); bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal); bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance); void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
bool TryBardMovementCasts(); bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false); bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack(); bool CheckDoubleRangedAttack();
@ -1189,8 +1164,6 @@ private:
Timer m_auto_save_timer; Timer m_auto_save_timer;
Timer m_combat_jitter_timer; Timer m_combat_jitter_timer;
bool m_combat_jitter_flag;
bool m_combat_out_of_range_jitter_flag;
bool m_dirtyautohaters; bool m_dirtyautohaters;
bool m_guard_flag; bool m_guard_flag;

View File

@ -21,6 +21,7 @@
#include "../common/types.h" #include "../common/types.h"
#include "../common/timer.h" #include "../common/timer.h"
#include "mob.h"
#include <sstream> #include <sstream>
@ -151,4 +152,39 @@ struct BotSpellTypesByClass {
std::string description; std::string description;
}; };
struct CombatRangeInput {
Mob* target;
float target_distance;
bool stop_melee_level;
const EQ::ItemInstance* p_item;
const EQ::ItemInstance* s_item;
};
struct CombatRangeOutput {
bool at_combat_range = false;
float melee_distance_min = 0.0f;
float melee_distance = 0.0f;
float melee_distance_max = 0.0f;
};
struct CombatPositioningInput {
Mob* tar;
bool stop_melee_level;
float tar_distance;
float melee_distance_min;
float melee_distance;
float melee_distance_max;
bool behind_mob;
bool front_mob;
};
struct FindPositionInput {
Mob* tar;
float distance_min;
float distance_max;
bool behind_only;
bool front_only;
bool bypass_los;
};
#endif // BOT_STRUCTS #endif // BOT_STRUCTS

View File

@ -1698,13 +1698,6 @@ void Mob::StopMoving()
void Mob::StopMoving(float new_heading) void Mob::StopMoving(float new_heading)
{ {
if (IsBot()) {
auto bot = CastToBot();
bot->SetCombatJitterFlag(false);
bot->SetCombatOutOfRangeJitterFlag(false);
}
StopNavigation(); StopNavigation();
RotateTo(new_heading); RotateTo(new_heading);