diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 8037b40c..29c25ce1 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -225,156 +225,96 @@ PlayerbotAI::~PlayerbotAI() void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) { + // Handle the AI check delay if (nextAICheckDelay > elapsed) nextAICheckDelay -= elapsed; else nextAICheckDelay = 0; - if (!bot || !bot->IsInWorld()) - { + // Early return if bot is in invalid state + if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld()) return; - } - if (!bot->GetSession() || bot->GetSession()->isLogingOut()) - { - return; - } - if (bot->IsDuringRemoveFromWorld()) - { - return; - } - // if (!GetMaster() || !GetMaster()->IsInWorld() || !GetMaster()->GetSession() || - // GetMaster()->GetSession()->isLogingOut()) { - // return; - // } - // if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) { - // bot->Say("Falling!", LANG_UNIVERSAL); - // } - // if (!bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING) && bot->GetPositionZ() - bot->GetFloorZ() > 0.1f) { - // bot->AddUnitMovementFlag(MOVEMENTFLAG_FALLING); - // // bot->GetMotionMaster()->MoveFall(); - // } - // if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING) && bot->GetPositionZ() - bot->GetFloorZ() <= 0.1f) { - // bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING); - // } - // else { - // bot->RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING); - // } - - // bot->SendMovementFlagUpdate(); - - // bot->GetMotionMaster()->MoveFall(); - // if (bot->HasUnitMovementFlag(MOVEMENTFLAG_FALLING)) { - // // bot->GetUnitMovementFlags(); - // bot->Say("falling... flag: " + std::to_string(bot->GetUnitMovementFlags()), LANG_UNIVERSAL); - // } - // bot->SendMovementFlagUpdate(); - // float x, y, z; - // bot->GetPosition(x, y, z); - // bot->UpdateGroundPositionZ(x, y, z); - // if (bot->GetPositionZ() - z > 0.1f) { - - // } - - // wake up if in combat - // if (bot->IsInCombat()) - // { - // if (!inCombat) - // nextAICheckDelay = 0; - // else if (!AllowActivity()) - // { - // if (AllowActivity(ALL_ACTIVITY, true)) - // nextAICheckDelay = 0; - // } - - // inCombat = true; - // } - // else - // { - // if (inCombat) - // nextAICheckDelay = 0; - - // inCombat = false; - // } - - // force stop if moving but should not - // shouldn't stop charging - // if (bot->isMoving() && !CanMove() && !bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING)) - // { - // bot->StopMoving(); - // bot->GetMotionMaster()->Clear(); - // bot->GetMotionMaster()->MoveIdle(); - // } - // cheat options - if (bot->IsAlive() && ((uint32)GetCheat() > 0 || (uint32)sPlayerbotAIConfig->botCheatMask > 0)) + // Handle cheat options (set bot health and power if cheats are enabled) + if (bot->IsAlive() && (static_cast(GetCheat()) > 0 || static_cast(sPlayerbotAIConfig->botCheatMask) > 0)) { if (HasCheat(BotCheatMask::health)) bot->SetFullHealth(); + if (HasCheat(BotCheatMask::mana) && bot->getPowerType() == POWER_MANA) bot->SetPower(POWER_MANA, bot->GetMaxPower(POWER_MANA)); + if (HasCheat(BotCheatMask::power) && bot->getPowerType() != POWER_MANA) bot->SetPower(bot->getPowerType(), bot->GetMaxPower(bot->getPowerType())); } AllowActivity(); - if (!CanUpdateAI()) return; + // Handle the current spell Spell* currentSpell = bot->GetCurrentSpell(CURRENT_GENERIC_SPELL); if (!currentSpell) currentSpell = bot->GetCurrentSpell(CURRENT_CHANNELED_SPELL); - if (currentSpell && currentSpell->GetSpellInfo() && currentSpell->getState() == SPELL_STATE_PREPARING) + + if (currentSpell) { const SpellInfo* spellInfo = currentSpell->GetSpellInfo(); - Unit* spellTarget = currentSpell->m_targets.GetUnitTarget(); - // interrupt if target is dead - if (spellTarget && !spellTarget->IsAlive() && - !spellInfo->IsAllowingDeadTarget()) + if (spellInfo && currentSpell->getState() == SPELL_STATE_PREPARING) { - InterruptSpell(); - YieldThread(GetReactDelay()); - return; - } - - bool isHeal = false; - bool isSingleTarget = true; - - for (uint8 i = 0; i < 3; ++i) - { - if (!spellInfo->Effects[i].Effect) - continue; - - if (spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL || - spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MAX_HEALTH || - spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MECHANICAL) - isHeal = true; - - if ((spellInfo->Effects[i].TargetA.GetTarget() && spellInfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY) || - (spellInfo->Effects[i].TargetB.GetTarget() && spellInfo->Effects[i].TargetB.GetTarget() != TARGET_UNIT_TARGET_ALLY)) + Unit* spellTarget = currentSpell->m_targets.GetUnitTarget(); + // Interrupt if target is dead or spell can't target dead units + if (spellTarget && !spellTarget->IsAlive() && !spellInfo->IsAllowingDeadTarget()) { - isSingleTarget = false; + InterruptSpell(); + YieldThread(GetReactDelay()); + return; } - } - // interrupt if target ally has full health (heal by other member) - if (isHeal && isSingleTarget && spellTarget && spellTarget->IsFullHealth()) - { - InterruptSpell(); + + bool isHeal = false; + bool isSingleTarget = true; + + for (uint8 i = 0; i < 3; ++i) + { + if (!spellInfo->Effects[i].Effect) + continue; + + // Check if spell is a heal + if (spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL || + spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MAX_HEALTH || + spellInfo->Effects[i].Effect == SPELL_EFFECT_HEAL_MECHANICAL) + isHeal = true; + + // Check if spell is single-target + if ((spellInfo->Effects[i].TargetA.GetTarget() && spellInfo->Effects[i].TargetA.GetTarget() != TARGET_UNIT_TARGET_ALLY) || + (spellInfo->Effects[i].TargetB.GetTarget() && spellInfo->Effects[i].TargetB.GetTarget() != TARGET_UNIT_TARGET_ALLY)) + { + isSingleTarget = false; + } + } + + // Interrupt if target ally has full health (heal by other member) + if (isHeal && isSingleTarget && spellTarget && spellTarget->IsFullHealth()) + { + InterruptSpell(); + YieldThread(GetReactDelay()); + return; + } + + // Ensure bot is facing target if necessary + if (spellTarget && !bot->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget) && (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT)) + { + sServerFacade->SetFacingTo(bot, spellTarget); + } + + // Wait for spell cast YieldThread(GetReactDelay()); return; } - - if (spellTarget && !bot->HasInArc(CAST_ANGLE_IN_FRONT, spellTarget) && (spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT)) - { - sServerFacade->SetFacingTo(bot, spellTarget); - } - - // wait for spell cast - YieldThread(GetReactDelay()); - return; } + // Handle transport check delay if (nextTransportCheck > elapsed) nextTransportCheck -= elapsed; else @@ -385,6 +325,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) nextTransportCheck = 1000; Transport* newTransport = bot->GetMap()->GetTransportForPos(bot->GetPhaseMask(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot); + if (newTransport != bot->GetTransport()) { LOG_DEBUG("playerbots", "Bot {} is on a transport", bot->GetName()); @@ -399,31 +340,50 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) } } - if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && bot->GetGroup() && !bot->GetGroup()->isLFGGroup()) - { - Player* leader = bot->GetGroup()->GetLeader(); - if (leader && leader != bot) // Checks if the leader is valid and is not the bot itself - { - PlayerbotAI* leaderAI = GET_PLAYERBOT_AI(leader); - if (leaderAI && !leaderAI->IsRealPlayer()) - { - bot->RemoveFromGroup(); - ResetStrategies(); - } - } - } + // Update the bot's group status (moved to helper function) + UpdateAIGroupMembership(); - if (bot->GetGroup() && bot->GetGroup()->isLFGGroup()) + // Update internal AI + UpdateAIInternal(elapsed, minimal); + YieldThread(GetReactDelay()); +} + +// Helper function for UpdateAI to check group membership and handle removal if necessary +void PlayerbotAI::UpdateAIGroupMembership() +{ + if (!bot || !bot->GetGroup()) + return; + + Group* group = bot->GetGroup(); + + if (!bot->InBattleground() && !bot->inRandomLfgDungeon() && !group->isLFGGroup()) + { + Player* leader = group->GetLeader(); + if (leader && leader != bot) // Ensure the leader is valid and not the bot itself + { + PlayerbotAI* leaderAI = GET_PLAYERBOT_AI(leader); + if (leaderAI && !leaderAI->IsRealPlayer()) + { + bot->RemoveFromGroup(); + ResetStrategies(); + } + } + } + else if (group->isLFGGroup()) { bool hasRealPlayer = false; - for (GroupReference* ref = bot->GetGroup()->GetFirstMember(); ref; ref = ref->next()) + + // Iterate over all group members to check if at least one is a real player + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); if (!member) continue; + PlayerbotAI* memberAI = GET_PLAYERBOT_AI(member); if (memberAI && !memberAI->IsRealPlayer()) continue; + hasRealPlayer = true; break; } @@ -433,10 +393,6 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal) ResetStrategies(); } } - - bool min = minimal; - UpdateAIInternal(elapsed, min); - YieldThread(GetReactDelay()); } void PlayerbotAI::UpdateAIInternal([[maybe_unused]] uint32 elapsed, bool minimal) @@ -1307,8 +1263,9 @@ void PlayerbotAI::DoNextAction(bool min) return; } - // change engine if just died - if (currentEngine != engines[BOT_STATE_DEAD] && !bot->IsAlive()) + // Change engine if just died + bool isBotAlive = bot->IsAlive(); + if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive) { bot->StopMoving(); bot->GetMotionMaster()->Clear(); @@ -1331,17 +1288,18 @@ void PlayerbotAI::DoNextAction(bool min) return; } - // change engine if just ressed - if (currentEngine == engines[BOT_STATE_DEAD] && bot->IsAlive()) + // Change engine if just ressed + if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive) { ChangeEngine(BOT_STATE_NON_COMBAT); return; } - // if in combat but stick with old data - clear targets + // Clear targets if in combat but sticking with old data if (currentEngine == engines[BOT_STATE_NON_COMBAT] && bot->IsInCombat()) { - if (aiObjectContext->GetValue("current target")->Get() != nullptr) + Unit* currentTarget = aiObjectContext->GetValue("current target")->Get(); + if (currentTarget != nullptr) { aiObjectContext->GetValue("current target")->Set(nullptr); } @@ -1367,7 +1325,7 @@ void PlayerbotAI::DoNextAction(bool min) if (master) masterBotAI = GET_PLAYERBOT_AI(master); - // test BG master set + // Test BG master set if ((!master || (masterBotAI && !masterBotAI->IsRealPlayer())) && group) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); @@ -1375,6 +1333,7 @@ void PlayerbotAI::DoNextAction(bool min) { return; } + // Ideally we want to have the leader as master. Player* newMaster = botAI->GetGroupMaster(); Player* playerMaster = nullptr; @@ -1385,20 +1344,7 @@ void PlayerbotAI::DoNextAction(bool min) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - - if (!member) - continue; - - if (member == bot) - continue; - - if (member == newMaster) - continue; - - if (!member->IsInWorld()) - continue; - - if (!member->IsInSameRaidWith(bot)) + if (!member || member == bot || member == newMaster || !member->IsInWorld() || !member->IsInSameRaidWith(bot)) continue; PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member); @@ -1410,38 +1356,20 @@ void PlayerbotAI::DoNextAction(bool min) continue; } - // same BG + // Same BG checks (optimize checking conditions here) if (bot->InBattleground() && bot->GetBattleground() && bot->GetBattleground()->GetBgTypeID() == BATTLEGROUND_AV && !GET_PLAYERBOT_AI(member) && member->InBattleground() && bot->GetMapId() == member->GetMapId()) { - // TODO disable move to objective if have master in bg - continue; - - if (!group->SameSubGroup(bot, member)) + // Skip if same BG but same subgroup or lower level + if (!group->SameSubGroup(bot, member) || member->GetLevel() < bot->GetLevel()) continue; - if (member->GetLevel() < bot->GetLevel()) + // Follow real player only if higher honor points + uint32 honorpts = member->GetHonorPoints(); + if (bot->GetHonorPoints() && honorpts < bot->GetHonorPoints()) continue; - // follow real player only if he has more honor/arena points - if (bot->GetBattleground()->isArena()) - { - if (group->IsLeader(member->GetGUID())) - { - playerMaster = member; - break; - } - else - continue; - } - else - { - uint32 honorpts = member->GetHonorPoints(); - if (bot->GetHonorPoints() && honorpts < bot->GetHonorPoints()) - continue; - } - playerMaster = member; continue; } @@ -1450,7 +1378,6 @@ void PlayerbotAI::DoNextAction(bool min) continue; newMaster = member; - break; } } @@ -1474,15 +1401,15 @@ void PlayerbotAI::DoNextAction(bool min) if (master && master->IsInWorld()) { - if (master->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING) && - sServerFacade->GetDistance2d(bot, master) < 20.0f) + float distance = sServerFacade->GetDistance2d(bot, master); + if (master->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_WALKING) && distance < 20.0f) bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_WALKING); else bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_WALKING); if (master->IsSitState() && nextAICheckDelay < 1000) { - if (!bot->isMoving() && sServerFacade->GetDistance2d(bot, master) < 10.0f) + if (!bot->isMoving() && distance < 10.0f) bot->SetStandState(UNIT_STAND_STATE_SIT); } else if (nextAICheckDelay < 1000) @@ -1500,51 +1427,6 @@ void PlayerbotAI::DoNextAction(bool min) bot->RemoveAurasByType(SPELL_AURA_MOD_INCREASE_MOUNTED_SPEED); bot->RemoveAurasByType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED); } - - // if (bot->IsFlying() && !bot->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && - // !bot->HasAuraType(SPELL_AURA_FLY)) - // { - // if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FLYING)) - // bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FLYING); - - // if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_CAN_FLY)) - // bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_CAN_FLY); - - // if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY)) - // bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY); - // } - - /* - // land after kncokback/jump - if (bot->m_movementInfo.HasMovementFlag(MOVEMENTFLAG_FALLING)) - { - // stop movement - bot->StopMoving(); - bot->GetMotionMaster()->Clear(); - bot->GetMotionMaster()->MoveIdle(); - - // remove moveFlags - bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_FALLING); - bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_PENDING_STOP); - - // set jump destination - bot->m_movementInfo.pos = !GetJumpDestination().m_positionZ == 0 ? GetJumpDestination() : - bot->GetPosition(); bot->m_movementInfo.jump = MovementInfo::JumpInfo(); - - WorldPacket land(MSG_MOVE_FALL_LAND); - land << bot->GetGUID().WriteAsPacked(); - bot->m_mover->BuildMovementPacket(&land); - bot->GetSession()->HandleMovementOpcodes(land); - - // move stop - WorldPacket stop(MSG_MOVE_STOP); - stop << bot->GetGUID().WriteAsPacked(); - bot->m_mover->BuildMovementPacket(&stop); - bot->GetSession()->HandleMovementOpcodes(stop); - - ResetJumpDestination(); - } - */ } void PlayerbotAI::ReInitCurrentEngine() @@ -1835,33 +1717,28 @@ bool PlayerbotAI::IsHealAssistantOfIndex(Player* player, int index) { return false; } + Group::MemberSlotList const& slots = group->GetMemberSlots(); int counter = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (group->IsAssistant(member->GetGUID()) && IsHeal(member)) + + if (IsHeal(member)) // Check if the member is a healer { - if (index == counter) - { - return player == member; - } - counter++; - } - } - // not enough - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!group->IsAssistant(member->GetGUID()) && IsHeal(member)) - { - if (index == counter) + bool isAssistant = group->IsAssistant(member->GetGUID()); + + // Check if the index matches for both assistant and non-assistant healers + if ((isAssistant && index == counter) || (!isAssistant && index == counter)) { return player == member; } + counter++; } } + return false; } @@ -1872,33 +1749,28 @@ bool PlayerbotAI::IsRangedDpsAssistantOfIndex(Player* player, int index) { return false; } + Group::MemberSlotList const& slots = group->GetMemberSlots(); int counter = 0; + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { Player* member = ref->GetSource(); - if (group->IsAssistant(member->GetGUID()) && IsRangedDps(member)) + + if (IsRangedDps(member)) // Check if the member is a ranged DPS { - if (index == counter) - { - return player == member; - } - counter++; - } - } - // not enough - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!group->IsAssistant(member->GetGUID()) && IsRangedDps(member)) - { - if (index == counter) + bool isAssistant = group->IsAssistant(member->GetGUID()); + + // Check the index for both assistant and non-assistant ranges + if ((isAssistant && index == counter) || (!isAssistant && index == counter)) { return player == member; } + counter++; } } + return false; } @@ -2744,44 +2616,50 @@ bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, boo std::wstring wnamepart; if (!Utf8toWStr(name, wnamepart)) - return 0; + return false; wstrToLower(wnamepart); int auraAmount = 0; + // Iterate through all aura types for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; auraType++) { Unit::AuraEffectList const& auras = unit->GetAuraEffectsByType((AuraType)auraType); if (auras.empty()) continue; + // Iterate through each aura effect for (AuraEffect const* aurEff : auras) { - SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + if (!aurEff) + continue; + SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + if (!spellInfo) + continue; + + // Check if the aura name matches std::string_view const auraName = spellInfo->SpellName[0]; if (auraName.empty() || auraName.length() != wnamepart.length() || !Utf8FitTo(auraName, wnamepart)) continue; + // Check if this is a valid aura for the bot if (IsRealAura(bot, aurEff, unit)) { - if (checkIsOwner && aurEff) - { - if (aurEff->GetCasterGUID() != bot->GetGUID()) - continue; - } + // Check caster if necessary + if (checkIsOwner && aurEff->GetCasterGUID() != bot->GetGUID()) + continue; - if (checkDuration && aurEff) - { - if (aurEff->GetBase()->GetDuration() == -1) - { - continue; - } - } + // Check aura duration if necessary + if (checkDuration && aurEff->GetBase()->GetDuration() == -1) + continue; + + // Count stacks and charges uint32 maxStackAmount = spellInfo->StackAmount; uint32 maxProcCharges = spellInfo->ProcCharges; + // Count the aura based on max stack and proc charges if (maxStack) { if (maxStackAmount && aurEff->GetBase()->GetStackAmount() >= maxStackAmount) @@ -2794,12 +2672,15 @@ bool PlayerbotAI::HasAura(std::string const name, Unit* unit, bool maxStack, boo { auraAmount++; } - if (maxAuraAmount < 0) - return auraAmount > 0; + + // Early exit if maxAuraAmount is reached + if (maxAuraAmount < 0 && auraAmount > 0) + return true; } } } + // Return based on the maximum aura amount conditions if (maxAuraAmount >= 0) { return auraAmount == maxAuraAmount || (auraAmount > 0 && auraAmount <= maxAuraAmount); @@ -2824,6 +2705,8 @@ bool PlayerbotAI::HasAura(uint32 spellId, Unit const* unit) // return false; } +// SAW + Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner, bool checkDuration, int checkStack) { if (!unit) @@ -2835,7 +2718,7 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner wstrToLower(wnamepart); - for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; auraType++) + for (uint32 auraType = SPELL_AURA_BIND_SIGHT; auraType < TOTAL_AURAS; ++auraType) { Unit::AuraEffectList const& auras = unit->GetAuraEffectsByType((AuraType)auraType); if (auras.empty()) @@ -2844,36 +2727,28 @@ Aura* PlayerbotAI::GetAura(std::string const name, Unit* unit, bool checkIsOwner for (AuraEffect const* aurEff : auras) { SpellInfo const* spellInfo = aurEff->GetSpellInfo(); + std::string const& auraName = spellInfo->SpellName[0]; - std::string const auraName = spellInfo->SpellName[0]; + // Directly skip if name mismatch (both length and content) if (auraName.empty() || auraName.length() != wnamepart.length() || !Utf8FitTo(auraName, wnamepart)) continue; - if (IsRealAura(bot, aurEff, unit)) - { - if (checkIsOwner && aurEff) - { - if (aurEff->GetCasterGUID() != bot->GetGUID()) - continue; - } + if (!IsRealAura(bot, aurEff, unit)) + continue; - if (checkDuration && aurEff) - { - if (aurEff->GetBase()->GetDuration() == -1) - { - continue; - } - } + // Check owner if necessary + if (checkIsOwner && aurEff->GetCasterGUID() != bot->GetGUID()) + continue; - if (checkStack != -1 && aurEff) - { - if (aurEff->GetBase()->GetStackAmount() < checkStack) - { - continue; - } - } - return aurEff->GetBase(); - } + // Check duration if necessary + if (checkDuration && aurEff->GetBase()->GetDuration() == -1) + continue; + + // Check stack if necessary + if (checkStack != -1 && aurEff->GetBase()->GetStackAmount() < checkStack) + continue; + + return aurEff->GetBase(); } } @@ -2889,15 +2764,14 @@ bool PlayerbotAI::HasAnyAuraOf(Unit* player, ...) va_start(vl, player); const char* cur; - do + while ((cur = va_arg(vl, const char*)) != nullptr) { - cur = va_arg(vl, const char*); - if (cur && HasAura(cur, player)) + if (HasAura(cur, player)) { va_end(vl); return true; } - } while (cur); + } va_end(vl); return false; @@ -4285,7 +4159,6 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) } } - // In bg queue. Speed up bg queue/join. if (bot->InBattlegroundQueue()) { @@ -4937,124 +4810,62 @@ void PlayerbotAI::Ping(float x, float y) } } -// Find Poison ...Natsukawa +// Helper function to iterate through items in the inventory and bags +Item* PlayerbotAI::FindItemInInventory(std::function checkItem) const +{ + // List out items in the main backpack + for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; ++slot) + { + if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + ItemTemplate const* pItemProto = pItem->GetTemplate(); + if (pItemProto && bot->CanUseItem(pItemProto) == EQUIP_ERR_OK && checkItem(pItemProto)) + return pItem; + } + } + + // List out items in other removable backpacks + for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) + { + if (Bag const* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) + { + for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) + { + if (Item* const pItem = bot->GetItemByPos(bag, slot)) + { + ItemTemplate const* pItemProto = pItem->GetTemplate(); + if (pItemProto && bot->CanUseItem(pItemProto) == EQUIP_ERR_OK && checkItem(pItemProto)) + return pItem; + } + } + } + } + + return nullptr; +} + +// Find Poison Item* PlayerbotAI::FindPoison() const { - // list out items in main backpack - for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) - { - if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6) - return pItem; - } - } - // list out items in other removable backpacks - for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) - { - if (Bag const* pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) - { - for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) - { - if (Item* const pItem = bot->GetItemByPos(bag, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && - pItemProto->SubClass == ITEM_SUBCLASS_ITEM_ENHANCEMENT) - return pItem; - } - } - } - } - - return nullptr; + return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool { + return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == 6; + }); } +// Find Consumable Item* PlayerbotAI::FindConsumable(uint32 displayId) const { - // list out items in main backpack - for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) - { - if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if ((pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && - pItemProto->DisplayInfoID == displayId) - return pItem; - } - } - - // list out items in other removable backpacks - for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) - { - if (Bag const* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) - { - for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) - { - if (Item* const pItem = bot->GetItemByPos(bag, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if ((pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && - pItemProto->DisplayInfoID == displayId) - return pItem; - } - } - } - } - - return nullptr; + return FindItemInInventory([displayId](ItemTemplate const* pItemProto) -> bool { + return (pItemProto->Class == ITEM_CLASS_CONSUMABLE || pItemProto->Class == ITEM_CLASS_TRADE_GOODS) && pItemProto->DisplayInfoID == displayId; + }); } +// Find Bandage Item* PlayerbotAI::FindBandage() const { - // list out items in main backpack - for (uint8 slot = INVENTORY_SLOT_ITEM_START; slot < INVENTORY_SLOT_ITEM_END; slot++) - { - if (Item* const pItem = bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) - return pItem; - } - } - - // list out items in other removable backpacks - for (uint8 bag = INVENTORY_SLOT_BAG_START; bag < INVENTORY_SLOT_BAG_END; ++bag) - { - if (Bag const* const pBag = (Bag*)bot->GetItemByPos(INVENTORY_SLOT_BAG_0, bag)) - { - for (uint8 slot = 0; slot < pBag->GetBagSize(); ++slot) - { - if (Item* const pItem = bot->GetItemByPos(bag, slot)) - { - ItemTemplate const* pItemProto = pItem->GetTemplate(); - if (!pItemProto || bot->CanUseItem(pItemProto) != EQUIP_ERR_OK) - continue; - - if (pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE) - return pItem; - } - } - } - } - - return nullptr; + return FindItemInInventory([](ItemTemplate const* pItemProto) -> bool { + return pItemProto->Class == ITEM_CLASS_CONSUMABLE && pItemProto->SubClass == ITEM_SUBCLASS_BANDAGE; + }); } static const uint32 uPriorizedSharpStoneIds[8] = {ADAMANTITE_SHARPENING_DISPLAYID, FEL_SHARPENING_DISPLAYID, @@ -6057,53 +5868,66 @@ std::set PlayerbotAI::GetCurrentIncompleteQuestIds() uint32 PlayerbotAI::GetReactDelay() { - uint32 base = sPlayerbotAIConfig->reactDelay; - // old calculate method + uint32 base = sPlayerbotAIConfig->reactDelay; // Default 100(ms) + + // If dynamic react delay is disabled, use a static calculation if (!sPlayerbotAIConfig->dynamicReactDelay) { - inCombat = bot->IsInCombat(); - bool min = false; - // test fix lags because of BG - bool inBG = bot->InBattleground() || bot->InArena(); - if (bot && !inCombat) - min = true; + if (HasRealPlayerMaster()) + return base; - if (HasRealPlayerMaster() || (sPlayerbotAIConfig->fastReactInBG && inBG)) - min = false; - if (min) - return base * 10; + bool inBG = bot->InBattleground() || bot->InArena(); + if (sPlayerbotAIConfig->fastReactInBG && inBG) + return base; + + bool inCombat = bot->IsInCombat(); + + if (!inCombat) + return base * 10.0f; + + else if (inCombat) + return base * 2.5f; return base; } - float multiplier = 1.0f; + // Dynamic react delay calculation: if (HasRealPlayerMaster()) + return base; + + float multiplier = 1.0f; + bool inBG = bot->InBattleground() || bot->InArena(); + + if (inBG) { - multiplier = 1.0f; - return base * multiplier; - } - - bool inBg = bot->InBattleground() || bot->InArena(); - if (inBg) - { - multiplier = sPlayerbotAIConfig->fastReactInBG ? 1.0f : 10.0f; - return base * multiplier; + if (bot->IsInCombat() || currentState == BOT_STATE_COMBAT) + { + multiplier = sPlayerbotAIConfig->fastReactInBG ? 2.5f : 5.0f; + return base * multiplier; + } + else + { + multiplier = sPlayerbotAIConfig->fastReactInBG ? 1.0f : 10.0f; + return base * multiplier; + } } + // When in combat, return 5 times the base if (bot->IsInCombat() || currentState == BOT_STATE_COMBAT) { multiplier = 5.0f; return base * multiplier; } - bool isResting = bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING); - if (!isResting) + // When not resting, return 10-30 times the base + if (!bot->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING)) { multiplier = urand(10, 30); return base * multiplier; } + // In other cases, return 20-200 times the base multiplier = urand(20, 200); return base * multiplier; } diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index 7c61d48b..2be8030e 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -579,7 +579,8 @@ private: static void _fillGearScoreData(Player* player, Item* item, std::vector* gearScore, uint32& twoHandScore, bool mixed = false); bool IsTellAllowed(PlayerbotSecurityLevel securityLevel = PLAYERBOT_SECURITY_ALLOW_ALL); - + void UpdateAIGroupMembership(); + Item* FindItemInInventory(std::function checkItem) const; void HandleCommands(); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); bool _isBotInitializing = false;