From 8cb51eb253f5f00c073f7f80d1b4ea31d3b7661a Mon Sep 17 00:00:00 2001 From: Akkadius Date: Wed, 25 Dec 2019 03:16:14 -0600 Subject: [PATCH] Scanning optimization work from over a year ago from EZ - cleaned up a bit --- common/eqemu_logsys.h | 8 +- common/eqemu_logsys_log_aliases.h | 30 ++++ common/ruletypes.h | 1 + world/world_server_command_handler.cpp | 4 +- zone/aggro.cpp | 110 +----------- zone/client.h | 1 - zone/client_process.cpp | 18 +- zone/entity.cpp | 54 +++++- zone/entity.h | 7 +- zone/mob.cpp | 14 +- zone/mob.h | 9 +- zone/mob_ai.cpp | 132 +++++---------- zone/npc.cpp | 224 ++++++++++++++++++++++++- zone/npc.h | 3 + 14 files changed, 393 insertions(+), 222 deletions(-) diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index d2d860101..460ff2fef 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -107,6 +107,9 @@ namespace Logs { Emergency, Alert, Notice, + AIScanClose, + AIYellForHelp, + AICastBeneficialClose, MaxCategoryID /* Don't Remove this */ }; @@ -172,7 +175,10 @@ namespace Logs { "Critical", "Emergency", "Alert", - "Notice" + "Notice", + "AI Scan Close", + "AI Yell For Help", + "AI Cast Beneficial Close", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index f90d0e223..d569fba77 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -491,6 +491,36 @@ OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ } while (0) +#define LogAIScanClose(message, ...) do {\ + if (LogSys.log_settings[Logs::AIScanClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIScanCloseDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AIScanClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AIScanClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIYellForHelp(message, ...) do {\ + if (LogSys.log_settings[Logs::AIYellForHelp].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAIYellForHelpDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AIYellForHelp].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AIYellForHelp, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAICastBeneficialClose(message, ...) do {\ + if (LogSys.log_settings[Logs::AICastBeneficialClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::General, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + +#define LogAICastBeneficialCloseDetail(message, ...) do {\ + if (LogSys.log_settings[Logs::AICastBeneficialClose].is_category_enabled == 1)\ + OutF(LogSys, Logs::Detail, Logs::AICastBeneficialClose, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ +} while (0) + #define Log(debug_level, log_category, message, ...) do {\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ diff --git a/common/ruletypes.h b/common/ruletypes.h index c7b4a1aee..6933b00e4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -568,6 +568,7 @@ RULE_INT(Range, ClientPositionUpdates, 300, "") RULE_INT(Range, ClientForceSpawnUpdateRange, 1000, "") RULE_INT(Range, CriticalDamage, 80, "") RULE_INT(Range, ClientNPCScan, 300, "") +RULE_INT(Range, MobCloseScanDistance, 300, "") RULE_CATEGORY_END() diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp index 003e9abe2..3a929d89f 100644 --- a/world/world_server_command_handler.cpp +++ b/world/world_server_command_handler.cpp @@ -189,10 +189,10 @@ namespace WorldserverCommandHandler { Json::Value schema; - schema["server_tables"] = server_tables_json; - schema["player_tables"] = player_tables_json; schema["content_tables"] = content_tables_json; schema["login_tables"] = login_tables_json; + schema["player_tables"] = player_tables_json; + schema["server_tables"] = server_tables_json; schema["state_tables"] = state_tables_json; schema["version_tables"] = version_tables_json; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1d579567f..ff0077353 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -36,19 +36,6 @@ extern Zone* zone; //#define LOSDEBUG 6 -//look around a client for things which might aggro the client. -void EntityList::CheckClientAggro(Client *around) -{ - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - Mob *mob = it->second; - if (mob->IsClient()) //also ensures that mob != around - continue; - - if (mob->CheckWillAggro(around) && !mob->CheckAggro(around)) - mob->AddToHateList(around, 25); - } -} - void EntityList::DescribeAggro(Client *towho, NPC *from_who, float d, bool verbose) { float d2 = d*d; @@ -402,22 +389,6 @@ bool Mob::CheckWillAggro(Mob *mob) { return(false); } -Mob* EntityList::AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange) { - if (!sender || !sender->IsNPC()) - return(nullptr); - - auto it = npc_list.begin(); - while (it != npc_list.end()) { - Mob *mob = it->second; - - if (sender->CheckWillAggro(mob)) - return mob; - ++it; - } - - return nullptr; -} - int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker @@ -462,82 +433,11 @@ int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) return Count; } -void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { - if(!sender || !attacker) - return; - 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; - - if ( - mob != sender - && mob != attacker -// && !mob->IsCorpse() -// && mob->IsAIControlled() - && mob->GetPrimaryFaction() != 0 - && DistanceSquared(mob->GetPosition(), sender->GetPosition()) <= r - && !mob->IsEngaged() - && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) - // If we're a pet we don't react to any calls for help if our owner is a client - ) - { - //if they are in range, make sure we are not green... - //then jump in if they are our friend - if(mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) - { - bool useprimfaction = false; - if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) - { - const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); - if(cf){ - if(cf->assistprimaryfaction != 0) - useprimfaction = true; - } - } - - if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) - { - //attacking someone on same faction, or a friend - //Father Nitwit: make sure we can see them. - if(mob->CheckLosFN(sender)) { -#if (EQDEBUG>=5) - LogDebug("AIYellForHelp(\"[{}]\",\"[{}]\") [{}] attacking [{}] Dist [{}] Z [{}]", - sender->GetName(), attacker->GetName(), mob->GetName(), - attacker->GetName(), DistanceSquared(mob->GetPosition(), - sender->GetPosition()), std::abs(sender->GetZ()+mob->GetZ())); -#endif - mob->AddToHateList(attacker, 25, 0, false); - sender->AddAssistCap(); - } - } - } - } - } -} - -/* -returns false if attack should not be allowed -I try to list every type of conflict that's possible here, so it's easy -to see how the decision is made. Yea, it could be condensed and made -faster, but I'm doing it this way to make it readable and easy to modify -*/ - +/** + * @param target + * @param isSpellAttack + * @return + */ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) { diff --git a/zone/client.h b/zone/client.h index 82b8c7475..8876e4645 100644 --- a/zone/client.h +++ b/zone/client.h @@ -226,7 +226,6 @@ public: Client(EQStreamInterface * ieqs); ~Client(); - std::unordered_map close_mobs; bool is_client_moving; void SetDisplayMobInfoWindow(bool display_mob_info_window); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a857c3c21..e0068d6e9 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -251,17 +251,21 @@ bool Client::Process() { } } - /* Build a close range list of NPC's */ + /** + * Scan close range mobs + * + * Used in aggro checks + */ if (npc_close_scan_timer.Check()) { close_mobs.clear(); - //Force spawn updates when traveled far - bool force_spawn_updates = false; - float client_update_range = (RuleI(Range, ClientForceSpawnUpdateRange) * RuleI(Range, ClientForceSpawnUpdateRange)); + float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan)); - auto &mob_list = entity_list.GetMobList(); - for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { - Mob* mob = itr->second; + auto &mob_list = entity_list.GetMobList(); + + for (auto itr : mob_list) { + Mob *mob = itr.second; float distance = DistanceSquared(m_Position, mob->GetPosition()); + if (mob->IsNPC()) { if (distance <= scan_range) { close_mobs.insert(std::pair(mob, distance)); diff --git a/zone/entity.cpp b/zone/entity.cpp index 7f016f197..597780878 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -2494,7 +2494,7 @@ bool EntityList::RemoveMob(uint16 delete_id) auto it = mob_list.find(delete_id); if (it != mob_list.end()) { - RemoveMobFromClientCloseLists(it->second); + RemoveMobFromCloseLists(it->second); if (npc_list.count(delete_id)) entity_list.RemoveNPC(delete_id); @@ -2512,17 +2512,19 @@ bool EntityList::RemoveMob(uint16 delete_id) // This is for if the ID is deleted for some reason bool EntityList::RemoveMob(Mob *delete_mob) { - if (delete_mob == 0) + if (delete_mob == 0) { return true; + } auto it = mob_list.begin(); while (it != mob_list.end()) { if (it->second == delete_mob) { - RemoveMobFromClientCloseLists(it->second); + RemoveMobFromCloseLists(it->second); safe_delete(it->second); - if (!corpse_list.count(it->first)) + if (!corpse_list.count(it->first)) { free_ids.push(it->first); + } mob_list.erase(it); return true; } @@ -2539,28 +2541,62 @@ bool EntityList::RemoveNPC(uint16 delete_id) // make sure its proximity is removed RemoveProximity(delete_id); // remove from client close lists - RemoveMobFromClientCloseLists(npc->CastToMob()); + RemoveMobFromCloseLists(npc->CastToMob()); // remove from the list npc_list.erase(it); // 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); + } + return true; } return false; } -bool EntityList::RemoveMobFromClientCloseLists(Mob *mob) +/** + * @param mob + * @return + */ +bool EntityList::RemoveMobFromCloseLists(Mob *mob) { - auto it = client_list.begin(); - while (it != client_list.end()) { + auto it = mob_list.begin(); + while (it != mob_list.end()) { it->second->close_mobs.erase(mob); ++it; } return false; } +/** + * @param close_mobs + * @param scanning_mob + */ +void EntityList::ScanCloseMobs(std::unordered_map &close_mobs, Mob *scanning_mob) +{ + float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance); + int list_count = 0; + + close_mobs.clear(); + + auto it = mob_list.begin(); + while (it != mob_list.end()) { + float distance = DistanceSquared(scanning_mob->GetPosition(), it->second->GetPosition()); + if (distance <= scan_range) { + close_mobs.insert(std::pair(it->second, distance)); + list_count++; + } + else if (it->second->GetAggroRange() >= scan_range) { + close_mobs.insert(std::pair(it->second, distance)); + list_count++; + } + ++it; + } + + LogAIScanClose("Close List Size [{}] for mob [{}]", list_count, scanning_mob->GetCleanName()); +} + bool EntityList::RemoveMerc(uint16 delete_id) { auto it = merc_list.find(delete_id); diff --git a/zone/entity.h b/zone/entity.h index 7631f0532..561e9387a 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -284,7 +284,7 @@ public: bool RemoveTrap(uint16 delete_id); bool RemoveObject(uint16 delete_id); bool RemoveProximity(uint16 delete_npc_id); - bool RemoveMobFromClientCloseLists(Mob *mob); + bool RemoveMobFromCloseLists(Mob *mob); void RemoveAllMobs(); void RemoveAllClients(); void RemoveAllNPCs(); @@ -443,11 +443,7 @@ public: bool LimitCheckBoth(uint32 npc_type, uint32 spawngroup_id, int group_count, int type_count); bool LimitCheckName(const char* npc_name); - void CheckClientAggro(Client *around); - Mob* AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange); int GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con); - void AIYellForHelp(Mob* sender, Mob* attacker); - bool AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes); bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes); Mob* GetTargetForMez(Mob* caster); uint32 CheckNPCsClose(Mob *center); @@ -501,6 +497,7 @@ public: void RefreshAutoXTargets(Client *c); void RefreshClientXTargets(Client *c); void SendAlternateAdvancementStats(); + void ScanCloseMobs(std::unordered_map &close_mobs, Mob *scanning_mob); void GetTrapInfo(Client* client); bool IsTrapGroupSpawned(uint32 trap_id, uint8 group); diff --git a/zone/mob.cpp b/zone/mob.cpp index 44ce45d93..f4d3acab6 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -116,7 +116,9 @@ Mob::Mob( m_specialattacks(eSpecialAttacks::None), attack_anim_timer(1000), position_update_melee_push_timer(500), - hate_list_cleanup_timer(6000) + hate_list_cleanup_timer(6000), + mob_scan_close(6000), + mob_check_moving_timer(1000) { mMovementManager = &MobMovementManager::Get(); mMovementManager->AddMob(this); @@ -524,6 +526,16 @@ uint32 Mob::GetAppearanceValue(EmuAppearance iAppearance) { return(ANIM_STAND); } +void Mob::GetCloseMobList(std::list> &m_list) +{ + m_list.clear(); + auto it = close_mobs.begin(); + while (it != close_mobs.end()) { + m_list.push_back(std::make_pair(it->first, it->second)); + ++it; + } +} + void Mob::SetInvisible(uint8 state) { invisible = state; diff --git a/zone/mob.h b/zone/mob.h index ab3df4277..5f9dcae03 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1,3 +1,4 @@ + /* EQEMu: Everquest Server Emulator Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) @@ -169,6 +170,12 @@ public: void DisplayInfo(Mob *mob); + std::unordered_map close_mobs; + Timer mob_scan_close; + Timer mob_check_moving_timer; + + void GetCloseMobList(std::list> &m_list); + //Somewhat sorted: needs documenting! //Attack @@ -968,7 +975,7 @@ public: void SetEntityVariable(const char *id, const char *m_var); bool EntityVariableExists(const char *id); - void AI_Event_Engaged(Mob* attacker, bool iYellForHelp = true); + void AI_Event_Engaged(Mob* attacker, bool yell_for_help = true); void AI_Event_NoLongerEngaged(); FACTION_VALUE GetSpecialFactionCon(Mob* iOther); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 5958fe18a..bb020060d 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -378,59 +378,6 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain return CastSpell(AIspells[i].spellid, tar->GetID(), EQEmu::spells::CastingSlot::Gem2, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIspells[i].resist_adjust)); } -bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { - //according to live, you can buff and heal through walls... - //now with PCs, this only applies if you can TARGET the target, but - // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. - // - // This check was put in to address an idle-mob CPU issue - LogError("Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); - return(false); - } - - if(!caster) - return false; - - if(caster->AI_HasSpells() == false) - return false; - - if(caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) - return false; - - if (iChance < 100) { - uint8 tmp = zone->random.Int(0, 99); - if (tmp >= iChance) - return false; - } - if (caster->GetPrimaryFaction() == 0 ) - return(false); // well, if we dont have a faction set, we're gonna be indiff to everybody - - float iRange2 = iRange*iRange; - - //Only iterate through NPCs - for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { - NPC* mob = it->second; - - if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { - continue; - } - - if (DistanceSquared(caster->GetPosition(), mob->GetPosition()) > iRange2) { - continue; - } - - if ((iSpellTypes & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { - if (mob != caster) - iSpellTypes = SpellType_Heal; - } - - if (caster->AICastSpell(mob, 100, iSpellTypes)) - return true; - } - return false; -} - void Mob::AI_Init() { pAIControlled = false; @@ -1415,12 +1362,20 @@ void Mob::AI_Process() { } else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) { - /* - * NPC to NPC aggro checking, npc needs npc_aggro flag - */ - Mob *temp_target = entity_list.AICheckNPCtoNPCAggro(this, GetAggroRange(), GetAssistRange()); - if (temp_target) { - AddToHateList(temp_target); + /** + * NPC to NPC aggro (npc_aggro flag set) + */ + for (auto &close_mob : close_mobs) { + Mob *mob = close_mob.first; + float distance = close_mob.second; + + if (mob->IsClient()) { + continue; + } + + if (this->CheckWillAggro(mob)) { + this->AddToHateList(mob); + } } AI_scan_area_timer->Disable(); @@ -1877,47 +1832,46 @@ void NPC::AI_SetupNextWaypoint() { } } -// Note: Mob that caused this may not get added to the hate list until after this function call completes -void Mob::AI_Event_Engaged(Mob* attacker, bool iYellForHelp) { - if (!IsAIControlled()) +/** + * @param attacker + * @param yell_for_help + */ +void Mob::AI_Event_Engaged(Mob *attacker, bool yell_for_help) +{ + if (!IsAIControlled()) { return; + } SetAppearance(eaStanding); - /* - Kick off auto cast timer - */ - if (this->IsNPC()) - this->CastToNPC()->AIautocastspell_timer->Start(300, false); + if (IsNPC()) { + CastToNPC()->AIautocastspell_timer->Start(300, false); - if (iYellForHelp) { - if(IsPet()) { - GetOwner()->AI_Event_Engaged(attacker, iYellForHelp); - } 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)); + if (yell_for_help) { + if (IsPet()) { + GetOwner()->AI_Event_Engaged(attacker, yell_for_help); + } + else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { + CastToNPC()->AIYellForHelp(this, attacker); + if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) { + assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); + } + } } - } - if(IsNPC()) - { - if(CastToNPC()->GetGrid() > 0) - { + if (CastToNPC()->GetGrid() > 0) { DistractedFromGrid = true; } - if(attacker && !attacker->IsCorpse()) - { + if (attacker && !attacker->IsCorpse()) { //Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged //if the target dies before it goes off - if(attacker->GetHP() > 0) - { - if(!CastToNPC()->GetCombatEvent() && GetHP() > 0) - { + if (attacker->GetHP() > 0) { + if (!CastToNPC()->GetCombatEvent() && GetHP() > 0) { parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0); uint16 emoteid = GetEmoteID(); - if(emoteid != 0) - CastToNPC()->DoNPCEmote(ENTERCOMBAT,emoteid); + if (emoteid != 0) { + CastToNPC()->DoNPCEmote(ENTERCOMBAT, emoteid); + } CastToNPC()->SetCombatEvent(true); } } @@ -1996,7 +1950,7 @@ bool NPC::AI_EngagedCastCheck() { // try casting a heal or gate if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby - if (!entity_list.AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) { + if (!AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) { //nobody to heal, try some detrimental spells. if(!AICastSpell(GetTarget(), AISpellVar.engaged_detrimental_chance, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) { //no spell to cast, try again soon. @@ -2033,7 +1987,7 @@ bool NPC::AI_IdleCastCheck() { if (AIautocastspell_timer->Check(false)) { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. if (!AICastSpell(this, AISpellVar.idle_beneficial_chance, SpellType_Heal | SpellType_Buff | SpellType_Pet)) { - if(!entity_list.AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { + if(!AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { //if we didnt cast any spells, our autocast timer just resets to the //last duration it was set to... try to put up a more reasonable timer... AIautocastspell_timer->Start(RandomTimer(AISpellVar.idle_no_sp_recast_min, AISpellVar.idle_no_sp_recast_max), false); diff --git a/zone/npc.cpp b/zone/npc.cpp index efbe98be1..38de9529a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -704,6 +704,30 @@ bool NPC::Process() SpellProcess(); + if (mob_scan_close.Check()) { + LogAIScanClose( + "is_moving [{}] npc [{}] timer [{}]", + moving ? "true" : "false", + GetCleanName(), + mob_scan_close.GetDuration() + ); + + entity_list.ScanCloseMobs(close_mobs, this); + + if (moving) { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(3000, 6000)); + } + else { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(6000, 60000)); + } + } + + if (mob_check_moving_timer.Check() && moving) { + mob_scan_close.Trigger(); + } + if (tic_timer.Check()) { parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); BuffProcess(); @@ -851,7 +875,7 @@ bool NPC::Process() if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { - entity_list.AIYellForHelp(this, GetTarget()); + AIYellForHelp(this, GetTarget()); if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); } @@ -2975,6 +2999,11 @@ bool NPC::IsProximitySet() return false; } +/** + * @param box_size + * @param move_distance + * @param move_delay + */ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) { AI_SetRoambox( @@ -2986,3 +3015,196 @@ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) move_delay ); } + +/** + * @param caster + * @param chance + * @param in_cast_range + * @param spell_types + * @return + */ +bool NPC::AICheckCloseBeneficialSpells( + NPC *caster, + uint8 chance, + float in_cast_range, + uint32 spell_types +) +{ + if((spell_types & SPELL_TYPES_DETRIMENTAL) != 0) { + LogError("Detrimental spells requested from AICheckCloseBeneficialSpells!"); + return false; + } + + if (!caster) { + return false; + } + + if (!caster->AI_HasSpells()) { + return false; + } + + if (caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) { + return false; + } + + if (chance < 100) { + uint8 tmp = zone->random.Int(0, 99); + if (tmp >= chance) { + return false; + } + } + + /** + * Indifferent + */ + if (caster->GetPrimaryFaction() == 0) { + return false; + } + + /** + * Cast range + */ + in_cast_range = (in_cast_range * in_cast_range); + + /** + * Check through close range mobs + */ + for (auto & close_mob : close_mobs) { + Mob *mob = close_mob.first; + float cached_close_mob_distance = close_mob.second; + + if (mob->IsClient()) { + continue; + } + + if (cached_close_mob_distance > in_cast_range) { + continue; + } + + LogAICastBeneficialClose( + "NPC [{}] Distance [{}] Cast Range [{}] Caster [{}]", + mob->GetCleanName(), + cached_close_mob_distance, + in_cast_range, + caster->GetCleanName() + ); + + if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { + continue; + } + + if ((spell_types & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { + if (mob != caster) { + spell_types = SpellType_Heal; + } + } + + if (caster->AICastSpell(mob, 100, spell_types)) { + return true; + } + } + + return false; +} + +/** + * @param sender + * @param attacker + */ +void NPC::AIYellForHelp(Mob *sender, Mob *attacker) +{ + if (!sender || !attacker) { + return; + } + + /** + * If we dont have a faction set, we're gonna be indiff to everybody + */ + if (sender->GetPrimaryFaction() == 0) { + return; + } + + if (sender->HasAssistAggro()) + return; + + LogAIYellForHelp( + "NPC [{}] ID [{}] is starting to scan", + GetCleanName(), + GetID() + ); + + for (auto & close_mob : close_mobs) { + Mob *mob = close_mob.first; + + float distance = DistanceSquared(m_Position, mob->GetPosition()); + + if (mob->IsClient()) { + continue; + } + + float assist_range = (mob->GetAssistRange() * mob->GetAssistRange()); + + if (distance > assist_range) { + continue; + } + + LogAIYellForHelpDetail( + "NPC [{}] ID [{}] is scanning - checking against NPC [{}] range [{}] dist [{}]", + GetCleanName(), + GetID(), + mob->GetCleanName(), + assist_range, + distance + ); + + if (mob->CheckAggro(attacker)) { + continue; + } + + if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) { + break; + } + + if ( + mob != sender + && mob != attacker + && mob->GetPrimaryFaction() != 0 + && !mob->IsEngaged() + && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) + ) { + + /** + * if they are in range, make sure we are not green... + * then jump in if they are our friend + */ + if (mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) { + bool use_primary_faction = false; + if (mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) { + const NPCFactionList *cf = database.GetNPCFactionEntry(mob->CastToNPC()->GetNPCFactionID()); + if (cf) { + if (cf->assistprimaryfaction != 0) { + use_primary_faction = true; + } + } + } + + if (use_primary_faction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE) { + //attacking someone on same faction, or a friend + //Father Nitwit: make sure we can see them. + if (mob->CheckLosFN(sender)) { + mob->AddToHateList(attacker, 25, 0, false); + sender->AddAssistCap(); + + LogAIYellForHelpDetail( + "NPC [{}] is assisting [{}] against target [{}]", + mob->GetCleanName(), + this->GetCleanName(), + attacker->GetCleanName() + ); + } + } + } + } + } + +} \ No newline at end of file diff --git a/zone/npc.h b/zone/npc.h index 5d7a7bd73..6d53d2106 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -143,6 +143,9 @@ public: virtual bool AI_IdleCastCheck(); virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); + bool AICheckCloseBeneficialSpells(NPC* caster, uint8 chance, float in_cast_range, uint32 spell_types); + void AIYellForHelp(Mob* sender, Mob* attacker); + void LevelScale(); virtual void SetTarget(Mob* mob);