Skip to content

Commit ee76ff6

Browse files
committed
NPCBots: New command .npcbot order pull for target pulling. Make orders expire eventually
(cherry picked from commit 6beb53fbc40b175a45b326a59b2589428f56c101) # Conflicts: # data/sql/custom/db_world/2024_08_31_00_command.sql # src/server/game/AI/NpcBots/bot_ai.cpp
1 parent 650389e commit ee76ff6

File tree

6 files changed

+242
-9
lines changed

6 files changed

+242
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--
2+
INSERT IGNORE INTO `command` (`name`,`security`,`help`) VALUES
3+
('npcbot order pull', '0', NULL);

src/server/game/AI/NpcBots/bot_ai.cpp

+75-6
Original file line numberDiff line numberDiff line change
@@ -3712,6 +3712,7 @@ bool bot_ai::CanBotAttack(Unit const* target, int8 byspell, bool secondary) cons
37123712
}
37133713
}
37143714

3715+
bool pulling = IsLastOrder(BOT_ORDER_PULL, 0, target->GetGUID());
37153716
uint8 followdist = IAmFree() ? BotMgr::GetBotFollowDistDefault() : master->GetBotMgr()->GetBotFollowDist();
37163717
float foldist = _getAttackDistance(float(followdist));
37173718
if (!IAmFree() && IsRanged() && me->IsWithinLOSInMap(target, VMAP::ModelIgnoreFlags::M2, LINEOFSIGHT_ALL_CHECKS))
@@ -3741,9 +3742,9 @@ bool bot_ai::CanBotAttack(Unit const* target, int8 byspell, bool secondary) cons
37413742
}
37423743

37433744
return
3744-
((master->IsInCombat() || target->IsInCombat() || IsWanderer() || (IAmFree() && me->GetFaction() == 14)) &&
3745+
((master->IsInCombat() || target->IsInCombat() || IsWanderer() || (IAmFree() && me->GetFaction() == 14) || pulling) &&
37453746
target->IsVisible() && target->isTargetableForAttack(false) && me->IsValidAttackTarget(target) &&
3746-
(!master->IsAlive() || target->IsControlledByPlayer() ||
3747+
(!master->IsAlive() || target->IsControlledByPlayer() || pulling ||
37473748
(followdist > 0 && (master->GetDistance(target) <= foldist || HasBotCommandState(BOT_COMMAND_STAY)))) &&//if master is killed pursue to the end
37483749
!IsInBotParty(target) && (target->InSamePhase(me) || CanSeeEveryone()) &&
37493750
(!HasBotCommandState(BOT_COMMAND_STAY) ||
@@ -3906,6 +3907,20 @@ std::tuple<Unit*, Unit*> bot_ai::_getTargets(bool byspell, bool ranged, bool &re
39063907
return { mytar, mytar };
39073908

39083909
//Immediate targets
3910+
//orders
3911+
if (!IAmFree() && HasOrders() && HasRole(BOT_ROLE_DPS) && !me->IsInCombat() && me->getAttackers().empty())
3912+
{
3913+
if (_orders.front()._type == BOT_ORDER_PULL)
3914+
{
3915+
ObjectGuid orderTargetGuid = ObjectGuid(_orders.front().params.pullParams.targetGuid);
3916+
if (Unit* orderTarget = mytar && mytar->GetGUID() == orderTargetGuid ? mytar : ObjectAccessor::GetUnit(*me, orderTargetGuid))
3917+
{
3918+
if (CanBotAttack(orderTarget))
3919+
return { orderTarget, nullptr };
3920+
}
3921+
}
3922+
}
3923+
//maps
39093924
if (!IAmFree() && me->GetMap()->GetEntry() && !me->GetMap()->GetEntry()->IsWorldMap())
39103925
{
39113926
static const std::array WMOAreaGroupLashlayer = { 29476u }; // Halls of Strife
@@ -15629,6 +15644,9 @@ void bot_ai::JustEngagedWith(Unit* u)
1562915644

1563015645
ResetChase(u);
1563115646

15647+
if (IsLastOrder(BOT_ORDER_PULL, 0, u->GetGUID()))
15648+
CompleteOrder(_orders.front());
15649+
1563215650
if (IAmFree() && me->GetVictim() && me->GetVictim() != u &&
1563315651
(me->getAttackers().empty() || (me->getAttackers().size() == 1u && *me->getAttackers().begin() == u)) &&
1563415652
me->GetVictim()->GetVictim() != me && !(me->GetVictim()->IsInCombat() || me->GetVictim()->IsInCombatWith(me)))
@@ -16253,6 +16271,23 @@ void bot_ai::CancelAllOrders()
1625316271
}
1625416272
void bot_ai::_ProcessOrders()
1625516273
{
16274+
ordersTimer = 500;
16275+
16276+
while (!_orders.empty())
16277+
{
16278+
BotOrder const& order = _orders.front();
16279+
if (order._timeout <= time(0))
16280+
{
16281+
if (DEBUG_BOT_ORDERS)
16282+
LOG_DEBUG("npcbots", "bot_ai::_ProcessOrders: {} front order (type {}) expired...", me->GetName(), uint32(order._type));
16283+
CancelOrder(order);
16284+
}
16285+
else if (order._type == BOT_ORDER_PULL && (!HasRole(BOT_ROLE_DPS) || me->IsInCombat() || !me->getAttackers().empty()))
16286+
CompleteOrder(order);
16287+
else
16288+
break;
16289+
}
16290+
1625616291
if (HasBotCommandState(BOT_COMMAND_ISSUED_ORDER))
1625716292
return;
1625816293

@@ -16262,8 +16297,6 @@ void bot_ai::_ProcessOrders()
1626216297
if (_orders.empty())
1626316298
return;
1626416299

16265-
ordersTimer = 500;
16266-
1626716300
BotOrder const& order = _orders.front();
1626816301
Unit* target = nullptr;
1626916302
switch (order._type)
@@ -16307,13 +16340,45 @@ void bot_ai::_ProcessOrders()
1630716340
doCast(target, _spells[order.params.spellCastParams.baseSpell]->spellId);
1630816341
break;
1630916342
}
16343+
case BOT_ORDER_PULL:
16344+
{
16345+
if (me->GetVictim())
16346+
break;
16347+
if (CCed(me))
16348+
break;
16349+
16350+
SetBotCommandState(BOT_COMMAND_ISSUED_ORDER);
16351+
16352+
if (order.params.pullParams.targetGuid)
16353+
target = ObjectAccessor::GetUnit(*me, ObjectGuid(order.params.pullParams.targetGuid));
16354+
else
16355+
{
16356+
LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid pullParams.targetGuid {}!", order.params.pullParams.targetGuid);
16357+
CancelOrder(order);
16358+
return;
16359+
}
16360+
16361+
if (!target || !target->IsInWorld())
16362+
{
16363+
LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} not found!", order.params.pullParams.targetGuid);
16364+
CancelOrder(order);
16365+
return;
16366+
}
16367+
if (!target->IsAlive() || target->IsInCombat() || !CanBotAttack(target))
16368+
{
16369+
LOG_ERROR("scripts", "bot_ai:_ProcessOrders: target {} cannot be pulled!", order.params.pullParams.targetGuid);
16370+
CancelOrder(order);
16371+
return;
16372+
}
16373+
break;
16374+
}
1631016375
default:
1631116376
LOG_ERROR("scripts", "bot_ai:_ProcessOrders: invalid order type {}!", uint32(order._type));
1631216377
CancelOrder(order);
1631316378
return;
1631416379
}
1631516380
}
16316-
bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1) const
16381+
bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1, ObjectGuid guidparam1) const
1631716382
{
1631816383
if (!_orders.empty())
1631916384
{
@@ -16323,7 +16388,11 @@ bool bot_ai::IsLastOrder(BotOrderTypes order_type, uint32 param1) const
1632316388
switch (order_type)
1632416389
{
1632516390
case BOT_ORDER_SPELLCAST:
16326-
if (order.params.spellCastParams.baseSpell == param1)
16391+
if (!param1 || order.params.spellCastParams.baseSpell == param1)
16392+
return true;
16393+
break;
16394+
case BOT_ORDER_PULL:
16395+
if (!guidparam1 || order.params.pullParams.targetGuid == guidparam1.GetRawValue())
1632716396
return true;
1632816397
break;
1632916398
default:

src/server/game/AI/NpcBots/bot_ai.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -789,9 +789,14 @@ class bot_ai : public CreatureAI
789789
uint32 baseSpell;
790790
} spellCastParams;
791791

792+
struct
793+
{
794+
uint64 targetGuid;
795+
} pullParams;
796+
792797
} params;
793798

794-
explicit BotOrder(BotOrderTypes order_type) : _type(order_type)
799+
explicit BotOrder(BotOrderTypes order_type, uint32 timeout_sec = 10) : _type(order_type), _timeout(time(0) + timeout_sec)
795800
{
796801
memset((char*)(&params), 0, sizeof(params));
797802
}
@@ -803,10 +808,11 @@ class bot_ai : public CreatureAI
803808

804809
private:
805810
BotOrderTypes _type;
811+
time_t _timeout;
806812
};
807813

808814
bool HasOrders() const { return !_orders.empty(); }
809-
bool IsLastOrder(BotOrderTypes order_type, uint32 param1) const;
815+
bool IsLastOrder(BotOrderTypes order_type, uint32 param1 = 0, ObjectGuid guidparam1 = ObjectGuid::Empty) const;
810816
std::size_t GetOrdersCount() const { return _orders.size(); }
811817
bool AddOrder(BotOrder&& order);
812818
void CancelOrder(BotOrder const& order);

src/server/game/AI/NpcBots/botcommands.cpp

+150
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ class script_bot_commands : public CommandScript
594594
static ChatCommandTable npcbotOrderCommandTable =
595595
{
596596
{ "cast", HandleNpcBotOrderCastCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No },
597+
{ "pull", HandleNpcBotOrderPullCommand, rbac::RBAC_PERM_COMMAND_NPCBOT_ORDER_CAST, Console::No },
597598
};
598599

599600
static ChatCommandTable npcbotVehicleCommandTable =
@@ -1728,6 +1729,155 @@ class script_bot_commands : public CommandScript
17281729
return true;
17291730
}
17301731

1732+
static bool HandleNpcBotOrderPullCommand(ChatHandler* handler, Optional<std::string> bot_name, Optional<std::string_view> target_token)
1733+
{
1734+
Player* owner = handler->GetSession()->GetPlayer();
1735+
if (!owner->HaveBot() || !bot_name)
1736+
{
1737+
handler->SendSysMessage(".npcbot order pull #bot_name #[target_token]");
1738+
handler->SendSysMessage("Orders bot to pull target immediately");
1739+
return true;
1740+
}
1741+
1742+
if (owner->GetBotMgr()->IsPartyInCombat())
1743+
{
1744+
handler->SendSysMessage("Can't do that while in combat!");
1745+
return true;
1746+
}
1747+
1748+
for (std::decay_t<decltype(*bot_name)>::size_type i = 0u; i < bot_name->size(); ++i)
1749+
if ((*bot_name)[i] == '_')
1750+
(*bot_name)[i] = ' ';
1751+
1752+
Creature* bot = owner->GetBotMgr()->GetBotByName(*bot_name);
1753+
if (bot)
1754+
{
1755+
if (!bot->IsInWorld())
1756+
{
1757+
handler->PSendSysMessage("Bot {} is not found!", *bot_name);
1758+
return true;
1759+
}
1760+
if (!bot->IsAlive())
1761+
{
1762+
handler->PSendSysMessage("{} is dead!", bot->GetName());
1763+
return true;
1764+
}
1765+
if (!bot->GetBotAI()->HasRole(BOT_ROLE_DPS) || bot->GetVictim() || bot->IsInCombat() || !bot->getAttackers().empty())
1766+
{
1767+
handler->PSendSysMessage("{} cannot pull target! Must be idle and have DPS role", bot->GetName());
1768+
return true;
1769+
}
1770+
}
1771+
else
1772+
{
1773+
auto const& class_name = *bot_name;
1774+
for (auto const c : class_name)
1775+
{
1776+
if (!std::islower(c))
1777+
{
1778+
handler->SendSysMessage("Bot class name must be in lower case!");
1779+
return true;
1780+
}
1781+
}
1782+
1783+
uint8 bot_class = BotMgr::BotClassByClassName(class_name);
1784+
if (bot_class == BOT_CLASS_NONE)
1785+
{
1786+
handler->PSendSysMessage("Unknown bot name or class {}!", class_name);
1787+
return true;
1788+
}
1789+
1790+
std::list<Creature*> cBots = owner->GetBotMgr()->GetAllBotsByClass(bot_class);
1791+
1792+
if (cBots.empty())
1793+
{
1794+
handler->PSendSysMessage("No bots of class {} found!", bot_class);
1795+
return true;
1796+
}
1797+
1798+
bot = cBots.size() == 1 ? cBots.front() : Acore::Containers::SelectRandomContainerElement(cBots);
1799+
1800+
if (!bot)
1801+
{
1802+
handler->SendSysMessage("None of {} found bots can use pull yet!", cBots.size());
1803+
return true;
1804+
}
1805+
}
1806+
1807+
ObjectGuid target_guid = ObjectGuid::Empty;
1808+
bool token_valid = true;
1809+
if (!target_token || target_token == "mytarget")
1810+
target_guid = owner->GetTarget();
1811+
else if (Group const* group = owner->GetGroup())
1812+
{
1813+
if (target_token == "star")
1814+
target_guid = group->GetTargetIcons()[0];
1815+
else if (target_token == "circle")
1816+
target_guid = group->GetTargetIcons()[1];
1817+
else if (target_token == "diamond")
1818+
target_guid = group->GetTargetIcons()[2];
1819+
else if (target_token == "triangle")
1820+
target_guid = group->GetTargetIcons()[3];
1821+
else if (target_token == "moon")
1822+
target_guid = group->GetTargetIcons()[4];
1823+
else if (target_token == "square")
1824+
target_guid = group->GetTargetIcons()[5];
1825+
else if (target_token == "cross")
1826+
target_guid = group->GetTargetIcons()[6];
1827+
else if (target_token == "skull")
1828+
target_guid = group->GetTargetIcons()[7];
1829+
else if (target_token->size() == 1u && std::isdigit(target_token->front()))
1830+
{
1831+
uint8 digit = static_cast<uint8>(std::stoi(std::string(*target_token)));
1832+
switch (digit)
1833+
{
1834+
case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
1835+
target_guid = group->GetTargetIcons()[digit - 1];
1836+
break;
1837+
default:
1838+
token_valid = false;
1839+
break;
1840+
}
1841+
}
1842+
else
1843+
token_valid = false;
1844+
}
1845+
else
1846+
token_valid = false;
1847+
1848+
if (!token_valid)
1849+
{
1850+
handler->PSendSysMessage("Invalid target token '{}'!", *target_token);
1851+
handler->SendSysMessage("Valid target tokens:\n '','mytarget', "
1852+
"'star','1', 'circle','2', 'diamond','3', 'triangle','4', 'moon','5', 'square','6', 'cross','7', 'skull','8'"
1853+
"\nNote that target icons tokens are only available while in group");
1854+
return true;
1855+
}
1856+
1857+
Unit* target = target_guid ? ObjectAccessor::GetUnit(*owner, target_guid) : nullptr;
1858+
if (!target || !bot->FindMap() || target->FindMap() != bot->FindMap())
1859+
{
1860+
handler->PSendSysMessage("Invalid target '{}'!", target ? target->GetName().c_str() : "unknown");
1861+
return true;
1862+
}
1863+
1864+
bot_ai::BotOrder order(BOT_ORDER_PULL);
1865+
order.params.pullParams.targetGuid = target_guid.GetRawValue();
1866+
1867+
if (bot->GetBotAI()->AddOrder(std::move(order)))
1868+
{
1869+
if (DEBUG_BOT_ORDERS)
1870+
handler->PSendSysMessage("Order given: {}: pull {}", bot->GetName(), target ? target->GetName().c_str() : "unknown");
1871+
}
1872+
else
1873+
{
1874+
if (DEBUG_BOT_ORDERS)
1875+
handler->PSendSysMessage("Order failed: {}: pull {}", bot->GetName(), target ? target->GetName().c_str() : "unknown");
1876+
}
1877+
1878+
return true;
1879+
}
1880+
17311881
static bool HandleNpcBotOrderCastCommand(ChatHandler* handler, Optional<std::string> bot_name, Optional<std::string> spell_name, Optional<std::string_view> target_token)
17321882
{
17331883
Player* owner = handler->GetSession()->GetPlayer();

src/server/game/AI/NpcBots/botcommon.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,10 @@ constexpr size_t MAX_SEND_POINTS = 5u;
566566
enum BotOrderTypes
567567
{
568568
BOT_ORDER_NONE = 0,
569-
BOT_ORDER_SPELLCAST = 1
569+
BOT_ORDER_SPELLCAST = 1,
570+
BOT_ORDER_PULL = 2,
571+
572+
BOT_ORDER_END
570573
};
571574
constexpr bool DEBUG_BOT_ORDERS = false;
572575
constexpr size_t MAX_BOT_ORDERS_QUEUE_SIZE = 3u;

src/server/game/AI/NpcBots/bpet_ai.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1588,6 +1588,8 @@ bool bot_pet_ai::CheckAttackTarget()
15881588

15891589
return false;
15901590
}
1591+
if (petOwner->GetBotAI()->IsLastOrder(BOT_ORDER_PULL, 0, opponent->GetGUID()))
1592+
return false;
15911593

15921594
if (reset)
15931595
SetBotCommandState(BOT_COMMAND_COMBATRESET);//reset AttackStart()

0 commit comments

Comments
 (0)