Skip to content

Commit

Permalink
NPCBots: New command .npcbot order pull for target pulling. Make or…
Browse files Browse the repository at this point in the history
…ders expire eventually
  • Loading branch information
trickerer committed Aug 31, 2024
1 parent c43f801 commit 2ef0daa
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 11 deletions.
3 changes: 3 additions & 0 deletions sql/Bots/updates/world/2024_08_31_00_command.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--
INSERT IGNORE INTO `command` (`name`) VALUES
('npcbot order pull');
85 changes: 77 additions & 8 deletions src/server/game/AI/NpcBots/bot_ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3708,6 +3708,7 @@ bool bot_ai::CanBotAttack(Unit const* target, int8 byspell, bool secondary) cons
}
}

bool pulling = IsLastOrder(BOT_ORDER_PULL, 0, target->GetGUID());
uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : master->GetBotMgr()->GetBotFollowDist();
float foldist = _getAttackDistance(float(followdist));
if (!IAmFree() && IsRanged() && me->IsWithinLOSInMap(target, LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::M2))
Expand Down Expand Up @@ -3737,9 +3738,9 @@ bool bot_ai::CanBotAttack(Unit const* target, int8 byspell, bool secondary) cons
}

return
((master->IsInCombat() || target->IsInCombat() || IsWanderer() || (IAmFree() && me->GetFaction() == 14)) &&
((master->IsInCombat() || target->IsInCombat() || IsWanderer() || (IAmFree() && me->GetFaction() == 14) || pulling) &&
target->IsVisible() && target->isTargetableForAttack(false) && me->IsValidAttackTarget(target) &&
(!master->IsAlive() || target->IsControlledByPlayer() ||
(!master->IsAlive() || target->IsControlledByPlayer() || pulling ||
(followdist > 0 && (master->GetDistance(target) <= foldist || HasBotCommandState(BOT_COMMAND_STAY)))) &&//if master is killed pursue to the end
!IsInBotParty(target) && (target->InSamePhase(me) || CanSeeEveryone()) &&
(!HasBotCommandState(BOT_COMMAND_STAY) ||
Expand Down Expand Up @@ -3902,6 +3903,20 @@ std::tuple<Unit*, Unit*> bot_ai::_getTargets(bool byspell, bool ranged, bool &re
return { mytar, mytar };

//Immediate targets
//orders
if (!IAmFree() && HasOrders() && HasRole(BOT_ROLE_DPS) && !me->IsInCombat() && me->getAttackers().empty())
{
if (_orders.front()._type == BOT_ORDER_PULL)
{
ObjectGuid orderTargetGuid = ObjectGuid(_orders.front().params.pullParams.targetGuid);
if (Unit* orderTarget = mytar && mytar->GetGUID() == orderTargetGuid ? mytar : ObjectAccessor::GetUnit(*me, orderTargetGuid))
{
if (CanBotAttack(orderTarget))
return { orderTarget, nullptr };
}
}
}
//maps
if (!IAmFree() && me->GetMap()->GetEntry() && !me->GetMap()->GetEntry()->IsWorldMap())
{
static const std::array WMOAreaGroupLashlayer = { 29476u }; // Halls of Strife
Expand Down Expand Up @@ -15601,6 +15616,9 @@ void bot_ai::JustEnteredCombat(Unit* u)

ResetChase(u);

if (IsLastOrder(BOT_ORDER_PULL, 0, u->GetGUID()))
CompleteOrder(_orders.front());

if (IAmFree() && me->GetVictim() && me->GetVictim() != u &&
(me->getAttackers().empty() || (me->getAttackers().size() == 1u && *me->getAttackers().begin() == u)) &&
me->GetVictim()->GetVictim() != me && !(me->GetVictim()->IsInCombat() || me->GetVictim()->IsInCombatWith(me)))
Expand Down Expand Up @@ -16224,6 +16242,23 @@ void bot_ai::CancelAllOrders()
}
void bot_ai::_ProcessOrders()
{
ordersTimer = 500;

while (!_orders.empty())
{
BotOrder const& order = _orders.front();
if (order._timeout <= time(0))
{
if (DEBUG_BOT_ORDERS)
TC_LOG_DEBUG("npcbots", "bot_ai::_ProcessOrders: {} front order (type {}) expired...", me->GetName(), uint32(order._type));
CancelOrder(order);
}
else if (order._type == BOT_ORDER_PULL && (!HasRole(BOT_ROLE_DPS) || me->IsInCombat() || !me->getAttackers().empty()))
CompleteOrder(order);
else
break;
}

if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER))
return;

Expand All @@ -16233,8 +16268,6 @@ void bot_ai::_ProcessOrders()
if (_orders.empty())
return;

ordersTimer = 500;

BotOrder const& order = _orders.front();
Unit* target = nullptr;
switch (order._type)
Expand All @@ -16260,14 +16293,14 @@ void bot_ai::_ProcessOrders()
}
else
{
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid spellCastParams.targetGuid " UI64FMTD "!", order.params.spellCastParams.targetGuid);
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid spellCastParams.targetGuid {}!", order.params.spellCastParams.targetGuid);
CancelOrder(order);
return;
}

if (!target || !target->IsInWorld())
{
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target " UI64FMTD " not found!", order.params.spellCastParams.targetGuid);
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} not found!", order.params.spellCastParams.targetGuid);
CancelOrder(order);
return;
}
Expand All @@ -16278,13 +16311,45 @@ void bot_ai::_ProcessOrders()
doCast(target, _spells[order.params.spellCastParams.baseSpell]->spellId);
break;
}
case BOT_ORDER_PULL:
{
if (me->GetVictim())
break;
if (CCed(me))
break;

SetBotCommandState(BOT_COMMAND_ISSUED_ORDER);

if (order.params.pullParams.targetGuid)
target = ObjectAccessor::GetUnit(*me, ObjectGuid(order.params.pullParams.targetGuid));
else
{
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid pullParams.targetGuid {}!", order.params.pullParams.targetGuid);
CancelOrder(order);
return;
}

if (!target || !target->IsInWorld())
{
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} not found!", order.params.pullParams.targetGuid);
CancelOrder(order);
return;
}
if (!target->IsAlive() || target->IsInCombat() || !CanBotAttack(target))
{
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} cannot be pulled!", order.params.pullParams.targetGuid);
CancelOrder(order);
return;
}
break;
}
default:
TC_LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid order type {}!", uint32(order._type));
CancelOrder(order);
return;
}
}
bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1) const
bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1, ObjectGuid guidparam1) const
{
if (!_orders.empty())
{
Expand All @@ -16294,7 +16359,11 @@ bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1) const
switch (order_type)
{
case BOT_ORDER_SPELLCAST:
if (order.params.spellCastParams.baseSpell == param1)
if (!param1 || order.params.spellCastParams.baseSpell == param1)
return true;
break;
case BOT_ORDER_PULL:
if (!guidparam1 || order.params.pullParams.targetGuid == guidparam1.GetRawValue())
return true;
break;
default:
Expand Down
10 changes: 8 additions & 2 deletions src/server/game/AI/NpcBots/bot_ai.h
Original file line number Diff line number Diff line change
Expand Up @@ -788,9 +788,14 @@ class bot_ai : public CreatureAI
uint32 baseSpell;
} spellCastParams;

struct
{
uint64 targetGuid;
} pullParams;

} params;

explicit BotOrder(BotOrderTypes order_type) : _type(order_type)
explicit BotOrder(BotOrderTypes order_type, uint32 timeout_sec = 10) : _type(order_type), _timeout(time(0) + timeout_sec)
{
memset((char*)(&params), 0, sizeof(params));
}
Expand All @@ -802,10 +807,11 @@ class bot_ai : public CreatureAI

private:
BotOrderTypes _type;
time_t _timeout;
};

bool HasOrders() const { return !_orders.empty(); }
bool IsLastOrder(BotOrderTypes order_type, uint32 param1) const;
bool IsLastOrder(BotOrderTypes order_type, uint32 param1 = 0, ObjectGuid guidparam1 = ObjectGuid::Empty) const;
std::size_t GetOrdersCount() const { return _orders.size(); }
bool AddOrder(BotOrder&& order);
void CancelOrder(BotOrder const& order);
Expand Down
150 changes: 150 additions & 0 deletions src/server/game/AI/NpcBots/botcommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,7 @@ class script_bot_commands : public CommandScript
static ChatCommandTable npcbotOrderCommandTable =
{
{ "cast", HandleNpcBotOrderCastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No },
{ "pull", HandleNpcBotOrderPullCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No },
};

static ChatCommandTable npcbotVehicleCommandTable =
Expand Down Expand Up @@ -1703,6 +1704,155 @@ class script_bot_commands : public CommandScript
return true;
}

static bool HandleNpcBotOrderPullCommand(ChatHandler* handler, Optional<std::string> bot_name, Optional<std::string_view> target_token)
{
Player* owner = handler->GetSession()->GetPlayer();
if (!owner->HaveBot() || !bot_name)
{
handler->SendSysMessage(".npcbot order pull #bot_name #[target_token]");
handler->SendSysMessage("Orders bot to pull target immediately");
return true;
}

if (owner->GetBotMgr()->IsPartyInCombat())
{
handler->SendSysMessage("Can't do that while in combat!");
return true;
}

for (std::decay_t<decltype(*bot_name)>::size_type i = 0u; i < bot_name->size(); ++i)
if ((*bot_name)[i] == '_')
(*bot_name)[i] = ' ';

Creature* bot = owner->GetBotMgr()->GetBotByName(*bot_name);
if (bot)
{
if (!bot->IsInWorld())
{
handler->PSendSysMessage("Bot %s is not found!", bot_name->c_str());
return true;
}
if (!bot->IsAlive())
{
handler->PSendSysMessage("%s is dead!", bot->GetName().c_str());
return true;
}
if (!bot->GetBotAI()->HasRole(BOT_ROLE_DPS) || bot->GetVictim() || bot->IsInCombat() || !bot->getAttackers().empty())
{
handler->PSendSysMessage("%s cannot pull target! Must be idle and have DPS role", bot->GetName().c_str());
return true;
}
}
else
{
auto const& class_name = *bot_name;
for (auto const c : class_name)
{
if (!std::islower(c))
{
handler->SendSysMessage("Bot class name must be in lower case!");
return true;
}
}

uint8 bot_class = BotMgr::BotClassByClassName(class_name);
if (bot_class == BOT_CLASS_NONE)
{
handler->PSendSysMessage("Unknown bot name or class %s!", class_name.c_str());
return true;
}

std::list<Creature*> cBots = owner->GetBotMgr()->GetAllBotsByClass(bot_class);

if (cBots.empty())
{
handler->PSendSysMessage("No bots of class %u found!", bot_class);
return true;
}

bot = cBots.size() == 1 ? cBots.front() : Trinity::Containers::SelectRandomContainerElement(cBots);

if (!bot)
{
handler->SendSysMessage("None of %u found bots can use pull yet!", cBots.size());
return true;
}
}

ObjectGuid target_guid = ObjectGuid::Empty;
bool token_valid = true;
if (!target_token || target_token == "mytarget")
target_guid = owner->GetTarget();
else if (Group const* group = owner->GetGroup())
{
if (target_token == "star")
target_guid = group->GetTargetIcons()[0];
else if (target_token == "circle")
target_guid = group->GetTargetIcons()[1];
else if (target_token == "diamond")
target_guid = group->GetTargetIcons()[2];
else if (target_token == "triangle")
target_guid = group->GetTargetIcons()[3];
else if (target_token == "moon")
target_guid = group->GetTargetIcons()[4];
else if (target_token == "square")
target_guid = group->GetTargetIcons()[5];
else if (target_token == "cross")
target_guid = group->GetTargetIcons()[6];
else if (target_token == "skull")
target_guid = group->GetTargetIcons()[7];
else if (target_token->size() == 1u && std::isdigit(target_token->front()))
{
uint8 digit = static_cast<uint8>(std::stoi(std::string(*target_token)));
switch (digit)
{
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
target_guid = group->GetTargetIcons()[digit - 1];
break;
default:
token_valid = false;
break;
}
}
else
token_valid = false;
}
else
token_valid = false;

if (!token_valid)
{
handler->PSendSysMessage("Invalid target token '%s'!", *target_token);
handler->SendSysMessage("Valid target tokens:\n '','mytarget', "
"'star','1', 'circle','2', 'diamond','3', 'triangle','4', 'moon','5', 'square','6', 'cross','7', 'skull','8'"
"\nNote that target icons tokens are only available while in group");
return true;
}

Unit* target = target_guid ? ObjectAccessor::GetUnit(*owner, target_guid) : nullptr;
if (!target || !bot->FindMap() || target->FindMap() != bot->FindMap())
{
handler->PSendSysMessage("Invalid target '%s'!", target ? target->GetName().c_str() : "unknown");
return true;
}

bot_ai::BotOrder order(BOT_ORDER_PULL);
order.params.pullParams.targetGuid = target_guid.GetRawValue();

if (bot->GetBotAI()->AddOrder(std::move(order)))
{
if (DEBUG_BOT_ORDERS)
handler->PSendSysMessage("Order given: %s: pull %s", bot->GetName().c_str(), target ? target->GetName().c_str() : "unknown");
}
else
{
if (DEBUG_BOT_ORDERS)
handler->PSendSysMessage("Order failed: %s: pull %s", bot->GetName().c_str(), target ? target->GetName().c_str() : "unknown");
}

return true;
}

static bool HandleNpcBotOrderCastCommand(ChatHandler* handler, Optional<std::string> bot_name, Optional<std::string> spell_name, Optional<std::string_view> target_token)
{
Player* owner = handler->GetSession()->GetPlayer();
Expand Down
5 changes: 4 additions & 1 deletion src/server/game/AI/NpcBots/botcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,10 @@ constexpr size_t MAX_SEND_POINTS = 5u;
enum BotOrderTypes
{
BOT_ORDER_NONE = 0,
BOT_ORDER_SPELLCAST = 1
BOT_ORDER_SPELLCAST = 1,
BOT_ORDER_PULL = 2,

BOT_ORDER_END
};
constexpr bool DEBUG_BOT_ORDERS = false;
constexpr size_t MAX_BOT_ORDERS_QUEUE_SIZE = 3u;
Expand Down
2 changes: 2 additions & 0 deletions src/server/game/AI/NpcBots/bpet_ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,8 @@ bool bot_pet_ai::CheckAttackTarget()

return false;
}
if (petOwner->GetBotAI()->IsLastOrder(BOT_ORDER_PULL, 0, opponent->GetGUID()))
return false;

if (reset)
SetBotCommandState(BOT_COMMAND_COMBATRESET);//reset AttackStart()
Expand Down

0 comments on commit 2ef0daa

Please sign in to comment.