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) "
);
auto results = QueryDatabase(query);
/* Save Bind Points */
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), "

View File

@ -107,6 +107,11 @@ namespace Logs {
Emergency,
Alert,
Notice,
AIScanClose,
AIYellForHelp,
AICastBeneficialClose,
AoeCast,
EntityManagement,
MaxCategoryID /* Don't Remove this */
};
@ -172,7 +177,12 @@ namespace Logs {
"Critical",
"Emergency",
"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__);\
} 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 {\
if (LogSys.log_settings[log_category].is_category_enabled == 1)\
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, ClientForceSpawnUpdateRange, 1000, "")
RULE_INT(Range, CriticalDamage, 80, "")
RULE_INT(Range, ClientNPCScan, 300, "")
RULE_INT(Range, MobCloseScanDistance, 600, "")
RULE_CATEGORY_END()

View File

@ -87,8 +87,9 @@
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 false;
}

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

@ -850,11 +850,12 @@ int Mob::ACSum()
auto over_cap = ac - softcap;
ac = softcap + (over_cap * returns);
}
LogCombat("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns);
LogCombatDetail("ACSum ac [{}] softcap [{}] returns [{}]", ac, softcap, returns);
}
else {
LogCombat("ACSum ac [{}]", ac);
LogCombatDetail("ACSum ac [{}]", 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.RemoveNPC(GetID());
// entity_list.RemoveMobFromCloseLists(this);
close_mobs.clear();
this->SetID(0);
if (killer != 0 && emoteid != 0)
@ -5487,4 +5491,4 @@ int32 Mob::GetHPRegen() const
int32 Mob::GetManaRegen() const
{
return mana_regen;
}
}

View File

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

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);
@ -1524,7 +1523,7 @@ private:
Timer afk_toggle_timer;
Timer helm_toggle_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_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 */

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();
//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;
float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
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 (mob->GetID() <= 0) {
continue;
}
if (mob->IsNPC() || mob->IsClient()) {
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) {
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()) {
int npc_scan_count = 0;
for (auto & close_mob : close_mobs) {
Mob *mob = close_mob.first;
Mob *mob = close_mob.second;
if (!mob)
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();
while (it != npc_list.end()) {
NPC *them = it->second;
float zdiff = taunter->GetZ() - them->GetZ();
if (zdiff < 0)
zdiff *= -1;
if (zdiff < 10
&& taunter->IsAttackAllowed(them)
&& DistanceSquaredNoZ(taunter->GetPosition(), them->GetPosition()) <= range) {
float range_squared = range * range;
for (auto &it : entity_list.GetCloseMobList(taunter, range)) {
Mob *them = it.second;
if (!them->IsNPC()) {
continue;
}
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)) {
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.
// 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)
/**
* Causes caster to hit every mob within dist range of center with spell_id
*
* @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);
float dist2 = dist * dist;
float min_range2 = spells[spell_id].min_range * spells[spell_id].min_range;
float dist_targ = 0;
Mob *current_mob = nullptr;
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
bool is_npc = caster_mob->IsNPC();
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 };
glm::vec2 max = { position.x + dist, position.y + dist };
/**
* If using Old Rain Targets - there is no max target limitation
*/
if (RuleB(Spells, OldRainTargets)) {
max_targets = nullptr;
}
bool bad = IsDetrimentalSpell(spell_id);
bool isnpc = caster->IsNPC();
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
/**
* Max AOE targets
*/
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;
else if (spells[spell_id].aemaxtargets)
}
else if (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;
}
int iCounter = 0;
int target_hit_counter = 0;
float distance_to_target = 0;
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) {
curmob = it->second;
// test to fix possible cause of random zone crashes..external methods accessing client properties before they're initialized
if (curmob->IsClient() && !curmob->CastToClient()->ClientFinishedLoading())
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;
LogAoeCast(
"Close scan distance [{}] cast distance [{}]",
RuleI(Range, MobCloseScanDistance),
distance
);
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;
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;
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
//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;
} else {
}
}
else {
//only affect mobs we would assist.
if (!(f <= FACTION_AMIABLE))
if (!(faction_value <= FACTION_AMIABLE)) {
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;
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;
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;
} 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
// check may still be needed. Any changes here should also reflect in BardAEPulse()
if (caster->IsAttackAllowed(curmob, true))
}
}
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
* check may still be needed. Any changes here should also reflect in BardAEPulse()
*/
if (caster_mob->IsAttackAllowed(current_mob, true)) {
continue;
if (caster->CheckAggro(curmob))
}
if (caster_mob->CheckAggro(current_mob)) {
continue;
}
}
curmob->CalcSpellPowerDistanceMod(spell_id, dist_targ);
caster->SpellOnTarget(spell_id, curmob, false, true, resist_adjust);
if (max_targets_allowed) { // if we have a limit, increment count
iCounter++;
if (iCounter >= max_targets_allowed) // we done
/**
* Increment hit count if max targets
*/
if (max_targets_allowed) {
target_hit_counter++;
if (target_hit_counter >= max_targets_allowed) {
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)
*max_targets = *max_targets - iCounter;
LogAoeCast("Done iterating [{}]", caster_mob->GetCleanName());
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);
float dist2 = dist * dist;
for (auto &it : entity_list.GetCloseMobList(caster, distance)) {
current_mob = it.second;
bool bad = IsDetrimentalSpell(spell_id);
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) {
curmob = it->second;
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
/**
* Skip center
*/
if (current_mob == center) {
continue;
}
//Only npcs mgb should hit are client pets...
if (curmob->IsNPC()) {
Mob *owner = curmob->GetOwner();
/**
* Skip self
*/
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->IsClient()) {
continue;
}
} else {
}
else {
continue;
}
}
if (bad) {
if (is_detrimental_spell) {
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.
// NPC spells will only affect other NPCs with compatible faction
void EntityList::AEBardPulse(Mob *caster, Mob *center, uint16 spell_id, bool affect_caster)
/**
* 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
*
* @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);
float dist2 = dist * dist;
for (auto &it : entity_list.GetCloseMobList(caster, distance)) {
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) {
curmob = it->second;
if (curmob == center) //do not affect center
if (current_mob == caster && !affect_caster) {
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;
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);
if (bad) {
}
/**
* check npc->npc casting
*/
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
//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;
} else {
}
}
else {
//only affect mobs we would assist.
if (!(f <= FACTION_AMIABLE))
if (!(faction <= FACTION_AMIABLE)) {
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;
} 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.
if (caster->IsAttackAllowed(curmob, true))
if (caster->IsAttackAllowed(current_mob, true)) {
continue;
if (caster->CheckAggro(curmob))
}
if (caster->CheckAggro(current_mob)) {
continue;
}
}
//if we get here... cast the spell.
curmob->BardPulse(spell_id, caster);
current_mob->BardPulse(spell_id, caster);
}
if (caster->IsClient())
if (caster->IsClient()) {
caster->CastToClient()->CheckSongSkillIncrease(spell_id);
}
}
// Rampage and stuff for clients. Normal and Duration rampages
//NPCs handle it differently in Mob::Rampage
void EntityList::AEAttack(Mob *attacker, float dist, int Hand, int count, bool IsFromSpell) {
//Dook- Will need tweaking, currently no pets or players or horses
Mob *curmob = nullptr;
/**
* Rampage - Normal and Duration rampages
* NPCs handle it differently in Mob::Rampage
*
* @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) {
curmob = it->second;
if (curmob->IsNPC()
&& curmob != attacker //this is not needed unless NPCs can use this
&&(attacker->IsAttackAllowed(curmob))
&& curmob->GetRace() != 216 && curmob->GetRace() != 472 /* dont attack horses */
&& (DistanceSquared(curmob->GetPosition(), attacker->GetPosition()) <= dist2)
) {
if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER)
attacker->Attack(curmob, Hand, false, false, IsFromSpell);
else
attacker->CastToClient()->DoAttackRounds(curmob, Hand, IsFromSpell);
hit++;
if (count != 0 && hit >= count)
if (!attacker->IsClient() || attacker->GetClass() == MONK || attacker->GetClass() == RANGER) {
attacker->Attack(current_mob, Hand, false, false, is_from_spell);
}
else {
attacker->CastToClient()->DoAttackRounds(current_mob, Hand, is_from_spell);
}
hit_count++;
if (count != 0 && hit_count >= count) {
return;
}
}
}
}

View File

@ -62,7 +62,8 @@ extern char errorname[32];
Entity::Entity()
{
id = 0;
id = 0;
initial_id = 0;
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) {
QueueClients(sender, app, ignore_sender);
return;
}
if (dist <= 0)
dist = 600;
float dist2 = dist * dist; //pow(dist, 2);
if (distance <= 0) {
distance = 600;
}
auto it = client_list.begin();
while (it != client_list.end()) {
Client *ent = it->second;
float distance_squared = distance * distance;
if ((!ignore_sender || ent != sender) && (ent != SkipThisMob)) {
eqFilterMode filter2 = ent->GetFilter(filter);
if(ent->Connected() &&
(filter == FilterNone
|| filter2 == FilterShow
|| (filter2 == FilterShowGroupOnly && (sender == ent ||
(ent->GetGroup() && ent->GetGroup()->IsGroupMember(sender))))
|| (filter2 == FilterShowSelfOnly && ent == sender))
&& (DistanceSquared(ent->GetPosition(), sender->GetPosition()) <= dist2)) {
ent->QueuePacket(app, ackreq, Client::CLIENT_CONNECTED);
for (auto &e : GetCloseMobList(sender, distance)) {
Mob *mob = e.second;
if (!mob->IsClient()) {
continue;
}
Client *client = mob->CastToClient();
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
void EntityList::QueueClients(Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, bool ackreq)
void EntityList::QueueClients(
Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, bool ackreq
)
{
auto it = client_list.begin();
while (it != client_list.end()) {
@ -2486,43 +2519,51 @@ void EntityList::RemoveAllEncounters()
}
}
/**
* @param delete_id
* @return
*/
bool EntityList::RemoveMob(uint16 delete_id)
{
if (delete_id == 0)
if (delete_id == 0) {
return true;
}
auto it = mob_list.find(delete_id);
if (it != mob_list.end()) {
RemoveMobFromClientCloseLists(it->second);
if (npc_list.count(delete_id))
if (npc_list.count(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);
}
safe_delete(it->second);
if (!corpse_list.count(delete_id))
if (!corpse_list.count(delete_id)) {
free_ids.push(it->first);
}
mob_list.erase(it);
return true;
}
return false;
}
// This is for if the ID is deleted for some reason
/**
* @param delete_mob
* @return
*/
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);
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;
}
@ -2531,36 +2572,91 @@ bool EntityList::RemoveMob(Mob *delete_mob)
return false;
}
/**
* @param delete_id
* @return
*/
bool EntityList::RemoveNPC(uint16 delete_id)
{
auto it = npc_list.find(delete_id);
if (it != npc_list.end()) {
NPC *npc = it->second;
// make sure its proximity is removed
RemoveProximity(delete_id);
// remove from client close lists
RemoveMobFromClientCloseLists(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()) {
it->second->close_mobs.erase(mob);
uint16 entity_id = mob->GetID() > 0 ? mob->GetID() : mob->GetInitialId();
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;
}
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)
{
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 Encounter *CastToEncounter() const;
inline const uint16& GetInitialId() const { return initial_id; }
inline const uint16& GetID() const { return id; }
inline const time_t& GetSpawnTimeStamp() const { return spawn_timestamp; }
@ -122,10 +123,17 @@ public:
protected:
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;
private:
uint16 id;
uint16 initial_id;
time_t spawn_timestamp;
};
@ -284,7 +292,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();
@ -376,7 +384,7 @@ public:
void RemoveFromXTargets(Mob* mob);
void RemoveFromAutoXTargets(Mob* mob);
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 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);
@ -388,11 +396,24 @@ public:
void QueueToGroupsForNPCHealthAA(Mob* sender, const EQApplicationPacket* app);
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 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);
void AEAttack(
Mob *attacker,
float distance,
int Hand = EQEmu::invslot::slotPrimary,
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
Mob* GetTrapTrigger(Trap* trap);
@ -443,11 +464,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);
@ -495,17 +512,21 @@ public:
inline const std::unordered_map<uint16, Object *> &GetObjectList() { return object_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);
uint16 GetFreeID();
void RefreshAutoXTargets(Client *c);
void RefreshClientXTargets(Client *c);
void SendAlternateAdvancementStats();
void ScanCloseMobs(std::unordered_map<uint16, Mob *> &close_mobs, Mob *scanning_mob);
void GetTrapInfo(Client* client);
bool IsTrapGroupSpawned(uint32 trap_id, uint8 group);
void UpdateAllTraps(bool respawn, bool repopnow = false);
void ClearTrapPointers();
protected:
friend class Zone;
void Depop(bool StartSpawnTimer = false);
@ -561,6 +582,7 @@ private:
private:
std::list<Bot*> bot_list;
#endif
};
class BulkZoneSpawnPacket {

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

View File

@ -1,3 +1,4 @@
/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org)
@ -169,6 +170,10 @@ public:
void DisplayInfo(Mob *mob);
std::unordered_map<uint16, Mob *> close_mobs;
Timer mob_scan_close;
Timer mob_check_moving_timer;
//Somewhat sorted: needs documenting!
//Attack
@ -968,7 +973,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,19 @@ 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.second;
if (mob->IsClient()) {
continue;
}
if (this->CheckWillAggro(mob)) {
this->AddToHateList(mob);
}
}
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) {
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 +1949,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 +1986,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,190 @@ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int 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 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();
virtual void SetTarget(Mob* mob);

View File

@ -4799,24 +4799,32 @@ int16 Mob::CalcFearResistChance()
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;
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) {
continue;
}
switch (blocked_spells[x].type) {
case ZoneBlockedSpellTypes::ZoneWide: {
return blocked_spells[x].message;
@ -2033,21 +2032,21 @@ void Zone::SetInstanceTimer(uint32 new_duration)
void Zone::LoadLDoNTraps()
{
const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates";
auto results = database.QueryDatabase(query);
if (!results.Success()) {
const std::string query = "SELECT id, type, spell_id, skill, locked FROM ldon_trap_templates";
auto results = database.QueryDatabase(query);
if (!results.Success()) {
return;
}
}
for (auto row = results.begin();row != results.end(); ++row) {
auto lt = new LDoNTrapTemplate;
lt->id = atoi(row[0]);
lt->type = (LDoNChestTypes)atoi(row[1]);
lt->spell_id = atoi(row[2]);
lt->skill = atoi(row[3]);
lt->locked = atoi(row[4]);
ldon_trap_list[lt->id] = lt;
}
for (auto row = results.begin(); row != results.end(); ++row) {
auto lt = new LDoNTrapTemplate;
lt->id = atoi(row[0]);
lt->type = (LDoNChestTypes) atoi(row[1]);
lt->spell_id = atoi(row[2]);
lt->skill = atoi(row[3]);
lt->locked = atoi(row[4]);
ldon_trap_list[lt->id] = lt;
}
}