From 3c05e47cb2dc8d17cb3a8effb3451d84688a6504 Mon Sep 17 00:00:00 2001 From: Brian Date: Sun, 1 Jun 2025 09:31:29 +0200 Subject: [PATCH] CMaNGOS Playerbots "lfg" command implemented (#1291) * CMaNGOS Playerbots "lfg" command implemented * Remove logging, fix warning, add suggestion - Remove LOG_INFO's console clutter, since 'lfg' command is working correctly now. - Warning C26813 fixed for: placeholders["%role"] = (role == BOT_ROLE_TANK ? "tank" : (role == BOT_ROLE_HEALER ? "healer" : "dps")); - Added suggestion to let bots do autogear & maintenance, so players can instantly start their dungeon or raid activities without manually having to configure the playerbots gear. It could save a lot of time. This is up to discussion for playerbots maintainers. --- src/PlayerbotAI.cpp | 1 + src/strategy/PassiveMultiplier.cpp | 1 + .../actions/AcceptInvitationAction.cpp | 9 +- src/strategy/actions/ChatActionContext.h | 2 + src/strategy/actions/InviteToGroupAction.cpp | 330 +++++++++++++++--- src/strategy/actions/InviteToGroupAction.h | 24 +- .../generic/ChatCommandHandlerStrategy.cpp | 1 + src/strategy/triggers/ChatTriggerContext.h | 2 + 8 files changed, 302 insertions(+), 68 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index c24f076d..20c04971 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -797,6 +797,7 @@ bool PlayerbotAI::IsAllowedCommand(std::string const text) unsecuredCommands.insert("sendmail"); unsecuredCommands.insert("invite"); unsecuredCommands.insert("leave"); + unsecuredCommands.insert("lfg"); unsecuredCommands.insert("rpg status"); } diff --git a/src/strategy/PassiveMultiplier.cpp b/src/strategy/PassiveMultiplier.cpp index ddcef847..461947d8 100644 --- a/src/strategy/PassiveMultiplier.cpp +++ b/src/strategy/PassiveMultiplier.cpp @@ -19,6 +19,7 @@ PassiveMultiplier::PassiveMultiplier(PlayerbotAI* botAI) : Multiplier(botAI, "pa allowedActions.push_back("nc"); allowedActions.push_back("reset botAI"); allowedActions.push_back("check mount state"); + allowedActions.push_back("lfg"); } if (allowedParts.empty()) diff --git a/src/strategy/actions/AcceptInvitationAction.cpp b/src/strategy/actions/AcceptInvitationAction.cpp index 4b8e3159..6c246992 100644 --- a/src/strategy/actions/AcceptInvitationAction.cpp +++ b/src/strategy/actions/AcceptInvitationAction.cpp @@ -22,8 +22,7 @@ bool AcceptInvitationAction::Execute(Event event) std::string name; packet >> flag >> name; - // Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID()); - Player* inviter = ObjectAccessor::FindPlayerByName(name, true); + Player* inviter = ObjectAccessor::FindPlayer(grp->GetLeaderGUID()); if (!inviter) return false; @@ -36,11 +35,17 @@ bool AcceptInvitationAction::Execute(Event event) return false; } + if (bot->isAFK()) + bot->ToggleAFK(); + WorldPacket p; uint32 roles_mask = 0; p << roles_mask; bot->GetSession()->HandleGroupAcceptOpcode(p); + if (!bot->GetGroup() || !bot->GetGroup()->IsMember(inviter->GetGUID())) + return false; + if (sRandomPlayerbotMgr->IsRandomBot(bot)) botAI->SetMaster(inviter); // else diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 7b7e10b2..4d2fe064 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -183,6 +183,7 @@ public: creators["bwl chat shortcut"] = &ChatActionContext::bwl_chat_shortcut; creators["tell estimated dps"] = &ChatActionContext::tell_estimated_dps; creators["join"] = &ChatActionContext::join; + creators["lfg"] = &ChatActionContext::lfg; creators["calc"] = &ChatActionContext::calc; } @@ -212,6 +213,7 @@ private: static Action* spirit_healer(PlayerbotAI* botAI) { return new SpiritHealerAction(botAI); } static Action* rti(PlayerbotAI* botAI) { return new RtiAction(botAI); } static Action* invite(PlayerbotAI* botAI) { return new InviteToGroupAction(botAI); } + static Action* lfg(PlayerbotAI* botAI) { return new LfgAction(botAI); } static Action* spell(PlayerbotAI* botAI) { return new TellSpellAction(botAI); } static Action* cast_custom_spell(PlayerbotAI* botAI) { return new CastCustomSpellAction(botAI); } static Action* cast_custom_nc_spell(PlayerbotAI* botAI) { return new CastCustomNcSpellAction(botAI); } diff --git a/src/strategy/actions/InviteToGroupAction.cpp b/src/strategy/actions/InviteToGroupAction.cpp index 7857f6bd..09b2487d 100644 --- a/src/strategy/actions/InviteToGroupAction.cpp +++ b/src/strategy/actions/InviteToGroupAction.cpp @@ -5,35 +5,27 @@ #include "InviteToGroupAction.h" +#include "BroadcastHelper.h" #include "Event.h" #include "GuildMgr.h" +#include "Log.h" #include "Playerbots.h" #include "ServerFacade.h" -#include "BroadcastHelper.h" -bool InviteToGroupAction::Execute(Event event) +bool InviteToGroupAction::Invite(Player* inviter, Player* player) { - Player* master = event.getOwner(); - if (!master) + if (!player) return false; - return Invite(master); -} - -bool InviteToGroupAction::Invite(Player* player) -{ - if (!player || !player->IsInWorld()) - return false; - - if (bot == player) + if (inviter == player) return false; if (!GET_PLAYERBOT_AI(player) && !botAI->GetSecurity()->CheckLevelFor(PLAYERBOT_SECURITY_INVITE, true, player)) return false; - if (Group* group = player->GetGroup()) + if (Group* group = inviter->GetGroup()) { - if(GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer()) + if (GET_PLAYERBOT_AI(player) && !GET_PLAYERBOT_AI(player)->IsRealPlayer()) if (!group->isRaidGroup() && group->GetMembersCount() > 4) group->ConvertToRaid(); } @@ -42,7 +34,7 @@ bool InviteToGroupAction::Invite(Player* player) uint32 roles_mask = 0; p << player->GetName(); p << roles_mask; - bot->GetSession()->HandleGroupInviteOpcode(p); + inviter->GetSession()->HandleGroupInviteOpcode(p); return true; } @@ -65,15 +57,23 @@ bool InviteNearbyToGroupAction::Execute(Event event) if (player->GetGroup()) continue; + if (!sPlayerbotAIConfig->randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer()) + continue; + + Group* group = bot->GetGroup(); + if (player->isDND()) continue; if (player->IsBeingTeleported()) continue; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) { - if (botAI->GetGrouperType() == GrouperType::SOLO && !botAI->HasRealPlayerMaster()) // Do not invite solo players. + if (botAI->GetGrouperType() == GrouperType::SOLO && + !botAI->HasRealPlayerMaster()) // Do not invite solo players. continue; if (botAI->HasActivePlayerMaster()) // Do not invite alts of active players. @@ -86,27 +86,25 @@ bool InviteNearbyToGroupAction::Execute(Event event) if (sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) continue; - Group* group = bot->GetGroup(); - Guild* guild = sGuildMgr->GetGuildById(bot->GetGuildId()); - if (!botAI->HasActivePlayerMaster()) - { - if (guild && bot->GetGuildId() == player->GetGuildId()) - { - BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); - } - else - { - std::map placeholders; - placeholders["%player"] = player->GetName(); + // When inviting the 5th member of the group convert to raid for future invites. + if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() && + bot->GetGroup()->GetMembersCount() > 3) + group->ConvertToRaid(); - if (group && group->isRaidGroup()) - bot->Say(BOT_TEXT2("join_raid", placeholders), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); - else - bot->Say(BOT_TEXT2("join_group", placeholders), (bot->GetTeamId() == TEAM_ALLIANCE ? LANG_COMMON : LANG_ORCISH)); - } + if (sPlayerbotAIConfig->inviteChat && sRandomPlayerbotMgr->IsRandomBot(bot)) + { + std::map placeholders; + placeholders["%player"] = player->GetName(); + + if (group && group->isRaidGroup()) + bot->Say(BOT_TEXT2("join_raid", placeholders), + (bot->GetTeamId() == ALLIANCE ? LANG_COMMON : LANG_ORCISH)); + else + bot->Say(BOT_TEXT2("join_group", placeholders), + (bot->GetTeamId() == ALLIANCE ? LANG_COMMON : LANG_ORCISH)); } - return Invite(player); + return Invite(bot, player); } return false; @@ -114,6 +112,9 @@ bool InviteNearbyToGroupAction::Execute(Event event) bool InviteNearbyToGroupAction::isUseful() { + if (!sPlayerbotAIConfig->randomBotGroupNearby) + return false; + if (bot->InBattleground()) return false; @@ -121,18 +122,22 @@ bool InviteNearbyToGroupAction::isUseful() return false; GrouperType grouperType = botAI->GetGrouperType(); + if (grouperType == GrouperType::SOLO || grouperType == GrouperType::MEMBER) return false; - if (Group* group = bot->GetGroup()) + Group* group = bot->GetGroup(); + + if (group) { - if (group->IsFull()) + if (group->isRaidGroup() && group->IsFull()) return false; if (botAI->GetGroupMaster() != bot) return false; uint32 memberCount = group->GetMembersCount(); + if (memberCount >= uint8(grouperType)) return false; } @@ -173,62 +178,273 @@ bool InviteGuildToGroupAction::Execute(Event event) if (player->isDND()) continue; + if (!sPlayerbotAIConfig->randomBotInvitePlayer && GET_PLAYERBOT_AI(player)->IsRealPlayer()) + continue; + if (player->IsBeingTeleported()) continue; + if (player->GetMapId() != bot->GetMapId() && player->GetLevel() < 30) + continue; + + if (WorldPosition(player).distance(bot) > 1000 && player->GetLevel() < 15) + continue; + PlayerbotAI* playerAi = GET_PLAYERBOT_AI(player); if (playerAi) { if (playerAi->GetGrouperType() == GrouperType::SOLO && - !playerAi->HasRealPlayerMaster()) // Do not invite solo players. + !playerAi->HasRealPlayerMaster()) // Do not invite solo players. continue; - if (playerAi->HasActivePlayerMaster()) // Do not invite alts of active players. + if (playerAi->HasActivePlayerMaster()) // Do not invite alts of active players. continue; - if (player->GetLevel() > bot->GetLevel() + 5) // Invite higher levels that need money so they can grind money and help out. + if (player->GetLevel() > + bot->GetLevel() + 5) // Invite higher levels that need money so they can grind money and help out. { - if (!PAI_VALUE(bool,"should get money")) + if (!PAI_VALUE(bool, "should get money")) continue; } } - else - { - if (!sPlayerbotAIConfig->randomBotGroupNearby) - return false; - } - if (bot->GetLevel() > player->GetLevel() + 5) // Do not invite members that too low level or risk dragging them to deadly places. + if (bot->GetLevel() > + player->GetLevel() + 5) // Do not invite members that too low level or risk dragging them to deadly places. continue; if (!playerAi && sServerFacade->GetDistance2d(bot, player) > sPlayerbotAIConfig->sightDistance) continue; Group* group = bot->GetGroup(); - BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); - return Invite(player); + // When inviting the 5th member of the group convert to raid for future invites. + if (group && botAI->GetGrouperType() > GrouperType::LEADER_5 && !group->isRaidGroup() && + bot->GetGroup()->GetMembersCount() > 3) + { + group->ConvertToRaid(); + } + + if (sPlayerbotAIConfig->inviteChat && + (sRandomPlayerbotMgr->IsRandomBot(bot) || !botAI->HasActivePlayerMaster())) + { + BroadcastHelper::BroadcastGuildGroupOrRaidInvite(botAI, bot, player, group); + } + + return Invite(bot, player); } return false; } -bool InviteGuildToGroupAction::isUseful() -{ - return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful(); -}; - bool JoinGroupAction::Execute(Event event) { + if (bot->InBattleground()) + return false; + + if (bot->InBattlegroundQueue()) + return false; + Player* master = event.getOwner(); + Group* group = master->GetGroup(); - if (group && (group->IsFull() || bot->GetGroup() == group)) + if (group) + { + if (group->IsFull()) + return false; + + if (bot->GetGroup() == group) + return false; + } + + if (bot->GetGroup()) + { + if (botAI->HasRealPlayerMaster()) + return false; + + if (!botAI->DoSpecificAction("leave", event, true)) + return false; + } + + return Invite(master, bot); +} + +bool LfgAction::Execute(Event event) +{ + Player* requester = event.getOwner() ? event.getOwner() : GetMaster(); + + if (bot->InBattleground()) + return false; + + if (bot->InBattlegroundQueue()) + return false; + + if (!botAI->IsSafe(requester)) + return false; + + if (requester->GetLevel() == DEFAULT_MAX_LEVEL && bot->GetLevel() != DEFAULT_MAX_LEVEL) + return false; + + if (requester->GetLevel() > bot->GetLevel() + 4 || bot->GetLevel() > requester->GetLevel() + 4) + return false; + + std::string param = event.getParam(); + + if (!param.empty() && param != "40" && param != "25" && param != "20" && param != "10" && param != "5") + return false; + + Group* group = requester->GetGroup(); + + std::unordered_map> allowedClassNr; + std::unordered_map allowedRoles; + + allowedRoles[BOT_ROLE_TANK] = 1; + allowedRoles[BOT_ROLE_HEALER] = 1; + allowedRoles[BOT_ROLE_DPS] = 3; + + BotRoles role = botAI->IsTank(requester, false) + ? BOT_ROLE_TANK + : (botAI->IsHeal(requester, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + Classes cls = (Classes)requester->getClass(); + + if (group) + { + if (param.empty() && group->isRaidGroup()) + // Default to WotLK Raiding. Max size 25 + param = "25"; + // Select optimal group layout. + if (param == "40") + { + allowedRoles[BOT_ROLE_TANK] = 4; + allowedRoles[BOT_ROLE_HEALER] = 16; + allowedRoles[BOT_ROLE_DPS] = 20; + /* + allowedClassNr[CLASS_PALADIN][BOT_ROLE_TANK] = 0; + allowedClassNr[CLASS_DRUID][BOT_ROLE_TANK] = 1; + + allowedClassNr[CLASS_DRUID][BOT_ROLE_HEALER] = 3; + allowedClassNr[CLASS_PALADIN][BOT_ROLE_HEALER] = 4; + allowedClassNr[CLASS_SHAMAN][BOT_ROLE_HEALER] = 4; + allowedClassNr[CLASS_PRIEST][BOT_ROLE_HEALER] = 11; + + allowedClassNr[CLASS_WARRIOR][BOT_ROLE_DPS] = 8; + allowedClassNr[CLASS_PALADIN][BOT_ROLE_DPS] = 4; + allowedClassNr[CLASS_HUNTER][BOT_ROLE_DPS] = 4; + allowedClassNr[CLASS_ROGUE][BOT_ROLE_DPS] = 6; + allowedClassNr[CLASS_PRIEST][BOT_ROLE_DPS] = 1; + allowedClassNr[CLASS_SHAMAN][BOT_ROLE_DPS] = 4; + allowedClassNr[CLASS_MAGE][BOT_ROLE_DPS] = 15; + allowedClassNr[CLASS_WARLOCK][BOT_ROLE_DPS] = 4; + allowedClassNr[CLASS_DRUID][BOT_ROLE_DPS] = 1; + */ + } + else if (param == "25") + { + allowedRoles[BOT_ROLE_TANK] = 3; + allowedRoles[BOT_ROLE_HEALER] = 7; + allowedRoles[BOT_ROLE_DPS] = 15; + } + else if (param == "20") + { + allowedRoles[BOT_ROLE_TANK] = 2; + allowedRoles[BOT_ROLE_HEALER] = 5; + allowedRoles[BOT_ROLE_DPS] = 13; + } + else if (param == "10") + { + allowedRoles[BOT_ROLE_TANK] = 2; + allowedRoles[BOT_ROLE_HEALER] = 3; + allowedRoles[BOT_ROLE_DPS] = 5; + } + + if (group->IsFull()) + { + if (param.empty() || param == "5" || group->isRaidGroup()) + return false; // Group or raid is full so stop trying. + else + group->ConvertToRaid(); // We want a raid but are in a group so convert and continue. + } + + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + // Only add group member targets that are alive and near the player + Player* player = ObjectAccessor::FindPlayer(itr->guid); + + if (!botAI->IsSafe(player)) + return false; + + role = botAI->IsTank(player, false) ? BOT_ROLE_TANK + : (botAI->IsHeal(player, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + cls = (Classes)player->getClass(); + + if (allowedRoles[role] > 0) + allowedRoles[role]--; + + if (allowedClassNr[cls].find(role) != allowedClassNr[cls].end() && allowedClassNr[cls][role] > 0) + allowedClassNr[cls][role]--; + } + } + else + { + if (allowedRoles[role] > 0) + allowedRoles[role]--; + + if (allowedClassNr[cls].find(role) != allowedClassNr[cls].end() && allowedClassNr[cls][role] > 0) + allowedClassNr[cls][role]--; + } + + role = botAI->IsTank(bot, false) ? BOT_ROLE_TANK : (botAI->IsHeal(bot, false) ? BOT_ROLE_HEALER : BOT_ROLE_DPS); + cls = (Classes)bot->getClass(); + + if (allowedRoles[role] == 0) + return false; + + if (allowedClassNr[cls].find(role) != allowedClassNr[cls].end() && allowedClassNr[cls][role] == 0) return false; if (bot->GetGroup()) - if (!botAI->DoSpecificAction("leave", event, true)) + { + if (botAI->HasRealPlayerMaster()) return false; - return Invite(bot); + if (!botAI->DoSpecificAction("leave", event, true)) + return false; + } + + bool invite = Invite(requester, bot); + + if (invite) + { + Event acceptEvent("accept invitation", requester ? requester->GetGUID() : ObjectGuid::Empty); + if (!botAI->DoSpecificAction("accept invitation", acceptEvent, true)) + return false; + + std::map placeholders; + placeholders["%role"] = (role & BOT_ROLE_TANK ? "tank" : (role & BOT_ROLE_HEALER ? "healer" : "dps")); + placeholders["%spotsleft"] = std::to_string(allowedRoles[role] - 1); + + std::ostringstream out; + if (allowedRoles[role] > 1) + { + out << "Joining as " << placeholders["%role"] << ", " << placeholders["%spotsleft"] << " " + << placeholders["%role"] << " spots left."; + botAI->TellMasterNoFacing(out.str()); + + //botAI->DoSpecificAction("autogear"); + //botAI->DoSpecificAction("maintenance"); + } + else + { + out << "Joining as " << placeholders["%role"] << "."; + botAI->TellMasterNoFacing(out.str()); + + //botAI->DoSpecificAction("autogear"); + //botAI->DoSpecificAction("maintenance"); + } + + return true; + } + + return false; } diff --git a/src/strategy/actions/InviteToGroupAction.h b/src/strategy/actions/InviteToGroupAction.h index cf4735c2..864af116 100644 --- a/src/strategy/actions/InviteToGroupAction.h +++ b/src/strategy/actions/InviteToGroupAction.h @@ -16,9 +16,13 @@ class InviteToGroupAction : public Action public: InviteToGroupAction(PlayerbotAI* botAI, std::string const name = "invite") : Action(botAI, name) {} - bool Execute(Event event) override; + bool Execute(Event event) override + { + Player* master = event.getOwner(); + return Invite(bot, master); + } - virtual bool Invite(Player* player); + virtual bool Invite(Player* inviter, Player* player); }; class JoinGroupAction : public InviteToGroupAction @@ -26,17 +30,12 @@ class JoinGroupAction : public InviteToGroupAction public: JoinGroupAction(PlayerbotAI* ai, std::string name = "join") : InviteToGroupAction(ai, name) {} bool Execute(Event event) override; - bool isUseful() override { return !bot->IsBeingTeleported(); } }; class InviteNearbyToGroupAction : public InviteToGroupAction { public: - InviteNearbyToGroupAction(PlayerbotAI* botAI, std::string const name = "invite nearby") - : InviteToGroupAction(botAI, name) - { - } - + InviteNearbyToGroupAction(PlayerbotAI* botAI, std::string const name = "invite nearby") : InviteToGroupAction(botAI, name) {} bool Execute(Event event) override; bool isUseful() override; }; @@ -64,10 +63,17 @@ public: } bool Execute(Event event) override; - bool isUseful() override; + bool isUseful() { return bot->GetGuildId() && InviteNearbyToGroupAction::isUseful(); }; private: std::vector getGuildMembers(); }; +class LfgAction : public InviteToGroupAction +{ +public: + LfgAction(PlayerbotAI* botAI, std::string name = "lfg") : InviteToGroupAction(botAI, name) {} + bool Execute(Event event) override; +}; + #endif diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 27f46f93..bf766d67 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -143,6 +143,7 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("gb"); supported.push_back("bank"); supported.push_back("invite"); + supported.push_back("lfg"); supported.push_back("spell"); supported.push_back("rti"); supported.push_back("position"); diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index ebc11214..e61ac6ad 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -88,6 +88,7 @@ public: creators["cast"] = &ChatTriggerContext::cast; creators["castnc"] = &ChatTriggerContext::castnc; creators["invite"] = &ChatTriggerContext::invite; + creators["lfg"] = &ChatTriggerContext::lfg; creators["spell"] = &ChatTriggerContext::spell; creators["rti"] = &ChatTriggerContext::rti; creators["revive"] = &ChatTriggerContext::revive; @@ -164,6 +165,7 @@ private: static Trigger* revive(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "revive"); } static Trigger* rti(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "rti"); } static Trigger* invite(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "invite"); } + static Trigger* lfg(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "lfg"); } static Trigger* cast(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "cast"); } static Trigger* castnc(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "castnc"); } static Trigger* talk(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "talk"); }