diff --git a/common/ruletypes.h b/common/ruletypes.h index 91d3479be..83ef9aff3 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -459,6 +459,8 @@ RULE_BOOL(Combat, ProjectileDmgOnImpact, true) //If enabled, projectiles (ie arr RULE_BOOL(Combat, MeleePush, true) // enable melee push RULE_INT(Combat, MeleePushChance, 50) // (NPCs) chance the target will be pushed. Made up, 100 actually isn't that bad RULE_BOOL(Combat, UseLiveCombatRounds, true) // turn this false if you don't want to worry about fixing up combat rounds for NPCs +RULE_INT(Combat, NPCAssistCap, 5) // Maxiumium number of NPCs that will assist another NPC at once +RULE_INT(Combat, NPCAssistCapTimer, 6000) // Time in milliseconds a NPC will take to clear assist aggro cap space RULE_CATEGORY_END() RULE_CATEGORY(NPC) @@ -494,6 +496,7 @@ RULE_INT(Aggro, PetSpellAggroMod, 10) RULE_REAL(Aggro, TunnelVisionAggroMod, 0.75) //people not currently the top hate generate this much hate on a Tunnel Vision mob RULE_INT(Aggro, MaxScalingProcAggro, 400) // Set to -1 for no limit. Maxmimum amount of aggro that HP scaling SPA effect in a proc will add. RULE_INT(Aggro, IntAggroThreshold, 75) // Int <= this will aggro regardless of level difference. +RULE_BOOL(Aggro, AllowTickPulling, false) // tick pulling is an exploit in an NPC's call for help fixed sometime in 2006 on live RULE_CATEGORY_END() RULE_CATEGORY(TaskSystem) diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 5febe5767..a2ee52357 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -431,11 +431,20 @@ void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { if (sender->GetPrimaryFaction() == 0 ) return; // well, if we dont have a faction set, we're gonna be indiff to everybody + if (sender->HasAssistAggro()) + return; + for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { NPC *mob = it->second; if (!mob) continue; + if (mob->CheckAggro(attacker)) + continue; + + if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) + break; + float r = mob->GetAssistRange(); r = r * r; @@ -477,6 +486,7 @@ void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { sender->GetPosition()), fabs(sender->GetZ()+mob->GetZ())); #endif mob->AddToHateList(attacker, 25, 0, false); + sender->AddAssistCap(); } } } diff --git a/zone/attack.cpp b/zone/attack.cpp index 2752752aa..6b2534a43 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2422,6 +2422,11 @@ void Mob::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b hate = 1; } + if (iYellForHelp) + SetPrimaryAggro(true); + else + SetAssistAggro(true); + bool wasengaged = IsEngaged(); Mob* owner = other->GetOwner(); Mob* mypet = this->GetPet(); diff --git a/zone/mob.cpp b/zone/mob.cpp index eccf04a76..6e778f004 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -433,6 +433,9 @@ Mob::Mob(const char* in_name, emoteid = 0; endur_upkeep = false; + PrimaryAggro = false; + AssistAggro = false; + npc_assist_cap = 0; } Mob::~Mob() @@ -2693,6 +2696,8 @@ bool Mob::RemoveFromHateList(Mob* mob) { AI_Event_NoLongerEngaged(); zone->DelAggroMob(); + if (IsNPC() && !RuleB(Aggro, AllowTickPulling)) + ResetAssistCap(); } } if(GetTarget() == mob) diff --git a/zone/mob.h b/zone/mob.h index 2ca027023..d02e0f089 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -504,6 +504,10 @@ public: Mob* GetHateRandom() { return hate_list.GetRandomEntOnHateList();} Mob* GetHateMost() { return hate_list.GetEntWithMostHateOnList();} bool IsEngaged() { return(!hate_list.IsHateListEmpty()); } + bool HasPrimaryAggro() { return PrimaryAggro; } + bool HasAssistAggro() { return AssistAggro; } + void SetPrimaryAggro(bool value) { PrimaryAggro = value; if (value) AssistAggro = false; } + void SetAssistAggro(bool value) { AssistAggro = value; if (PrimaryAggro) AssistAggro = false; } bool HateSummon(); void FaceTarget(Mob* MobToFace = 0); void SetHeading(float iHeading) { if(m_Position.w != iHeading) { pLastChange = Timer::GetCurrentTime(); @@ -993,6 +997,11 @@ public: void ApplyAABonuses(const AA::Rank &rank, StatBonuses* newbon); bool CheckAATimer(int timer); + int NPCAssistCap() { return npc_assist_cap; } + void AddAssistCap() { ++npc_assist_cap; } + void DelAssistCap() { --npc_assist_cap; } + void ResetAssistCap() { npc_assist_cap = 0; } + protected: void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const SkillUseTypes attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special = 0); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); @@ -1293,6 +1302,11 @@ protected: glm::vec4 m_CurrentWayPoint; int cur_wp_pause; + bool PrimaryAggro; + bool AssistAggro; + int npc_assist_cap; + Timer assist_cap_timer; // clear assist cap so more nearby mobs can be called for help + int patrol; glm::vec3 m_FearWalkTarget; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 25ed72441..79c4493ea 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1735,8 +1735,10 @@ void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) { if (iYellForHelp) { if(IsPet()) { GetOwner()->AI_Event_Engaged(attacker, iYellForHelp); - } else { + } else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { entity_list.AIYellForHelp(this, attacker); + if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) + assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); } } @@ -1787,6 +1789,8 @@ void Mob::AI_Event_NoLongerEngaged() { if(IsNPC()) { + SetPrimaryAggro(false); + SetAssistAggro(false); if(CastToNPC()->GetCombatEvent() && GetHP() > 0) { if(entity_list.GetNPCByID(this->GetID())) diff --git a/zone/npc.cpp b/zone/npc.cpp index ebdd8a79d..fd7f6880c 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -712,8 +712,18 @@ bool NPC::Process() } //Handle assists... - if(assist_timer.Check() && IsEngaged() && !Charmed()) { + if (assist_cap_timer.Check()) { + if (NPCAssistCap() > 0) + DelAssistCap(); + else + assist_cap_timer.Disable(); + } + + if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && + NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { entity_list.AIYellForHelp(this, GetTarget()); + if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) + assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); } if(qGlobals)