From 88050219606dcf92ab34876ebeac030515684eca Mon Sep 17 00:00:00 2001 From: Uleat Date: Wed, 31 Jan 2018 19:31:09 -0500 Subject: [PATCH] Re-worked Bot::AI_Process(); Added 'leash,' 'main assist' and 'combat abort' features --- changelog.txt | 11 + zone/aggro.cpp | 22 + zone/bot.cpp | 944 ++++++++++++++++++++++++----------------- zone/bot.h | 18 +- zone/bot_command.cpp | 6 +- zone/heal_rotation.cpp | 23 +- zone/mob.cpp | 14 +- zone/mob.h | 1 + 8 files changed, 611 insertions(+), 428 deletions(-) diff --git a/changelog.txt b/changelog.txt index 41e9a9342..a7e75de62 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,16 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 01/31/2018 == +Uleat: Re-work of Bot::AI_Process(). Overall behavior is much improved. + - Removed a 'ton' of unneeded packet updates + - Added a 'leash' to the distance a bot can travel + - Added a 'main assist' feature to target control (set using group roles) + - Added combat 'jitter' movement to complement the existing rogue movement + - Attack can now be aborted if target contains no leash owner nor bot hate and leash owner turns off auto-attack + - Please report any issues with the bot AI code + +Added a work-around for heal rotations crashing the server - under certain conditions. + == 01/28/2018 == Mackal: Spell AI tweaks diff --git a/zone/aggro.cpp b/zone/aggro.cpp index cc70f1a4c..d73b96f6f 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -1007,6 +1007,28 @@ bool Mob::CheckLosFN(float posX, float posY, float posZ, float mobSize) { return zone->zonemap->CheckLoS(myloc, oloc); } +bool Mob::CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget) { + if (zone->zonemap == nullptr) { + //not sure what the best return is on error + //should make this a database variable, but im lazy today +#ifdef LOS_DEFAULT_CAN_SEE + return(true); +#else + return(false); +#endif + } + +#define LOS_DEFAULT_HEIGHT 6.0f + + posWatcher.z += (sizeWatcher == 0.0f ? LOS_DEFAULT_HEIGHT : sizeWatcher) / 2 * HEAD_POSITION; + posTarget.z += (sizeTarget == 0.0f ? LOS_DEFAULT_HEIGHT : sizeTarget) / 2 * SEE_POSITION; + +#if LOSDEBUG>=5 + Log(Logs::General, Logs::None, "LOS from (%.2f, %.2f, %.2f) to (%.2f, %.2f, %.2f) sizes: (%.2f, %.2f) [static]", posWatcher.x, posWatcher.y, posWatcher.z, posTarget.x, posTarget.y, posTarget.z, sizeWatcher, sizeTarget); +#endif + return zone->zonemap->CheckLoS(posWatcher, posTarget); +} + //offensive spell aggro int32 Mob::CheckAggroAmount(uint16 spell_id, Mob *target, bool isproc) { diff --git a/zone/bot.cpp b/zone/bot.cpp index 6a1521d6b..844381336 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -70,7 +70,6 @@ Bot::Bot(NPCType npcTypeData, Client* botOwner) : NPC(&npcTypeData, nullptr, glm SetBotCharmer(false); SetPetChooser(false); SetRangerAutoWeaponSelect(false); - SetHasBeenSummoned(false); SetTaunting(GetClass() == WARRIOR); SetDefaultBotStance(); @@ -140,7 +139,6 @@ Bot::Bot(uint32 botID, uint32 botOwnerCharacterID, uint32 botSpellsID, double to SetBotCharmer(false); SetPetChooser(false); SetRangerAutoWeaponSelect(false); - SetHasBeenSummoned(false); bool stance_flag = false; if (!botdb.LoadStance(this, stance_flag) && bot_owner) @@ -2043,70 +2041,91 @@ void Bot::SetTarget(Mob* mob) { } } -float Bot::GetMaxMeleeRangeToTarget(Mob* target) { - float result = 0; - if(target) { - float size_mod = GetSize(); - float other_size_mod = target->GetSize(); +void Bot::ForceMovementEnd() { + FixZ(); + SetCurrentSpeed(0); + if (moved) + moved = false; +} - if(GetRace() == 49 || GetRace() == 158 || GetRace() == 196) //For races with a fixed size - size_mod = 60.0f; - else if (size_mod < 6.0) - size_mod = 8.0f; - - if(target->GetRace() == 49 || target->GetRace() == 158 || target->GetRace() == 196) //For races with a fixed size - other_size_mod = 60.0f; - else if (other_size_mod < 6.0) - other_size_mod = 8.0f; - - if (other_size_mod > size_mod) - size_mod = other_size_mod; - - if (size_mod > 29) - size_mod *= size_mod; - else if (size_mod > 19) - size_mod *= (size_mod * 2); - else - size_mod *= (size_mod * 4); - - // prevention of ridiculously sized hit boxes - if (size_mod > 10000) - size_mod = (size_mod / 7); - - result = size_mod; - } - - return result; +void Bot::ForceMovementEnd(float new_heading) { + SetHeading(new_heading); + ForceMovementEnd(); } // AI Processing for the Bot object void Bot::AI_Process() { - // TODO: Need to add root checks to all movement code - if (!IsAIControlled()) - return; - if (GetPauseAI()) +#define TEST_TARGET() if (!GetTarget()) { return; } + + Client* bot_owner = (GetBotOwner() && GetBotOwner()->IsClient() ? GetBotOwner()->CastToClient() : nullptr); + Group* bot_group = GetGroup(); + Mob* follow_mob = entity_list.GetMob(GetFollowID()); + + // Primary reasons for not processing AI + if (!bot_owner || !bot_group || !follow_mob || !IsAIControlled()) return; - uint8 botClass = GetClass(); - uint8 botLevel = GetLevel(); + if (bot_owner->IsDead()) { + SetTarget(nullptr); + SetBotOwner(nullptr); + + return; + } + + // We also need a leash owner (subset of primary AI criteria) + Client* leash_owner = (bot_group->GetLeader() && bot_group->GetLeader()->IsClient() ? bot_group->GetLeader()->CastToClient() : bot_owner); + if (!leash_owner) + return; + + // Berserk updates should occur if primary AI criteria are met + if (GetClass() == WARRIOR || GetClass() == BERSERKER) { + if (!berserk && GetHP() > 0 && GetHPRatio() < 30.0f) { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); + berserk = true; + } + + if (berserk && GetHPRatio() >= 30.0f) { + entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); + berserk = false; + } + } + + // Secondary reasons for not processing AI + if (GetPauseAI() || IsStunned() || IsMezzed() || (GetAppearance() == eaDead)) { + if (IsCasting()) + InterruptSpell(); + if (IsMyHealRotationSet() || (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this)) { + AdvanceHealRotation(false); + m_member_of_heal_rotation->SetMemberIsCasting(this, false); + } + + return; + } + + auto fm_dist = DistanceSquared(m_Position, follow_mob->GetPosition()); + auto lo_distance = DistanceSquared(m_Position, leash_owner->GetPosition()); if (IsCasting()) { - if ( - IsHealRotationMember() && + if (IsHealRotationMember() && m_member_of_heal_rotation->CastingOverride() && m_member_of_heal_rotation->CastingTarget() != nullptr && m_member_of_heal_rotation->CastingReady() && m_member_of_heal_rotation->CastingMember() == this && - !m_member_of_heal_rotation->MemberIsCasting(this) - ) { + !m_member_of_heal_rotation->MemberIsCasting(this)) + { InterruptSpell(); } else if (AmICastingForHealRotation() && m_member_of_heal_rotation->CastingMember() == this) { AdvanceHealRotation(false); return; } - else if (botClass != BARD) { + else if (GetClass() != BARD) { + if (IsEngaged()) + return; + if (fm_dist > GetFollowDistance()) // Cancel out-of-combat casting if movement is required + InterruptSpell(); + return; } } @@ -2114,28 +2133,13 @@ void Bot::AI_Process() { m_member_of_heal_rotation->SetMemberIsCasting(this, false); } - // A bot wont start its AI if not grouped - if(!GetBotOwner() || !IsGrouped() || GetAppearance() == eaDead) - return; - - Mob* BotOwner = GetBotOwner(); - if(!BotOwner) - return; - - try { - if(BotOwner->CastToClient()->IsDead()) { - SetTarget(0); - SetBotOwner(0); - return; - } - } - catch(...) { - SetTarget(0); - SetBotOwner(0); + // Can't move if rooted... + if (IsRooted() && IsMoving()) { + ForceMovementEnd(); return; } - if(IsMyHealRotationSet()) { + if (IsMyHealRotationSet()) { Mob* delete_me = HealRotationTarget(); if (AIHealRotation(HealRotationTarget(), UseHealRotationFastHeals())) { #if (EQDEBUG >= 12) @@ -2154,432 +2158,615 @@ void Bot::AI_Process() { } } - if(GetHasBeenSummoned()) { - if(IsBotCaster() || IsBotArcher()) { - if (AI_movement_timer->Check()) { - if(!GetTarget() || (IsBotCaster() && !IsBotCasterAtCombatRange(GetTarget())) || (IsBotArcher() && IsArcheryRange(GetTarget())) || (DistanceSquaredNoZ(static_cast(m_Position), m_PreSummonLocation) < 10)) { - if(GetTarget()) - FaceTarget(GetTarget()); + // Empty hate list - let's find a target + if (!IsEngaged()) { + Mob* lo_target = leash_owner->GetTarget(); - SetHasBeenSummoned(false); - } else if(!IsRooted()) { - if(GetTarget() && GetTarget()->GetHateTop() && GetTarget()->GetHateTop() != this) { - Log(Logs::Detail, Logs::AI, "Returning to location prior to being summoned."); - CalculateNewPosition2(m_PreSummonLocation.x, m_PreSummonLocation.y, m_PreSummonLocation.z, GetBotRunspeed()); - SetHeading(CalculateHeadingToTarget(m_PreSummonLocation.x, m_PreSummonLocation.y)); - return; - } - } - - if(IsMoving()) - SendPositionUpdate(); - else - SendPosition(); - } - } else { - if(GetTarget()) - FaceTarget(GetTarget()); - - SetHasBeenSummoned(false); + if (lo_target && lo_target->IsNPC() && + !lo_target->IsMezzed() && + (lo_target->GetHateAmount(leash_owner) || leash_owner->AutoAttackEnabled()) && + lo_distance <= BOT_LEASH_DISTANCE && + DistanceSquared(m_Position, lo_target->GetPosition()) <= BOT_LEASH_DISTANCE && + (CheckLosFN(lo_target) || leash_owner->CheckLosFN(lo_target)) && + IsAttackAllowed(lo_target)) + { + AddToHateList(lo_target, 1); + if (HasPet()) + GetPet()->AddToHateList(lo_target, 1); } - return; - } + else { + for (int counter = 0; counter < bot_group->GroupCount(); counter++) { + Mob* bg_member = bot_group->members[counter]; + if (!bg_member) + continue; - if(!IsEngaged()) { - if(GetFollowID()) { - if(BotOwner && BotOwner->GetTarget() && BotOwner->GetTarget()->IsNPC() && (BotOwner->GetTarget()->GetHateAmount(BotOwner) || BotOwner->CastToClient()->AutoAttackEnabled()) && IsAttackAllowed(BotOwner->GetTarget())) { - AddToHateList(BotOwner->GetTarget(), 1); - if(HasPet()) - GetPet()->AddToHateList(BotOwner->GetTarget(), 1); - } else { - Group* g = GetGroup(); - if(g) { - for(int counter = 0; counter < g->GroupCount(); counter++) { - if(g->members[counter]) { - Mob* tar = g->members[counter]->GetTarget(); - if(tar && tar->IsNPC() && tar->GetHateAmount(g->members[counter]) && IsAttackAllowed(g->members[counter]->GetTarget())) { - AddToHateList(tar, 1); - if(HasPet()) - GetPet()->AddToHateList(tar, 1); + Mob* bgm_target = bg_member->GetTarget(); + if (!bgm_target || !bgm_target->IsNPC()) + continue; - break; - } - } - } + if (!bgm_target->IsMezzed() && + bgm_target->GetHateAmount(bg_member) && + lo_distance <= BOT_LEASH_DISTANCE && + DistanceSquared(m_Position, bgm_target->GetPosition()) <= BOT_LEASH_DISTANCE && + (CheckLosFN(bgm_target) || leash_owner->CheckLosFN(bgm_target)) && + IsAttackAllowed(bgm_target)) + { + AddToHateList(bgm_target, 1); + if (HasPet()) + GetPet()->AddToHateList(bgm_target, 1); + + break; } } } } - if(IsEngaged()) { - if(rest_timer.Enabled()) + glm::vec3 Goal(0, 0, 0); + + // We have aggro to choose from + if (IsEngaged()) { + if (rest_timer.Enabled()) rest_timer.Disable(); - if(IsRooted()) - SetTarget(hate_list.GetClosestEntOnHateList(this)); - else - SetTarget(hate_list.GetEntWithMostHateOnList(this)); + // Group roles can be expounded upon in the future + auto assist_mob = entity_list.GetMob(bot_group->GetMainAssistName()); + bool find_target = true; + + if (assist_mob) { + if (assist_mob->GetTarget()) { + if (assist_mob != this) + SetTarget(assist_mob->GetTarget()); - if(!GetTarget()) + find_target = false; + } + else if (assist_mob != this) { + SetTarget(nullptr); + if (HasPet()) + GetPet()->SetTarget(nullptr); + + find_target = false; + } + } + + if (find_target) { + if (IsRooted()) + SetTarget(hate_list.GetClosestEntOnHateList(this)); + else + SetTarget(hate_list.GetEntWithMostHateOnList(this)); + } + + TEST_TARGET(); + + Mob* tar = GetTarget(); + if (!tar) return; - if(HasPet()) - GetPet()->SetTarget(GetTarget()); - - if(!IsSitting()) - FaceTarget(GetTarget()); - - if(DivineAura()) - return; + float tar_distance = DistanceSquared(m_Position, tar->GetPosition()); // Let's check if we have a los with our target. // If we don't, our hate_list is wiped. // Else, it was causing the bot to aggro behind wall etc... causing massive trains. - if(GetTarget()->IsMezzed() || !IsAttackAllowed(GetTarget())) { - WipeHateList(); - if(IsMoving()) { - SetHeading(0); - SetRunAnimSpeed(0); - SetCurrentSpeed(GetBotRunspeed()); - if(moved) - SetCurrentSpeed(0); + if (!tar->IsNPC() || + tar->IsMezzed() || + (!tar->GetHateAmount(this) && !tar->GetHateAmount(leash_owner) && !leash_owner->AutoAttackEnabled()) || + lo_distance > BOT_LEASH_DISTANCE || + tar_distance > BOT_LEASH_DISTANCE || + (!CheckLosFN(tar) && !leash_owner->CheckLosFN(tar)) || + !IsAttackAllowed(tar)) + { + if (HasPet()) { + GetPet()->RemoveFromHateList(tar); + GetPet()->SetTarget(nullptr); } - return; - } - else if (!CheckLosFN(GetTarget())) { - if (RuleB(Bots, UsePathing) && zone->pathing) { - bool WaypointChanged, NodeReached; - glm::vec3 Goal = UpdatePath(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), - GetBotRunspeed(), WaypointChanged, NodeReached); + RemoveFromHateList(tar); + SetTarget(nullptr); - if (WaypointChanged) - tar_ndx = 20; - - CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotRunspeed()); - } - else { - Mob* follow = entity_list.GetMob(GetFollowID()); - if (follow) - CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), GetBotRunspeed()); - } + if (IsMoving()) + ForceMovementEnd(); return; } + if (HasPet()) // this causes conflicts with default pet handler (bounces between targets) + GetPet()->SetTarget(tar); + + if (DivineAura()) + return; + if (!(m_PlayerState & static_cast(PlayerState::Aggressive))) SendAddPlayerState(PlayerState::Aggressive); bool atCombatRange = false; - float meleeDistance = GetMaxMeleeRangeToTarget(GetTarget()); - if(botClass == SHADOWKNIGHT || botClass == PALADIN || botClass == WARRIOR) - meleeDistance = (meleeDistance * .30); - else - meleeDistance *= (float)zone->random.Real(.50, .85); - bool atArcheryRange = IsArcheryRange(GetTarget()); + // Calculate melee distance + float melee_distance_max = 0.0f; + { + float size_mod = GetSize(); + float other_size_mod = tar->GetSize(); - if(GetRangerAutoWeaponSelect()) { + if (GetRace() == RT_DRAGON || GetRace() == RT_WURM || GetRace() == RT_DRAGON_7) //For races with a fixed size + size_mod = 60.0f; + else if (size_mod < 6.0f) + size_mod = 8.0f; + + if (tar->GetRace() == RT_DRAGON || tar->GetRace() == RT_WURM || tar->GetRace() == RT_DRAGON_7) //For races with a fixed size + other_size_mod = 60.0f; + else if (other_size_mod < 6.0f) + other_size_mod = 8.0f; + + if (other_size_mod > size_mod) + size_mod = other_size_mod; + + if (size_mod > 29.0f) + size_mod *= size_mod; + else if (size_mod > 19.0f) + size_mod *= (size_mod * 2.0f); + else + size_mod *= (size_mod * 4.0f); + + // prevention of ridiculously sized hit boxes + if (size_mod > 10000.0f) + size_mod = (size_mod / 7.0f); + + melee_distance_max = size_mod; + } + + float melee_distance = 0.0f; + + const auto* p_item = GetBotItem(EQEmu::inventory::slotPrimary); + const auto* s_item = GetBotItem(EQEmu::inventory::slotSecondary); + + switch (GetClass()) { + case WARRIOR: + case PALADIN: + case SHADOWKNIGHT: + if (p_item && p_item->GetItem()->IsType2HWeapon()) + melee_distance = melee_distance_max * 0.45f; + else if ((s_item && s_item->GetItem()->IsTypeShield()) || (!p_item && !s_item)) + melee_distance = melee_distance_max * 0.35f; + else + melee_distance = melee_distance_max * 0.40f; + + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + if (p_item && p_item->GetItem()->IsType2HWeapon()) + melee_distance = melee_distance_max * 0.95f; + else + melee_distance = melee_distance_max * 0.75f; + + break; + default: + if (p_item && p_item->GetItem()->IsType2HWeapon()) + melee_distance = melee_distance_max * 0.70f; + else + melee_distance = melee_distance_max * 0.50f; + + break; + } + + float melee_distance_min = melee_distance / 2.0f; + + // Calculate casting distance + float caster_distance_max = 0.0f; + { + if (GetLevel() >= RuleI(Bots, CasterStopMeleeLevel)) { + switch (GetClass()) { + case CLERIC: + caster_distance_max = 1156.0f; // as DSq value (34 units) + break; + case DRUID: + caster_distance_max = 1764.0f; // as DSq value (42 units) + break; + case SHAMAN: + caster_distance_max = 1444.0f; // as DSq value (38 units) + break; + case NECROMANCER: + caster_distance_max = 2916.0f; // as DSq value (54 units) + break; + case WIZARD: + caster_distance_max = 2304.0f; // as DSq value (48 units) + break; + case MAGICIAN: + caster_distance_max = 2704.0f; // as DSq value (52 units) + break; + case ENCHANTER: + caster_distance_max = 2500.0f; // as DSq value (50 units) + break; + default: + break; + } + } + } + + float caster_distance_min = 0.0f; + if (caster_distance_max) { + caster_distance_min = melee_distance_max; + + if (caster_distance_max <= caster_distance_min) + caster_distance_max = caster_distance_min * 1.25f; + } + + bool atArcheryRange = IsArcheryRange(tar); + + if (GetRangerAutoWeaponSelect()) { bool changeWeapons = false; - if(atArcheryRange && !IsBotArcher()) { + if (atArcheryRange && !IsBotArcher()) { SetBotArcher(true); changeWeapons = true; - } else if(!atArcheryRange && IsBotArcher()) { + } + else if (!atArcheryRange && IsBotArcher()) { SetBotArcher(false); changeWeapons = true; } - if(changeWeapons) + if (changeWeapons) ChangeBotArcherWeapons(IsBotArcher()); } - if (IsBotArcher() && atArcheryRange) { + if (IsBotArcher() && atArcheryRange) + atCombatRange = true; + else if (caster_distance_max && tar_distance <= caster_distance_max) + atCombatRange = true; + else if (tar_distance <= melee_distance) + atCombatRange = true; + + // We can fight + if (atCombatRange) { if (IsMoving()) { - SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SetRunAnimSpeed(0); - SetCurrentSpeed(0); - if (moved) { - moved = false; - SetCurrentSpeed(0); - } + ForceMovementEnd(CalculateHeadingToTarget(tar->GetX(), tar->GetY())); + return; } - atCombatRange = true; - } - else if (GetLevel() >= RuleI(Bots, CasterStopMeleeLevel) && IsBotCasterAtCombatRange(GetTarget())) { - atCombatRange = true; - } - else if (DistanceSquared(m_Position, GetTarget()->GetPosition()) <= meleeDistance) { - atCombatRange = true; - } + + // Combat 'jitter' code + if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == BARD)) { + if (!IsRooted()) { + if (HasTargetReflection()) { + if (GetClass() == ROGUE) { + if (!tar->IsFeared() && !tar->IsStunned()) { + if (evade_timer.Check(false)) { // Attempt to evade + int timer_duration = (HideReuseTime - GetSkillReuseTime(EQEmu::skills::SkillHide)) * 1000; + if (timer_duration < 0) + timer_duration = 0; - if(atCombatRange) { - if(IsMoving()) { - SetHeading(CalculateHeadingToTarget(GetTarget()->GetX(), GetTarget()->GetY())); - SetCurrentSpeed(0); - if(moved) { - moved = false; - SetCurrentSpeed(0); - } - } - - if(AI_movement_timer->Check()) { - if (!IsMoving()) { - if (GetClass() == ROGUE) { - if (HasTargetReflection() && !GetTarget()->IsFeared() && !GetTarget()->IsStunned()) { - // Hate redux actions - if (evade_timer.Check(false)) { - // Attempt to evade - int timer_duration = (HideReuseTime - GetSkillReuseTime(EQEmu::skills::SkillHide)) * 1000; - if (timer_duration < 0) - timer_duration = 0; - evade_timer.Start(timer_duration); - - Bot::BotGroupSay(this, "Attempting to evade %s", GetTarget()->GetCleanName()); - if (zone->random.Int(0, 260) < (int)GetSkill(EQEmu::skills::SkillHide)) - RogueEvade(GetTarget()); + evade_timer.Start(timer_duration); + if (zone->random.Int(0, 260) < (int)GetSkill(EQEmu::skills::SkillHide)) + RogueEvade(tar); + return; + } + else if (tar->IsRooted()) { // Move rogue back from rooted mob - out of combat range, if necessary + if (tar_distance <= melee_distance_max) { + if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotWalkspeed()); + return; + } + } + } + } + } + } + else { + if (caster_distance_min && tar_distance < caster_distance_min) { // Caster back-off adjustment + if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { + if (DistanceSquared(Goal, tar->GetPosition()) <= caster_distance_max) { + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotWalkspeed()); + return; + } + } + else if (!IsFacingMob(tar)) { + FaceTarget(tar); return; } - else if (GetTarget()->IsRooted()) { - // Move rogue back from rooted mob - out of combat range, if necessary - float melee_distance = GetMaxMeleeRangeToTarget(GetTarget()); - float current_distance = DistanceSquared(static_cast(m_Position), static_cast(GetTarget()->GetPosition())); - - if (current_distance <= melee_distance) { - float newX = 0; - float newY = 0; - float newZ = 0; - FaceTarget(GetTarget()); - if (PlotPositionAroundTarget(this, newX, newY, newZ)) { - CalculateNewPosition2(newX, newY, newZ, GetBotRunspeed()); + } + else if (tar_distance < melee_distance_min) { // Melee back-off adjustment + if (PlotPositionAroundTarget(this, Goal.x, Goal.y, Goal.z)) { + if (DistanceSquared(Goal, tar->GetPosition()) <= melee_distance_max) { + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotWalkspeed()); + return; + } + } + else if (!IsFacingMob(tar)) { + FaceTarget(tar); + return; + } + } + else if (GetClass() == ROGUE) { + if (!BehindMob(tar, GetX(), GetY())) { // Move the rogue to behind the mob + if (PlotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z)) { + if (DistanceSquared(Goal, tar->GetPosition()) <= melee_distance_max) { + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotRunspeed()); // rogues are agile enough to run in melee range return; } } } } - else if (!BehindMob(GetTarget(), GetX(), GetY())) { - // Move the rogue to behind the mob - float newX = 0; - float newY = 0; - float newZ = 0; - if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ)) { - CalculateNewPosition2(newX, newY, newZ, GetBotRunspeed()); - return; + else { + if (caster_distance_max == 0.0f && // Not a caster or a caster still below melee stop level (standard combat jitter) + zone->random.Int(1, 100) >= 94 && // 7:100 chance + PlotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z)) // If we're behind the mob, we can attack when it's enraged + { + if (DistanceSquared(Goal, tar->GetPosition()) <= melee_distance_max) { + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotWalkspeed()); + return; + } } } } - else if (GetClass() != ROGUE && (DistanceSquaredNoZ(m_Position, GetTarget()->GetPosition()) < GetTarget()->GetSize())) { - // If we are not a rogue trying to backstab, let's try to adjust our melee range so we don't appear to be bunched up - float newX = 0; - float newY = 0; - float newZ = 0; - if (PlotPositionAroundTarget(GetTarget(), newX, newY, newZ, false) && GetArchetype() != ARCHETYPE_CASTER) { - CalculateNewPosition2(newX, newY, newZ, GetBotRunspeed()); - return; - } + } + else { + if (!IsSitting() && !IsFacingMob(tar)) { + FaceTarget(tar); + return; } } - - // TODO: Test RuleB(Bots, UpdatePositionWithTimer) - if(IsMoving()) - SendPositionUpdate(); - else - SendPosition(); } - if(IsBotArcher() && ranged_timer.Check(false)) { - if(GetTarget()->GetHPRatio() <= 99.0f) - BotRangedAttack(GetTarget()); + // Up to this point, GetTarget() has been safe to dereference since the initial + // TEST_TARGET() call. Due to the chance of the target dying and our pointer + // being nullified, we need to test it before dereferencing to avoid crashes + + if (IsBotArcher() && ranged_timer.Check(false)) { // can shoot mezzed, stunned and dead!? + TEST_TARGET(); + if (GetTarget()->GetHPRatio() <= 99.0f) + BotRangedAttack(tar); } - else if(!IsBotArcher() && (!(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel))) && GetTarget() && !IsStunned() && !IsMezzed() && (GetAppearance() != eaDead)) { + else if (!IsBotArcher() && (!(IsBotCaster() && GetLevel() >= RuleI(Bots, CasterStopMeleeLevel)))) { // we can't fight if we don't have a target, are stun/mezzed or dead.. // Stop attacking if the target is enraged - if((IsEngaged() && !BehindMob(GetTarget(), GetX(), GetY()) && GetTarget()->IsEnraged()) || GetBotStance() == BotStancePassive) + TEST_TARGET(); + if (GetBotStance() == BotStancePassive || (tar->IsEnraged() && !BehindMob(tar, GetX(), GetY()))) return; // First, special attack per class (kick, backstab etc..) - DoClassAttacks(GetTarget()); - if(attack_timer.Check()) { - Attack(GetTarget(), EQEmu::inventory::slotPrimary); - TriggerDefensiveProcs(GetTarget(), EQEmu::inventory::slotPrimary, false); - EQEmu::ItemInstance *wpn = GetBotItem(EQEmu::inventory::slotPrimary); - TryWeaponProc(wpn, GetTarget(), EQEmu::inventory::slotPrimary); - bool tripleSuccess = false; - if(BotOwner && GetTarget() && CanThisClassDoubleAttack()) { - if(BotOwner && CheckBotDoubleAttack()) - Attack(GetTarget(), EQEmu::inventory::slotPrimary, true); + TEST_TARGET(); + DoClassAttacks(tar); - if(BotOwner && GetTarget() && GetSpecialAbility(SPECATK_TRIPLE) && CheckBotDoubleAttack(true)) { - tripleSuccess = true; - Attack(GetTarget(), EQEmu::inventory::slotPrimary, true); + TEST_TARGET(); + if (attack_timer.Check()) { // Process primary weapon attacks + Attack(tar, EQEmu::inventory::slotPrimary); + + TEST_TARGET(); + TriggerDefensiveProcs(tar, EQEmu::inventory::slotPrimary, false); + + TEST_TARGET(); + TryWeaponProc(p_item, tar, EQEmu::inventory::slotPrimary); + + //bool tripleSuccess = false; + + TEST_TARGET(); + if (CanThisClassDoubleAttack()) { + if (CheckBotDoubleAttack()) + Attack(tar, EQEmu::inventory::slotPrimary, true); + + TEST_TARGET(); + if (GetSpecialAbility(SPECATK_TRIPLE) && CheckBotDoubleAttack(true)) { + //tripleSuccess = true; + Attack(tar, EQEmu::inventory::slotPrimary, true); } + TEST_TARGET(); //quad attack, does this belong here?? - if(BotOwner && GetTarget() && GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) - Attack(GetTarget(), EQEmu::inventory::slotPrimary, true); + if (GetSpecialAbility(SPECATK_QUAD) && CheckBotDoubleAttack(true)) + Attack(tar, EQEmu::inventory::slotPrimary, true); } + TEST_TARGET(); //Live AA - Flurry, Rapid Strikes ect (Flurry does not require Triple Attack). int32 flurrychance = (aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance); - if (GetTarget() && flurrychance) { - if(zone->random.Int(0, 100) < flurrychance) { + if (flurrychance) { + if (zone->random.Int(0, 100) < flurrychance) { Message_StringID(MT_NPCFlurry, YOU_FLURRY); - Attack(GetTarget(), EQEmu::inventory::slotPrimary, false); - Attack(GetTarget(), EQEmu::inventory::slotPrimary, false); + Attack(tar, EQEmu::inventory::slotPrimary, false); + + TEST_TARGET(); + Attack(tar, EQEmu::inventory::slotPrimary, false); } } + TEST_TARGET(); int32 ExtraAttackChanceBonus = (spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance + aabonuses.ExtraAttackChance); - if (GetTarget() && ExtraAttackChanceBonus) { - EQEmu::ItemInstance *wpn = GetBotItem(EQEmu::inventory::slotPrimary); - if(wpn) { - if (wpn->GetItem()->IsType2HWeapon()) { - if(zone->random.Int(0, 100) < ExtraAttackChanceBonus) - Attack(GetTarget(), EQEmu::inventory::slotPrimary, false); - } + if (ExtraAttackChanceBonus) { + if (p_item && p_item->GetItem()->IsType2HWeapon()) { + if (zone->random.Int(0, 100) < ExtraAttackChanceBonus) + Attack(tar, EQEmu::inventory::slotPrimary, false); } } } - if (GetClass() == WARRIOR || GetClass() == BERSERKER) { - if(GetHP() > 0 && !berserk && this->GetHPRatio() < 30) { - entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_START, GetName()); - this->berserk = true; - } - - if (berserk && this->GetHPRatio() > 30) { - entity_list.MessageClose_StringID(this, false, 200, 0, BERSERK_END, GetName()); - this->berserk = false; - } - } - - //now off hand - if(GetTarget() && attack_dw_timer.Check() && CanThisClassDualWield()) { - const EQEmu::ItemInstance* instweapon = GetBotItem(EQEmu::inventory::slotSecondary); - const EQEmu::ItemData* weapon = nullptr; + TEST_TARGET(); + if (attack_dw_timer.Check() && CanThisClassDualWield()) { // Process secondary weapon attacks + const EQEmu::ItemData* s_itemdata = nullptr; //can only dual wield without a weapon if you're a monk - if(instweapon || (botClass == MONK)) { - if(instweapon) - weapon = instweapon->GetItem(); + if (s_item || (GetClass() == MONK)) { + if(s_item) + s_itemdata = s_item->GetItem(); - int weapontype = 0; // No weapon type. - bool bIsFist = true; - if(weapon) { - weapontype = weapon->ItemType; - bIsFist = false; + int weapon_type = 0; // No weapon type. + bool use_fist = true; + if (s_itemdata) { + weapon_type = s_itemdata->ItemType; + use_fist = false; } - if (bIsFist || !weapon->IsType2HWeapon()) { + if (use_fist || !s_itemdata->IsType2HWeapon()) { float DualWieldProbability = 0.0f; + int32 Ambidexterity = (aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity); DualWieldProbability = ((GetSkill(EQEmu::skills::SkillDualWield) + GetLevel() + Ambidexterity) / 400.0f); // 78.0 max + int32 DWBonus = (spellbonuses.DualWieldChance + itembonuses.DualWieldChance); DualWieldProbability += (DualWieldProbability * float(DWBonus) / 100.0f); + float random = zone->random.Real(0, 1); + if (random < DualWieldProbability){ // Max 78% of DW - Attack(GetTarget(), EQEmu::inventory::slotSecondary); // Single attack with offhand - EQEmu::ItemInstance *wpn = GetBotItem(EQEmu::inventory::slotSecondary); - TryWeaponProc(wpn, GetTarget(), EQEmu::inventory::slotSecondary); - if( CanThisClassDoubleAttack() && CheckBotDoubleAttack()) { - if(GetTarget() && GetTarget()->GetHP() > -10) - Attack(GetTarget(), EQEmu::inventory::slotSecondary); // Single attack with offhand + Attack(tar, EQEmu::inventory::slotSecondary); // Single attack with offhand + + TEST_TARGET(); + TryWeaponProc(s_item, tar, EQEmu::inventory::slotSecondary); + + TEST_TARGET(); + if (CanThisClassDoubleAttack() && CheckBotDoubleAttack()) { + if (tar->GetHP() > -10) + Attack(tar, EQEmu::inventory::slotSecondary); // Single attack with offhand } } } } } } - } else { - if(GetTarget()->IsFeared() && !spellend_timer.Enabled()){ - // This is a mob that is fleeing either because it has been feared or is low on hitpoints - if(GetBotStance() != BotStancePassive) - AI_PursueCastCheck(); - } - - if (AI_movement_timer->Check()) { - if(!IsRooted()) { + } + else { // To far away to fight (GetTarget() validity can be iffy below this point - including outer scopes) + if (AI_movement_timer->Check() && (!spellend_timer.Enabled() || GetClass() == BARD)) { // Pursue processing + if (GetTarget() && !IsRooted()) { Log(Logs::Detail, Logs::AI, "Pursuing %s while engaged.", GetTarget()->GetCleanName()); - CalculateNewPosition2(GetTarget()->GetX(), GetTarget()->GetY(), GetTarget()->GetZ(), GetBotRunspeed()); + + Goal = GetTarget()->GetPosition(); + + if (RuleB(Bots, UsePathing) && zone->pathing) { + bool WaypointChanged, NodeReached; + + Goal = UpdatePath(Goal.x, Goal.y, Goal.z, + GetBotRunspeed(), WaypointChanged, NodeReached); + + if (WaypointChanged) + tar_ndx = 20; + } + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, GetBotRunspeed()); + return; } + else { + if (IsMoving()) + ForceMovementEnd(); + else + SendPosition(); - if(IsMoving()) - SendPositionUpdate(); - else - SendPosition(); + return; + } + } + + // Fix Z when following during pull, not when engaged and stationary + if (IsMoving() && fix_z_timer_engaged.Check()) { + FixZ(); + return; + } + + if (GetTarget() && GetTarget()->IsFeared() && !spellend_timer.Enabled() && AI_think_timer->Check()) { + if (!IsFacingMob(GetTarget())) + FaceTarget(GetTarget()); + + // This is a mob that is fleeing either because it has been feared or is low on hitpoints + if (GetBotStance() != BotStancePassive) { + AI_PursueCastCheck(); // This appears to always return true..can't trust for success/fail + return; + } } } // end not in combat range - if(!IsMoving() && !spellend_timer.Enabled()) { - if(GetBotStance() == BotStancePassive) - return; + if (!IsMoving() && !spellend_timer.Enabled()) { // This may actually need work... + SendPosition(); - if(AI_EngagedCastCheck()) + if (GetBotStance() == BotStancePassive) + return; + + if (GetTarget() && AI_EngagedCastCheck()) BotMeditate(false); - else if(GetArchetype() == ARCHETYPE_CASTER) + else if (GetArchetype() == ARCHETYPE_CASTER) BotMeditate(true); + + return; } } - else { + else { // Out-of-combat behavior SetTarget(nullptr); + + if (HasPet()) { + GetPet()->WipeHateList(); + GetPet()->SetTarget(nullptr); + } + if (m_PlayerState & static_cast(PlayerState::Aggressive)) SendRemovePlayerState(PlayerState::Aggressive); - Mob* follow = entity_list.GetMob(GetFollowID()); - if (!follow) - return; + // Leash the bot + if (lo_distance > BOT_LEASH_DISTANCE) { + if (IsMoving()) + ForceMovementEnd(); - if (!IsMoving() && AI_think_timer->Check() && !spellend_timer.Enabled()) { - if (GetBotStance() != BotStancePassive) { - if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) - BotMeditate(true); - } - else { - if (GetClass() != BARD) - BotMeditate(true); - } + Warp(glm::vec3(leash_owner->GetPosition())); + + if (HasPet()) + GetPet()->Warp(glm::vec3(leash_owner->GetPosition())); + + return; } - if (AI_movement_timer->Check()) { - float dist = DistanceSquared(m_Position, follow->GetPosition()); - int speed = GetBotRunspeed(); - - if (dist < GetFollowDistance() + BOT_FOLLOW_DISTANCE_WALK) - speed = GetBotWalkspeed(); - - SetRunAnimSpeed(0); - - if (dist > GetFollowDistance()) { - if (RuleB(Bots, UsePathing) && zone->pathing) { - bool WaypointChanged, NodeReached; - - glm::vec3 Goal = UpdatePath(follow->GetX(), follow->GetY(), follow->GetZ(), - speed, WaypointChanged, NodeReached); - - if (WaypointChanged) - tar_ndx = 20; - - CalculateNewPosition2(Goal.x, Goal.y, Goal.z, speed); + // Ok to idle + if (fm_dist <= GetFollowDistance()) { + if (!IsMoving() && AI_think_timer->Check() && !spellend_timer.Enabled()) { + if (GetBotStance() != BotStancePassive) { + if (!AI_IdleCastCheck() && !IsCasting() && GetClass() != BARD) + BotMeditate(true); } else { - CalculateNewPosition2(follow->GetX(), follow->GetY(), follow->GetZ(), speed); + if (GetClass() != BARD) + BotMeditate(true); } - if (rest_timer.Enabled()) - rest_timer.Disable(); + return; + } + } + + // Non-engaged movement checks + if (AI_movement_timer->Check() && (!IsCasting() || GetClass() == BARD)) { + if (fm_dist > GetFollowDistance()) { + if (!IsRooted()) { + if (rest_timer.Enabled()) + rest_timer.Disable(); + + int speed = GetBotRunspeed(); + if (fm_dist < GetFollowDistance() + BOT_FOLLOW_DISTANCE_WALK) + speed = GetBotWalkspeed(); + + Goal = follow_mob->GetPosition(); + + if (RuleB(Bots, UsePathing) && zone->pathing) { + bool WaypointChanged, NodeReached; + + Goal = UpdatePath(Goal.x, Goal.y, Goal.z, + speed, WaypointChanged, NodeReached); + + if (WaypointChanged) + tar_ndx = 20; + } + + CalculateNewPosition2(Goal.x, Goal.y, Goal.z, speed); + + return; + } } else { - if (moved) { - moved = false; - SetCurrentSpeed(0); + if (IsMoving()) { + ForceMovementEnd(); + return; } } } + // Basically, bard bots get a chance to cast idle spells while moving if (IsMoving()) { - if (GetClass() == BARD && GetBotStance() != BotStancePassive && !spellend_timer.Enabled() && AI_think_timer->Check()) { - AI_IdleCastCheck(); + if (GetBotStance() != BotStancePassive) { + if (GetClass() == BARD && !spellend_timer.Enabled() && AI_think_timer->Check()) { + AI_IdleCastCheck(); + return; + } } } } @@ -2589,7 +2776,7 @@ void Bot::AI_Process() { void Bot::PetAIProcess() { if( !HasPet() || !GetPet() || !GetPet()->IsNPC()) return; - + Mob* BotOwner = this->GetBotOwner(); NPC* botPet = this->GetPet()->CastToNPC(); if(!botPet->GetOwner() || !botPet->GetID() || !botPet->GetOwnerID()) { @@ -7046,33 +7233,6 @@ bool Bot::IsArcheryRange(Mob *target) { return result; } -bool Bot::IsBotCasterAtCombatRange(Mob *target) -{ - static const float local[PLAYER_CLASS_COUNT] = { - 0.0f, // WARRIOR - 1156.0f, // CLERIC as DSq value (34 units) - 0.0f, 0.0f, 0.0f, // PALADIN, RANGER, SHADOWKNIGHT - 1764.0f, // DRUID as DSq value (42 units) - 0.0f, 0.0f, 0.0f, // MONK, BARD, ROGUE - 1444.0f, // SHAMAN as DSq value (38 units) - 2916.0f, // NECROMANCER as DSq value (54 units) - 2304.0f, // WIZARD as DSq value (48 units) - 2704.0f, // MAGICIAN as DSq value (52 units) - 2500.0f, // ENCHANTER as DSq value (50 units) - 0.0f, 0.0f // BEASTLORD, BERSERKER - }; - - if (!target) - return false; - if (GetClass() < WARRIOR || GetClass() > BERSERKER) - return false; - - float targetDistance = DistanceSquaredNoZ(m_Position, target->GetPosition()); - if (targetDistance < local[GetClass() - 1]) - return true; - return false; -} - void Bot::UpdateGroupCastingRoles(const Group* group, bool disband) { if (!group) @@ -8417,12 +8577,6 @@ bool Bot::HasOrMayGetAggro() { return mayGetAggro; } -void Bot::SetHasBeenSummoned(bool wasSummoned) { - _hasBeenSummoned = wasSummoned; - if(!wasSummoned) - m_PreSummonLocation = glm::vec3(); -} - void Bot::SetDefaultBotStance() { BotStanceType defaultStance = BotStanceBalanced; if (GetClass() == WARRIOR) diff --git a/zone/bot.h b/zone/bot.h index 1a6bcf7cd..baee2f0b4 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -42,6 +42,8 @@ #define BOT_FOLLOW_DISTANCE_DEFAULT_MAX 2500 // as DSq value (50 units) #define BOT_FOLLOW_DISTANCE_WALK 1000 // as DSq value (~31.623 units) +#define BOT_LEASH_DISTANCE 250000 // as DSq value (500 units) + extern WorldServer worldserver; const int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this @@ -337,7 +339,6 @@ public: bool IsStanding(); int GetBotWalkspeed() const { return (int)((float)_GetWalkSpeed() * 1.786f); } // 1.25 / 0.7 = 1.7857142857142857142857142857143 int GetBotRunspeed() const { return (int)((float)_GetRunSpeed() * 1.786f); } - bool IsBotCasterAtCombatRange(Mob *target); bool UseDiscipline(uint32 spell_id, uint32 target); uint8 GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets); bool GetNeedsCured(Mob *tar); @@ -404,7 +405,8 @@ public: bool AIHealRotation(Mob* tar, bool useFastHeals); bool GetPauseAI() { return _pauseAI; } void SetPauseAI(bool pause_flag) { _pauseAI = pause_flag; } - + void ForceMovementEnd(); + void ForceMovementEnd(float new_heading); // Mob AI Virtual Override Methods virtual void AI_Process(); @@ -532,9 +534,7 @@ public: bool IsBotWISCaster() { return IsWISCasterClass(GetClass()); } bool CanHeal(); int GetRawACNoShield(int &shield_ac); - bool GetHasBeenSummoned() { return _hasBeenSummoned; } - const glm::vec3 GetPreSummonLocation() const { return m_PreSummonLocation; } - + // new heal rotation code bool CreateHealRotation(uint32 cycle_duration_ms = 5000, bool fast_heals = false, bool adaptive_targeting = false, bool casting_override = false); bool DestroyHealRotation(); @@ -628,9 +628,6 @@ public: void SetBotStance(BotStanceType botStance) { _botStance = ((botStance != BotStanceUnknown) ? (botStance) : (BotStancePassive)); } void SetSpellRecastTimer(int timer_index, int32 recast_delay); void SetDisciplineRecastTimer(int timer_index, int32 recast_delay); - void SetHasBeenSummoned(bool s); - void SetPreSummonLocation(const glm::vec3& location) { m_PreSummonLocation = location; } - void SetAltOutOfCombatBehavior(bool behavior_flag) { _altoutofcombatbehavior = behavior_flag;} void SetShowHelm(bool showhelm) { _showhelm = showhelm; } void SetBeardColor(uint8 value) { beardcolor = value; } @@ -688,7 +685,6 @@ protected: virtual int32 CalcBotAAFocus(BotfocusType type, uint32 aa_ID, uint32 points, uint16 spell_id); virtual void PerformTradeWithClient(int16 beginSlotID, int16 endSlotID, Client* client); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); - virtual float GetMaxMeleeRangeToTarget(Mob* target); BotCastingRoles& GetCastingRoles() { return m_CastingRoles; } void SetGroupHealer(bool flag = true) { m_CastingRoles.GroupHealer = flag; } @@ -734,9 +730,7 @@ private: int32 max_end; int32 end_regen; uint32 timers[MaxTimer]; - bool _hasBeenSummoned; - glm::vec3 m_PreSummonLocation; - + Timer evade_timer; // can be moved to pTimers at some point BotCastingRoles m_CastingRoles; diff --git a/zone/bot_command.cpp b/zone/bot_command.cpp index 5ba73d068..b45c2b26a 100644 --- a/zone/bot_command.cpp +++ b/zone/bot_command.cpp @@ -5146,10 +5146,10 @@ void bot_subcommand_bot_summon(Client *c, const Seperator *sep) if (!bot_iter) continue; - Bot::BotGroupSay(bot_iter, "Whee!"); + //Bot::BotGroupSay(bot_iter, "Whee!"); bot_iter->WipeHateList(); - bot_iter->SetTarget(bot_iter->GetBotOwner()); + bot_iter->SetTarget(nullptr); bot_iter->Warp(glm::vec3(c->GetPosition())); bot_iter->DoAnim(0); @@ -5157,7 +5157,7 @@ void bot_subcommand_bot_summon(Client *c, const Seperator *sep) continue; bot_iter->GetPet()->WipeHateList(); - bot_iter->GetPet()->SetTarget(bot_iter); + bot_iter->GetPet()->SetTarget(nullptr); bot_iter->GetPet()->Warp(glm::vec3(c->GetPosition())); } diff --git a/zone/heal_rotation.cpp b/zone/heal_rotation.cpp index c9bf54189..489d1ab45 100644 --- a/zone/heal_rotation.cpp +++ b/zone/heal_rotation.cpp @@ -169,9 +169,10 @@ bool HealRotation::ClearMemberPool() m_casting_target_poke = false; m_active_heal_target = false; - ClearTargetPool(); + if (!ClearTargetPool()) + Log(Logs::General, Logs::Error, "HealRotation::ClearTargetPool() failed to clear m_target_pool (size: %u)", m_target_pool.size()); - auto clear_list = m_member_pool; + auto clear_list = const_cast&>(m_member_pool); for (auto member_iter : clear_list) member_iter->LeaveHealRotationMemberPool(); @@ -183,13 +184,23 @@ bool HealRotation::ClearTargetPool() m_hot_target = nullptr; m_hot_active = false; m_is_active = false; - - auto clear_list = m_target_pool; + + auto clear_list = const_cast&>(m_target_pool); for (auto target_iter : clear_list) target_iter->LeaveHealRotationTargetPool(); - m_casting_target_poke = false; - bias_targets(); + //m_casting_target_poke = false; + //bias_targets(); + + // strange crash point... + // bias_targets() should be returning on m_target_pool.empty() + // and setting this two properties as below + m_casting_target_poke = true; + m_active_heal_target = false; + // instead, the list retains mob shared_ptrs and + // attempts to process them - and crashes program + // predominate when adaptive_healing = true + // (shared_ptr now has a delayed gc action? this did work before...) return m_target_pool.empty(); } diff --git a/zone/mob.cpp b/zone/mob.cpp index 39efb1d3d..9239e5a14 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2723,20 +2723,10 @@ bool Mob::HateSummon() { if(summon_level == 1) { entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() ); - if (target->IsClient()) { + if (target->IsClient()) target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), m_Position.x, m_Position.y, m_Position.z, target->GetHeading(), 0, SummonPC); - } - else { -#ifdef BOTS - if(target && target->IsBot()) { - // set pre summoning info to return to (to get out of melee range for caster) - target->CastToBot()->SetHasBeenSummoned(true); - target->CastToBot()->SetPreSummonLocation(glm::vec3(target->GetPosition())); - - } -#endif //BOTS + else target->GMMove(m_Position.x, m_Position.y, m_Position.z, target->GetHeading()); - } return true; } else if(summon_level == 2) { diff --git a/zone/mob.h b/zone/mob.h index f661a8600..a06485659 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -612,6 +612,7 @@ public: std::list& GetHateList() { return hate_list.GetHateList(); } bool CheckLosFN(Mob* other); bool CheckLosFN(float posX, float posY, float posZ, float mobSize); + static bool CheckLosFN(glm::vec3 posWatcher, float sizeWatcher, glm::vec3 posTarget, float sizeTarget); inline void SetChanged() { pLastChange = Timer::GetCurrentTime(); } inline const uint32 LastChange() const { return pLastChange; } inline void SetLastLosState(bool value) { last_los_check = value; }