Merge pull request #940 from EQEmu/feature/ae-scanning-optimizations

Mob Scanning and Loop Optimizations
This commit is contained in:
Chris Miles 2019-12-31 03:13:04 -06:00 committed by GitHub
commit 3e0ded6c39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 985 additions and 509 deletions

View File

@ -683,6 +683,7 @@ bool Database::SaveCharacterCreate(uint32 character_id, uint32 account_id, Playe
pp->RestTimer // " RestTimer) " pp->RestTimer // " RestTimer) "
); );
auto results = QueryDatabase(query); auto results = QueryDatabase(query);
/* Save Bind Points */ /* Save Bind Points */
query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)" query = StringFormat("REPLACE INTO `character_bind` (id, zone_id, instance_id, x, y, z, heading, slot)"
" VALUES (%u, %u, %u, %f, %f, %f, %f, %i), " " VALUES (%u, %u, %u, %f, %f, %f, %f, %i), "

View File

@ -107,6 +107,11 @@ namespace Logs {
Emergency, Emergency,
Alert, Alert,
Notice, Notice,
AIScanClose,
AIYellForHelp,
AICastBeneficialClose,
AoeCast,
EntityManagement,
MaxCategoryID /* Don't Remove this */ MaxCategoryID /* Don't Remove this */
}; };
@ -172,7 +177,12 @@ namespace Logs {
"Critical", "Critical",
"Emergency", "Emergency",
"Alert", "Alert",
"Notice" "Notice",
"AI Scan Close",
"AI Yell For Help",
"AI Cast Beneficial Close",
"AOE Cast",
"Entity Management",
}; };
} }

View File

@ -491,6 +491,56 @@
OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ OutF(LogSys, Logs::Detail, Logs::Status, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0) } 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 LogAoeCast(message, ...) do {\
if (LogSys.log_settings[Logs::AoeCast].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogAoeCastDetail(message, ...) do {\
if (LogSys.log_settings[Logs::AoeCast].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::AoeCast, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogEntityManagement(message, ...) do {\
if (LogSys.log_settings[Logs::EntityManagement].is_category_enabled == 1)\
OutF(LogSys, Logs::General, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogEntityManagementDetail(message, ...) do {\
if (LogSys.log_settings[Logs::EntityManagement].is_category_enabled == 1)\
OutF(LogSys, Logs::Detail, Logs::EntityManagement, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define Log(debug_level, log_category, message, ...) do {\ #define Log(debug_level, log_category, message, ...) do {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\ if (LogSys.log_settings[log_category].is_category_enabled == 1)\
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\ LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -567,7 +567,7 @@ RULE_INT(Range, MobPositionUpdates, 600, "")
RULE_INT(Range, ClientPositionUpdates, 300, "") RULE_INT(Range, ClientPositionUpdates, 300, "")
RULE_INT(Range, ClientForceSpawnUpdateRange, 1000, "") RULE_INT(Range, ClientForceSpawnUpdateRange, 1000, "")
RULE_INT(Range, CriticalDamage, 80, "") RULE_INT(Range, CriticalDamage, 80, "")
RULE_INT(Range, ClientNPCScan, 300, "") RULE_INT(Range, MobCloseScanDistance, 600, "")
RULE_CATEGORY_END() RULE_CATEGORY_END()

View File

@ -87,8 +87,9 @@
bool IsTargetableAESpell(uint16 spell_id) bool IsTargetableAESpell(uint16 spell_id)
{ {
if (IsValidSpell(spell_id) && spells[spell_id].targettype == ST_AETarget) if (IsValidSpell(spell_id) && spells[spell_id].targettype == ST_AETarget) {
return true; return true;
}
return false; return false;
} }

View File

@ -189,10 +189,10 @@ namespace WorldserverCommandHandler {
Json::Value schema; Json::Value schema;
schema["server_tables"] = server_tables_json;
schema["player_tables"] = player_tables_json;
schema["content_tables"] = content_tables_json; schema["content_tables"] = content_tables_json;
schema["login_tables"] = login_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["state_tables"] = state_tables_json;
schema["version_tables"] = version_tables_json; schema["version_tables"] = version_tables_json;

View File

@ -36,19 +36,6 @@
extern Zone* zone; extern Zone* zone;
//#define LOSDEBUG 6 //#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) { void EntityList::DescribeAggro(Client *towho, NPC *from_who, float d, bool verbose) {
float d2 = d*d; float d2 = d*d;
@ -402,22 +389,6 @@ bool Mob::CheckWillAggro(Mob *mob) {
return(false); 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) 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 // 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; return Count;
} }
void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { /**
if(!sender || !attacker) * @param target
return; * @param isSpellAttack
if (sender->GetPrimaryFaction() == 0 ) * @return
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
*/
bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack)
{ {

View File

@ -850,11 +850,12 @@ int Mob::ACSum()
auto over_cap = ac - softcap; auto over_cap = ac - softcap;
ac = softcap + (over_cap * returns); ac = softcap + (over_cap * returns);
} }
LogCombat("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns); LogCombatDetail("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns);
} }
else { else {
LogCombat("ACSum ac [{}]", ac); LogCombatDetail("ACSum ac [{}]", ac);
} }
return ac; return ac;
} }
@ -2465,6 +2466,9 @@ bool NPC::Death(Mob* killer_mob, int32 damage, uint16 spell, EQEmu::skills::Skil
entity_list.UnMarkNPC(GetID()); entity_list.UnMarkNPC(GetID());
entity_list.RemoveNPC(GetID()); entity_list.RemoveNPC(GetID());
// entity_list.RemoveMobFromCloseLists(this);
close_mobs.clear();
this->SetID(0); this->SetID(0);
if (killer != 0 && emoteid != 0) if (killer != 0 && emoteid != 0)

View File

@ -123,49 +123,49 @@ Client::Client(EQStreamInterface* ieqs)
0, 0,
0 0
), ),
hpupdate_timer(2000), hpupdate_timer(2000),
camp_timer(29000), camp_timer(29000),
process_timer(100), process_timer(100),
consume_food_timer(CONSUMPTION_TIMER), consume_food_timer(CONSUMPTION_TIMER),
zoneinpacket_timer(1000), zoneinpacket_timer(1000),
linkdead_timer(RuleI(Zone,ClientLinkdeadMS)), linkdead_timer(RuleI(Zone,ClientLinkdeadMS)),
dead_timer(2000), dead_timer(2000),
global_channel_timer(1000), global_channel_timer(1000),
shield_timer(500), shield_timer(500),
fishing_timer(8000), fishing_timer(8000),
endupkeep_timer(1000), endupkeep_timer(1000),
forget_timer(0), forget_timer(0),
autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000), autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000),
client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000), client_scan_npc_aggro_timer(RuleI(Aggro, ClientAggroCheckInterval) * 1000),
client_zone_wide_full_position_update_timer(5 * 60 * 1000), client_zone_wide_full_position_update_timer(5 * 60 * 1000),
tribute_timer(Tribute_duration), tribute_timer(Tribute_duration),
proximity_timer(ClientProximity_interval), proximity_timer(ClientProximity_interval),
TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000), TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000),
charm_update_timer(6000), charm_update_timer(6000),
rest_timer(1), rest_timer(1),
charm_class_attacks_timer(3000), charm_class_attacks_timer(3000),
charm_cast_timer(3500), charm_cast_timer(3500),
qglobal_purge_timer(30000), qglobal_purge_timer(30000),
TrackingTimer(2000), TrackingTimer(2000),
RespawnFromHoverTimer(0), RespawnFromHoverTimer(0),
merc_timer(RuleI(Mercs, UpkeepIntervalMS)), merc_timer(RuleI(Mercs, UpkeepIntervalMS)),
ItemTickTimer(10000), ItemTickTimer(10000),
ItemQuestTimer(500), ItemQuestTimer(500),
anon_toggle_timer(250), anon_toggle_timer(250),
afk_toggle_timer(250), afk_toggle_timer(250),
helm_toggle_timer(250), helm_toggle_timer(250),
aggro_meter_timer(AGGRO_METER_UPDATE_MS), aggro_meter_timer(AGGRO_METER_UPDATE_MS),
m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number
m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f), m_ZoneSummonLocation(-2.0f,-2.0f,-2.0f),
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), mob_close_scan_timer(6000),
hp_self_update_throttle_timer(300), hp_self_update_throttle_timer(300),
hp_other_update_throttle_timer(500), hp_other_update_throttle_timer(500),
position_update_timer(10000), position_update_timer(10000),
tmSitting(0) tmSitting(0)
{ {
for (int client_filter = 0; client_filter < _FilterCount; client_filter++) for (int client_filter = 0; client_filter < _FilterCount; client_filter++)

View File

@ -226,7 +226,6 @@ public:
Client(EQStreamInterface * ieqs); Client(EQStreamInterface * ieqs);
~Client(); ~Client();
std::unordered_map<Mob *, float> close_mobs;
bool is_client_moving; bool is_client_moving;
void SetDisplayMobInfoWindow(bool display_mob_info_window); void SetDisplayMobInfoWindow(bool display_mob_info_window);
@ -1524,7 +1523,7 @@ private:
Timer afk_toggle_timer; Timer afk_toggle_timer;
Timer helm_toggle_timer; Timer helm_toggle_timer;
Timer aggro_meter_timer; Timer aggro_meter_timer;
Timer npc_close_scan_timer; Timer mob_close_scan_timer;
Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */ Timer hp_self_update_throttle_timer; /* This is to prevent excessive packet sending under trains/fast combat */
Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */ Timer hp_other_update_throttle_timer; /* This is to keep clients from DOSing the server with macros that change client targets constantly */
Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */ Timer position_update_timer; /* Timer used when client hasn't updated within a 10 second window */

View File

@ -251,23 +251,30 @@ bool Client::Process() {
} }
} }
/* Build a close range list of NPC's */ /**
if (npc_close_scan_timer.Check()) { * Scan close range mobs
* Used in aggro checks
*/
if (mob_close_scan_timer.Check()) {
close_mobs.clear(); close_mobs.clear();
//Force spawn updates when traveled far
bool force_spawn_updates = false; float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
float client_update_range = (RuleI(Range, ClientForceSpawnUpdateRange) * RuleI(Range, ClientForceSpawnUpdateRange)); auto &mob_list = entity_list.GetMobList();
float scan_range = (RuleI(Range, ClientNPCScan) * RuleI(Range, ClientNPCScan));
auto &mob_list = entity_list.GetMobList(); for (auto itr : mob_list) {
for (auto itr = mob_list.begin(); itr != mob_list.end(); ++itr) { Mob *mob = itr.second;
Mob* mob = itr->second;
float distance = DistanceSquared(m_Position, mob->GetPosition()); float distance = DistanceSquared(m_Position, mob->GetPosition());
if (mob->IsNPC()) {
if (mob->GetID() <= 0) {
continue;
}
if (mob->IsNPC() || mob->IsClient()) {
if (distance <= scan_range) { if (distance <= scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance)); close_mobs.insert(std::pair<uint16, Mob *>(mob->GetID(), mob));
} }
else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) { else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) {
close_mobs.insert(std::pair<Mob *, float>(mob, distance)); close_mobs.insert(std::pair<uint16, Mob *>(mob->GetID(), mob));
} }
} }
} }
@ -593,7 +600,7 @@ bool Client::Process() {
if (zone->CanDoCombat() && ret && !GetFeigned() && client_scan_npc_aggro_timer.Check()) { if (zone->CanDoCombat() && ret && !GetFeigned() && client_scan_npc_aggro_timer.Check()) {
int npc_scan_count = 0; int npc_scan_count = 0;
for (auto & close_mob : close_mobs) { for (auto & close_mob : close_mobs) {
Mob *mob = close_mob.first; Mob *mob = close_mob.second;
if (!mob) if (!mob)
continue; continue;

View File

@ -673,252 +673,427 @@ void Client::SendDisciplineTimer(uint32 timer_id, uint32 duration)
} }
} }
void EntityList::AETaunt(Client* taunter, float range, int32 bonus_hate) /**
* @param taunter
* @param range
* @param bonus_hate
*/
void EntityList::AETaunt(Client *taunter, float range, int32 bonus_hate)
{ {
if (range == 0)
range = 40; //Live AE taunt range - Hardcoded.
range = range * range; /**
* Live AE taunt range - Hardcoded.
*/
if (range == 0) {
range = 40;
}
auto it = npc_list.begin(); float range_squared = range * range;
while (it != npc_list.end()) {
NPC *them = it->second; for (auto &it : entity_list.GetCloseMobList(taunter, range)) {
float zdiff = taunter->GetZ() - them->GetZ(); Mob *them = it.second;
if (zdiff < 0)
zdiff *= -1; if (!them->IsNPC()) {
if (zdiff < 10 continue;
&& taunter->IsAttackAllowed(them) }
&& DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range) {
float z_difference = taunter->GetZ() - them->GetZ();
if (z_difference < 0) {
z_difference *= -1;
}
if (z_difference < 10
&& taunter->IsAttackAllowed(them)
&& DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range_squared) {
if (taunter->CheckLosFN(them)) { if (taunter->CheckLosFN(them)) {
taunter->Taunt(them, true,0,true,bonus_hate); taunter->Taunt(them->CastToNPC(), true, 0, true, bonus_hate);
} }
} }
++it;
} }
} }
// causes caster to hit every mob within dist range of center with /**
// spell_id. * Causes caster to hit every mob within dist range of center with spell_id
// NPC spells will only affect other NPCs with compatible faction *
void EntityList::AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster, int16 resist_adjust, int *max_targets) * @param caster_mob
* @param center_mob
* @param spell_id
* @param affect_caster
* @param resist_adjust
* @param max_targets
*/
void EntityList::AESpell(
Mob *caster_mob,
Mob *center_mob,
uint16 spell_id,
bool affect_caster,
int16 resist_adjust,
int *max_targets
)
{ {
Mob *curmob = nullptr; const auto &cast_target_position =
spells[spell_id].targettype == ST_Ring ?
caster_mob->GetTargetRingLocation() :
static_cast<glm::vec3>(center_mob->GetPosition());
float dist = caster->GetAOERange(spell_id); Mob *current_mob = nullptr;
float dist2 = dist * dist; bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range; bool is_npc = caster_mob->IsNPC();
float dist_targ = 0; float distance = caster_mob->GetAOERange(spell_id);
float distance_squared = distance * distance;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
glm::vec2 min = {cast_target_position.x - distance, cast_target_position.y - distance};
glm::vec2 max = {cast_target_position.x + distance, cast_target_position.y + distance};
const auto &position = spells[spell_id].targettype == ST_Ring ? caster->GetTargetRingLocation() : static_cast<glm::vec3>(center->GetPosition()); /**
glm::vec2 min = { position.x - dist, position.y - dist }; * If using Old Rain Targets - there is no max target limitation
glm::vec2 max = { position.x + dist, position.y + dist }; */
if (RuleB(Spells, OldRainTargets)) {
max_targets = nullptr;
}
bool bad = IsDetrimentalSpell(spell_id); /**
bool isnpc = caster->IsNPC(); * Max AOE targets
*/
if (RuleB(Spells, OldRainTargets))
max_targets = nullptr; // ignore it!
// if we have a passed in value, use it, otherwise default to data
// detrimental Target AEs have a default value of 4 for PCs and unlimited for NPCs
int max_targets_allowed = 0; // unlimited int max_targets_allowed = 0; // unlimited
if (max_targets) // rains pass this in since they need to preserve the count through waves if (max_targets) { // rains pass this in since they need to preserve the count through waves
max_targets_allowed = *max_targets; max_targets_allowed = *max_targets;
else if (spells[spell_id].aemaxtargets) }
else if (spells[spell_id].aemaxtargets) {
max_targets_allowed = spells[spell_id].aemaxtargets; max_targets_allowed = spells[spell_id].aemaxtargets;
else if (IsTargetableAESpell(spell_id) && bad && !isnpc) }
else if (IsTargetableAESpell(spell_id) && is_detrimental_spell && !is_npc) {
max_targets_allowed = 4; max_targets_allowed = 4;
}
int iCounter = 0; int target_hit_counter = 0;
float distance_to_target = 0;
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { LogAoeCast(
curmob = it->second; "Close scan distance [{}] cast distance [{}]",
// test to fix possible cause of random zone crashes..external methods accessing client properties before they're initialized RuleI(Range, MobCloseScanDistance),
if (curmob->IsClient() && !curmob->CastToClient()->ClientFinishedLoading()) distance
continue; );
if (curmob == caster && !affect_caster) //watch for caster too
continue;
if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && curmob->IsPetOwnerClient())
continue;
if (spells[spell_id].targettype == ST_AreaClientOnly && !curmob->IsClient())
continue;
if (spells[spell_id].targettype == ST_AreaNPCOnly && !curmob->IsNPC())
continue;
// check PC/NPC only flag 1 = PCs, 2 = NPCs
if (spells[spell_id].pcnpc_only_flag == 1 && !curmob->IsClient() && !curmob->IsMerc())
continue;
if (spells[spell_id].pcnpc_only_flag == 2 && (curmob->IsClient() || curmob->IsMerc()))
continue;
if (!IsWithinAxisAlignedBox(static_cast<glm::vec2>(curmob->GetPosition()), min, max))
continue;
dist_targ = DistanceSquared(curmob->GetPosition(), position); for (auto &it : entity_list.GetCloseMobList(caster_mob, distance)) {
current_mob = it.second;
if (dist_targ > dist2) //make sure they are in range if (!current_mob) {
continue; continue;
if (dist_targ < min_range2) //make sure they are in range }
LogAoeCast("Checking AOE against mob [{}]", current_mob->GetCleanName());
if (current_mob->IsClient() && !current_mob->CastToClient()->ClientFinishedLoading()) {
continue; continue;
if (isnpc && curmob->IsNPC() && spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting }
FACTION_VALUE f = curmob->GetReverseFactionCon(caster);
if (bad) { if (current_mob == caster_mob && !affect_caster) {
continue;
}
if (spells[spell_id].targettype == ST_TargetAENoPlayersPets && current_mob->IsPetOwnerClient()) {
continue;
}
if (spells[spell_id].targettype == ST_AreaClientOnly && !current_mob->IsClient()) {
continue;
}
if (spells[spell_id].targettype == ST_AreaNPCOnly && !current_mob->IsNPC()) {
continue;
}
/**
* Check PC / NPC
* 1 = PC
* 2 = NPC
*/
if (spells[spell_id].pcnpc_only_flag == 1 && !current_mob->IsClient() && !current_mob->IsMerc()) {
continue;
}
if (spells[spell_id].pcnpc_only_flag == 2 && (current_mob->IsClient() || current_mob->IsMerc())) {
continue;
}
if (!IsWithinAxisAlignedBox(static_cast<glm::vec2>(current_mob->GetPosition()), min, max)) {
continue;
}
distance_to_target = DistanceSquared(current_mob->GetPosition(), cast_target_position);
if (distance_to_target > distance_squared) {
continue;
}
if (distance_to_target < min_range2) {
continue;
}
if (is_npc && current_mob->IsNPC() &&
spells[spell_id].targettype != ST_AreaNPCOnly) { //check npc->npc casting
FACTION_VALUE faction_value = current_mob->GetReverseFactionCon(caster_mob);
if (is_detrimental_spell) {
//affect mobs that are on our hate list, or //affect mobs that are on our hate list, or
//which have bad faction with us //which have bad faction with us
if (!(caster->CheckAggro(curmob) || f == FACTION_THREATENLY || f == FACTION_SCOWLS) ) if (
!(caster_mob->CheckAggro(current_mob) ||
faction_value == FACTION_THREATENLY ||
faction_value == FACTION_SCOWLS)) {
continue; continue;
} else { }
}
else {
//only affect mobs we would assist. //only affect mobs we would assist.
if (!(f <= FACTION_AMIABLE)) if (!(faction_value <= FACTION_AMIABLE)) {
continue; continue;
}
} }
} }
//finally, make sure they are within range
if (bad) { /**
if (!caster->IsAttackAllowed(curmob, true)) * Finally, make sure they are within range
*/
if (is_detrimental_spell) {
if (!caster_mob->IsAttackAllowed(current_mob, true)) {
continue; continue;
if (center && !spells[spell_id].npc_no_los && !center->CheckLosFN(curmob)) }
if (center_mob && !spells[spell_id].npc_no_los && !center_mob->CheckLosFN(current_mob)) {
continue; continue;
if (!center && !spells[spell_id].npc_no_los && !caster->CheckLosFN(caster->GetTargetRingX(), caster->GetTargetRingY(), caster->GetTargetRingZ(), curmob->GetSize())) }
if (!center_mob && !spells[spell_id].npc_no_los && !caster_mob->CheckLosFN(
caster_mob->GetTargetRingX(),
caster_mob->GetTargetRingY(),
caster_mob->GetTargetRingZ(),
current_mob->GetSize())) {
continue; continue;
} else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... }
// This does not check faction for beneficial AE buffs..only agro and attackable. }
// I've tested for spells that I can find without problem, but a faction-based else {
// check may still be needed. Any changes here should also reflect in BardAEPulse()
if (caster->IsAttackAllowed(curmob, true)) /**
* Check to stop casting beneficial ae buffs (to wit: bard songs) on enemies...
* This does not check faction for beneficial AE buffs... only agro and attackable.
* I've tested for spells that I can find without problem, but a faction-based
* check may still be needed. Any changes here should also reflect in BardAEPulse()
*/
if (caster_mob->IsAttackAllowed(current_mob, true)) {
continue; continue;
if (caster->CheckAggro(curmob)) }
if (caster_mob->CheckAggro(current_mob)) {
continue; continue;
}
} }
curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ); /**
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust); * Increment hit count if max targets
*/
if (max_targets_allowed) { // if we have a limit, increment count if (max_targets_allowed) {
iCounter++; target_hit_counter++;
if (iCounter >= max_targets_allowed) // we done if (target_hit_counter >= max_targets_allowed) {
break; break;
}
} }
current_mob->CalcSpellPowerDistanceMod(spell_id, distance_to_target);
caster_mob->SpellOnTarget(spell_id, current_mob, false, true, resist_adjust);
} }
if (max_targets && max_targets_allowed) LogAoeCast("Done iterating [{}]", caster_mob->GetCleanName());
*max_targets = *max_targets - iCounter;
if (max_targets && max_targets_allowed) {
*max_targets = *max_targets - target_hit_counter;
}
} }
void EntityList::MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster) /**
* @param caster
* @param center
* @param spell_id
* @param affect_caster
*/
void EntityList::MassGroupBuff(
Mob *caster,
Mob *center,
uint16 spell_id,
bool affect_caster)
{ {
Mob *curmob = nullptr; Mob *current_mob = nullptr;
float distance = caster->GetAOERange(spell_id);
float distance_squared = distance * distance;
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
float dist = caster->GetAOERange(spell_id); for (auto &it : entity_list.GetCloseMobList(caster, distance)) {
float dist2 = dist * dist; current_mob = it.second;
bool bad = IsDetrimentalSpell(spell_id); /**
* Skip center
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { */
curmob = it->second; if (current_mob == center) {
if (curmob == center) //do not affect center
continue;
if (curmob == caster && !affect_caster) //watch for caster too
continue;
if (DistanceSquared(center->GetPosition(), curmob->GetPosition()) > dist2) //make sure they are in range
continue; continue;
}
//Only npcs mgb should hit are client pets... /**
if (curmob->IsNPC()) { * Skip self
Mob *owner = curmob->GetOwner(); */
if (current_mob == caster && !affect_caster) {
continue;
}
if (DistanceSquared(center->GetPosition(), current_mob->GetPosition()) > distance_squared) { //make sure they are in range
continue;
}
/**
* Pets
*/
if (current_mob->IsNPC()) {
Mob *owner = current_mob->GetOwner();
if (owner) { if (owner) {
if (!owner->IsClient()) { if (!owner->IsClient()) {
continue; continue;
} }
} else { }
else {
continue; continue;
} }
} }
if (bad) { if (is_detrimental_spell) {
continue; continue;
} }
caster->SpellOnTarget(spell_id, curmob); caster->SpellOnTarget(spell_id, current_mob);
} }
} }
// causes caster to hit every mob within dist range of center with /**
// a bard pulse of spell_id. * Causes caster to hit every mob within dist range of center with a bard pulse of spell_id
// NPC spells will only affect other NPCs with compatible faction * NPC spells will only affect other NPCs with compatible faction
void EntityList::AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster) *
* @param caster
* @param center
* @param spell_id
* @param affect_caster
*/
void EntityList::AEBardPulse(
Mob *caster,
Mob *center,
uint16 spell_id,
bool affect_caster)
{ {
Mob *curmob = nullptr; Mob *current_mob = nullptr;
float distance = caster->GetAOERange(spell_id);
float distance_squared = distance * distance;
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
bool is_npc = caster->IsNPC();
float dist = caster->GetAOERange(spell_id); for (auto &it : entity_list.GetCloseMobList(caster, distance)) {
float dist2 = dist * dist; current_mob = it.second;
bool bad = IsDetrimentalSpell(spell_id); /**
bool isnpc = caster->IsNPC(); * Skip self
*/
if (current_mob == center) {
continue;
}
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { if (current_mob == caster && !affect_caster) {
curmob = it->second;
if (curmob == center) //do not affect center
continue; continue;
if (curmob == caster && !affect_caster) //watch for caster too }
if (DistanceSquared(center->GetPosition(), current_mob->GetPosition()) > distance_squared) { //make sure they are in range
continue; continue;
if (DistanceSquared(center->GetPosition(), curmob->GetPosition()) > dist2) //make sure they are in range }
continue;
if (isnpc && curmob->IsNPC()) { //check npc->npc casting /**
FACTION_VALUE f = curmob->GetReverseFactionCon(caster); * check npc->npc casting
if (bad) { */
if (is_npc && current_mob->IsNPC()) {
FACTION_VALUE faction = current_mob->GetReverseFactionCon(caster);
if (is_detrimental_spell) {
//affect mobs that are on our hate list, or //affect mobs that are on our hate list, or
//which have bad faction with us //which have bad faction with us
if (!(caster->CheckAggro(curmob) || f == FACTION_THREATENLY || f == FACTION_SCOWLS) ) if (!(caster->CheckAggro(current_mob) || faction == FACTION_THREATENLY || faction == FACTION_SCOWLS)) {
continue; continue;
} else { }
}
else {
//only affect mobs we would assist. //only affect mobs we would assist.
if (!(f <= FACTION_AMIABLE)) if (!(faction <= FACTION_AMIABLE)) {
continue; continue;
}
} }
} }
//finally, make sure they are within range
if (bad) { /**
if (!center->CheckLosFN(curmob)) * LOS
*/
if (is_detrimental_spell) {
if (!center->CheckLosFN(current_mob)) {
continue; continue;
} else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies... }
}
else { // check to stop casting beneficial ae buffs (to wit: bard songs) on enemies...
// See notes in AESpell() above for more info. // See notes in AESpell() above for more info.
if (caster->IsAttackAllowed(curmob, true)) if (caster->IsAttackAllowed(current_mob, true)) {
continue; continue;
if (caster->CheckAggro(curmob)) }
if (caster->CheckAggro(current_mob)) {
continue; continue;
}
} }
//if we get here... cast the spell. current_mob->BardPulse(spell_id, caster);
curmob->BardPulse(spell_id, caster);
} }
if (caster->IsClient()) if (caster->IsClient()) {
caster->CastToClient()->CheckSongSkillIncrease(spell_id); caster->CastToClient()->CheckSongSkillIncrease(spell_id);
}
} }
// Rampage and stuff for clients. Normal and Duration rampages /**
//NPCs handle it differently in Mob::Rampage * Rampage - Normal and Duration rampages
void EntityList::AEAttack(Mob *attacker, float dist, int Hand, int count, bool IsFromSpell) { * NPCs handle it differently in Mob::Rampage
//Dook- Will need tweaking, currently no pets or players or horses *
Mob *curmob = nullptr; * @param attacker
* @param distance
* @param Hand
* @param count
* @param is_from_spell
*/
void EntityList::AEAttack(
Mob *attacker,
float distance,
int Hand,
int count,
bool is_from_spell)
{
Mob *current_mob = nullptr;
float distance_squared = distance * distance;
int hit_count = 0;
float dist2 = dist * dist; for (auto &it : entity_list.GetCloseMobList(attacker, distance)) {
current_mob = it.second;
int hit = 0; if (current_mob->IsNPC()
&& current_mob != attacker //this is not needed unless NPCs can use this
&& (attacker->IsAttackAllowed(current_mob))
&& current_mob->GetRace() != 216 && current_mob->GetRace() != 472 /* dont attack horses */
&& (DistanceSquared(current_mob->GetPosition(), attacker->GetPosition()) <= distance_squared)
) {
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) {
curmob = it->second; attacker->Attack(current_mob, Hand, false, false, is_from_spell);
if (curmob->IsNPC() }
&& curmob != attacker //this is not needed unless NPCs can use this else {
&&(attacker->IsAttackAllowed(curmob)) attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell);
&& curmob->GetRace() != 216 && curmob->GetRace() != 472 /* dont attack horses */ }
&& (DistanceSquared(curmob->GetPosition(), attacker->GetPosition()) <= dist2)
) { hit_count++;
if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) if (count != 0 && hit_count >= count) {
attacker->Attack(curmob, Hand, false, false, IsFromSpell);
else
attacker->CastToClient()->DoAttackRounds(curmob, Hand, IsFromSpell);
hit++;
if (count != 0 && hit >= count)
return; return;
}
} }
} }
} }

View File

@ -62,7 +62,8 @@ extern char errorname[32];
Entity::Entity() Entity::Entity()
{ {
id = 0; id = 0;
initial_id = 0;
spawn_timestamp = time(nullptr); spawn_timestamp = time(nullptr);
} }
@ -1582,41 +1583,73 @@ void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *a
} }
} }
void EntityList::QueueCloseClients(Mob *sender, const EQApplicationPacket *app, /**
bool ignore_sender, float dist, Mob *SkipThisMob, bool ackreq, eqFilterType filter) * @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,
bool ignore_sender,
float distance,
Mob *skipped_mob,
bool is_ack_required,
eqFilterType filter
)
{ {
if (sender == nullptr) { if (sender == nullptr) {
QueueClients(sender, app, ignore_sender); QueueClients(sender, app, ignore_sender);
return; return;
} }
if (dist <= 0) if (distance <= 0) {
dist = 600; distance = 600;
float dist2 = dist * dist; //pow(dist, 2); }
auto it = client_list.begin(); float distance_squared = distance * distance;
while (it != client_list.end()) {
Client *ent = it->second;
if ((!ignore_sender || ent != sender) && (ent != SkipThisMob)) { for (auto &e : GetCloseMobList(sender, distance)) {
eqFilterMode filter2 = ent->GetFilter(filter); Mob *mob = e.second;
if(ent->Connected() &&
(filter == FilterNone if (!mob->IsClient()) {
|| filter2 == FilterShow continue;
|| (filter2 == FilterShowGroupOnly && (sender == ent || }
(ent->GetGroup() && ent->GetGroup()->IsGroupMember(sender))))
|| (filter2 == FilterShowSelfOnly && ent == sender)) Client *client = mob->CastToClient();
&& (DistanceSquared(ent->GetPosition(), sender->GetPosition()) <= dist2)) {
ent->QueuePacket(app, ackreq, Client::CLIENT_CONNECTED); if ((!ignore_sender || client != sender) && (client != skipped_mob)) {
if (DistanceSquared(client->GetPosition(), sender->GetPosition()) >= distance_squared) {
continue;
}
if (!client->Connected()) {
continue;
}
eqFilterMode client_filter = client->GetFilter(filter);
if (
filter == FilterNone || client_filter == FilterShow ||
(client_filter == FilterShowGroupOnly &&
(sender == client || (client->GetGroup() && client->GetGroup()->IsGroupMember(sender)))) ||
(client_filter == FilterShowSelfOnly && client == sender)
) {
client->QueuePacket(app, is_ack_required, Client::CLIENT_CONNECTED);
} }
} }
++it;
} }
} }
//sender can be null //sender can be null
void EntityList::QueueClients(Mob *sender, const EQApplicationPacket *app, void EntityList::QueueClients(
bool ignore_sender, bool ackreq) Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, bool ackreq
)
{ {
auto it = client_list.begin(); auto it = client_list.begin();
while (it != client_list.end()) { while (it != client_list.end()) {
@ -2486,43 +2519,51 @@ void EntityList::RemoveAllEncounters()
} }
} }
/**
* @param delete_id
* @return
*/
bool EntityList::RemoveMob(uint16 delete_id) bool EntityList::RemoveMob(uint16 delete_id)
{ {
if (delete_id == 0) if (delete_id == 0) {
return true; return true;
}
auto it = mob_list.find(delete_id); auto it = mob_list.find(delete_id);
if (it != mob_list.end()) { if (it != mob_list.end()) {
if (npc_list.count(delete_id)) {
RemoveMobFromClientCloseLists(it->second);
if (npc_list.count(delete_id))
entity_list.RemoveNPC(delete_id); entity_list.RemoveNPC(delete_id);
else if (client_list.count(delete_id)) }
else if (client_list.count(delete_id)) {
entity_list.RemoveClient(delete_id); entity_list.RemoveClient(delete_id);
}
safe_delete(it->second); safe_delete(it->second);
if (!corpse_list.count(delete_id)) if (!corpse_list.count(delete_id)) {
free_ids.push(it->first); free_ids.push(it->first);
}
mob_list.erase(it); mob_list.erase(it);
return true; return true;
} }
return false; return false;
} }
// This is for if the ID is deleted for some reason /**
* @param delete_mob
* @return
*/
bool EntityList::RemoveMob(Mob *delete_mob) bool EntityList::RemoveMob(Mob *delete_mob)
{ {
if (delete_mob == 0) if (delete_mob == 0) {
return true; return true;
}
auto it = mob_list.begin(); auto it = mob_list.begin();
while (it != mob_list.end()) { while (it != mob_list.end()) {
if (it->second == delete_mob) { if (it->second == delete_mob) {
RemoveMobFromClientCloseLists(it->second);
safe_delete(it->second); safe_delete(it->second);
if (!corpse_list.count(it->first)) if (!corpse_list.count(it->first)) {
free_ids.push(it->first); free_ids.push(it->first);
}
mob_list.erase(it); mob_list.erase(it);
return true; return true;
} }
@ -2531,36 +2572,91 @@ bool EntityList::RemoveMob(Mob *delete_mob)
return false; return false;
} }
/**
* @param delete_id
* @return
*/
bool EntityList::RemoveNPC(uint16 delete_id) 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; NPC *npc = it->second;
// make sure its proximity is removed
RemoveProximity(delete_id); RemoveProximity(delete_id);
// remove from client close lists
RemoveMobFromClientCloseLists(npc->CastToMob());
// remove from the list
npc_list.erase(it); 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); npc_limit_list.erase(delete_id);
}
return true; return true;
} }
return false; return false;
} }
bool EntityList::RemoveMobFromClientCloseLists(Mob *mob) /**
* @param mob
* @return
*/
bool EntityList::RemoveMobFromCloseLists(Mob *mob)
{ {
auto it = client_list.begin(); uint16 entity_id = mob->GetID() > 0 ? mob->GetID() : mob->GetInitialId();
while (it != client_list.end()) {
it->second->close_mobs.erase(mob); LogEntityManagement(
"Attempting to remove mob [{}] from close lists entity_id ({})",
mob->GetCleanName(),
entity_id
);
auto it = mob_list.begin();
while (it != mob_list.end()) {
LogEntityManagement(
"Removing mob [{}] from [{}] close list entity_id ({})",
mob->GetCleanName(),
it->second->GetCleanName(),
entity_id
);
it->second->close_mobs.erase(entity_id);
++it; ++it;
} }
return false; return false;
} }
/**
* @param close_mobs
* @param scanning_mob
*/
void EntityList::ScanCloseMobs(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->IsNPC() && !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));
}
}
LogAIScanClose("Close List Size [{}] for mob [{}]", close_mobs.size(), scanning_mob->GetCleanName());
}
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);
@ -4972,3 +5068,21 @@ void EntityList::ReloadMerchants() {
} }
} }
} }
/**
* If we have a distance requested that is greater than our scanning distance
* then we return the full list
*
* @param mob
* @param distance
* @return
*/
std::unordered_map<uint16, Mob *> &EntityList::GetCloseMobList(Mob *mob, float distance)
{
if (distance <= RuleI(Range, MobCloseScanDistance)) {
return mob->close_mobs;
}
return mob_list;
}

View File

@ -109,6 +109,7 @@ public:
const Beacon *CastToBeacon() const; const Beacon *CastToBeacon() const;
const Encounter *CastToEncounter() const; const Encounter *CastToEncounter() const;
inline const uint16& GetInitialId() const { return initial_id; }
inline const uint16& GetID() const { return id; } inline const uint16& GetID() const { return id; }
inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; } inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; }
@ -122,10 +123,17 @@ public:
protected: protected:
friend class EntityList; friend class EntityList;
inline virtual void SetID(uint16 set_id) { id = set_id; } inline virtual void SetID(uint16 set_id) {
id = set_id;
if (initial_id == 0 && set_id > 0) {
initial_id = set_id;
}
}
uint32 pDBAsyncWorkID; uint32 pDBAsyncWorkID;
private: private:
uint16 id; uint16 id;
uint16 initial_id;
time_t spawn_timestamp; time_t spawn_timestamp;
}; };
@ -284,7 +292,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 RemoveMobFromClientCloseLists(Mob *mob); bool RemoveMobFromCloseLists(Mob *mob);
void RemoveAllMobs(); void RemoveAllMobs();
void RemoveAllClients(); void RemoveAllClients();
void RemoveAllNPCs(); void RemoveAllNPCs();
@ -376,7 +384,7 @@ public:
void RemoveFromXTargets(Mob* mob); void RemoveFromXTargets(Mob* mob);
void RemoveFromAutoXTargets(Mob* mob); void RemoveFromAutoXTargets(Mob* mob);
void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget); void ReplaceWithTarget(Mob* pOldMob, Mob*pNewTarget);
void QueueCloseClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, float dist=200, Mob* SkipThisMob = 0, bool ackreq = true,eqFilterType filter=FilterNone); void QueueCloseClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, float distance=200, Mob* skipped_mob = 0, bool is_ack_required = true, eqFilterType filter=FilterNone);
void QueueClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); void QueueClients(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true);
void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = 0, uint8 maxstatus = 0); void QueueClientsStatus(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint8 minstatus = 0, uint8 maxstatus = 0);
void QueueClientsGuild(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint32 guildeqid = 0); void QueueClientsGuild(Mob* sender, const EQApplicationPacket* app, bool ignore_sender = false, uint32 guildeqid = 0);
@ -388,11 +396,24 @@ public:
void QueueToGroupsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app); void QueueToGroupsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app);
void QueueManaged(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true); void QueueManaged(Mob* sender, const EQApplicationPacket* app, bool ignore_sender=false, bool ackreq = true);
void AEAttack(Mob *attacker, float dist, int Hand = EQEmu::invslot::slotPrimary, int count = 0, bool IsFromSpell = false); void AEAttack(
void AETaunt(Client *caster, float range=0, int32 bonus_hate=0); Mob *attacker,
void AESpell(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true, int16 resist_adjust = 0, int *max_targets = nullptr); float distance,
void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); int Hand = EQEmu::invslot::slotPrimary,
void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true); int count = 0,
bool is_from_spell = false
);
void AETaunt(Client *caster, float range = 0, int32 bonus_hate = 0);
void AESpell(
Mob *caster,
Mob *center,
uint16 spell_id,
bool affect_caster = true,
int16 resist_adjust = 0,
int *max_targets = nullptr
);
void MassGroupBuff(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true);
void AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster = true);
//trap stuff //trap stuff
Mob* GetTrapTrigger(Trap* trap); Mob* GetTrapTrigger(Trap* trap);
@ -443,11 +464,7 @@ public:
bool LimitCheckBoth(uint32 npc_type, uint32 spawngroup_id, int group_count, int type_count); bool LimitCheckBoth(uint32 npc_type, uint32 spawngroup_id, int group_count, int type_count);
bool LimitCheckName(const char* npc_name); 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); 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); bool Merc_AICheckCloseBeneficialSpells(Merc* caster, uint8 iChance, float iRange, uint32 iSpellTypes);
Mob* GetTargetForMez(Mob* caster); Mob* GetTargetForMez(Mob* caster);
uint32 CheckNPCsClose(Mob *center); uint32 CheckNPCsClose(Mob *center);
@ -495,17 +512,21 @@ public:
inline const std::unordered_map<uint16, Object *> &GetObjectList() { return object_list; } inline const std::unordered_map<uint16, Object *> &GetObjectList() { return object_list; }
inline const std::unordered_map<uint16, Doors *> &GetDoorsList() { return door_list; } inline const std::unordered_map<uint16, Doors *> &GetDoorsList() { return door_list; }
std::unordered_map<uint16, Mob *> &GetCloseMobList(Mob *mob, float distance = 0);
void DepopAll(int NPCTypeID, bool StartSpawnTimer = true); void DepopAll(int NPCTypeID, bool StartSpawnTimer = true);
uint16 GetFreeID(); uint16 GetFreeID();
void RefreshAutoXTargets(Client *c); void RefreshAutoXTargets(Client *c);
void RefreshClientXTargets(Client *c); void RefreshClientXTargets(Client *c);
void SendAlternateAdvancementStats(); void SendAlternateAdvancementStats();
void ScanCloseMobs(std::unordered_map<uint16, Mob *> &close_mobs, Mob *scanning_mob);
void GetTrapInfo(Client* client); void GetTrapInfo(Client* client);
bool IsTrapGroupSpawned(uint32 trap_id, uint8 group); bool IsTrapGroupSpawned(uint32 trap_id, uint8 group);
void UpdateAllTraps(bool respawn, bool repopnow = false); void UpdateAllTraps(bool respawn, bool repopnow = false);
void ClearTrapPointers(); void ClearTrapPointers();
protected: protected:
friend class Zone; friend class Zone;
void Depop(bool StartSpawnTimer = false); void Depop(bool StartSpawnTimer = false);
@ -561,6 +582,7 @@ private:
private: private:
std::list<Bot*> bot_list; std::list<Bot*> bot_list;
#endif #endif
}; };
class BulkZoneSpawnPacket { class BulkZoneSpawnPacket {

View File

@ -116,7 +116,9 @@ Mob::Mob(
m_specialattacks(eSpecialAttacks::None), m_specialattacks(eSpecialAttacks::None),
attack_anim_timer(1000), attack_anim_timer(1000),
position_update_melee_push_timer(500), 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 = &MobMovementManager::Get();
mMovementManager->AddMob(this); mMovementManager->AddMob(this);
@ -465,35 +467,42 @@ Mob::~Mob()
AI_Stop(); AI_Stop();
if (GetPet()) { if (GetPet()) {
if (GetPet()->Charmed()) if (GetPet()->Charmed()) {
GetPet()->BuffFadeByEffect(SE_Charm); GetPet()->BuffFadeByEffect(SE_Charm);
else }
else {
SetPet(0); SetPet(0);
}
} }
EQApplicationPacket app; EQApplicationPacket app;
CreateDespawnPacket(&app, !IsCorpse()); CreateDespawnPacket(&app, !IsCorpse());
Corpse* corpse = entity_list.GetCorpseByID(GetID()); Corpse *corpse = entity_list.GetCorpseByID(GetID());
if(!corpse || (corpse && !corpse->IsPlayerCorpse())) if (!corpse || (corpse && !corpse->IsPlayerCorpse())) {
entity_list.QueueClients(this, &app, true); entity_list.QueueClients(this, &app, true);
}
entity_list.RemoveFromTargets(this, true); entity_list.RemoveFromTargets(this, true);
if(trade) { if (trade) {
Mob *with = trade->With(); Mob *with = trade->With();
if(with && with->IsClient()) { if (with && with->IsClient()) {
with->CastToClient()->FinishTrade(with); with->CastToClient()->FinishTrade(with);
with->trade->Reset(); with->trade->Reset();
} }
delete trade; delete trade;
} }
if(HasTempPetsActive()){ if (HasTempPetsActive()) {
entity_list.DestroyTempPets(this); entity_list.DestroyTempPets(this);
} }
entity_list.UnMarkNPC(GetID()); entity_list.UnMarkNPC(GetID());
UninitializeBuffSlots(); UninitializeBuffSlots();
entity_list.RemoveMobFromCloseLists(this);
close_mobs.clear();
#ifdef BOTS #ifdef BOTS
LeaveHealRotationTargetPool(); LeaveHealRotationTargetPool();
#endif #endif

View File

@ -1,3 +1,4 @@
/* EQEMu: Everquest Server Emulator /* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org)
@ -169,6 +170,10 @@ public:
void DisplayInfo(Mob *mob); void DisplayInfo(Mob *mob);
std::unordered_map<uint16, Mob *> close_mobs;
Timer mob_scan_close;
Timer mob_check_moving_timer;
//Somewhat sorted: needs documenting! //Somewhat sorted: needs documenting!
//Attack //Attack
@ -968,7 +973,7 @@ public:
void SetEntityVariable(const char *id, const char *m_var); void SetEntityVariable(const char *id, const char *m_var);
bool EntityVariableExists(const char *id); 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(); void AI_Event_NoLongerEngaged();
FACTION_VALUE GetSpecialFactionCon(Mob* iOther); 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)); 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() void Mob::AI_Init()
{ {
pAIControlled = false; pAIControlled = false;
@ -1415,12 +1362,19 @@ void Mob::AI_Process() {
} }
else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) { else if (zone->CanDoCombat() && CastToNPC()->WillAggroNPCs() && AI_scan_area_timer->Check()) {
/* /**
* NPC to NPC aggro checking, npc needs npc_aggro flag * NPC to NPC aggro (npc_aggro flag set)
*/ */
Mob *temp_target = entity_list.AICheckNPCtoNPCAggro(this, GetAggroRange(), GetAssistRange()); for (auto &close_mob : close_mobs) {
if (temp_target) { Mob *mob = close_mob.second;
AddToHateList(temp_target);
if (mob->IsClient()) {
continue;
}
if (this->CheckWillAggro(mob)) {
this->AddToHateList(mob);
}
} }
AI_scan_area_timer->Disable(); AI_scan_area_timer->Disable();
@ -1877,47 +1831,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) { * @param attacker
if (!IsAIControlled()) * @param yell_for_help
*/
void Mob::AI_Event_Engaged(Mob *attacker, bool yell_for_help)
{
if (!IsAIControlled()) {
return; return;
}
SetAppearance(eaStanding); SetAppearance(eaStanding);
/* if (IsNPC()) {
Kick off auto cast timer CastToNPC()->AIautocastspell_timer->Start(300, false);
*/
if (this->IsNPC())
this->CastToNPC()->AIautocastspell_timer->Start(300, false);
if (iYellForHelp) { if (yell_for_help) {
if(IsPet()) { if (IsPet()) {
GetOwner()->AI_Event_Engaged(attacker, iYellForHelp); GetOwner()->AI_Event_Engaged(attacker, yell_for_help);
} else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { }
entity_list.AIYellForHelp(this, attacker); else if (!HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) {
if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) CastToNPC()->AIYellForHelp(this, attacker);
assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); 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; DistractedFromGrid = true;
} }
if(attacker && !attacker->IsCorpse()) if (attacker && !attacker->IsCorpse()) {
{
//Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged //Because sometimes the AIYellForHelp triggers another engaged and then immediately a not engaged
//if the target dies before it goes off //if the target dies before it goes off
if(attacker->GetHP() > 0) if (attacker->GetHP() > 0) {
{ if (!CastToNPC()->GetCombatEvent() && GetHP() > 0) {
if(!CastToNPC()->GetCombatEvent() && GetHP() > 0)
{
parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0); parse->EventNPC(EVENT_COMBAT, CastToNPC(), attacker, "1", 0);
uint16 emoteid = GetEmoteID(); uint16 emoteid = GetEmoteID();
if(emoteid != 0) if (emoteid != 0) {
CastToNPC()->DoNPCEmote(ENTERCOMBAT,emoteid); CastToNPC()->DoNPCEmote(ENTERCOMBAT, emoteid);
}
CastToNPC()->SetCombatEvent(true); CastToNPC()->SetCombatEvent(true);
} }
} }
@ -1996,7 +1949,7 @@ bool NPC::AI_EngagedCastCheck() {
// try casting a heal or gate // try casting a heal or gate
if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) {
// try casting a heal on nearby // 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. //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)) { 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. //no spell to cast, try again soon.
@ -2033,7 +1986,7 @@ bool NPC::AI_IdleCastCheck() {
if (AIautocastspell_timer->Check(false)) { if (AIautocastspell_timer->Check(false)) {
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. 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 (!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 //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... //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); 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(); 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()) { if (tic_timer.Check()) {
parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); parse->EventNPC(EVENT_TICK, this, nullptr, "", 0);
BuffProcess(); BuffProcess();
@ -851,7 +875,7 @@ bool NPC::Process()
if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() &&
NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { NPCAssistCap() < RuleI(Combat, NPCAssistCap)) {
entity_list.AIYellForHelp(this, GetTarget()); AIYellForHelp(this, GetTarget());
if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled())
assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer));
} }
@ -2975,6 +2999,11 @@ bool NPC::IsProximitySet()
return false; return false;
} }
/**
* @param box_size
* @param move_distance
* @param move_delay
*/
void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay)
{ {
AI_SetRoambox( AI_SetRoambox(
@ -2986,3 +3015,190 @@ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay)
move_delay move_delay
); );
} }
/**
* @param caster
* @param chance
* @param cast_range
* @param spell_types
* @return
*/
bool NPC::AICheckCloseBeneficialSpells(
NPC *caster,
uint8 chance,
float 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;
}
/**
* Check through close range mobs
*/
for (auto & close_mob : entity_list.GetCloseMobList(caster, cast_range)) {
Mob *mob = close_mob.second;
if (mob->IsClient()) {
continue;
}
float distance = Distance(mob->GetPosition(), caster->GetPosition());
if (distance > cast_range) {
continue;
}
LogAICastBeneficialClose(
"NPC [{}] Distance [{}] Cast Range [{}] Caster [{}]",
mob->GetCleanName(),
distance,
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 : entity_list.GetCloseMobList(sender)) {
Mob *mob = close_mob.second;
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 [{}] in_range [{}]",
GetCleanName(),
GetID(),
mob->GetCleanName(),
assist_range,
distance,
(distance < assist_range)
);
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 bool AI_IdleCastCheck();
virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot);
bool AICheckCloseBeneficialSpells(NPC* caster, uint8 chance, float cast_range, uint32 spell_types);
void AIYellForHelp(Mob* sender, Mob* attacker);
void LevelScale(); void LevelScale();
virtual void SetTarget(Mob* mob); virtual void SetTarget(Mob* mob);

View File

@ -4799,24 +4799,32 @@ int16 Mob::CalcFearResistChance()
return resistchance; return resistchance;
} }
float Mob::GetAOERange(uint16 spell_id) { /**
float range; * @param spell_id
* @return
*/
float Mob::GetAOERange(uint16 spell_id)
{
float range = spells[spell_id].aoerange;
range = spells[spell_id].aoerange; /**
if(range == 0) //for TGB spells, they prolly do not have an aoe range * For TGB
*/
if (range == 0) {
range = spells[spell_id].range; range = spells[spell_id].range;
if(range == 0)
range = 10; //something....
if(IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) {
//Live AA - Extended Notes, SionachiesCrescendo
float song_bonus = static_cast<float>(aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange);
range += range*song_bonus /100.0f;
} }
range = GetActSpellRange(spell_id, range); if (range == 0) {
range = 10;
}
return(range); if (IsBardSong(spell_id) && IsBeneficialSpell(spell_id)) {
//Live AA - Extended Notes, SionachiesCrescendo
float song_bonus = static_cast<float>(aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange);
range += range * song_bonus / 100.0f;
}
return GetActSpellRange(spell_id, range);
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@ -1997,7 +1997,6 @@ const char *Zone::GetSpellBlockedMessage(uint32 spell_id, const glm::vec3 &locat
if (spell_id != blocked_spells[x].spellid && blocked_spells[x].spellid != 0) { if (spell_id != blocked_spells[x].spellid && blocked_spells[x].spellid != 0) {
continue; continue;
} }
switch (blocked_spells[x].type) { switch (blocked_spells[x].type) {
case ZoneBlockedSpellTypes::ZoneWide: { case ZoneBlockedSpellTypes::ZoneWide: {
return blocked_spells[x].message; return blocked_spells[x].message;
@ -2033,21 +2032,21 @@ void Zone::SetInstanceTimer(uint32 new_duration)
void Zone::LoadLDoNTraps() void Zone::LoadLDoNTraps()
{ {
const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates"; const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates";
auto results = database.QueryDatabase(query); auto results = database.QueryDatabase(query);
if (!results.Success()) { if (!results.Success()) {
return; return;
} }
for (auto row = results.begin();row != results.end(); ++row) { for (auto row = results.begin(); row != results.end(); ++row) {
auto lt = new LDoNTrapTemplate; auto lt = new LDoNTrapTemplate;
lt->id = atoi(row[0]); lt->id = atoi(row[0]);
lt->type = (LDoNChestTypes)atoi(row[1]); lt->type = (LDoNChestTypes) atoi(row[1]);
lt->spell_id = atoi(row[2]); lt->spell_id = atoi(row[2]);
lt->skill = atoi(row[3]); lt->skill = atoi(row[3]);
lt->locked = atoi(row[4]); lt->locked = atoi(row[4]);
ldon_trap_list[lt->id] = lt; ldon_trap_list[lt->id] = lt;
} }
} }