[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_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_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_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.")

View File

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

View File

@ -39,9 +39,6 @@
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;
extern WorldServer worldserver;
@ -232,21 +229,6 @@ static std::map<uint16, std::string> botSubType_names = {
{ 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 {
friend class Mob;
public:
@ -577,7 +559,7 @@ public:
uint16 GetPetBotSpellType(uint16 spell_type);
// 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);
void SetSpellTargetList(std::vector<Mob*> spell_target_list) { _spell_target_list = spell_target_list; }
std::vector<Mob*> GetGroupSpellTargetList() { return _group_spell_target_list; }
@ -1102,15 +1084,8 @@ public:
bool CheckIfCasting(float fm_distance);
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 SetCombatOutOfRangeJitter();
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);
bool DoCombatPositioning(const CombatPositioningInput& input);
void RunToGoalWithJitter(glm::vec3 Goal);
bool RequiresLoSForPositioning();
bool HasRequiredLoSForPositioning(Mob* tar);
@ -1118,12 +1093,12 @@ public:
// Try Combat Methods
bool TryEvade(Mob* tar);
bool TryFacingTarget(Mob* tar);
bool TryPursueTarget(float leash_distance, glm::vec3& Goal);
bool TryPursueTarget(float leash_distance);
bool TryMeditate();
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, float leash_distance, float fm_distance);
bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack();
@ -1189,8 +1164,6 @@ private:
Timer m_auto_save_timer;
Timer m_combat_jitter_timer;
bool m_combat_jitter_flag;
bool m_combat_out_of_range_jitter_flag;
bool m_dirtyautohaters;
bool m_guard_flag;

View File

@ -21,6 +21,7 @@
#include "../common/types.h"
#include "../common/timer.h"
#include "mob.h"
#include <sstream>
@ -151,4 +152,39 @@ struct BotSpellTypesByClass {
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

View File

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