diff --git a/zone/bot.cpp b/zone/bot.cpp index cb4959629..7a2509db6 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2241,16 +2241,16 @@ void Bot::AI_Bot_Init() AIautocastspell_timer.reset(nullptr); casting_spell_AIindex = static_cast(AIBot_spells.size()); - roambox_max_x = 0; - roambox_max_y = 0; - roambox_min_x = 0; - roambox_min_y = 0; - roambox_distance = 0; - roambox_destination_x = 0; - roambox_destination_y = 0; - roambox_destination_z = 0; - roambox_min_delay = 2500; - roambox_delay = 2500; + m_roambox.max_x = 0; + m_roambox.max_y = 0; + m_roambox.min_x = 0; + m_roambox.min_y = 0; + m_roambox.distance = 0; + m_roambox.dest_x = 0; + m_roambox.dest_y = 0; + m_roambox.dest_z = 0; + m_roambox.delay = 2500; + m_roambox.min_delay = 2500; } void Bot::SpellProcess() { diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 07d6c2df4..72415b21f 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -404,16 +404,16 @@ void NPC::AI_Init() AIautocastspell_timer.reset(nullptr); casting_spell_AIindex = static_cast(AIspells.size()); - roambox_max_x = 0; - roambox_max_y = 0; - roambox_min_x = 0; - roambox_min_y = 0; - roambox_distance = 0; - roambox_destination_x = 0; - roambox_destination_y = 0; - roambox_destination_z = 0; - roambox_min_delay = 2500; - roambox_delay = 2500; + m_roambox.max_x = 0; + m_roambox.max_y = 0; + m_roambox.min_x = 0; + m_roambox.min_y = 0; + m_roambox.distance = 0; + m_roambox.dest_x = 0; + m_roambox.dest_y = 0; + m_roambox.dest_z = 0; + m_roambox.delay = 2500; + m_roambox.min_delay = 2500; } void Client::AI_Init() @@ -1592,123 +1592,9 @@ void NPC::AI_DoMovement() { return; } - /** - * Roambox logic sets precedence - */ - if (roambox_distance > 0) { - - // Check if we're already moving to a WP - // If so, if we're not moving we have arrived and need to set delay - - if (GetCWP() == EQ::WaypointStatus::RoamBoxPauseInProgress && !IsMoving()) { - // We have arrived - - int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay()); - int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4); - int random_timer = RandomTimer( - GetRoamboxMinDelay(), - move_delay_max - ); - - LogNPCRoamBoxDetail( - "({}) Timer calc | random_timer [{}] roambox_move_delay [{}] move_min [{}] move_max [{}]", - GetCleanName(), - random_timer, - roambox_move_delay, - (int) GetRoamboxMinDelay(), - move_delay_max - ); - - time_until_can_move = Timer::GetCurrentTime() + random_timer; - SetCurrentWP(0); - return; - } - - // Set a new destination - if (!IsMoving() && time_until_can_move < Timer::GetCurrentTime()) { - auto move_x = static_cast(zone->random.Real(-roambox_distance, roambox_distance)); - auto move_y = static_cast(zone->random.Real(-roambox_distance, roambox_distance)); - - roambox_destination_x = EQ::Clamp((GetX() + move_x), roambox_min_x, roambox_max_x); - roambox_destination_y = EQ::Clamp((GetY() + move_y), roambox_min_y, roambox_max_y); - - /** - * If our roambox was configured with large distances, chances of hitting the min or max end of - * the clamp is high, this causes NPC's to gather on the border of a box, to reduce clustering - * either lower the roambox distance or the code will do a simple random between min - max when it - * hits the min or max of the clamp - */ - if (roambox_destination_x == roambox_min_x || roambox_destination_x == roambox_max_x) { - roambox_destination_x = static_cast(zone->random.Real(roambox_min_x, roambox_max_x)); - } - - if (roambox_destination_y == roambox_min_y || roambox_destination_y == roambox_max_y) { - roambox_destination_y = static_cast(zone->random.Real(roambox_min_y, roambox_max_y)); - } - - /** - * If mob was not spawned in water, let's not randomly roam them into water - * if the roam box was sloppily configured - */ - if (!GetWasSpawnedInWater()) { - roambox_destination_z = GetGroundZ(roambox_destination_x, roambox_destination_y); - if (zone->HasMap() && zone->HasWaterMap()) { - auto position = glm::vec3( - roambox_destination_x, - roambox_destination_y, - roambox_destination_z - ); - - /** - * If someone brought us into water when we naturally wouldn't path there, return to spawn - */ - if (zone->watermap->InLiquid(position) && zone->watermap->InLiquid(m_Position)) { - roambox_destination_x = m_SpawnPoint.x; - roambox_destination_y = m_SpawnPoint.y; - } - - if (zone->watermap->InLiquid(position)) { - LogNPCRoamBoxDetail("[{}] | My destination is in water and I don't belong there!", GetCleanName()); - - return; - } - } - } - else { // Mob was in water, make sure new spot is in water also - roambox_destination_z = m_Position.z; - auto position = glm::vec3( - roambox_destination_x, - roambox_destination_y, - m_Position.z + 15 - ); - if (zone->HasWaterMap() && !zone->watermap->InLiquid(position)) { - roambox_destination_x = m_SpawnPoint.x; - roambox_destination_y = m_SpawnPoint.y; - roambox_destination_z = m_SpawnPoint.z; - } - } - - LogNPCRoamBox("[{}] | Pathing to [{}] [{}] [{}]", GetCleanName(), - roambox_destination_x, roambox_destination_y, - roambox_destination_z); - - LogNPCRoamBox( - "NPC ({}) distance [{}] X (min/max) [{} / {}] Y (min/max) [{} / {}] | Dest x/y/z [{} / {} / {}]", - GetCleanName(), - roambox_distance, - roambox_min_x, - roambox_max_x, - roambox_min_y, - roambox_max_y, - roambox_destination_x, - roambox_destination_y, - roambox_destination_z - ); - - SetCurrentWP(EQ::WaypointStatus::RoamBoxPauseInProgress); - NavigateTo(roambox_destination_x, roambox_destination_y, roambox_destination_z); - } - + // Roambox logic sets precedence + if (m_roambox.distance > 0) { + HandleRoambox(); return; } else if (roamer) { diff --git a/zone/npc.cpp b/zone/npc.cpp index c95cf1177..ff5b45b79 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -255,15 +255,18 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi } guard_anim = eaStanding; - roambox_distance = 0; - roambox_max_x = -2; - roambox_max_y = -2; - roambox_min_x = -2; - roambox_min_y = -2; - roambox_destination_x = -2; - roambox_destination_y = -2; - roambox_min_delay = 1000; - roambox_delay = 1000; + + m_roambox.max_x = -2; + m_roambox.max_y = -2; + m_roambox.min_x = -2; + m_roambox.min_y = -2; + m_roambox.distance = 0; + m_roambox.dest_x = -2; + m_roambox.dest_y = -2; + m_roambox.dest_z = 0; + m_roambox.delay = 1000; + m_roambox.min_delay = 1000; + p_depop = false; loottable_id = npc_type_data->loottable_id; skip_global_loot = npc_type_data->skip_global_loot; @@ -440,52 +443,52 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi float NPC::GetRoamboxMaxX() const { - return roambox_max_x; + return m_roambox.max_x; } float NPC::GetRoamboxMaxY() const { - return roambox_max_y; + return m_roambox.max_y; } float NPC::GetRoamboxMinX() const { - return roambox_min_x; + return m_roambox.min_x; } float NPC::GetRoamboxMinY() const { - return roambox_min_y; + return m_roambox.min_y; } float NPC::GetRoamboxDistance() const { - return roambox_distance; + return m_roambox.distance; } float NPC::GetRoamboxDestinationX() const { - return roambox_destination_x; + return m_roambox.dest_x; } float NPC::GetRoamboxDestinationY() const { - return roambox_destination_y; + return m_roambox.dest_y; } float NPC::GetRoamboxDestinationZ() const { - return roambox_destination_z; + return m_roambox.dest_z; } uint32 NPC::GetRoamboxDelay() const { - return roambox_delay; + return m_roambox.delay; } uint32 NPC::GetRoamboxMinDelay() const { - return roambox_min_delay; + return m_roambox.min_delay; } NPC::~NPC() @@ -3811,3 +3814,149 @@ void NPC::SendPositionToClients() } safe_delete(p); } + +void NPC::HandleRoambox() +{ + bool has_arrived = GetCWP() == EQ::WaypointStatus::RoamBoxPauseInProgress && !IsMoving(); + if (has_arrived) { + int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay()); + int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4); + int random_timer = RandomTimer( + GetRoamboxMinDelay(), + move_delay_max + ); + + LogNPCRoamBoxDetail( + "({}) random_timer [{}] roambox_move_delay [{}] move_min [{}] move_max [{}]", + GetCleanName(), + random_timer, + roambox_move_delay, + (int) GetRoamboxMinDelay(), + move_delay_max + ); + + time_until_can_move = Timer::GetCurrentTime() + random_timer; + SetCurrentWP(0); + return; + } + + bool ready_to_set_new_destination = !IsMoving() && time_until_can_move < Timer::GetCurrentTime(); + if (ready_to_set_new_destination) { + // make several attempts to find a valid next move in the box + bool can_path = false; + for (int i = 0; i < 10; i++) { + auto move_x = static_cast(zone->random.Real(-m_roambox.distance, m_roambox.distance)); + auto move_y = static_cast(zone->random.Real(-m_roambox.distance, m_roambox.distance)); + auto requested_x = EQ::Clamp((GetX() + move_x), m_roambox.min_x, m_roambox.max_x); + auto requested_y = EQ::Clamp((GetY() + move_y), m_roambox.min_y, m_roambox.max_y); + auto requested_z = GetGroundZ(requested_x, requested_y); + + std::vector heights = {0, 250, -250}; + for (auto &h: heights) { + if (CheckLosFN(requested_x, requested_y, requested_z + h, GetSize())) { + LogNPCRoamBox("[{}] Found line of sight to path attempt [{}] at height [{}]", GetCleanName(), i, h); + can_path = true; + break; + } + } + + if (!can_path) { + LogNPCRoamBox("[{}] | Failed line of sight to path attempt [{}]", GetCleanName(), i); + continue; + } + + m_roambox.dest_x = requested_x; + m_roambox.dest_y = requested_y; + + /** + * If our roambox was configured with large distances, chances of hitting the min or max end of + * the clamp is high, this causes NPC's to gather on the border of a box, to reduce clustering + * either lower the roambox distance or the code will do a simple random between min - max when it + * hits the min or max of the clamp + */ + if (m_roambox.dest_x == m_roambox.min_x || m_roambox.dest_x == m_roambox.max_x) { + m_roambox.dest_x = static_cast(zone->random.Real(m_roambox.min_x, m_roambox.max_x)); + } + + if (m_roambox.dest_y == m_roambox.min_y || m_roambox.dest_y == m_roambox.max_y) { + m_roambox.dest_y = static_cast(zone->random.Real(m_roambox.min_y, m_roambox.max_y)); + } + + // If mob was not spawned in water, let's not randomly roam them into water + // if the roam box was sloppily configured + if (!GetWasSpawnedInWater()) { + m_roambox.dest_z = GetGroundZ(m_roambox.dest_x, m_roambox.dest_y); + if (zone->HasMap() && zone->HasWaterMap()) { + auto position = glm::vec3( + m_roambox.dest_x, + m_roambox.dest_y, + m_roambox.dest_z + ); + + // If someone brought us into water when we naturally wouldn't path there, return to spawn + if (zone->watermap->InLiquid(position) && zone->watermap->InLiquid(m_Position)) { + m_roambox.dest_x = m_SpawnPoint.x; + m_roambox.dest_y = m_SpawnPoint.y; + } + + if (zone->watermap->InLiquid(position)) { + LogNPCRoamBoxDetail("[{}] | My destination is in water and I don't belong there!", GetCleanName()); + + return; + } + } + } + else { // Mob was in water, make sure new spot is in water also + m_roambox.dest_z = m_Position.z; + auto position = glm::vec3( + m_roambox.dest_x, + m_roambox.dest_y, + m_Position.z + 15 + ); + if (zone->HasWaterMap() && !zone->watermap->InLiquid(position)) { + m_roambox.dest_x = m_SpawnPoint.x; + m_roambox.dest_y = m_SpawnPoint.y; + m_roambox.dest_z = m_SpawnPoint.z; + } + } + + LogNPCRoamBox( + "[{}] | Pathing to [{}] [{}] [{}]", + GetCleanName(), + m_roambox.dest_x, + m_roambox.dest_y, + m_roambox.dest_z + ); + + LogNPCRoamBox( + "NPC ({}) distance [{}] X (min/max) [{} / {}] Y (min/max) [{} / {}] | Dest x/y/z [{} / {} / {}]", + GetCleanName(), + m_roambox.distance, + m_roambox.min_x, + m_roambox.max_x, + m_roambox.min_y, + m_roambox.max_y, + m_roambox.dest_x, + m_roambox.dest_y, + m_roambox.dest_z + ); + + if (can_path) { + SetCurrentWP(EQ::WaypointStatus::RoamBoxPauseInProgress); + NavigateTo(m_roambox.dest_x, m_roambox.dest_y, m_roambox.dest_z); + return; + } + } + + // failed to find path, reset timer + int roambox_move_delay = EQ::ClampLower(GetRoamboxDelay(), GetRoamboxMinDelay()); + int move_delay_max = (roambox_move_delay > 0 ? roambox_move_delay : (int) GetRoamboxMinDelay() * 4); + int random_timer = RandomTimer( + GetRoamboxMinDelay(), + move_delay_max + ); + time_until_can_move = Timer::GetCurrentTime() + random_timer; + } + + return; +} diff --git a/zone/npc.h b/zone/npc.h index 72729223d..9a703cf7b 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -80,6 +80,19 @@ struct AISpellsVar_Struct { uint8 idle_beneficial_chance; }; +struct Roambox { + float max_x; + float max_y; + float min_x; + float min_y; + float distance; + float dest_x; + float dest_y; + float dest_z; + uint32 delay; + uint32 min_delay; +}; + class SwarmPet; class Client; class Group; @@ -538,6 +551,8 @@ public: protected: + void HandleRoambox(); + const NPCType* NPCTypedata; NPCType* NPCTypedata_ours; //special case for npcs with uniquely created data. @@ -635,16 +650,8 @@ protected: glm::vec4 m_GuardPoint; glm::vec4 m_GuardPointSaved; EmuAppearance guard_anim; - float roambox_max_x; - float roambox_max_y; - float roambox_min_x; - float roambox_min_y; - float roambox_distance; - float roambox_destination_x; - float roambox_destination_y; - float roambox_destination_z; - uint32 roambox_delay; - uint32 roambox_min_delay; + + Roambox m_roambox = {}; uint16 skills[EQ::skills::HIGHEST_SKILL + 1]; diff --git a/zone/waypoints.cpp b/zone/waypoints.cpp index 7d5e3e02e..afa36c114 100644 --- a/zone/waypoints.cpp +++ b/zone/waypoints.cpp @@ -66,14 +66,14 @@ void NPC::AI_SetRoambox( uint32 min_delay ) { - roambox_distance = distance; - roambox_max_x = max_x; - roambox_min_x = min_x; - roambox_max_y = max_y; - roambox_min_y = min_y; - roambox_destination_x = roambox_max_x + 1; // this will trigger a recalc - roambox_delay = delay; - roambox_min_delay = min_delay; + m_roambox.distance = distance; + m_roambox.max_x = max_x; + m_roambox.min_x = min_x; + m_roambox.max_y = max_y; + m_roambox.min_y = min_y; + m_roambox.dest_x = max_x + 1; // this will trigger a recalc + m_roambox.delay = delay; + m_roambox.min_delay = min_delay; } void NPC::DisplayWaypointInfo(Client *client) {