From 816d506867f73815c90aac0f4735202899be4a5d Mon Sep 17 00:00:00 2001 From: Nico Date: Sun, 20 Sep 2026 20:04:35 +0200 Subject: [PATCH] added custom AI for dungeonhelper bot --- src/server/game/DungeonFinding/LFGMgr.cpp | 13 ++ .../scripts/Custom/custom_script_loader.cpp | 4 + .../scripts/Custom/viano_adventurers.cpp | 180 ++++++++++++++++++ src/server/scripts/World/item_scripts.cpp | 4 +- 4 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/server/scripts/Custom/viano_adventurers.cpp diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index da959db76b..dc3a87895e 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -344,6 +344,19 @@ void LFGMgr::Update(uint32 diff) TC_LOG_DEBUG("lfg", "Low-population solo LFG group %s expired after %u seconds outside the dungeon.", gguid.ToString().c_str(), uint32(lowPopulationOutsideTimeout)); + // LOWPOP_TIMEOUT_CLEANUP + // Mirror the clean client-side state produced by voluntary LeaveParty(). + // Group::Disband()/RemoveGroupData() resets player state/group linkage, + // but the RideTicket must be cleared explicitly. + GuidSet const& lowPopulationPlayers = GetPlayers(gguid); + if (lowPopulationPlayers.size() == 1) + { + ObjectGuid const pguid = *lowPopulationPlayers.begin(); + ClearTicket(pguid); + SendLfgUpdateStatus(pguid, LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE), true); + SendLfgUpdateStatus(pguid, LfgUpdateData(LFG_UPDATETYPE_REMOVED_FROM_QUEUE), false); + } + LeaveLfg(gguid); group->Disband(); } diff --git a/src/server/scripts/Custom/custom_script_loader.cpp b/src/server/scripts/Custom/custom_script_loader.cpp index 1df319bc50..ae00dc1afd 100644 --- a/src/server/scripts/Custom/custom_script_loader.cpp +++ b/src/server/scripts/Custom/custom_script_loader.cpp @@ -18,8 +18,12 @@ // This is where scripts' loading functions should be declared: +// VIANO_ADVENTURERS +void AddSC_viano_adventurers(); + // The name of this function should match: // void Add${NameOfDirectory}Scripts() void AddCustomScripts() { + AddSC_viano_adventurers(); } diff --git a/src/server/scripts/Custom/viano_adventurers.cpp b/src/server/scripts/Custom/viano_adventurers.cpp new file mode 100644 index 0000000000..3e7f50df7b --- /dev/null +++ b/src/server/scripts/Custom/viano_adventurers.cpp @@ -0,0 +1,180 @@ +/* + * Viano custom dungeon adventurers + * Initial melee companion implementation. + */ + +#include "ScriptMgr.h" +#include "ScriptedCreature.h" +#include "ObjectAccessor.h" +#include "Player.h" +#include "TemporarySummon.h" +#include "MotionMaster.h" + +namespace VianoAdventurers +{ + static constexpr float FOLLOW_DISTANCE = 2.5f; + static constexpr float FOLLOW_ANGLE = 3.14159265f; + static constexpr float TELEPORT_DISTANCE = 45.0f; + static constexpr uint32 UPDATE_INTERVAL = 500; + + class npc_horde_adventurer_melee : public CreatureScript + { + public: + npc_horde_adventurer_melee() : CreatureScript("npc_horde_adventurer_melee") { } + + struct npc_horde_adventurer_meleeAI : public ScriptedAI + { + npc_horde_adventurer_meleeAI(Creature* creature) : ScriptedAI(creature) { } + + ObjectGuid ownerGuid; + uint32 updateTimer = 0; + + Player* GetOwnerPlayer() const + { + if (!ownerGuid) + return nullptr; + + return ObjectAccessor::GetPlayer(*me, ownerGuid); + } + + void FollowOwner(Player* owner) + { + if (!owner) + return; + + me->GetMotionMaster()->MoveFollow(owner, FOLLOW_DISTANCE, FOLLOW_ANGLE); + } + + void InitializeAI() override + { + ScriptedAI::InitializeAI(); + + TempSummon* summon = me->ToTempSummon(); + if (!summon) + { + me->DespawnOrUnsummon(); + return; + } + + Unit* summoner = summon->GetSummoner(); + Player* owner = summoner ? summoner->ToPlayer() : nullptr; + if (!owner) + { + me->DespawnOrUnsummon(); + return; + } + + ownerGuid = owner->GetGUID(); + + me->SetOwnerGUID(ownerGuid); + me->SetFaction(owner->GetFaction()); + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + + me->SetReactState(REACT_PASSIVE); + FollowOwner(owner); + } + + void Reset() override + { + if (Player* owner = GetOwnerPlayer()) + FollowOwner(owner); + } + + void MoveInLineOfSight(Unit* /*who*/) override + { + } + + Unit* SelectAssistTarget(Player* owner) + { + if (!owner) + return nullptr; + + if (Unit* target = owner->GetVictim()) + if (target->IsAlive() && me->IsValidAttackTarget(target)) + return target; + + for (Unit* attacker : owner->getAttackers()) + if (attacker && attacker->IsAlive() && me->IsValidAttackTarget(attacker)) + return attacker; + + return nullptr; + } + + void UpdateAI(uint32 diff) override + { + if (updateTimer > diff) + { + updateTimer -= diff; + + if (UpdateVictim()) + DoMeleeAttackIfReady(); + + return; + } + + updateTimer = UPDATE_INTERVAL; + + Player* owner = GetOwnerPlayer(); + if (!owner || !owner->IsInWorld() || !me->IsInMap(owner)) + { + me->DespawnOrUnsummon(); + return; + } + + if (me->getLevel() != owner->getLevel()) + { + me->SetLevel(owner->getLevel()); + me->UpdateLevelDependantStats(); + } + + if (me->GetDistance(owner) > TELEPORT_DISTANCE) + { + me->CombatStop(true); + me->GetMotionMaster()->Clear(); + + me->NearTeleportTo( + owner->GetPositionX() + 1.5f, + owner->GetPositionY(), + owner->GetPositionZ(), + owner->GetOrientation() + ); + + FollowOwner(owner); + return; + } + + Unit* desiredTarget = SelectAssistTarget(owner); + + if (desiredTarget) + { + if (me->GetVictim() != desiredTarget) + AttackStart(desiredTarget); + } + else if (me->IsInCombat()) + { + me->CombatStop(true); + FollowOwner(owner); + } + else if (!me->GetVictim()) + { + FollowOwner(owner); + } + + if (UpdateVictim()) + DoMeleeAttackIfReady(); + } + }; + + CreatureAI* GetAI(Creature* creature) const override + { + return new npc_horde_adventurer_meleeAI(creature); + } + }; +} + +void AddSC_viano_adventurers() +{ + using namespace VianoAdventurers; + new npc_horde_adventurer_melee(); +} diff --git a/src/server/scripts/World/item_scripts.cpp b/src/server/scripts/World/item_scripts.cpp index 501e329f87..294faace10 100644 --- a/src/server/scripts/World/item_scripts.cpp +++ b/src/server/scripts/World/item_scripts.cpp @@ -419,7 +419,7 @@ class item_generic_limit_chance_above_60 : public ItemScript enum HordeAdventurerHorn { - NPC_ORGRIMMAR_GRUNT = 3296 + NPC_HORDE_ADVENTURER = 900001 }; class item_horde_adventurer_horn : public ItemScript @@ -445,7 +445,7 @@ public: } player->SummonCreature( - NPC_ORGRIMMAR_GRUNT, + NPC_HORDE_ADVENTURER, player->GetPositionX() + 2.0f, player->GetPositionY(), player->GetPositionZ(),