[Performance] Reworked how client to NPC aggro checks are made

- Before when reverse aggro checks were done (client to NPC), checks would happen every 750 millseconds where a client would
		check an entire entity list with distance calcs and other checks for aggro, with many clients in a zone and many NPC's this would
		add a lot of unecessary overhead. A temporary adjustment on 3/25 was made and upped the check to 6 seconds.
	- Now, there is a new methodology to scanning. The client will build a cache list of NPC's within close range as defined in new rule:
		RULE_INT(Range, ClientNPCScan, 300) and will also get any NPC that has an aggro range beyond that defined range to use in
		the frequent checks for aggro, the result is far less overhead
	- Client scanning changes when moving versus not moving, the client will scan aggro every 500 milliseconds while moving, and
		3000 millseconds aggro check when not moving, with a 6000ms re-fetch for close NPC's
	- A demo of these changes can be found here:
		https://youtu.be/aGroiwLSTVU
This commit is contained in:
Akkadius 2017-03-28 01:30:42 -05:00
parent 940f3b03e8
commit 4b6ce1c19e
9 changed files with 191 additions and 105 deletions

View File

@ -1,8 +1,21 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) EQEMu Changelog (Started on Sept 24, 2003 15:50)
------------------------------------------------------- -------------------------------------------------------
== 03/27/2017 ==
Akkadius: [Performance] Reworked how client to NPC aggro checks are made
- Before when reverse aggro checks were done (client to NPC), checks would happen every 750 millseconds where a client would
check an entire entity list with distance calcs and other checks for aggro, with many clients in a zone and many NPC's this would
add a lot of unecessary overhead. A temporary adjustment on 3/25 was made and upped the check to 6 seconds.
- Now, there is a new methodology to scanning. The client will build a cache list of NPC's within close range as defined in new rule:
RULE_INT(Range, ClientNPCScan, 300) and will also get any NPC that has an aggro range beyond that defined range to use in
the frequent checks for aggro, the result is far less overhead
- Client scanning changes when moving versus not moving, the client will scan aggro every 500 milliseconds while moving, and
3000 millseconds aggro check when not moving, with a 6000ms re-fetch for close NPC's
- A demo of these changes can be found here:
https://youtu.be/aGroiwLSTVU
== 03/25/2017 == == 03/25/2017 ==
Akkadius: Reduced CPU footprint in non-combat zones doing constant checks for combat related activities Akkadius: [Performance] Reduced CPU footprint in non-combat zones doing constant checks for combat related activities
Akkadius: Reduced CPU footprint in cases where a client is checking for aggro excessively every 750 millseconds. This has Akkadius: [Performance] Reduced CPU footprint in cases where a client is checking for aggro excessively every 750 millseconds. This has
been adjusted to 6 seconds per new rule RULE_INT(Aggro, ClientAggroCheckInterval) been adjusted to 6 seconds per new rule RULE_INT(Aggro, ClientAggroCheckInterval)
- When zones have many players, with many NPC's, this adds up quickly - When zones have many players, with many NPC's, this adds up quickly

View File

@ -559,6 +559,7 @@ RULE_INT(Range, SpellMessages, 75)
RULE_INT(Range, SongMessages, 75) RULE_INT(Range, SongMessages, 75)
RULE_INT(Range, MobPositionUpdates, 600) RULE_INT(Range, MobPositionUpdates, 600)
RULE_INT(Range, CriticalDamage, 80) RULE_INT(Range, CriticalDamage, 80)
RULE_INT(Range, ClientNPCScan, 300)
RULE_CATEGORY_END() RULE_CATEGORY_END()

View File

@ -284,7 +284,7 @@ bool Mob::CheckWillAggro(Mob *mob) {
if(( t1 > iAggroRange) if(( t1 > iAggroRange)
|| ( t2 > iAggroRange) || ( t2 > iAggroRange)
|| ( t3 > iAggroRange) || ( t3 > iAggroRange)
||(mob->IsInvisible(this)) || (mob->IsInvisible(this))
|| (mob->IsClient() && || (mob->IsClient() &&
(!mob->CastToClient()->Connected() (!mob->CastToClient()->Connected()
|| mob->CastToClient()->IsLD() || mob->CastToClient()->IsLD()

View File

@ -135,7 +135,7 @@ Client::Client(EQStreamInterface* ieqs)
forget_timer(0), forget_timer(0),
autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000),
#ifdef REVERSE_AGGRO #ifdef REVERSE_AGGRO
scanarea_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000), client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000),
#endif #endif
tribute_timer(Tribute_duration), tribute_timer(Tribute_duration),
proximity_timer(ClientProximity_interval), proximity_timer(ClientProximity_interval),
@ -160,7 +160,8 @@ Client::Client(EQStreamInterface* ieqs)
m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f), m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f),
m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f), m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f),
last_region_type(RegionTypeUnsupported), last_region_type(RegionTypeUnsupported),
m_dirtyautohaters(false) m_dirtyautohaters(false),
npc_close_scan_timer(6000)
{ {
for(int cf=0; cf < _FilterCount; cf++) for(int cf=0; cf < _FilterCount; cf++)
ClientFilters[cf] = FilterShow; ClientFilters[cf] = FilterShow;
@ -358,6 +359,8 @@ Client::~Client() {
m_tradeskill_object = nullptr; m_tradeskill_object = nullptr;
} }
close_npcs.clear();
if(IsDueling() && GetDuelTarget() != 0) { if(IsDueling() && GetDuelTarget() != 0) {
Entity* entity = entity_list.GetID(GetDuelTarget()); Entity* entity = entity_list.GetID(GetDuelTarget());
if(entity != nullptr && entity->IsClient()) { if(entity != nullptr && entity->IsClient()) {

View File

@ -221,6 +221,9 @@ public:
Client(EQStreamInterface * ieqs); Client(EQStreamInterface * ieqs);
~Client(); ~Client();
std::unordered_map<NPC *, float> close_npcs;
bool is_client_moving;
//abstract virtual function implementations required by base abstract class //abstract virtual function implementations required by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None);
@ -1458,7 +1461,7 @@ private:
Timer forget_timer; // our 2 min everybody forgets you timer Timer forget_timer; // our 2 min everybody forgets you timer
Timer autosave_timer; Timer autosave_timer;
#ifdef REVERSE_AGGRO #ifdef REVERSE_AGGRO
Timer scanarea_timer; Timer client_scan_npc_aggro_timer;
#endif #endif
Timer tribute_timer; Timer tribute_timer;
@ -1477,6 +1480,7 @@ private:
Timer helm_toggle_timer; Timer helm_toggle_timer;
Timer light_update_timer; Timer light_update_timer;
Timer aggro_meter_timer; Timer aggro_meter_timer;
Timer npc_close_scan_timer;
glm::vec3 m_Proximity; glm::vec3 m_Proximity;

View File

@ -4580,6 +4580,34 @@ void Client::Handle_OP_ClientUpdate(const EQApplicationPacket *app)
rewind_timer.Start(30000, true); rewind_timer.Start(30000, true);
} }
/* Handle client aggro scanning timers NPCs */
is_client_moving = (ppu->y_pos == m_Position.y && ppu->x_pos == m_Position.x) ? false : true;
if (is_client_moving) {
Log.Out(Logs::Detail, Logs::Normal, "ClientUpdate: Client is moving - scan timer is: %u", client_scan_npc_aggro_timer.GetDuration());
if (client_scan_npc_aggro_timer.GetDuration() > 1000) {
npc_close_scan_timer.Disable();
npc_close_scan_timer.Start(500);
client_scan_npc_aggro_timer.Disable();
client_scan_npc_aggro_timer.Start(500);
}
}
else {
Log.Out(Logs::Detail, Logs::Normal, "ClientUpdate: Client is NOT moving - scan timer is: %u", client_scan_npc_aggro_timer.GetDuration());
if (client_scan_npc_aggro_timer.GetDuration() < 1000) {
npc_close_scan_timer.Disable();
npc_close_scan_timer.Start(6000);
client_scan_npc_aggro_timer.Disable();
client_scan_npc_aggro_timer.Start(3000);
}
}
// Outgoing client packet // Outgoing client packet
float tmpheading = EQ19toFloat(ppu->heading); float tmpheading = EQ19toFloat(ppu->heading);
/* The clients send an update at best every 1.3 seconds /* The clients send an update at best every 1.3 seconds

View File

@ -63,7 +63,7 @@ extern EntityList entity_list;
bool Client::Process() { bool Client::Process() {
bool ret = true; bool ret = true;
if(Connected() || IsLD()) if (Connected() || IsLD())
{ {
// try to send all packets that weren't sent before // try to send all packets that weren't sent before
if (!IsLD() && zoneinpacket_timer.Check()) if (!IsLD() && zoneinpacket_timer.Check())
@ -71,58 +71,58 @@ bool Client::Process() {
SendAllPackets(); SendAllPackets();
} }
if(adventure_request_timer) if (adventure_request_timer)
{ {
if(adventure_request_timer->Check()) if (adventure_request_timer->Check())
{ {
safe_delete(adventure_request_timer); safe_delete(adventure_request_timer);
} }
} }
if(adventure_create_timer) if (adventure_create_timer)
{ {
if(adventure_create_timer->Check()) if (adventure_create_timer->Check())
{ {
safe_delete(adventure_create_timer); safe_delete(adventure_create_timer);
} }
} }
if(adventure_leave_timer) if (adventure_leave_timer)
{ {
if(adventure_leave_timer->Check()) if (adventure_leave_timer->Check())
{ {
safe_delete(adventure_leave_timer); safe_delete(adventure_leave_timer);
} }
} }
if(adventure_door_timer) if (adventure_door_timer)
{ {
if(adventure_door_timer->Check()) if (adventure_door_timer->Check())
{ {
safe_delete(adventure_door_timer); safe_delete(adventure_door_timer);
} }
} }
if(adventure_stats_timer) if (adventure_stats_timer)
{ {
if(adventure_stats_timer->Check()) if (adventure_stats_timer->Check())
{ {
safe_delete(adventure_stats_timer); safe_delete(adventure_stats_timer);
} }
} }
if(adventure_leaderboard_timer) if (adventure_leaderboard_timer)
{ {
if(adventure_leaderboard_timer->Check()) if (adventure_leaderboard_timer->Check())
{ {
safe_delete(adventure_leaderboard_timer); safe_delete(adventure_leaderboard_timer);
} }
} }
if(dead) if (dead)
{ {
SetHP(-100); SetHP(-100);
if(RespawnFromHoverTimer.Check()) if (RespawnFromHoverTimer.Check())
HandleRespawnFromHover(0); HandleRespawnFromHover(0);
} }
@ -131,13 +131,13 @@ bool Client::Process() {
// SendHPUpdate calls hpupdate_timer.Start so it can delay this timer, so lets not reset with the check // SendHPUpdate calls hpupdate_timer.Start so it can delay this timer, so lets not reset with the check
// since the function will anyways // since the function will anyways
if(hpupdate_timer.Check(false)) if (hpupdate_timer.Check(false))
SendHPUpdate(); SendHPUpdate();
if(mana_timer.Check()) if (mana_timer.Check())
SendManaUpdatePacket(); SendManaUpdatePacket();
if(dead && dead_timer.Check()) { if (dead && dead_timer.Check()) {
database.MoveCharacterToZone(GetName(), database.GetZoneName(m_pp.binds[0].zoneId)); database.MoveCharacterToZone(GetName(), database.GetZoneName(m_pp.binds[0].zoneId));
m_pp.zone_id = m_pp.binds[0].zoneId; m_pp.zone_id = m_pp.binds[0].zoneId;
@ -150,7 +150,7 @@ bool Client::Process() {
Group *mygroup = GetGroup(); Group *mygroup = GetGroup();
if (mygroup) if (mygroup)
{ {
entity_list.MessageGroup(this,true,15,"%s died.", GetName()); entity_list.MessageGroup(this, true, 15, "%s died.", GetName());
mygroup->MemberZoned(this); mygroup->MemberZoned(this);
} }
Raid *myraid = entity_list.GetRaidByClient(this); Raid *myraid = entity_list.GetRaidByClient(this);
@ -161,34 +161,29 @@ bool Client::Process() {
return(false); return(false);
} }
if(charm_update_timer.Check()) if (charm_update_timer.Check()) {
{
CalcItemScale(); CalcItemScale();
} }
if(TaskPeriodic_Timer.Check() && taskstate) if (TaskPeriodic_Timer.Check() && taskstate)
taskstate->TaskPeriodicChecks(this); taskstate->TaskPeriodicChecks(this);
if(linkdead_timer.Check()) if (linkdead_timer.Check()) {
{
LeaveGroup(); LeaveGroup();
Save(); Save();
if (GetMerc()) if (GetMerc()) {
{
GetMerc()->Save(); GetMerc()->Save();
GetMerc()->Depop(); GetMerc()->Depop();
} }
Raid *myraid = entity_list.GetRaidByClient(this); Raid *myraid = entity_list.GetRaidByClient(this);
if (myraid) if (myraid) {
{
myraid->MemberZoned(this); myraid->MemberZoned(this);
} }
return false; //delete client return false; //delete client
} }
if (camp_timer.Check()) if (camp_timer.Check()) {
{
LeaveGroup(); LeaveGroup();
Save(); Save();
if (GetMerc()) if (GetMerc())
@ -202,8 +197,7 @@ bool Client::Process() {
if (IsStunned() && stunned_timer.Check()) if (IsStunned() && stunned_timer.Check())
Mob::UnStun(); Mob::UnStun();
if(!m_CheatDetectMoved) if (!m_CheatDetectMoved) {
{
m_TimeSinceLastPositionCheck = Timer::GetCurrentTime(); m_TimeSinceLastPositionCheck = Timer::GetCurrentTime();
} }
@ -211,32 +205,32 @@ bool Client::Process() {
//NOTE: this is kinda a heavy-handed check to make sure the mob still exists before //NOTE: this is kinda a heavy-handed check to make sure the mob still exists before
//doing the next pulse on them... //doing the next pulse on them...
Mob *song_target = nullptr; Mob *song_target = nullptr;
if(bardsong_target_id == GetID()) { if (bardsong_target_id == GetID()) {
song_target = this; song_target = this;
} else { }
else {
song_target = entity_list.GetMob(bardsong_target_id); song_target = entity_list.GetMob(bardsong_target_id);
} }
if (song_target == nullptr) { if (song_target == nullptr) {
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong);
} else { }
if(!ApplyNextBardPulse(bardsong, song_target, bardsong_slot)) else {
if (!ApplyNextBardPulse(bardsong, song_target, bardsong_slot))
InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong); InterruptSpell(SONG_ENDS_ABRUPTLY, 0x121, bardsong);
//SpellFinished(bardsong, bardsong_target, bardsong_slot, spells[bardsong].mana); //SpellFinished(bardsong, bardsong_target, bardsong_slot, spells[bardsong].mana);
} }
} }
if(GetMerc()) if (GetMerc()) {
{
UpdateMercTimer(); UpdateMercTimer();
} }
if(GetMercInfo().MercTemplateID != 0 && GetMercInfo().IsSuspended) if (GetMercInfo().MercTemplateID != 0 && GetMercInfo().IsSuspended) {
{
CheckMercSuspendTimer(); CheckMercSuspendTimer();
} }
if(IsAIControlled()) if (IsAIControlled())
AI_Process(); AI_Process();
// Don't reset the bindwound timer so we can check it in BindWound as well. // Don't reset the bindwound timer so we can check it in BindWound as well.
@ -244,31 +238,49 @@ bool Client::Process() {
BindWound(bindwound_target, false); BindWound(bindwound_target, false);
} }
if(KarmaUpdateTimer) if (KarmaUpdateTimer) {
{ if (KarmaUpdateTimer->Check(false)) {
if(KarmaUpdateTimer->Check(false))
{
KarmaUpdateTimer->Start(RuleI(Chat, KarmaUpdateIntervalMS)); KarmaUpdateTimer->Start(RuleI(Chat, KarmaUpdateIntervalMS));
database.UpdateKarma(AccountID(), ++TotalKarma); database.UpdateKarma(AccountID(), ++TotalKarma);
} }
} }
if(qGlobals) if (qGlobals) {
{ if (qglobal_purge_timer.Check()) {
if(qglobal_purge_timer.Check())
{
qGlobals->PurgeExpiredGlobals(); qGlobals->PurgeExpiredGlobals();
} }
} }
if(light_update_timer.Check()) { if (light_update_timer.Check()) {
UpdateEquipmentLight(); UpdateEquipmentLight();
if(UpdateActiveLight()) { if (UpdateActiveLight()) {
SendAppearancePacket(AT_Light, GetActiveLightType()); SendAppearancePacket(AT_Light, GetActiveLightType());
} }
} }
/* Build a close range list of NPC's */
if (npc_close_scan_timer.Check()) {
close_npcs.clear();
std::list<NPC*> npc_list;
entity_list.GetNPCList(npc_list);
float scan_range = RuleI(Range, ClientNPCScan);
for (auto itr = npc_list.begin(); itr != npc_list.end(); ++itr) {
NPC* npc = *itr;
float distance = DistanceNoZ(m_Position, npc->GetPosition());
if(distance <= scan_range) {
close_npcs.insert(std::pair<NPC *, float>(npc, distance));
}
else if (npc->GetAggroRange() > scan_range) {
close_npcs.insert(std::pair<NPC *, float>(npc, distance));
}
}
}
bool may_use_attacks = false; bool may_use_attacks = false;
/* /*
Things which prevent us from attacking: Things which prevent us from attacking:
@ -279,35 +291,35 @@ bool Client::Process() {
- being stunned or mezzed - being stunned or mezzed
- having used a ranged weapon recently - having used a ranged weapon recently
*/ */
if(auto_attack) { if (auto_attack) {
if(!IsAIControlled() && !dead if (!IsAIControlled() && !dead
&& !(spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id)) && !(spellend_timer.Enabled() && casting_spell_id && !IsBardSong(casting_spell_id))
&& !IsStunned() && !IsFeared() && !IsMezzed() && GetAppearance() != eaDead && !IsMeleeDisabled() && !IsStunned() && !IsFeared() && !IsMezzed() && GetAppearance() != eaDead && !IsMeleeDisabled()
) )
may_use_attacks = true; may_use_attacks = true;
if(may_use_attacks && ranged_timer.Enabled()) { if (may_use_attacks && ranged_timer.Enabled()) {
//if the range timer is enabled, we need to consider it //if the range timer is enabled, we need to consider it
if(!ranged_timer.Check(false)) { if (!ranged_timer.Check(false)) {
//the ranged timer has not elapsed, cannot attack. //the ranged timer has not elapsed, cannot attack.
may_use_attacks = false; may_use_attacks = false;
} }
} }
} }
if(AutoFireEnabled()){ if (AutoFireEnabled()) {
EQEmu::ItemInstance *ranged = GetInv().GetItem(EQEmu::inventory::slotRange); EQEmu::ItemInstance *ranged = GetInv().GetItem(EQEmu::inventory::slotRange);
if(ranged) if (ranged)
{ {
if (ranged->GetItem() && ranged->GetItem()->ItemType == EQEmu::item::ItemTypeBow){ if (ranged->GetItem() && ranged->GetItem()->ItemType == EQEmu::item::ItemTypeBow) {
if(ranged_timer.Check(false)){ if (ranged_timer.Check(false)) {
if(GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())){ if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())) {
if(GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())){ if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) {
if(CheckLosFN(GetTarget())){ if (CheckLosFN(GetTarget())) {
//client has built in los check, but auto fire does not.. done last. //client has built in los check, but auto fire does not.. done last.
RangedAttack(GetTarget()); RangedAttack(GetTarget());
if (CheckDoubleRangedAttack()) if (CheckDoubleRangedAttack())
RangedAttack(GetTarget(), true); RangedAttack(GetTarget(), true);
} }
else else
ranged_timer.Start(); ranged_timer.Start();
@ -319,11 +331,11 @@ bool Client::Process() {
ranged_timer.Start(); ranged_timer.Start();
} }
} }
else if (ranged->GetItem() && (ranged->GetItem()->ItemType == EQEmu::item::ItemTypeLargeThrowing || ranged->GetItem()->ItemType == EQEmu::item::ItemTypeSmallThrowing)){ else if (ranged->GetItem() && (ranged->GetItem()->ItemType == EQEmu::item::ItemTypeLargeThrowing || ranged->GetItem()->ItemType == EQEmu::item::ItemTypeSmallThrowing)) {
if(ranged_timer.Check(false)){ if (ranged_timer.Check(false)) {
if(GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())){ if (GetTarget() && (GetTarget()->IsNPC() || GetTarget()->IsClient())) {
if(GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())){ if (GetTarget()->InFrontMob(this, GetTarget()->GetX(), GetTarget()->GetY())) {
if(CheckLosFN(GetTarget())){ if (CheckLosFN(GetTarget())) {
//client has built in los check, but auto fire does not.. done last. //client has built in los check, but auto fire does not.. done last.
ThrowingAttack(GetTarget()); ThrowingAttack(GetTarget());
} }
@ -345,9 +357,9 @@ bool Client::Process() {
{ {
//check if change //check if change
//only check on primary attack.. sorry offhand you gotta wait! //only check on primary attack.. sorry offhand you gotta wait!
if(aa_los_them_mob) if (aa_los_them_mob)
{ {
if(auto_attack_target != aa_los_them_mob || if (auto_attack_target != aa_los_them_mob ||
m_AutoAttackPosition.x != GetX() || m_AutoAttackPosition.x != GetX() ||
m_AutoAttackPosition.y != GetY() || m_AutoAttackPosition.y != GetY() ||
m_AutoAttackPosition.z != GetZ() || m_AutoAttackPosition.z != GetZ() ||
@ -379,11 +391,11 @@ bool Client::Process() {
if (!CombatRange(auto_attack_target)) if (!CombatRange(auto_attack_target))
{ {
Message_StringID(MT_TooFarAway,TARGET_TOO_FAR); Message_StringID(MT_TooFarAway, TARGET_TOO_FAR);
} }
else if (auto_attack_target == this) else if (auto_attack_target == this)
{ {
Message_StringID(MT_TooFarAway,TRY_ATTACKING_SOMEONE); Message_StringID(MT_TooFarAway, TRY_ATTACKING_SOMEONE);
} }
else if (!los_status || !los_status_facing) else if (!los_status || !los_status_facing)
{ {
@ -412,23 +424,23 @@ bool Client::Process() {
} }
} }
if(auto_attack && may_use_attacks && auto_attack_target != nullptr if (auto_attack && may_use_attacks && auto_attack_target != nullptr
&& CanThisClassDualWield() && attack_dw_timer.Check()) && CanThisClassDualWield() && attack_dw_timer.Check())
{ {
// Range check // Range check
if(!CombatRange(auto_attack_target)) { if (!CombatRange(auto_attack_target)) {
// this is a duplicate message don't use it. // this is a duplicate message don't use it.
//Message_StringID(MT_TooFarAway,TARGET_TOO_FAR); //Message_StringID(MT_TooFarAway,TARGET_TOO_FAR);
} }
// Don't attack yourself // Don't attack yourself
else if(auto_attack_target == this) { else if (auto_attack_target == this) {
//Message_StringID(MT_TooFarAway,TRY_ATTACKING_SOMEONE); //Message_StringID(MT_TooFarAway,TRY_ATTACKING_SOMEONE);
} }
else if (!los_status || !los_status_facing) else if (!los_status || !los_status_facing)
{ {
//you can't see your target //you can't see your target
} }
else if(auto_attack_target->GetHP() > -10) { else if (auto_attack_target->GetHP() > -10) {
CheckIncreaseSkill(EQEmu::skills::SkillDualWield, auto_attack_target, -10); CheckIncreaseSkill(EQEmu::skills::SkillDualWield, auto_attack_target, -10);
if (CheckDualWield()) { if (CheckDualWield()) {
EQEmu::ItemInstance *wpn = GetInv().GetItem(EQEmu::inventory::slotSecondary); EQEmu::ItemInstance *wpn = GetInv().GetItem(EQEmu::inventory::slotSecondary);
@ -442,7 +454,7 @@ bool Client::Process() {
if (position_timer.Check()) { if (position_timer.Check()) {
if (IsAIControlled()) if (IsAIControlled())
{ {
if(!IsMoving()) if (!IsMoving())
{ {
animation = 0; animation = 0;
m_Delta = glm::vec4(0.0f, 0.0f, 0.0f, m_Delta.w); m_Delta = glm::vec4(0.0f, 0.0f, 0.0f, m_Delta.w);
@ -464,25 +476,25 @@ bool Client::Process() {
} }
} }
if(HasVirus()) { if (HasVirus()) {
if(viral_timer.Check()) { if (viral_timer.Check()) {
viral_timer_counter++; viral_timer_counter++;
for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { for (int i = 0; i < MAX_SPELL_TRIGGER * 2; i += 2) {
if(viral_spells[i]) { if (viral_spells[i]) {
if(viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) { if (viral_timer_counter % spells[viral_spells[i]].viral_timer == 0) {
SpreadVirus(viral_spells[i], viral_spells[i+1]); SpreadVirus(viral_spells[i], viral_spells[i + 1]);
} }
} }
} }
} }
if(viral_timer_counter > 999) if (viral_timer_counter > 999)
viral_timer_counter = 0; viral_timer_counter = 0;
} }
ProjectileAttack(); ProjectileAttack();
if(spellbonuses.GravityEffect == 1) { if (spellbonuses.GravityEffect == 1) {
if(gravity_timer.Check()) if (gravity_timer.Check())
DoGravityEffect(); DoGravityEffect();
} }
@ -514,7 +526,7 @@ bool Client::Process() {
} }
SpellProcess(); SpellProcess();
if (endupkeep_timer.Check() && !dead){ if (endupkeep_timer.Check() && !dead) {
DoEnduranceUpkeep(); DoEnduranceUpkeep();
} }
@ -530,7 +542,7 @@ bool Client::Process() {
BuffProcess(); BuffProcess();
DoStaminaUpdate(); DoStaminaUpdate();
if(tribute_timer.Check()) { if (tribute_timer.Check()) {
ToggleTribute(true); //re-activate the tribute. ToggleTribute(true); //re-activate the tribute.
} }
@ -542,18 +554,18 @@ bool Client::Process() {
Save(0); Save(0);
} }
if(m_pp.intoxication > 0) if (m_pp.intoxication > 0)
{ {
--m_pp.intoxication; --m_pp.intoxication;
CalcBonuses(); CalcBonuses();
} }
if(ItemTickTimer.Check()) if (ItemTickTimer.Check())
{ {
TickItemCheck(); TickItemCheck();
} }
if(ItemQuestTimer.Check()) if (ItemQuestTimer.Check())
{ {
ItemTimerCheck(); ItemTimerCheck();
} }
@ -592,8 +604,8 @@ bool Client::Process() {
} }
return false; return false;
} }
else if(!linkdead_timer.Enabled()){ else if (!linkdead_timer.Enabled()) {
linkdead_timer.Start(RuleI(Zone,ClientLinkdeadMS)); linkdead_timer.Start(RuleI(Zone, ClientLinkdeadMS));
client_state = CLIENT_LINKDEAD; client_state = CLIENT_LINKDEAD;
AI_Start(CLIENT_LD_TIMEOUT); AI_Start(CLIENT_LD_TIMEOUT);
SendAppearancePacket(AT_Linkdead, 1); SendAppearancePacket(AT_Linkdead, 1);
@ -603,10 +615,10 @@ bool Client::Process() {
/************ Get all packets from packet manager out queue and process them ************/ /************ Get all packets from packet manager out queue and process them ************/
EQApplicationPacket *app = nullptr; EQApplicationPacket *app = nullptr;
if(!eqs->CheckState(CLOSING)) if (!eqs->CheckState(CLOSING))
{ {
while(ret && (app = (EQApplicationPacket *)eqs->PopPacket())) { while (ret && (app = (EQApplicationPacket *)eqs->PopPacket())) {
if(app) if (app)
ret = HandlePacket(app); ret = HandlePacket(app);
safe_delete(app); safe_delete(app);
} }
@ -616,8 +628,17 @@ bool Client::Process() {
//At this point, we are still connected, everything important has taken //At this point, we are still connected, everything important has taken
//place, now check to see if anybody wants to aggro us. //place, now check to see if anybody wants to aggro us.
// only if client is not feigned // only if client is not feigned
if(zone->CanDoCombat() && ret && !GetFeigned() && scanarea_timer.Check()) { if (zone->CanDoCombat() && ret && !GetFeigned() && client_scan_npc_aggro_timer.Check()) {
entity_list.CheckClientAggro(this); int npc_scan_count = 0;
for (auto it = close_npcs.begin(); it != close_npcs.end(); ++it) {
NPC *npc = it->first;
if (npc->CheckWillAggro(this) && !npc->CheckAggro(this)) {
npc->AddToHateList(this, 25);
}
npc_scan_count++;
}
Log.Out(Logs::General, Logs::Aggro, "Checking Reverse Aggro (client->npc) scanned_npcs (%i)", npc_scan_count);
} }
#endif #endif

View File

@ -2288,10 +2288,15 @@ bool EntityList::RemoveNPC(uint16 delete_id)
{ {
auto it = npc_list.find(delete_id); auto it = npc_list.find(delete_id);
if (it != npc_list.end()) { if (it != npc_list.end()) {
NPC *npc = it->second;
// make sure its proximity is removed // make sure its proximity is removed
RemoveProximity(delete_id); RemoveProximity(delete_id);
// remove from client close lists
RemoveNPCFromClientCloseLists(npc);
// remove from the list // remove from the list
npc_list.erase(it); npc_list.erase(it);
// remove from limit list if needed // remove from limit list if needed
if (npc_limit_list.count(delete_id)) if (npc_limit_list.count(delete_id))
npc_limit_list.erase(delete_id); npc_limit_list.erase(delete_id);
@ -2300,6 +2305,16 @@ bool EntityList::RemoveNPC(uint16 delete_id)
return false; return false;
} }
bool EntityList::RemoveNPCFromClientCloseLists(NPC *npc)
{
auto it = client_list.begin();
while (it != client_list.end()) {
it->second->close_npcs.erase(npc);
++it;
}
return false;
}
bool EntityList::RemoveMerc(uint16 delete_id) bool EntityList::RemoveMerc(uint16 delete_id)
{ {
auto it = merc_list.find(delete_id); auto it = merc_list.find(delete_id);

View File

@ -279,6 +279,7 @@ public:
bool RemoveTrap(uint16 delete_id); bool RemoveTrap(uint16 delete_id);
bool RemoveObject(uint16 delete_id); bool RemoveObject(uint16 delete_id);
bool RemoveProximity(uint16 delete_npc_id); bool RemoveProximity(uint16 delete_npc_id);
bool RemoveNPCFromClientCloseLists(NPC *npc);
void RemoveAllMobs(); void RemoveAllMobs();
void RemoveAllClients(); void RemoveAllClients();
void RemoveAllNPCs(); void RemoveAllNPCs();