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.
This commit is contained in:
Brian
2025-06-01 09:31:29 +02:00
committed by GitHub
parent 89556dafa1
commit 3c05e47cb2
8 changed files with 302 additions and 68 deletions

View File

@@ -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");
}

View File

@@ -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())

View File

@@ -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

View File

@@ -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); }

View File

@@ -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<std::string, std::string> 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<std::string, std::string> 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<Classes, std::unordered_map<BotRoles, uint32>> allowedClassNr;
std::unordered_map<BotRoles, uint32> 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<std::string, std::string> 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;
}

View File

@@ -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<Player*> getGuildMembers();
};
class LfgAction : public InviteToGroupAction
{
public:
LfgAction(PlayerbotAI* botAI, std::string name = "lfg") : InviteToGroupAction(botAI, name) {}
bool Execute(Event event) override;
};
#endif

View File

@@ -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");

View File

@@ -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"); }