Compare commits

...

9 Commits

Author SHA1 Message Date
Chris Miles 80e8634a48 [Release] 23.0.2 (#4711) 2025-02-21 23:18:29 -06:00
zimp-wow 98c2fa5127 [Bug Fix] Fix bad Mob reference in QuestManager::resumetimer() (#4710) 2025-02-21 23:14:50 -06:00
nytmyr 8b8b41dab3 [Bots] Add checks to ensure bots and pets do not engage on ^pull (#4708) 2025-02-21 23:13:54 -06:00
nytmyr 63b1e6b4b4 [Bots] Improve positioning (#4709)
- Prevent bots not set to ^behindmob from finding positions behind their target.
- Backs up a bot if they're too close and have target reflection, not just taunting bots.
- Backs up bots if the target is rooting and they are not taunting to the appropriate position.
- This will particularly help with casters running around.
2025-02-21 23:12:50 -06:00
nytmyr 21b7b6e7ab [Bots] Prevent medding in combat if any mob has bot targeted (#4707)
- Previously this only checked the bots target to med. It will now check the hatelist of the bot to ensure no mobs have the bot targeted before medding.
2025-02-21 23:12:29 -06:00
Alex King 878a5377ae [Bug Fix] Fix Lua Zone ID Exports (#4700) 2025-02-21 23:12:04 -06:00
Alex King 486c7c44be [Quest API] Add GetSpawn() to Perl and Lua (#4702)
* [Quest API] Add GetSpawn() to Perl and Lua

* Push

* Update npc.h
2025-02-21 22:57:41 -06:00
nytmyr aa4869c6e9 [Hotfix] Fix cursor load on zone (#4704)
* [Hotfix] Fix cursor load on zone

- Corrects an issue where the item on your cursor would become invisible upon trying to place it anywhere in your inventory.

* Remove general to prevent duplicate loads
2025-02-21 22:54:35 -06:00
zimp-wow 94e1b4edfa [Bug Fix] Fix infinite loop in QuestManager::stoptimer() (#4703) 2025-02-20 19:52:08 -05:00
13 changed files with 166 additions and 104 deletions
+23
View File
@@ -1,3 +1,26 @@
## [23.0.2] 2/21/2025
### Bots
* Add checks to ensure bots and pets do not engage on ^pull ([#4708](https://github.com/EQEmu/Server/pull/4708)) @nytmyr 2025-02-22
* Improve positioning ([#4709](https://github.com/EQEmu/Server/pull/4709)) @nytmyr 2025-02-22
* Prevent medding in combat if any mob has bot targeted ([#4707](https://github.com/EQEmu/Server/pull/4707)) @nytmyr 2025-02-22
### Client Mod
* Adds a hacked fast camp rule for GMs ([#4697](https://github.com/EQEmu/Server/pull/4697)) @KimLS 2025-02-20
### Fixes
* Fix Lua Zone ID Exports ([#4700](https://github.com/EQEmu/Server/pull/4700)) @Kinglykrab 2025-02-22
* Fix bad Mob reference in QuestManager::resumetimer() ([#4710](https://github.com/EQEmu/Server/pull/4710)) @zimp-wow 2025-02-22
* Fix cursor load on zone ([#4704](https://github.com/EQEmu/Server/pull/4704)) @nytmyr 2025-02-22
* Fix infinite loop in QuestManager::stoptimer() ([#4703](https://github.com/EQEmu/Server/pull/4703)) @zimp-wow 2025-02-21
### Quest API
* Add GetSpawn() to Perl and Lua ([#4702](https://github.com/EQEmu/Server/pull/4702)) @Kinglykrab 2025-02-22
## [23.0.1] 2/20/2025
### Fixes
+1 -1
View File
@@ -25,7 +25,7 @@
// Build variables
// these get injected during the build pipeline
#define CURRENT_VERSION "23.0.1-dev" // always append -dev to the current version for custom-builds
#define CURRENT_VERSION "23.0.2-dev" // always append -dev to the current version for custom-builds
#define LOGIN_VERSION "0.8.0"
#define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "eqemu-server",
"version": "23.0.1",
"version": "23.0.2",
"repository": {
"type": "git",
"url": "https://github.com/EQEmu/Server.git"
+117 -77
View File
@@ -1856,6 +1856,7 @@ bool Bot::BotRangedAttack(Mob* other, bool can_double_attack) {
if (
!GetPullingFlag() &&
!GetReturningFlag() &&
(
(
GetBotStance() != Stance::Aggressive &&
@@ -2115,8 +2116,10 @@ void Bot::SetHoldMode() {
void Bot::AI_Process()
{
#define PULLING_BOT (GetPullingFlag() || GetReturningFlag())
#define NOT_PULLING_BOT (!GetPullingFlag() && !GetReturningFlag())
#define PULLING_BOT (GetPullingFlag())
#define NOT_PULLING_BOT (!GetPullingFlag())
#define RETURNING_BOT (GetReturningFlag())
#define NOT_RETURNING_BOT (!GetReturningFlag())
#define GUARDING (GetGuardFlag())
#define NOT_GUARDING (!GetGuardFlag())
#define HOLDING (GetHoldFlag())
@@ -2224,7 +2227,7 @@ void Bot::AI_Process()
// PULLING FLAG (TARGET VALIDATION)
if (GetPullingFlag()) {
if (PULLING_BOT) {
if (!PullingFlagChecks(bot_owner)) {
return;
}
@@ -2232,10 +2235,10 @@ void Bot::AI_Process()
// RETURNING FLAG
if (GetReturningFlag()) {
ReturningFlagChecks(bot_owner, leash_owner, fm_distance);
return;
if (RETURNING_BOT) {
if (ReturningFlagChecks(bot_owner, leash_owner, fm_distance)) {
return;
}
}
// DEFAULT (ACQUIRE TARGET)
@@ -2268,7 +2271,7 @@ void Bot::AI_Process()
}
// This causes conflicts with default pet handler (bounces between targets)
if (NOT_PULLING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
if (NOT_PULLING_BOT && NOT_RETURNING_BOT && HasPet() && (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 2)) {
// We don't add to hate list here because it's assumed to already be on the list
GetPet()->SetTarget(tar);
}
@@ -2309,7 +2312,7 @@ void Bot::AI_Process()
// PULLING FLAG (ACTIONABLE RANGE)
if (GetPullingFlag()) {
if (PULLING_BOT || RETURNING_BOT) {
if (!TargetValidation(tar)) { return; }
if (!DoLosChecks(tar)) {
@@ -2368,7 +2371,11 @@ void Bot::AI_Process()
// ENGAGED AT COMBAT RANGE
// We can fight
if (at_combat_range) {
bool other_bot_pulling =
(bot_owner->GetBotPulling() && NOT_PULLING_BOT) &&
(bot_owner->GetBotPulling() && NOT_RETURNING_BOT);
if (!other_bot_pulling && at_combat_range) {
bool jitter_cooldown = false;
if (m_combat_jitter_timer.GetRemainingTime() > 1 && m_combat_jitter_timer.Enabled()) {
@@ -2459,60 +2466,20 @@ void Bot::AI_Process()
// ENGAGED NOT AT COMBAT RANGE
else if (!TryPursueTarget(leash_distance, Goal)) {
else if (!other_bot_pulling && !TryPursueTarget(leash_distance, Goal)) {
return;
}
// End not in combat range
TryMeditate();
}
else { // Out-of-combat behavior
SetAttackFlag(false);
SetCombatRoundForAlerts(false);
SetAttackingFlag(false);
if (!bot_owner->GetBotPulling()) {
SetPullingFlag(false);
SetReturningFlag(false);
}
// AUTO DEFEND
if (TryAutoDefend(bot_owner, leash_distance) ) {
return;
}
SetTarget(nullptr);
if (
HasPet() &&
(
GetClass() != Class::Enchanter ||
GetPet()->GetPetType() != petAnimation ||
GetAA(aaAnimationEmpathy) >= 1
)
) {
if (bot_owner->GetBotPulling() && HasPet()) {
GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr);
}
if (m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)) {
SendRemovePlayerState(PlayerState::Aggressive);
}
// OK TO IDLE
// Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
return;
}
if (!IsBotNonSpellFighter() && AI_HasSpells() && TryIdleChecks(fm_distance)) {
return;
}
if (GetClass() == Class::Bard && AI_HasSpells() && TryBardMovementCasts()) {
return;
}
TryMeditate();
}
else { // Out-of-combat behavior
DoOutOfCombatChecks(bot_owner, follow_mob, Goal, leash_distance, fm_distance);
}
}
@@ -2540,7 +2507,7 @@ bool Bot::TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, g
float destination_distance = DistanceSquared(GetPosition(), Goal);
if ((!bot_owner->GetBotPulling() || PULLING_BOT) && (destination_distance > GetFollowDistance())) {
if (destination_distance > GetFollowDistance()) {
if (!IsRooted()) {
if (rest_timer.Enabled()) {
rest_timer.Disable();
@@ -2580,9 +2547,56 @@ bool Bot::TryIdleChecks(float fm_distance) {
return true;
}
return false;
}
void Bot::DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance) {
SetAttackFlag(false);
SetCombatRoundForAlerts(false);
SetAttackingFlag(false);
if (PULLING_BOT || RETURNING_BOT || !bot_owner->GetBotPulling()) {
SetPullingFlag(false);
SetReturningFlag(false);
}
if (TryAutoDefend(bot_owner, leash_distance) ) {
return;
}
SetTarget(nullptr);
if (
HasPet() &&
(
GetClass() != Class::Enchanter ||
GetPet()->GetPetType() != petAnimation ||
GetAA(aaAnimationEmpathy) >= 1
)
) {
GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr);
}
if (m_PlayerState & static_cast<uint32>(PlayerState::Aggressive)) {
SendRemovePlayerState(PlayerState::Aggressive);
}
// Ok to idle
if (TryNonCombatMovementChecks(bot_owner, follow_mob, Goal)) {
return;
}
if (!IsBotNonSpellFighter() && AI_HasSpells() && TryIdleChecks(fm_distance)) {
return;
}
if (GetClass() == Class::Bard && AI_HasSpells() && TryBardMovementCasts()) {
return;
}
}
// This is as close as I could get without modifying the aggro mechanics and making it an expensive process...
// 'class Client' doesn't make use of hate_list
bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
@@ -2746,11 +2760,26 @@ bool Bot::TryAutoDefend(Client* bot_owner, float leash_distance) {
bool Bot::TryMeditate() {
if (!IsMoving() && !spellend_timer.Enabled()) {
if (IsEngaged() && HasOrMayGetAggro(IsSitting())) {
if (IsSitting()) {
Stand();
if (IsEngaged()) {
if (HasOrMayGetAggro(IsSitting())) {
if (IsSitting()) {
Stand();
}
return false;
}
for (auto mob : hate_list.GetHateList()) {
auto tar = mob->entity_on_hatelist;
if (tar) {
Mob* tar_target = tar->GetTarget();
if (tar_target && tar_target == this) {
return false;
}
}
}
}
BotMeditate(IsSitting());
@@ -2758,6 +2787,7 @@ bool Bot::TryMeditate() {
if (!(GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive))) {
SendAddPlayerState(PlayerState::Aggressive);
}
return true;
}
@@ -3214,7 +3244,7 @@ bool Bot::IsValidTarget(
SetCombatRoundForAlerts(false);
SetAttackingFlag(false);
if (PULLING_BOT) {
if (PULLING_BOT || RETURNING_BOT) {
SetPullingFlag(false);
SetReturningFlag(false);
bot_owner->SetBotPulling(false);
@@ -3247,7 +3277,7 @@ Mob* Bot::GetBotTarget(Client* bot_owner)
SetAttackFlag(false);
SetAttackingFlag(false);
if (PULLING_BOT) {
if (PULLING_BOT || RETURNING_BOT) {
// 'Flags' should only be set on the bot that is pulling
SetPullingFlag(false);
SetReturningFlag(false);
@@ -3277,23 +3307,33 @@ bool Bot::TargetValidation(Mob* other) {
}
bool Bot::ReturningFlagChecks(Client* bot_owner, Mob* leash_owner, float fm_distance) {
auto engage_range = (GetBotDistanceRanged() < 30 ? 30 : GetBotDistanceRanged());
if (
(NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())
(GetTarget() && Distance(GetPosition(), GetTarget()->GetPosition()) <= engage_range) &&
(
(NOT_GUARDING && fm_distance <= GetFollowDistance()) ||
(GUARDING && DistanceSquared(GetPosition(), GetGuardPoint()) <= GetFollowDistance())
)
) { // Once we're back, clear blocking flags so everyone else can join in
WipeHateList();
SetTarget(nullptr);
SetPullingFlag(false);
SetReturningFlag(false);
bot_owner->SetBotPulling(false);
if (GetPet()) {
GetPet()->SetPetOrder(m_previous_pet_order);
if (GetClass() != Class::Enchanter || GetPet()->GetPetType() != petAnimation || GetAA(aaAnimationEmpathy) >= 1) {
GetPet()->WipeHateList();
GetPet()->SetTarget(nullptr);
}
}
return false;
}
// Need to keep puller out of combat until they reach their 'return to' destination
WipeHateList();
if (!IsMoving()) {
glm::vec3 Goal(0, 0, 0);
@@ -3332,8 +3372,6 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
return false;
}
else if (GetTarget()->GetHateList().size()) {
WipeHateList();
SetTarget(nullptr);
SetPullingFlag(false);
SetReturningFlag();
@@ -3347,6 +3385,8 @@ bool Bot::PullingFlagChecks(Client* bot_owner) {
if (GetPlayerState() & static_cast<uint32>(PlayerState::Aggressive)) {
SendRemovePlayerState(PlayerState::Aggressive);
}
return false;
}
return true;
@@ -3500,7 +3540,7 @@ Client* Bot::SetLeashOwner(Client* bot_owner, Group* bot_group, Raid* raid, uint
}
void Bot::SetOwnerTarget(Client* bot_owner) {
if (GetPet() && PULLING_BOT) {
if (GetPet() && (PULLING_BOT || RETURNING_BOT)) {
GetPet()->SetPetOrder(m_previous_pet_order);
}
@@ -4943,7 +4983,7 @@ bool Bot::Death(Mob *killer_mob, int64 damage, uint16 spell_id, EQ::skills::Skil
LeaveHealRotationMemberPool();
if ((GetPullingFlag() || GetReturningFlag()) && my_owner && my_owner->IsClient()) {
if ((PULLING_BOT || RETURNING_BOT) && my_owner && my_owner->IsClient()) {
my_owner->CastToClient()->SetBotPulling(false);
}
@@ -11814,15 +11854,15 @@ void Bot::DoCombatPositioning(
}
else if (tar->IsRooted() && !IsTaunting()) { // Move non-taunters out of range - Above already checks if bot is targeted, otherwise they would stay
if (tar_distance <= melee_distance_max) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), false, IsTaunting())) {
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, (melee_distance_max + 1), (melee_distance_max * 2), GetBehindMob(), false)) {
RunToGoalWithJitter(Goal);
return;
}
}
}
else if (IsTaunting() && ((tar_distance < melee_distance_min) || !front_mob)) { // Back up any bots that are too close
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, false, IsTaunting())) {
else if (tar_distance < melee_distance_min || (!front_mob && IsTaunting())) { // Back up any bots that are too close
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
return;
@@ -11857,8 +11897,8 @@ void Bot::DoCombatPositioning(
}
}
}
else if (tar_distance < melee_distance_min || (GetBehindMob() && !behind_mob) || (IsTaunting() && !front_mob)|| !HasRequiredLoSForPositioning(tar)) { // Regular adjustment
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), IsTaunting())) {
else if (tar_distance < melee_distance_min || (GetBehindMob() && !behind_mob) || (IsTaunting() && !front_mob) || !HasRequiredLoSForPositioning(tar)) { // Regular adjustment
if (PlotBotPositionAroundTarget(tar, Goal.x, Goal.y, Goal.z, melee_distance_min, melee_distance, GetBehindMob(), (IsTaunting() || !GetBehindMob()))) {
RunToGoalWithJitter(Goal);
return;
@@ -13193,4 +13233,4 @@ bool Bot::IsValidBotStance(uint8 stance) {
}
return false;
}
}
+1
View File
@@ -1127,6 +1127,7 @@ public:
bool TryAutoDefend(Client* bot_owner, float leash_distance);
bool TryIdleChecks(float fm_distance);
bool TryNonCombatMovementChecks(Client* bot_owner, const Mob* follow_mob, glm::vec3& Goal);
void DoOutOfCombatChecks(Client* bot_owner, Mob* follow_mob, glm::vec3& Goal, float leash_distance, float fm_distance);
bool TryBardMovementCasts();
bool BotRangedAttack(Mob* other, bool can_double_attack = false);
bool CheckDoubleRangedAttack();
+2 -18
View File
@@ -776,24 +776,8 @@ void Client::BulkSendInventoryItems()
EQ::OutBuffer ob;
EQ::OutBuffer::pos_type last_pos = ob.tellp();
// Equipment items
for (int16 slot_id = EQ::invslot::EQUIPMENT_BEGIN; slot_id <= EQ::invslot::EQUIPMENT_END; slot_id++) {
const EQ::ItemInstance* inst = m_inv[slot_id];
if (!inst) {
continue;
}
inst->Serialize(ob, slot_id);
if (ob.tellp() == last_pos) {
LogInventory("Serialization failed on item slot [{}] during BulkSendInventoryItems. Item skipped", slot_id);
}
last_pos = ob.tellp();
}
// General items
for (int16 slot_id = EQ::invslot::GENERAL_BEGIN; slot_id <= EQ::invslot::GENERAL_END; slot_id++) {
// Possessions items
for (int16 slot_id = EQ::invslot::POSSESSIONS_BEGIN; slot_id <= EQ::invslot::POSSESSIONS_END; slot_id++) {
const EQ::ItemInstance* inst = m_inv[slot_id];
if (!inst) {
continue;
+8
View File
@@ -9,6 +9,7 @@
#include "lua_client.h"
#include "lua_item.h"
#include "lua_iteminst.h"
#include "lua_spawn.h"
struct Lua_NPC_Loot_List {
std::vector<uint32> entries;
@@ -932,6 +933,12 @@ void Lua_NPC::ReturnHandinItems(Lua_Client c)
self->ReturnHandinItems(c);
}
Lua_Spawn Lua_NPC::GetSpawn(lua_State* L)
{
Lua_Safe_Call_Class(Lua_Spawn);
return Lua_Spawn(self->GetSpawn());
}
luabind::scope lua_register_npc() {
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
.def(luabind::constructor<>())
@@ -1009,6 +1016,7 @@ luabind::scope lua_register_npc() {
.def("GetSilver", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSilver)
.def("GetSlowMitigation", (int(Lua_NPC::*)(void))&Lua_NPC::GetSlowMitigation)
.def("GetSp2", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetSp2)
.def("GetSpawn", (Lua_Spawn(Lua_NPC::*)(void))&Lua_NPC::GetSpawn)
.def("GetSpawnKillCount", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnKillCount)
.def("GetSpawnPointH", (float(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointH)
.def("GetSpawnPointID", (int(Lua_NPC::*)(void))&Lua_NPC::GetSpawnPointID)
+2
View File
@@ -10,6 +10,7 @@ class Lua_NPC;
class Lua_Client;
struct Lua_NPC_Loot_List;
class Lua_Inventory;
class Lua_Spawn;
namespace luabind {
struct scope;
@@ -196,6 +197,7 @@ public:
luabind::adl::object items_table
);
void ReturnHandinItems(Lua_Client c);
Lua_Spawn GetSpawn(lua_State* L);
};
#endif
+1 -1
View File
@@ -728,7 +728,7 @@ std::string Lua_Zone::GetBucketRemaining(const std::string& bucket_name)
}
luabind::scope lua_register_zone() {
return luabind::class_<Lua_Zone>("Zone")
return luabind::class_<Lua_Zone>("Zones")
.def(luabind::constructor<>())
.def("BuffTimersSuspended", &Lua_Zone::BuffTimersSuspended)
.def("BypassesExpansionCheck", &Lua_Zone::BypassesExpansionCheck)
+1 -4
View File
@@ -2751,10 +2751,7 @@ void NPC::LevelScale() {
uint32 NPC::GetSpawnPointID() const
{
if (respawn2) {
return respawn2->GetID();
}
return 0;
return respawn2 ? respawn2->GetID() : 0;
}
void NPC::NPCSlotTexture(uint8 slot, uint32 texture)
+1
View File
@@ -248,6 +248,7 @@ public:
uint16 GetWaypointMax() const { return wp_m; }
int32 GetGrid() const { return grid; }
Spawn2* GetSpawn() { return respawn2 ? respawn2 : nullptr; }
uint32 GetSpawnGroupId() const { return spawn_group_id; }
uint32 GetSpawnPointID() const;
+6
View File
@@ -875,6 +875,11 @@ void Perl_NPC_ReturnHandinItems(NPC *self, Client* c)
self->ReturnHandinItems(c);
}
Spawn2* Perl_NPC_GetSpawn(NPC* self)
{
return self->GetSpawn();
}
void perl_register_npc()
{
perl::interpreter perl(PERL_GET_THX);
@@ -953,6 +958,7 @@ void perl_register_npc()
package.add("GetSilver", &Perl_NPC_GetSilver);
package.add("GetSlowMitigation", &Perl_NPC_GetSlowMitigation);
package.add("GetSp2", &Perl_NPC_GetSp2);
package.add("GetSpawn", &Perl_NPC_GetSpawn);
package.add("GetSpawnKillCount", &Perl_NPC_GetSpawnKillCount);
package.add("GetSpawnPointH", &Perl_NPC_GetSpawnPointH);
package.add("GetSpawnPointID", &Perl_NPC_GetSpawnPointID);
+2 -2
View File
@@ -680,7 +680,7 @@ void QuestManager::stoptimer(const std::string& timer_name, Mob* m)
return;
}
for (auto e = QTimerList.begin(); e != QTimerList.end();) {
for (auto e = QTimerList.begin(); e != QTimerList.end(); ++e) {
if (e->mob && e->mob == m) {
parse->EventMob(EVENT_TIMER_STOP, m, nullptr, [&]() { return timer_name; });
@@ -890,7 +890,7 @@ void QuestManager::resumetimer(const std::string& timer_name, Mob* m)
}
}
QTimerList.emplace_back(QuestTimer(milliseconds, m, timer_name));
QTimerList.emplace_back(QuestTimer(milliseconds, mob, timer_name));
parse->EventMob(EVENT_TIMER_RESUME, mob, nullptr, f);