diff --git a/common/database.cpp b/common/database.cpp index 4e762f917..fde091322 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -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), " diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index d2d860101..fc1dd36f2 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -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", }; } diff --git a/common/eqemu_logsys_log_aliases.h b/common/eqemu_logsys_log_aliases.h index f90d0e223..24abfdfc9 100644 --- a/common/eqemu_logsys_log_aliases.h +++ b/common/eqemu_logsys_log_aliases.h @@ -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__);\ diff --git a/common/ruletypes.h b/common/ruletypes.h index c7b4a1aee..c6e99e5f4 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -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() diff --git a/common/spdat.cpp b/common/spdat.cpp index 3fd6d19e4..3f0ae6e41 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -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; } diff --git a/world/world_server_command_handler.cpp b/world/world_server_command_handler.cpp index 003e9abe2..3a929d89f 100644 --- a/world/world_server_command_handler.cpp +++ b/world/world_server_command_handler.cpp @@ -189,10 +189,10 @@ namespace WorldserverCommandHandler { Json::Value schema; - schema["server_tables"] = server_tables_json; - schema["player_tables"] = player_tables_json; schema["content_tables"] = content_tables_json; schema["login_tables"] = login_tables_json; + schema["player_tables"] = player_tables_json; + schema["server_tables"] = server_tables_json; schema["state_tables"] = state_tables_json; schema["version_tables"] = version_tables_json; diff --git a/zone/aggro.cpp b/zone/aggro.cpp index 1d579567f..ff0077353 100644 --- a/zone/aggro.cpp +++ b/zone/aggro.cpp @@ -36,19 +36,6 @@ extern Zone* zone; //#define LOSDEBUG 6 -//look around a client for things which might aggro the client. -void EntityList::CheckClientAggro(Client *around) -{ - for (auto it = mob_list.begin(); it != mob_list.end(); ++it) { - Mob *mob = it->second; - if (mob->IsClient()) //also ensures that mob != around - continue; - - if (mob->CheckWillAggro(around) && !mob->CheckAggro(around)) - mob->AddToHateList(around, 25); - } -} - void EntityList::DescribeAggro(Client *towho, NPC *from_who, float d, bool verbose) { float d2 = d*d; @@ -402,22 +389,6 @@ bool Mob::CheckWillAggro(Mob *mob) { return(false); } -Mob* EntityList::AICheckNPCtoNPCAggro(Mob* sender, float iAggroRange, float iAssistRange) { - if (!sender || !sender->IsNPC()) - return(nullptr); - - auto it = npc_list.begin(); - while (it != npc_list.end()) { - Mob *mob = it->second; - - if (sender->CheckWillAggro(mob)) - return mob; - ++it; - } - - return nullptr; -} - int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) { // Return a list of how many non-feared, non-mezzed, non-green mobs, within aggro range, hate *attacker @@ -462,82 +433,11 @@ int EntityList::GetHatedCount(Mob *attacker, Mob *exclude, bool inc_gray_con) return Count; } -void EntityList::AIYellForHelp(Mob* sender, Mob* attacker) { - if(!sender || !attacker) - return; - if (sender->GetPrimaryFaction() == 0 ) - return; // well, if we dont have a faction set, we're gonna be indiff to everybody - - if (sender->HasAssistAggro()) - return; - - for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { - NPC *mob = it->second; - if (!mob) - continue; - - if (mob->CheckAggro(attacker)) - continue; - - if (sender->NPCAssistCap() >= RuleI(Combat, NPCAssistCap)) - break; - - float r = mob->GetAssistRange(); - r = r * r; - - if ( - mob != sender - && mob != attacker -// && !mob->IsCorpse() -// && mob->IsAIControlled() - && mob->GetPrimaryFaction() != 0 - && DistanceSquared(mob->GetPosition(), sender->GetPosition()) <= r - && !mob->IsEngaged() - && ((!mob->IsPet()) || (mob->IsPet() && mob->GetOwner() && !mob->GetOwner()->IsClient())) - // If we're a pet we don't react to any calls for help if our owner is a client - ) - { - //if they are in range, make sure we are not green... - //then jump in if they are our friend - if(mob->GetLevel() >= 50 || attacker->GetLevelCon(mob->GetLevel()) != CON_GRAY) - { - bool useprimfaction = false; - if(mob->GetPrimaryFaction() == sender->CastToNPC()->GetPrimaryFaction()) - { - const NPCFactionList *cf = database.GetNPCFactionEntry(mob->GetNPCFactionID()); - if(cf){ - if(cf->assistprimaryfaction != 0) - useprimfaction = true; - } - } - - if(useprimfaction || sender->GetReverseFactionCon(mob) <= FACTION_AMIABLE ) - { - //attacking someone on same faction, or a friend - //Father Nitwit: make sure we can see them. - if(mob->CheckLosFN(sender)) { -#if (EQDEBUG>=5) - LogDebug("AIYellForHelp(\"[{}]\",\"[{}]\") [{}] attacking [{}] Dist [{}] Z [{}]", - sender->GetName(), attacker->GetName(), mob->GetName(), - attacker->GetName(), DistanceSquared(mob->GetPosition(), - sender->GetPosition()), std::abs(sender->GetZ()+mob->GetZ())); -#endif - mob->AddToHateList(attacker, 25, 0, false); - sender->AddAssistCap(); - } - } - } - } - } -} - -/* -returns false if attack should not be allowed -I try to list every type of conflict that's possible here, so it's easy -to see how the decision is made. Yea, it could be condensed and made -faster, but I'm doing it this way to make it readable and easy to modify -*/ - +/** + * @param target + * @param isSpellAttack + * @return + */ bool Mob::IsAttackAllowed(Mob *target, bool isSpellAttack) { diff --git a/zone/attack.cpp b/zone/attack.cpp index f39fb6b90..a48f4008e 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -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; -} +} \ No newline at end of file diff --git a/zone/client.cpp b/zone/client.cpp index a99b2818a..f77dc1fd4 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -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++) diff --git a/zone/client.h b/zone/client.h index 82b8c7475..4ff431520 100644 --- a/zone/client.h +++ b/zone/client.h @@ -226,7 +226,6 @@ public: Client(EQStreamInterface * ieqs); ~Client(); - std::unordered_map close_mobs; bool is_client_moving; void SetDisplayMobInfoWindow(bool display_mob_info_window); @@ -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 */ diff --git a/zone/client_process.cpp b/zone/client_process.cpp index a857c3c21..14b2036cf 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -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, distance)); + close_mobs.insert(std::pair(mob->GetID(), mob)); } else if ((mob->GetAggroRange() * mob->GetAggroRange()) > scan_range) { - close_mobs.insert(std::pair(mob, distance)); + close_mobs.insert(std::pair(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; diff --git a/zone/effects.cpp b/zone/effects.cpp index ba82cf3f0..5528f784a 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -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(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(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(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(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; + } } } } diff --git a/zone/entity.cpp b/zone/entity.cpp index 7f016f197..adb29011f 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -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 &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(mob->GetID(), mob)); + } + else if (mob->GetAggroRange() >= scan_range) { + close_mobs.insert(std::pair(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 &EntityList::GetCloseMobList(Mob *mob, float distance) +{ + if (distance <= RuleI(Range, MobCloseScanDistance)) { + return mob->close_mobs; + } + + return mob_list; +} + diff --git a/zone/entity.h b/zone/entity.h index 7631f0532..ea97446a6 100644 --- a/zone/entity.h +++ b/zone/entity.h @@ -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 &GetObjectList() { return object_list; } inline const std::unordered_map &GetDoorsList() { return door_list; } + std::unordered_map &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 &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_list; #endif + }; class BulkZoneSpawnPacket { diff --git a/zone/mob.cpp b/zone/mob.cpp index fedc5298f..8b8758771 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -116,7 +116,9 @@ Mob::Mob( m_specialattacks(eSpecialAttacks::None), attack_anim_timer(1000), position_update_melee_push_timer(500), - hate_list_cleanup_timer(6000) + hate_list_cleanup_timer(6000), + mob_scan_close(6000), + mob_check_moving_timer(1000) { mMovementManager = &MobMovementManager::Get(); mMovementManager->AddMob(this); @@ -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 diff --git a/zone/mob.h b/zone/mob.h index ab3df4277..cdd6f7041 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1,3 +1,4 @@ + /* EQEMu: Everquest Server Emulator Copyright (C) 2001-2016 EQEMu Development Team (http://eqemu.org) @@ -169,6 +170,10 @@ public: void DisplayInfo(Mob *mob); + std::unordered_map 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); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 5958fe18a..525948bb2 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -378,59 +378,6 @@ bool NPC::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain return CastSpell(AIspells[i].spellid, tar->GetID(), EQEmu::spells::CastingSlot::Gem2, AIspells[i].manacost == -2 ? 0 : -1, mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIspells[i].resist_adjust)); } -bool EntityList::AICheckCloseBeneficialSpells(NPC* caster, uint8 iChance, float iRange, uint32 iSpellTypes) { - if((iSpellTypes & SPELL_TYPES_DETRIMENTAL) != 0) { - //according to live, you can buff and heal through walls... - //now with PCs, this only applies if you can TARGET the target, but - // according to Rogean, Live NPCs will just cast through walls/floors, no problem.. - // - // This check was put in to address an idle-mob CPU issue - LogError("Error: detrimental spells requested from AICheckCloseBeneficialSpells!!"); - return(false); - } - - if(!caster) - return false; - - if(caster->AI_HasSpells() == false) - return false; - - if(caster->GetSpecialAbility(NPC_NO_BUFFHEAL_FRIENDS)) - return false; - - if (iChance < 100) { - uint8 tmp = zone->random.Int(0, 99); - if (tmp >= iChance) - return false; - } - if (caster->GetPrimaryFaction() == 0 ) - return(false); // well, if we dont have a faction set, we're gonna be indiff to everybody - - float iRange2 = iRange*iRange; - - //Only iterate through NPCs - for (auto it = npc_list.begin(); it != npc_list.end(); ++it) { - NPC* mob = it->second; - - if (mob->GetReverseFactionCon(caster) >= FACTION_KINDLY) { - continue; - } - - if (DistanceSquared(caster->GetPosition(), mob->GetPosition()) > iRange2) { - continue; - } - - if ((iSpellTypes & SpellType_Buff) && !RuleB(NPC, BuffFriends)) { - if (mob != caster) - iSpellTypes = SpellType_Heal; - } - - if (caster->AICastSpell(mob, 100, iSpellTypes)) - return true; - } - return false; -} - void Mob::AI_Init() { pAIControlled = false; @@ -1415,12 +1362,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); diff --git a/zone/npc.cpp b/zone/npc.cpp index efbe98be1..8b4996177 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -704,6 +704,30 @@ bool NPC::Process() SpellProcess(); + if (mob_scan_close.Check()) { + LogAIScanClose( + "is_moving [{}] npc [{}] timer [{}]", + moving ? "true" : "false", + GetCleanName(), + mob_scan_close.GetDuration() + ); + + entity_list.ScanCloseMobs(close_mobs, this); + + if (moving) { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(3000, 6000)); + } + else { + mob_scan_close.Disable(); + mob_scan_close.Start(RandomTimer(6000, 60000)); + } + } + + if (mob_check_moving_timer.Check() && moving) { + mob_scan_close.Trigger(); + } + if (tic_timer.Check()) { parse->EventNPC(EVENT_TICK, this, nullptr, "", 0); BuffProcess(); @@ -851,7 +875,7 @@ bool NPC::Process() if (assist_timer.Check() && IsEngaged() && !Charmed() && !HasAssistAggro() && NPCAssistCap() < RuleI(Combat, NPCAssistCap)) { - entity_list.AIYellForHelp(this, GetTarget()); + AIYellForHelp(this, GetTarget()); if (NPCAssistCap() > 0 && !assist_cap_timer.Enabled()) assist_cap_timer.Start(RuleI(Combat, NPCAssistCapTimer)); } @@ -2975,6 +2999,11 @@ bool NPC::IsProximitySet() return false; } +/** + * @param box_size + * @param move_distance + * @param move_delay + */ void NPC::SetSimpleRoamBox(float box_size, float move_distance, int move_delay) { AI_SetRoambox( @@ -2986,3 +3015,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() + ); + } + } + } + } + } + +} \ No newline at end of file diff --git a/zone/npc.h b/zone/npc.h index 5d7a7bd73..84ab8b4ef 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -143,6 +143,9 @@ public: virtual bool AI_IdleCastCheck(); virtual void AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot); + bool AICheckCloseBeneficialSpells(NPC* caster, uint8 chance, float cast_range, uint32 spell_types); + void AIYellForHelp(Mob* sender, Mob* attacker); + void LevelScale(); virtual void SetTarget(Mob* mob); diff --git a/zone/spells.cpp b/zone/spells.cpp index 091f1a32c..afeac90e9 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -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(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(aabonuses.SongRange + spellbonuses.SongRange + itembonuses.SongRange); + range += range * song_bonus / 100.0f; + } + + return GetActSpellRange(spell_id, range); } /////////////////////////////////////////////////////////////////////////////// diff --git a/zone/zone.cpp b/zone/zone.cpp index 99c120cab..b88ddd071 100755 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -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; + } }