[Performance] Client / NPC Position Update Optimizations (#4602)

* Zone optimizations

* More changes

* More

* Update entity.cpp

* Beautiful

* Amazing

* Feature flag all logic

* Broadcast to group

* Update mob.cpp

* Updates

* Update client.cpp

* Update client.cpp

* Add rule Zone:EnableEntityClipping

* Little bit of cleanup

* Don't send update to self while in group

* Remove visibility work and feature flags

* Cleanup

* Logging

* Improve CheckSendBulkNpcPositions

* No need to cast

* Field cleanup

* Build initial list on zone-in
This commit is contained in:
Chris Miles 2025-01-21 15:35:19 -06:00 committed by GitHub
parent 1a27127c39
commit 25826c6686
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 203 additions and 214 deletions

View File

@ -6289,7 +6289,20 @@ INSERT INTO `items_evolving_details` VALUES
)",
.content_schema_update = true
}
},
ManifestEntry{
.version = 9291,
.description = "2025_01_21_add_remove_zone_fields",
.check = "SHOW COLUMNS FROM `zone` LIKE 'client_update_range'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE zone DROP COLUMN IF EXISTS npc_update_range;
ALTER TABLE zone DROP COLUMN IF EXISTS max_movement_update_range;
ALTER TABLE `zone` ADD COLUMN `client_update_range` int(11) NOT NULL DEFAULT 600 AFTER `npc_max_aggro_dist`;
)",
.content_schema_update = true
},
// -- template; copy/paste this when you need to create a new entry
// ManifestEntry{
// .version = 9228,

View File

@ -143,6 +143,7 @@ namespace Logs {
Corpses,
XTargets,
EvolveItem,
PositionUpdate,
MaxCategoryID /* Don't Remove this */
};
@ -244,7 +245,8 @@ namespace Logs {
"EqTime",
"Corpses",
"XTargets",
"EvolveItem"
"EvolveItem",
"PositionUpdate"
};
}

View File

@ -854,6 +854,16 @@
OutF(LogSys, Logs::Detail, Logs::XTargets, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogPositionUpdate(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::PositionUpdate))\
OutF(LogSys, Logs::General, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogPositionUpdateDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::PositionUpdate))\
OutF(LogSys, Logs::Detail, Logs::PositionUpdate, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\
if (LogSys.IsLogEnabled(debug_level, log_category))\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -111,7 +111,7 @@ public:
int32_t fast_regen_mana;
int32_t fast_regen_endurance;
int32_t npc_max_aggro_dist;
uint32_t max_movement_update_range;
uint32_t client_update_range;
int32_t underworld_teleport_index;
int32_t lava_damage;
int32_t min_lava_damage;
@ -220,7 +220,7 @@ public:
"fast_regen_mana",
"fast_regen_endurance",
"npc_max_aggro_dist",
"max_movement_update_range",
"client_update_range",
"underworld_teleport_index",
"lava_damage",
"min_lava_damage",
@ -325,7 +325,7 @@ public:
"fast_regen_mana",
"fast_regen_endurance",
"npc_max_aggro_dist",
"max_movement_update_range",
"client_update_range",
"underworld_teleport_index",
"lava_damage",
"min_lava_damage",
@ -464,7 +464,7 @@ public:
e.fast_regen_mana = 180;
e.fast_regen_endurance = 180;
e.npc_max_aggro_dist = 600;
e.max_movement_update_range = 600;
e.client_update_range = 600;
e.underworld_teleport_index = 0;
e.lava_damage = 50;
e.min_lava_damage = 10;
@ -599,7 +599,7 @@ public:
e.fast_regen_mana = row[89] ? static_cast<int32_t>(atoi(row[89])) : 180;
e.fast_regen_endurance = row[90] ? static_cast<int32_t>(atoi(row[90])) : 180;
e.npc_max_aggro_dist = row[91] ? static_cast<int32_t>(atoi(row[91])) : 600;
e.max_movement_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.client_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.underworld_teleport_index = row[93] ? static_cast<int32_t>(atoi(row[93])) : 0;
e.lava_damage = row[94] ? static_cast<int32_t>(atoi(row[94])) : 50;
e.min_lava_damage = row[95] ? static_cast<int32_t>(atoi(row[95])) : 10;
@ -730,7 +730,7 @@ public:
v.push_back(columns[89] + " = " + std::to_string(e.fast_regen_mana));
v.push_back(columns[90] + " = " + std::to_string(e.fast_regen_endurance));
v.push_back(columns[91] + " = " + std::to_string(e.npc_max_aggro_dist));
v.push_back(columns[92] + " = " + std::to_string(e.max_movement_update_range));
v.push_back(columns[92] + " = " + std::to_string(e.client_update_range));
v.push_back(columns[93] + " = " + std::to_string(e.underworld_teleport_index));
v.push_back(columns[94] + " = " + std::to_string(e.lava_damage));
v.push_back(columns[95] + " = " + std::to_string(e.min_lava_damage));
@ -850,7 +850,7 @@ public:
v.push_back(std::to_string(e.fast_regen_mana));
v.push_back(std::to_string(e.fast_regen_endurance));
v.push_back(std::to_string(e.npc_max_aggro_dist));
v.push_back(std::to_string(e.max_movement_update_range));
v.push_back(std::to_string(e.client_update_range));
v.push_back(std::to_string(e.underworld_teleport_index));
v.push_back(std::to_string(e.lava_damage));
v.push_back(std::to_string(e.min_lava_damage));
@ -978,7 +978,7 @@ public:
v.push_back(std::to_string(e.fast_regen_mana));
v.push_back(std::to_string(e.fast_regen_endurance));
v.push_back(std::to_string(e.npc_max_aggro_dist));
v.push_back(std::to_string(e.max_movement_update_range));
v.push_back(std::to_string(e.client_update_range));
v.push_back(std::to_string(e.underworld_teleport_index));
v.push_back(std::to_string(e.lava_damage));
v.push_back(std::to_string(e.min_lava_damage));
@ -1110,7 +1110,7 @@ public:
e.fast_regen_mana = row[89] ? static_cast<int32_t>(atoi(row[89])) : 180;
e.fast_regen_endurance = row[90] ? static_cast<int32_t>(atoi(row[90])) : 180;
e.npc_max_aggro_dist = row[91] ? static_cast<int32_t>(atoi(row[91])) : 600;
e.max_movement_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.client_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.underworld_teleport_index = row[93] ? static_cast<int32_t>(atoi(row[93])) : 0;
e.lava_damage = row[94] ? static_cast<int32_t>(atoi(row[94])) : 50;
e.min_lava_damage = row[95] ? static_cast<int32_t>(atoi(row[95])) : 10;
@ -1233,7 +1233,7 @@ public:
e.fast_regen_mana = row[89] ? static_cast<int32_t>(atoi(row[89])) : 180;
e.fast_regen_endurance = row[90] ? static_cast<int32_t>(atoi(row[90])) : 180;
e.npc_max_aggro_dist = row[91] ? static_cast<int32_t>(atoi(row[91])) : 600;
e.max_movement_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.client_update_range = row[92] ? static_cast<uint32_t>(strtoul(row[92], nullptr, 10)) : 600;
e.underworld_teleport_index = row[93] ? static_cast<int32_t>(atoi(row[93])) : 0;
e.lava_damage = row[94] ? static_cast<int32_t>(atoi(row[94])) : 50;
e.min_lava_damage = row[95] ? static_cast<int32_t>(atoi(row[95])) : 10;
@ -1406,7 +1406,7 @@ public:
v.push_back(std::to_string(e.fast_regen_mana));
v.push_back(std::to_string(e.fast_regen_endurance));
v.push_back(std::to_string(e.npc_max_aggro_dist));
v.push_back(std::to_string(e.max_movement_update_range));
v.push_back(std::to_string(e.client_update_range));
v.push_back(std::to_string(e.underworld_teleport_index));
v.push_back(std::to_string(e.lava_damage));
v.push_back(std::to_string(e.min_lava_damage));
@ -1527,7 +1527,7 @@ public:
v.push_back(std::to_string(e.fast_regen_mana));
v.push_back(std::to_string(e.fast_regen_endurance));
v.push_back(std::to_string(e.npc_max_aggro_dist));
v.push_back(std::to_string(e.max_movement_update_range));
v.push_back(std::to_string(e.client_update_range));
v.push_back(std::to_string(e.underworld_teleport_index));
v.push_back(std::to_string(e.lava_damage));
v.push_back(std::to_string(e.min_lava_damage));

View File

@ -86,6 +86,9 @@ struct BenchTimer
void reset() { start_time = clock::now(); }
// this is seconds
double elapsed() { return std::chrono::duration<double> (clock::now() - start_time).count(); }
std::chrono::milliseconds::rep elapsedMilliseconds() { return std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - start_time).count(); }
std::chrono::microseconds::rep elapsedMicroseconds() { return std::chrono::duration_cast<std::chrono::microseconds>(clock::now() - start_time).count(); }
std::chrono::nanoseconds::rep elapsedNanoseconds() { return std::chrono::duration_cast<std::chrono::nanoseconds>(clock::now() - start_time).count(); }
private:
std::chrono::time_point<std::chrono::high_resolution_clock> start_time;
};

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/
#define CURRENT_BINARY_DATABASE_VERSION 9290
#define CURRENT_BINARY_DATABASE_VERSION 9291
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9045
#endif

View File

@ -676,12 +676,6 @@ int ZoneStore::GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version)
return z ? z->npc_max_aggro_dist : DEFAULT_ZONE_MAX_AGGRO_DISTANCE;
}
uint32 ZoneStore::GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version)
{
const auto& z = GetZoneVersionWithFallback(zone_id, version);
return z ? z->max_movement_update_range : DEFAULT_ZONE_MAX_MOVEMENT_UPDATE_RANGE;
}
int8 ZoneStore::GetZoneMinimumExpansion(uint32 zone_id, int version)
{
const auto& z = GetZoneVersionWithFallback(zone_id, version);

View File

@ -94,7 +94,6 @@ public:
int GetZoneFastRegenMana(uint32 zone_id, int version = 0);
int GetZoneFastRegenEndurance(uint32 zone_id, int version = 0);
int GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version = 0);
uint32 GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version = 0);
int8 GetZoneMinimumExpansion(uint32 zone_id, int version = 0);
int8 GetZoneMaximumExpansion(uint32 zone_id, int version = 0);
const std::string GetZoneContentFlags(uint32 zone_id, int version = 0);

View File

@ -7390,39 +7390,6 @@ void EntityList::ShowSpawnWindow(Client* client, int Distance, bool NamedOnly) {
return;
}
/**
* @param close_mobs
* @param scanning_mob
*/
void EntityList::ScanCloseClientMobs(std::unordered_map<uint16, Mob*>& close_mobs, Mob* scanning_mob)
{
float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
close_mobs.clear();
for (auto& e : mob_list) {
auto mob = e.second;
if (!mob->IsClient()) {
continue;
}
if (mob->GetID() <= 0) {
continue;
}
float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition());
if (distance <= scan_range) {
close_mobs.insert(std::pair<uint16, Mob*>(mob->GetID(), mob));
}
else if (mob->GetAggroRange() >= scan_range) {
close_mobs.insert(std::pair<uint16, Mob*>(mob->GetID(), mob));
}
}
LogAIScanCloseDetail("Close Client Mob List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName());
}
uint8 Bot::GetNumberNeedingHealedInGroup(uint8 hpr, bool includePets, Raid* raid) {
uint8 need_healed = 0;

View File

@ -157,7 +157,7 @@ Client::Client(EQStreamInterface *ieqs) : Mob(
endupkeep_timer(1000),
autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000),
m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)),
m_client_zone_wide_full_position_update_timer(5 * 60 * 1000),
m_client_bulk_npc_pos_update_timer(60 * 1000),
tribute_timer(Tribute_duration),
proximity_timer(ClientProximity_interval),
TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000),
@ -12939,50 +12939,77 @@ void Client::SendTopLevelInventory()
}
}
// On a normal basis we limit mob movement updates based on distance
// This ensures we send a periodic full zone update to a client that has started moving after 5 or so minutes
//
// For very large zones we will also force a full update based on distance
//
// We ignore a small distance around us so that we don't interrupt already pathing deltas as those npcs will appear
// to full stop when they are actually still pathing
void Client::CheckSendBulkClientPositionUpdate()
void Client::CheckSendBulkNpcPositions()
{
float distance_moved = DistanceNoZ(m_last_position_before_bulk_update, GetPosition());
bool moved_far_enough_before_bulk_update = distance_moved >= zone->GetNpcPositionUpdateDistance();
float update_range = RuleI(Range, MobCloseScanDistance);
bool moved_far_enough_before_bulk_update = distance_moved >= update_range;
bool is_ready_to_update = (
m_client_zone_wide_full_position_update_timer.Check() || moved_far_enough_before_bulk_update
m_client_bulk_npc_pos_update_timer.Check() || moved_far_enough_before_bulk_update
);
if (IsMoving() && is_ready_to_update) {
LogDebug("[[{}]] Client Zone Wide Position Update NPCs", GetCleanName());
int updated_count = 0;
int skipped_count = 0;
if (is_ready_to_update) {
auto &mob_movement_manager = MobMovementManager::Get();
auto &mob_list = entity_list.GetMobList();
for (auto &it : mob_list) {
Mob *entity = it.second;
if (!entity->IsNPC()) {
for (auto &e: entity_list.GetMobList()) {
Mob *mob = e.second;
if (!mob->IsNPC()) {
continue;
}
int animation_speed = 0;
if (entity->IsMoving()) {
if (entity->IsRunning()) {
animation_speed = (entity->IsFeared() ? entity->GetFearSpeed() : entity->GetRunspeed());
if (mob->IsMoving()) {
if (mob->IsRunning()) {
animation_speed = (mob->IsFeared() ? mob->GetFearSpeed() : mob->GetRunspeed());
}
else {
animation_speed = entity->GetWalkspeed();
animation_speed = mob->GetWalkspeed();
}
}
mob_movement_manager.SendCommandToClients(entity, 0.0, 0.0, 0.0, 0.0, animation_speed, ClientRangeAny, this);
// if we have seen this mob before, and it hasn't moved, skip it
if (m_last_seen_mob_position.contains(mob->GetID())) {
if (m_last_seen_mob_position[mob->GetID()] == mob->GetPosition()) {
LogPositionUpdateDetail(
"Mob [{}] has already been sent to client [{}] at this position, skipping",
mob->GetCleanName(),
GetCleanName()
);
skipped_count++;
continue;
}
}
mob_movement_manager.SendCommandToClients(
mob,
0.0,
0.0,
0.0,
0.0,
animation_speed,
ClientRangeAny,
this
);
updated_count++;
}
LogPositionUpdate(
"[{}] Sent [{}] bulk updated NPC positions, skipped [{}] distance_moved [{}] update_range [{}]",
GetCleanName(),
updated_count,
skipped_count,
distance_moved,
update_range
);
m_last_position_before_bulk_update = GetPosition();
}
}
const uint16 scan_npc_aggro_timer_idle = RuleI(Aggro, ClientAggroCheckIdleInterval);
const uint16 scan_npc_aggro_timer_moving = RuleI(Aggro, ClientAggroCheckMovingInterval);
@ -13123,3 +13150,32 @@ void Client::SetAAEXPPercentage(uint8 percentage)
SendAlternateAdvancementStats();
SendAlternateAdvancementTable();
}
void Client::BroadcastPositionUpdate()
{
EQApplicationPacket outapp(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
PlayerPositionUpdateServer_Struct *spu = (PlayerPositionUpdateServer_Struct *) outapp.pBuffer;
memset(spu, 0x00, sizeof(PlayerPositionUpdateServer_Struct));
spu->spawn_id = GetID();
spu->x_pos = FloatToEQ19(GetX());
spu->y_pos = FloatToEQ19(GetY());
spu->z_pos = FloatToEQ19(GetZ());
spu->heading = FloatToEQ12(GetHeading());
spu->delta_x = FloatToEQ13(0);
spu->delta_y = FloatToEQ13(0);
spu->delta_z = FloatToEQ13(0);
spu->delta_heading = FloatToEQ10(0);
spu->animation = 0;
entity_list.QueueCloseClients(this, &outapp, true, zone->GetClientUpdateRange());
Group *g = GetGroup();
if (g) {
for (auto & m : g->members) {
if (m && m->IsClient() && m != this) {
m->CastToClient()->QueuePacket(&outapp);
}
}
}
}

View File

@ -2087,12 +2087,13 @@ private:
Timer m_client_npc_aggro_scan_timer;
void CheckClientToNpcAggroTimer();
void ClientToNpcAggroProcess();
void BroadcastPositionUpdate();
// bulk position updates
glm::vec4 m_last_position_before_bulk_update;
Timer m_client_zone_wide_full_position_update_timer;
Timer m_client_bulk_npc_pos_update_timer;
Timer m_position_update_timer;
void CheckSendBulkClientPositionUpdate();
void CheckSendBulkNpcPositions();
void BulkSendInventoryItems();

View File

@ -971,6 +971,16 @@ void Client::CompleteConnect()
RecordStats();
AutoGrantAAPoints();
// set initial position for mob tracking
m_last_seen_mob_position.reserve(entity_list.GetMobList().size());
for (auto& mob : entity_list.GetMobList()) {
if (!mob.second->IsNPC()) {
continue;
}
m_last_seen_mob_position[mob.second->GetID()] = mob.second->GetPosition();
}
// enforce some rules..
if (!CanEnterZone()) {
LogInfo("Kicking character [{}] from zone, not allowed here (missing requirements)", GetCleanName());
@ -4956,7 +4966,7 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app) {
CheckScanCloseMobsMovingTimer();
}
CheckSendBulkClientPositionUpdate();
CheckSendBulkNpcPositions();
int32 new_animation = ppu->animation;

View File

@ -122,7 +122,7 @@ bool Client::Process() {
/* I haven't naturally updated my position in 10 seconds, updating manually */
if (!IsMoving() && m_position_update_timer.Check()) {
SentPositionPacket(0.0f, 0.0f, 0.0f, 0.0f, 0);
BroadcastPositionUpdate();
}
if (mana_timer.Check())

View File

@ -5653,16 +5653,6 @@ int Perl__GetZoneNPCMaximumAggroDistance(uint32 zone_id, int version)
return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version);
}
uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id)
{
return zone_store.GetZoneMaximumMovementUpdateRange(zone_id);
}
uint32 Perl__GetZoneMaximumMovementUpdateRange(uint32 zone_id, int version)
{
return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version);
}
int8 Perl__GetZoneMinimumExpansion(uint32 zone_id)
{
return zone_store.GetZoneMinimumExpansion(zone_id);
@ -6112,8 +6102,6 @@ void perl_register_quest()
package.add("GetZoneMaximumExpansion", (int8(*)(uint32, int))&Perl__GetZoneMaximumExpansion);
package.add("GetZoneMaximumLevel", (uint8(*)(uint32))&Perl__GetZoneMaximumLevel);
package.add("GetZoneMaximumLevel", (uint8(*)(uint32, int))&Perl__GetZoneMaximumLevel);
package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32))&Perl__GetZoneMaximumMovementUpdateRange);
package.add("GetZoneMaximumMovementUpdateRange", (uint32(*)(uint32, int))&Perl__GetZoneMaximumMovementUpdateRange);
package.add("GetZoneMaximumPlayers", (int(*)(uint32))&Perl__GetZoneMaximumPlayers);
package.add("GetZoneMaximumPlayers", (int(*)(uint32, int))&Perl__GetZoneMaximumPlayers);
package.add("GetZoneMinimumClip", (float(*)(uint32))&Perl__GetZoneMinimumClip);

View File

@ -1717,15 +1717,6 @@ void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *a
}
}
/**
* @param sender
* @param app
* @param ignore_sender
* @param distance
* @param skipped_mob
* @param is_ack_required
* @param filter
*/
void EntityList::QueueCloseClients(
Mob *sender,
const EQApplicationPacket *app,
@ -1742,7 +1733,7 @@ void EntityList::QueueCloseClients(
}
if (distance <= 0) {
distance = 600;
distance = zone->GetClientUpdateRange();
}
float distance_squared = distance * distance;
@ -2878,6 +2869,8 @@ bool EntityList::RemoveMobFromCloseLists(Mob *mob)
);
it->second->m_close_mobs.erase(entity_id);
it->second->m_last_seen_mob_position.erase(entity_id);
++it;
}
@ -2931,6 +2924,9 @@ void EntityList::RemoveAuraFromMobs(Mob *aura)
// All of the above makes a tremendous impact on the bottom line of cpu cycle performance because we run an order of magnitude
// less checks by focusing our hot path logic down to a very small subset of relevant entities instead of looping an entire
// entity list (zone wide)
BenchTimer g_scan_bench_timer;
void EntityList::ScanCloseMobs(Mob *scanning_mob)
{
if (!scanning_mob) {
@ -2941,7 +2937,9 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
return;
}
float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
g_scan_bench_timer.reset();
float scan_range = RuleI(Range, MobCloseScanDistance);
// Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved.
// Assuming mob_list.size() as an upper bound for reservation.
@ -2957,7 +2955,7 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
continue;
}
float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition());
float distance = Distance(scanning_mob->GetPosition(), mob->GetPosition());
if (distance <= scan_range || mob->GetAggroRange() >= scan_range) {
// add mob to scanning_mob's close list and vice versa
// check if the mob is already in the close mobs list before inserting
@ -2969,10 +2967,11 @@ void EntityList::ScanCloseMobs(Mob *scanning_mob)
}
LogAIScanClose(
"[{}] Scanning close list > list_size [{}] moving [{}]",
"[{}] Scanning close list > list_size [{}] moving [{}] elapsed [{}] us",
scanning_mob->GetCleanName(),
scanning_mob->m_close_mobs.size(),
scanning_mob->IsMoving() ? "true" : "false"
scanning_mob->IsMoving() ? "true" : "false",
g_scan_bench_timer.elapsedMicroseconds()
);
}
@ -5759,10 +5758,6 @@ void EntityList::ReloadMerchants() {
* then we return the full list
*
* See comments @EntityList::ScanCloseMobs for system explanation
*
* @param mob
* @param distance
* @return
*/
std::unordered_map<uint16, Mob *> &EntityList::GetCloseMobList(Mob *mob, float distance)
{

View File

@ -631,8 +631,6 @@ private:
bool Bot_AICheckCloseBeneficialSpells(Bot* caster, uint8 iChance, float iRange, uint32 iSpellTypes); // TODO: Evaluate this closesly in hopes to eliminate
void ShowSpawnWindow(Client* client, int Distance, bool NamedOnly); // TODO: Implement ShowSpawnWindow in the bot class but it needs entity list stuff
void ScanCloseClientMobs(std::unordered_map<uint16, Mob*>& close_mobs, Mob* scanning_mob);
void GetBotList(std::list<Bot*> &b_list);
private:
std::list<Bot*> bot_list;

View File

@ -4668,16 +4668,6 @@ int lua_get_zone_npc_maximum_aggro_distance(uint32 zone_id, int version)
return zone_store.GetZoneNPCMaximumAggroDistance(zone_id, version);
}
uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id)
{
return zone_store.GetZoneMaximumMovementUpdateRange(zone_id);
}
uint32 lua_get_zone_maximum_movement_update_range(uint32 zone_id, int version)
{
return zone_store.GetZoneMaximumMovementUpdateRange(zone_id, version);
}
int8 lua_get_zone_minimum_expansion(uint32 zone_id)
{
return zone_store.GetZoneMinimumExpansion(zone_id);
@ -6287,8 +6277,6 @@ luabind::scope lua_register_general() {
luabind::def("get_zone_fast_regen_endurance", (int(*)(uint32,int))&lua_get_zone_fast_regen_endurance),
luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32))&lua_get_zone_npc_maximum_aggro_distance),
luabind::def("get_zone_npc_maximum_aggro_distance", (int(*)(uint32,int))&lua_get_zone_npc_maximum_aggro_distance),
luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32))&lua_get_zone_maximum_movement_update_range),
luabind::def("get_zone_npc_maximum_movement_update_range", (uint32(*)(uint32,int))&lua_get_zone_maximum_movement_update_range),
luabind::def("get_zone_minimum_expansion", (int8(*)(uint32))&lua_get_zone_minimum_expansion),
luabind::def("get_zone_minimum_expansion", (int8(*)(uint32,int))&lua_get_zone_minimum_expansion),
luabind::def("get_zone_maximum_expansion", (int8(*)(uint32))&lua_get_zone_maximum_expansion),

View File

@ -129,6 +129,7 @@ Mob::Mob(
position_update_melee_push_timer(500),
hate_list_cleanup_timer(6000),
m_scan_close_mobs_timer(6000),
m_see_close_mobs_timer(1000),
m_mob_check_moving_timer(1000),
bot_attack_flag_timer(10000)
{
@ -8612,15 +8613,15 @@ std::unordered_map<uint16, Mob *> &Mob::GetCloseMobList(float distance)
void Mob::ClearDataBucketCache()
{
if (IsOfClientBot()) {
uint64 id = 0;
uint64 id = 0;
DataBucketLoadType::Type t{};
if (IsBot()) {
id = CastToBot()->GetBotID();
t = DataBucketLoadType::Bot;
t = DataBucketLoadType::Bot;
}
else if (IsClient()) {
id = CastToClient()->CharacterID();
t = DataBucketLoadType::Client;
t = DataBucketLoadType::Client;
}
DataBucket::DeleteFromCache(id, t);

View File

@ -201,9 +201,11 @@ public:
void DisplayInfo(Mob *mob);
std::unordered_map<uint16, Mob *> m_close_mobs;
Timer m_scan_close_mobs_timer;
Timer m_mob_check_moving_timer;
std::unordered_map<uint16, Mob *> m_close_mobs;
std::unordered_map<int, glm::vec4> m_last_seen_mob_position;
Timer m_scan_close_mobs_timer;
Timer m_see_close_mobs_timer;
Timer m_mob_check_moving_timer;
// Bot attack flag
Timer bot_attack_flag_timer;

View File

@ -851,12 +851,24 @@ void MobMovementManager::SendCommandToClients(
_impl->Stats.TotalSentPosition++;
}
if (c->m_last_seen_mob_position.contains(mob->GetID())) {
if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) {
LogPositionUpdate(
"Mob [{}] has already been sent to client [{}] at this position, skipping",
mob->GetCleanName(),
c->GetCleanName()
);
continue;
}
}
c->QueuePacket(&outapp, false);
c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition();
}
}
else {
float short_range = RuleR(Pathing, ShortMovementUpdateRange);
float long_range = zone->GetNpcPositionUpdateDistance();
float long_range = RuleI(Range, MobCloseScanDistance);
for (auto &c : _impl->Clients) {
if (single_client && c != single_client) {
@ -901,7 +913,19 @@ void MobMovementManager::SendCommandToClients(
_impl->Stats.TotalSentPosition++;
}
if (c->m_last_seen_mob_position.contains(mob->GetID())) {
if (c->m_last_seen_mob_position[mob->GetID()] == mob->GetPosition() && anim == 0) {
LogPositionUpdate(
"Mob [{}] has already been sent to client [{}] at this position, skipping",
mob->GetCleanName(),
c->GetCleanName()
);
continue;
}
}
c->QueuePacket(&outapp, false);
c->m_last_seen_mob_position[mob->GetID()] = mob->GetPosition();
}
}
}

View File

@ -997,8 +997,8 @@ NPC * NPC::SpawnNodeNPC(std::string name, std::string last_name, const glm::vec4
npc_type->current_hp = 4000000;
npc_type->max_hp = 4000000;
npc_type->race = 2254;
npc_type->gender = 2;
npc_type->race = 127;
npc_type->gender = 0;
npc_type->class_ = 9;
npc_type->deity = 1;
npc_type->level = 200;

View File

@ -7378,15 +7378,17 @@ void Mob::SetHP(int64 hp)
void Mob::DrawDebugCoordinateNode(std::string node_name, const glm::vec4 vec)
{
NPC *node = nullptr;
NPC *node = nullptr;
for (const auto &n: entity_list.GetNPCList()) {
if (n.second->GetCleanName() == node_name) {
if (n.second->GetEntityVariable("node_parent_id") == std::to_string(GetID())) {
node = n.second;
break;
}
}
if (!node) {
node = NPC::SpawnNodeNPC(node_name, "", GetPosition());
node->SetEntityVariable("node_parent_id", std::to_string(GetID()));
}
}

View File

@ -1091,7 +1091,6 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name)
mMovementManager = &MobMovementManager::Get();
SetNpcPositionUpdateDistance(0);
SetQuestHotReloadQueued(false);
}
@ -1374,17 +1373,17 @@ bool Zone::LoadZoneCFG(const char* filename, uint16 instance_version)
newzone_data.suspend_buffs = z->suspendbuffs;
// local attributes
can_bind = z->canbind != 0;
is_city = z->canbind == 2;
can_combat = z->cancombat != 0;
can_levitate = z->canlevitate != 0;
can_castoutdoor = z->castoutdoor != 0;
is_hotzone = z->hotzone != 0;
max_movement_update_range = z->max_movement_update_range;
default_ruleset = z->ruleset;
allow_mercs = true;
m_graveyard_id = z->graveyard_id;
m_max_clients = z->maxclients;
can_bind = z->canbind != 0;
is_city = z->canbind == 2;
can_combat = z->cancombat != 0;
can_levitate = z->canlevitate != 0;
can_castoutdoor = z->castoutdoor != 0;
is_hotzone = z->hotzone != 0;
m_client_update_range = z->client_update_range;
default_ruleset = z->ruleset;
allow_mercs = true;
m_graveyard_id = z->graveyard_id;
m_max_clients = z->maxclients;
SetIdleWhenEmpty(z->idle_when_empty);
SetSecondsBeforeIdle(z->seconds_before_idle);
@ -1538,10 +1537,6 @@ bool Zone::Process() {
if (adv_data && !did_adventure_actions) {
DoAdventureActions();
}
if (GetNpcPositionUpdateDistance() == 0) {
CalculateNpcUpdateDistanceSpread();
}
}
if (hot_reload_timer.Check() && IsQuestHotReloadQueued()) {
@ -2735,62 +2730,6 @@ void Zone::SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp) {
m_ucss_available = ucss_available;
}
int Zone::GetNpcPositionUpdateDistance() const
{
return npc_position_update_distance;
}
void Zone::SetNpcPositionUpdateDistance(int in_npc_position_update_distance)
{
Zone::npc_position_update_distance = in_npc_position_update_distance;
}
void Zone::CalculateNpcUpdateDistanceSpread()
{
float max_x = 0;
float max_y = 0;
float min_x = 0;
float min_y = 0;
auto &mob_list = entity_list.GetMobList();
for (auto &it : mob_list) {
Mob *entity = it.second;
if (!entity->IsNPC()) {
continue;
}
if (entity->GetX() <= min_x) {
min_x = entity->GetX();
}
if (entity->GetY() <= min_y) {
min_y = entity->GetY();
}
if (entity->GetX() >= max_x) {
max_x = entity->GetX();
}
if (entity->GetY() >= max_y) {
max_y = entity->GetY();
}
}
int x_spread = int(std::abs(max_x - min_x));
int y_spread = int(std::abs(max_y - min_y));
int combined_spread = int(std::abs((x_spread + y_spread) / 2));
int update_distance = EQ::ClampLower(int(combined_spread / 4), int(zone->GetMaxMovementUpdateRange()));
SetNpcPositionUpdateDistance(update_distance);
Log(Logs::General, Logs::Debug,
"NPC update spread distance set to [%i] combined_spread [%i]",
update_distance,
combined_spread
);
}
bool Zone::IsQuestHotReloadQueued() const
{
return quest_hot_reload_queued;

View File

@ -156,9 +156,6 @@ public:
bool SaveZoneCFG();
bool DoesAlternateCurrencyExist(uint32 currency_id);
int GetNpcPositionUpdateDistance() const;
void SetNpcPositionUpdateDistance(int in_npc_position_update_distance);
char *adv_data;
const char *GetSpellBlockedMessage(uint32 spell_id, const glm::vec3 &location);
@ -417,7 +414,7 @@ public:
SendDiscordMessage(webhook_id, message_prefix + Discord::FormatDiscordMessage(log_category, message));
};
double GetMaxMovementUpdateRange() const { return max_movement_update_range; }
double GetClientUpdateRange() const { return m_client_update_range; }
void SetIsHotzone(bool is_hotzone);
@ -469,7 +466,7 @@ private:
bool staticzone;
bool zone_has_current_time;
bool quest_hot_reload_queued;
double max_movement_update_range;
double m_client_update_range;
char *long_name;
char *map_name;
char *short_name;