Scanning optimization work from over a year ago from EZ - cleaned up a bit

This commit is contained in:
Akkadius 2019-12-25 03:16:14 -06:00
parent 07fd803d41
commit 8cb51eb253
14 changed files with 393 additions and 222 deletions

View File

@ -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",
};
}

View File

@ -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__);\

View File

@ -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()

View File

@ -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;

View File

@ -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)
{

View File

@ -226,7 +226,6 @@ public:
Client(EQStreamInterface * ieqs);
~Client();
std::unordered_map<Mob *, float> close_mobs;
bool is_client_moving;
void SetDisplayMobInfoWindow(bool display_mob_info_window);

View File

@ -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 *, float>(mob, distance));

View File

@ -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<Mob *, float> &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<Mob *, float>(it->second, distance));
list_count++;
}
else if (it->second->GetAggroRange() >= scan_range) {
close_mobs.insert(std::pair<Mob *, float>(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);

View File

@ -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<Mob *, float> &close_mobs, Mob *scanning_mob);
void GetTrapInfo(Client* client);
bool IsTrapGroupSpawned(uint32 trap_id, uint8 group);

View File

@ -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<std::pair<Mob *, float>> &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;

View File

@ -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<Mob *, float> close_mobs;
Timer mob_scan_close;
Timer mob_check_moving_timer;
void GetCloseMobList(std::list<std::pair<Mob *, float>> &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);

View File

@ -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);

View File

@ -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()
);
}
}
}
}
}
}

View File

@ -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);