mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-30 15:01:29 +00:00
Merge pull request #940 from EQEmu/feature/ae-scanning-optimizations
Mob Scanning and Loop Optimizations
This commit is contained in:
commit
3e0ded6c39
@ -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), "
|
||||
|
||||
@ -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",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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__);\
|
||||
|
||||
@ -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()
|
||||
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
110
zone/aggro.cpp
110
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)
|
||||
{
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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++)
|
||||
|
||||
@ -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 */
|
||||
|
||||
@ -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;
|
||||
|
||||
489
zone/effects.cpp
489
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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
202
zone/entity.cpp
202
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<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;
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
25
zone/mob.cpp
25
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
|
||||
|
||||
@ -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);
|
||||
|
||||
131
zone/mob_ai.cpp
131
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);
|
||||
|
||||
218
zone/npc.cpp
218
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user