mirror of
https://github.com/EQEmu/Server.git
synced 2026-05-16 18:52:22 +00:00
Merge
This commit is contained in:
@@ -5,6 +5,7 @@ SET(zone_sources
|
||||
aa_ability.cpp
|
||||
aggro.cpp
|
||||
aggromanager.cpp
|
||||
aura.cpp
|
||||
attack.cpp
|
||||
beacon.cpp
|
||||
bonuses.cpp
|
||||
@@ -140,6 +141,7 @@ SET(zone_headers
|
||||
aa.h
|
||||
aa_ability.h
|
||||
aggromanager.h
|
||||
aura.h
|
||||
basic_functions.h
|
||||
beacon.h
|
||||
bot.h
|
||||
|
||||
+936
@@ -0,0 +1,936 @@
|
||||
#include "../common/string_util.h"
|
||||
|
||||
#include "aura.h"
|
||||
#include "client.h"
|
||||
#include "string_ids.h"
|
||||
#include "raids.h"
|
||||
|
||||
Aura::Aura(NPCType *type_data, Mob *owner, AuraRecord &record)
|
||||
: NPC(type_data, 0, owner->GetPosition(), FlyMode3), spell_id(record.spell_id), distance(record.distance),
|
||||
remove_timer(record.duration), movement_timer(100), process_timer(100), aura_id(-1)
|
||||
{
|
||||
GiveNPCTypeData(type_data); // we will delete this later on
|
||||
m_owner = owner->GetID();
|
||||
|
||||
if (record.cast_time) {
|
||||
cast_timer.SetTimer(record.cast_time);
|
||||
cast_timer.Disable(); // we don't want to be enabled yet
|
||||
}
|
||||
|
||||
if (record.aura_type < static_cast<int>(AuraType::Max))
|
||||
type = static_cast<AuraType>(record.aura_type);
|
||||
else
|
||||
type = AuraType::OnAllGroupMembers;
|
||||
|
||||
if (record.spawn_type < static_cast<int>(AuraSpawns::Max))
|
||||
spawn_type = static_cast<AuraSpawns>(record.spawn_type);
|
||||
else
|
||||
spawn_type = AuraSpawns::GroupMembers;
|
||||
|
||||
if (record.movement < static_cast<int>(AuraMovement::Max))
|
||||
movement_type = static_cast<AuraMovement>(record.movement);
|
||||
else
|
||||
movement_type = AuraMovement::Follow;
|
||||
|
||||
switch (type) {
|
||||
case AuraType::OnAllFriendlies:
|
||||
process_func = &Aura::ProcessOnAllFriendlies;
|
||||
break;
|
||||
case AuraType::OnAllGroupMembers:
|
||||
process_func = &Aura::ProcessOnAllGroupMembers;
|
||||
break;
|
||||
case AuraType::OnGroupMembersPets:
|
||||
process_func = &Aura::ProcessOnGroupMembersPets;
|
||||
break;
|
||||
case AuraType::Totem:
|
||||
process_func = &Aura::ProcessTotem;
|
||||
break;
|
||||
case AuraType::EnterTrap:
|
||||
process_func = &Aura::ProcessEnterTrap;
|
||||
break;
|
||||
case AuraType::ExitTrap:
|
||||
process_func = &Aura::ProcessExitTrap;
|
||||
break;
|
||||
default:
|
||||
process_func = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Mob *Aura::GetOwner()
|
||||
{
|
||||
return entity_list.GetMob(m_owner);
|
||||
}
|
||||
|
||||
// not 100% sure how this one should work and PVP affects ...
|
||||
void Aura::ProcessOnAllFriendlies(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
if (mob->IsClient() || mob->IsPetOwnerClient() || mob->IsMerc()) {
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
|
||||
if (it != casted_on.end()) { // we are already on the list, let's check for removal
|
||||
if (DistanceSquared(GetPosition(), mob->GetPosition()) > distance)
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else { // not on list, lets check if we're in range
|
||||
if (DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &e : delayed_remove) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove
|
||||
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
|
||||
casted_on.erase(e);
|
||||
}
|
||||
|
||||
// so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it
|
||||
if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty())
|
||||
cast_timer.Start();
|
||||
|
||||
if (!cast_timer.Enabled() || !cast_timer.Check())
|
||||
return;
|
||||
|
||||
for (auto &e : casted_on) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
|
||||
void Aura::ProcessOnAllGroupMembers(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
if (owner->IsRaidGrouped() && owner->IsClient()) { // currently raids are just client, but safety check
|
||||
auto raid = owner->GetRaid();
|
||||
if (raid == nullptr) { // well shit
|
||||
owner->RemoveAura(GetID(), false, true);
|
||||
return;
|
||||
}
|
||||
auto group_id = raid->GetGroup(owner->CastToClient());
|
||||
|
||||
// some lambdas so the for loop is less horrible ...
|
||||
auto verify_raid_client = [&raid, &group_id, this](Client *c) {
|
||||
auto idx = raid->GetPlayerIndex(c);
|
||||
if (c->GetID() == m_owner) {
|
||||
return DistanceSquared(GetPosition(), c->GetPosition()) <= distance;
|
||||
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
|
||||
return false;
|
||||
} else if (DistanceSquared(GetPosition(), c->GetPosition()) > distance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto verify_raid_client_pet = [&raid, &group_id, this](Mob *m) {
|
||||
auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient());
|
||||
if (m->GetOwner()->GetID() == m_owner) {
|
||||
return DistanceSquared(GetPosition(), m->GetPosition()) <= distance;
|
||||
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
|
||||
return false;
|
||||
} else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto verify_raid_client_swarm = [&raid, &group_id, this](NPC *n) {
|
||||
auto owner = entity_list.GetMob(n->GetSwarmOwner());
|
||||
if (owner == nullptr)
|
||||
return false;
|
||||
auto idx = raid->GetPlayerIndex(owner->CastToClient());
|
||||
if (owner->GetID() == m_owner) {
|
||||
return DistanceSquared(GetPosition(), n->GetPosition()) <= distance;
|
||||
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
|
||||
return false;
|
||||
} else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
// step 1: check if we're already managing this NPC's buff
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
if (it != casted_on.end()) {
|
||||
// verify still good!
|
||||
if (mob->IsClient()) {
|
||||
if (!verify_raid_client(mob->CastToClient()))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) {
|
||||
if (!verify_raid_client_pet(mob))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
|
||||
auto npc = mob->CastToNPC();
|
||||
if (!verify_raid_client_swarm(npc))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else { // we're not on it!
|
||||
if (mob->IsClient() && verify_raid_client(mob->CastToClient())) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
|
||||
auto npc = mob->CastToNPC();
|
||||
if (verify_raid_client_swarm(npc)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (owner->IsGrouped()) {
|
||||
auto group = owner->GetGroup();
|
||||
if (group == nullptr) { // uh oh
|
||||
owner->RemoveAura(GetID(), false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// lambdas to make for loop less ugly
|
||||
auto verify_group_pet = [&group, this](Mob *m) {
|
||||
auto owner = m->GetOwner();
|
||||
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
auto verify_group_swarm = [&group, this](NPC *n) {
|
||||
auto owner = entity_list.GetMob(n->GetSwarmOwner());
|
||||
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
|
||||
if (it != casted_on.end()) { // make sure we're still valid
|
||||
if (mob->IsPet()) {
|
||||
if (!verify_group_pet(mob))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) {
|
||||
if (!verify_group_swarm(mob->CastToNPC()))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (!group->IsGroupMember(mob) || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) {
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else { // not on, check if we should be!
|
||||
if (mob->IsPet() && verify_group_pet(mob)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (group->IsGroupMember(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto verify_solo = [&owner, this](Mob *m) {
|
||||
if (m->IsPet() && m->GetOwnerID() == owner->GetID())
|
||||
return true;
|
||||
else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == owner->GetID())
|
||||
return true;
|
||||
else if (m->GetID() == owner->GetID())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
};
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
bool good = verify_solo(mob);
|
||||
|
||||
if (it != casted_on.end()) { // make sure still valid
|
||||
if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) {
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &e : delayed_remove) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove
|
||||
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
|
||||
casted_on.erase(e);
|
||||
}
|
||||
|
||||
// so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it
|
||||
if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty())
|
||||
cast_timer.Start();
|
||||
|
||||
if (!cast_timer.Enabled() || !cast_timer.Check())
|
||||
return;
|
||||
|
||||
// some auras have to recast (DRU for example, non-buff too)
|
||||
for (auto &e : casted_on) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
|
||||
void Aura::ProcessOnGroupMembersPets(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
// This type can either live on the pet (level 55/70 MAG aura) or on the pet owner (level 85 MAG aura)
|
||||
auto group_member = owner->GetOwnerOrSelf();
|
||||
|
||||
if (group_member->IsRaidGrouped() && group_member->IsClient()) { // currently raids are just client, but safety check
|
||||
auto raid = group_member->GetRaid();
|
||||
if (raid == nullptr) { // well shit
|
||||
owner->RemoveAura(GetID(), false, true);
|
||||
return;
|
||||
}
|
||||
auto group_id = raid->GetGroup(group_member->CastToClient());
|
||||
|
||||
// some lambdas so the for loop is less horrible ...
|
||||
auto verify_raid_client_pet = [&raid, &group_id, &group_member, this](Mob *m) {
|
||||
auto idx = raid->GetPlayerIndex(m->GetOwner()->CastToClient());
|
||||
if (m->GetOwner()->GetID() == group_member->GetID()) {
|
||||
return DistanceSquared(GetPosition(), m->GetPosition()) <= distance;
|
||||
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
|
||||
return false;
|
||||
} else if (DistanceSquared(GetPosition(), m->GetPosition()) > distance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto verify_raid_client_swarm = [&raid, &group_id, &group_member, this](NPC *n) {
|
||||
auto owner = entity_list.GetMob(n->GetSwarmOwner());
|
||||
if (owner == nullptr)
|
||||
return false;
|
||||
auto idx = raid->GetPlayerIndex(owner->CastToClient());
|
||||
if (owner->GetID() == group_member->GetID()) {
|
||||
return DistanceSquared(GetPosition(), n->GetPosition()) <= distance;
|
||||
} else if (idx == 0xFFFFFFFF || raid->members[idx].GroupNumber != group_id || raid->members[idx].GroupNumber == 0xFFFFFFFF) {
|
||||
return false;
|
||||
} else if (DistanceSquared(GetPosition(), n->GetPosition()) > distance) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
// step 1: check if we're already managing this NPC's buff
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
if (it != casted_on.end()) {
|
||||
// verify still good!
|
||||
if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner()) {
|
||||
if (!verify_raid_client_pet(mob))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
|
||||
auto npc = mob->CastToNPC();
|
||||
if (!verify_raid_client_swarm(npc))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else { // we're not on it!
|
||||
if (mob->IsClient()) {
|
||||
continue; // never hit client
|
||||
} else if (mob->IsPet() && mob->IsPetOwnerClient() && mob->GetOwner() && verify_raid_client_pet(mob)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (mob->IsNPC() && mob->IsPetOwnerClient()) {
|
||||
auto npc = mob->CastToNPC();
|
||||
if (verify_raid_client_swarm(npc)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (group_member->IsGrouped()) {
|
||||
auto group = group_member->GetGroup();
|
||||
if (group == nullptr) { // uh oh
|
||||
owner->RemoveAura(GetID(), false, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// lambdas to make for loop less ugly
|
||||
auto verify_group_pet = [&group, this](Mob *m) {
|
||||
auto owner = m->GetOwner();
|
||||
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), m->GetPosition()) <= distance)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
auto verify_group_swarm = [&group, this](NPC *n) {
|
||||
auto owner = entity_list.GetMob(n->GetSwarmOwner());
|
||||
if (owner != nullptr && group->IsGroupMember(owner) && DistanceSquared(GetPosition(), n->GetPosition()) <= distance)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
|
||||
if (it != casted_on.end()) { // make sure we're still valid
|
||||
if (mob->IsPet()) {
|
||||
if (!verify_group_pet(mob))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo()) {
|
||||
if (!verify_group_swarm(mob->CastToNPC()))
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else { // not on, check if we should be!
|
||||
if (mob->IsClient()) {
|
||||
continue;
|
||||
} else if (mob->IsPet() && verify_group_pet(mob)) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
} else if (mob->IsNPC() && mob->CastToNPC()->GetSwarmInfo() && verify_group_swarm(mob->CastToNPC())) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto verify_solo = [&group_member, this](Mob *m) {
|
||||
if (m->IsPet() && m->GetOwnerID() == group_member->GetID())
|
||||
return true;
|
||||
else if (m->IsNPC() && m->CastToNPC()->GetSwarmOwner() == group_member->GetID())
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
};
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
bool good = verify_solo(mob);
|
||||
|
||||
if (it != casted_on.end()) { // make sure still valid
|
||||
if (!good || DistanceSquared(GetPosition(), mob->GetPosition()) > distance) {
|
||||
delayed_remove.insert(mob->GetID());
|
||||
}
|
||||
} else if (good && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
|
||||
casted_on.insert(mob->GetID());
|
||||
if (is_buff)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &e : delayed_remove) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove
|
||||
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
|
||||
casted_on.erase(e);
|
||||
}
|
||||
|
||||
// so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it
|
||||
if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty())
|
||||
cast_timer.Start();
|
||||
|
||||
if (!cast_timer.Enabled() || !cast_timer.Check())
|
||||
return;
|
||||
|
||||
// some auras have to recast (DRU for example, non-buff too)
|
||||
for (auto &e : casted_on) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
|
||||
void Aura::ProcessTotem(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
std::set<int> delayed_remove;
|
||||
bool is_buff = IsBuffSpell(spell_id); // non-buff spells don't cast on enter
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
if (mob == this)
|
||||
continue;
|
||||
if (mob == owner)
|
||||
continue;
|
||||
if (owner->IsAttackAllowed(mob)) { // might need more checks ...
|
||||
bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
if (it != casted_on.end()) {
|
||||
if (!in_range)
|
||||
delayed_remove.insert(mob->GetID());
|
||||
} else if (in_range) {
|
||||
casted_on.insert(mob->GetID());
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &e : delayed_remove) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr && is_buff) // some auras cast instant spells so no need to remove
|
||||
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
|
||||
casted_on.erase(e);
|
||||
}
|
||||
|
||||
// so if we have a cast timer and our set isn't empty and timer is disabled we need to enable it
|
||||
if (cast_timer.GetDuration() > 0 && !cast_timer.Enabled() && !casted_on.empty())
|
||||
cast_timer.Start();
|
||||
|
||||
if (!cast_timer.Enabled() || !cast_timer.Check())
|
||||
return;
|
||||
|
||||
for (auto &e : casted_on) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr)
|
||||
SpellFinished(spell_id, mob);
|
||||
}
|
||||
}
|
||||
|
||||
void Aura::ProcessEnterTrap(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
if (mob == this)
|
||||
continue;
|
||||
// might need more checks ...
|
||||
if (owner->IsAttackAllowed(mob) && DistanceSquared(GetPosition(), mob->GetPosition()) <= distance) {
|
||||
SpellFinished(spell_id, mob);
|
||||
owner->RemoveAura(GetID(), false); // if we're a buff (ex. NEC) we don't want to strip :P
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Aura::ProcessExitTrap(Mob *owner)
|
||||
{
|
||||
auto &mob_list = entity_list.GetMobList(); // read only reference so we can do it all inline
|
||||
|
||||
for (auto &e : mob_list) {
|
||||
auto mob = e.second;
|
||||
if (mob == this)
|
||||
continue;
|
||||
// might need more checks ...
|
||||
if (owner->IsAttackAllowed(mob)) {
|
||||
bool in_range = DistanceSquared(GetPosition(), mob->GetPosition()) <= distance;
|
||||
auto it = casted_on.find(mob->GetID());
|
||||
if (it != casted_on.end()) {
|
||||
if (!in_range) {
|
||||
SpellFinished(spell_id, mob);
|
||||
owner->RemoveAura(GetID(), false); // if we're a buff we don't want to strip :P
|
||||
break;
|
||||
}
|
||||
} else if (in_range) {
|
||||
casted_on.insert(mob->GetID());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is less than ideal, but other solutions are a bit all over the place
|
||||
// and hard to reason about
|
||||
void Aura::ProcessSpawns()
|
||||
{
|
||||
const auto &clients = entity_list.GetClientList();
|
||||
for (auto &e : clients) {
|
||||
auto c = e.second;
|
||||
bool spawned = spawned_for.find(c->GetID()) != spawned_for.end();
|
||||
if (ShouldISpawnFor(c)) {
|
||||
if (!spawned) {
|
||||
EQApplicationPacket app;
|
||||
CreateSpawnPacket(&app, this);
|
||||
c->QueuePacket(&app);
|
||||
SendArmorAppearance(c);
|
||||
spawned_for.insert(c->GetID());
|
||||
}
|
||||
} else if (spawned) {
|
||||
EQApplicationPacket app;
|
||||
CreateDespawnPacket(&app, false);
|
||||
c->QueuePacket(&app);
|
||||
spawned_for.erase(c->GetID());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool Aura::Process()
|
||||
{
|
||||
// Aura::Depop clears buffs
|
||||
if (p_depop)
|
||||
return false;
|
||||
|
||||
auto owner = entity_list.GetMob(m_owner);
|
||||
if (owner == nullptr) {
|
||||
Depop();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (remove_timer.Check()) {
|
||||
owner->RemoveAura(GetID(), false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (movement_type == AuraMovement::Follow && GetPosition() != owner->GetPosition() && movement_timer.Check()) {
|
||||
m_Position = owner->GetPosition();
|
||||
auto app = new EQApplicationPacket(OP_ClientUpdate, sizeof(PlayerPositionUpdateServer_Struct));
|
||||
auto spu = (PlayerPositionUpdateServer_Struct*)app->pBuffer;
|
||||
MakeSpawnUpdate(spu);
|
||||
auto it = spawned_for.begin();
|
||||
while (it != spawned_for.end()) {
|
||||
auto client = entity_list.GetClientByID(*it);
|
||||
if (client) {
|
||||
client->QueuePacket(app);
|
||||
++it;
|
||||
} else {
|
||||
it = spawned_for.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: waypoints?
|
||||
|
||||
if (!process_timer.Check())
|
||||
return true;
|
||||
|
||||
if (spawn_type != AuraSpawns::Noone)
|
||||
ProcessSpawns(); // bit of a hack
|
||||
|
||||
if (process_func)
|
||||
process_func(*this, owner);
|
||||
|
||||
// TODO: quest calls
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Aura::ShouldISpawnFor(Client *c)
|
||||
{
|
||||
if (spawn_type == AuraSpawns::Noone)
|
||||
return false;
|
||||
|
||||
if (spawn_type == AuraSpawns::Everyone)
|
||||
return true;
|
||||
|
||||
// hey, it's our owner!
|
||||
if (c->GetID() == m_owner)
|
||||
return true;
|
||||
|
||||
// so this one is a bit trickier
|
||||
auto owner = GetOwner();
|
||||
if (owner == nullptr)
|
||||
return false; // hmm
|
||||
|
||||
owner = owner->GetOwnerOrSelf(); // pet auras we need the pet's owner
|
||||
if (owner == nullptr) // shouldn't really be needed
|
||||
return false;
|
||||
|
||||
// gotta check again for pet aura case -.-
|
||||
if (owner == c)
|
||||
return true;
|
||||
|
||||
if (owner->IsRaidGrouped() && owner->IsClient()) {
|
||||
auto raid = owner->GetRaid();
|
||||
if (raid == nullptr)
|
||||
return false; // hmm
|
||||
auto group_id = raid->GetGroup(owner->CastToClient());
|
||||
if (group_id == 0xFFFFFFFF) // owner handled above, and they're in a raid and groupless
|
||||
return false;
|
||||
|
||||
auto idx = raid->GetPlayerIndex(c);
|
||||
if (idx == 0xFFFFFFFF) // they're not in our raid!
|
||||
return false;
|
||||
|
||||
if (raid->members[idx].GroupNumber != group_id) // in our raid, but not our group
|
||||
return false;
|
||||
|
||||
return true; // we got here so we know that 1 they're in our raid and 2 they're in our group!
|
||||
} else if (owner->IsGrouped()) {
|
||||
auto group = owner->GetGroup();
|
||||
if (group == nullptr)
|
||||
return false; // hmm
|
||||
|
||||
// easy, in our group
|
||||
return group->IsGroupMember(c);
|
||||
}
|
||||
|
||||
// our owner is not raided or grouped, and they're handled above so we don't spawn!
|
||||
return false;
|
||||
}
|
||||
|
||||
void Aura::Depop(bool skip_strip)
|
||||
{
|
||||
// NEC trap casts a dot, so we need some way to not strip :P
|
||||
if (!skip_strip && IsBuffSpell(spell_id)) {
|
||||
for (auto &e : casted_on) {
|
||||
auto mob = entity_list.GetMob(e);
|
||||
if (mob != nullptr)
|
||||
mob->BuffFadeBySpellIDAndCaster(spell_id, GetID());
|
||||
}
|
||||
}
|
||||
casted_on.clear();
|
||||
p_depop = true;
|
||||
}
|
||||
|
||||
// This creates an aura from a casted spell
|
||||
void Mob::MakeAura(uint16 spell_id)
|
||||
{
|
||||
// TODO: verify room in AuraMgr
|
||||
if (!IsValidSpell(spell_id))
|
||||
return;
|
||||
|
||||
AuraRecord record;
|
||||
if (!database.GetAuraEntry(spell_id, record)) {
|
||||
Message(13, "Unable to find data for aura %s", spells[spell_id].name);
|
||||
Log(Logs::General, Logs::Error, "Unable to find data for aura %d, check auras table.", spell_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValidSpell(record.spell_id)) {
|
||||
Message(13, "Casted spell (%d) is not valid for aura %s", record.spell_id, spells[spell_id].name);
|
||||
Log(Logs::General, Logs::Error, "Casted spell (%d) is not valid for aura %d, check auras table.",
|
||||
record.spell_id, spell_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.aura_type > static_cast<int>(AuraType::Max)) {
|
||||
return; // TODO: log
|
||||
}
|
||||
|
||||
bool trap = false;
|
||||
|
||||
switch (static_cast<AuraType>(record.aura_type)) {
|
||||
case AuraType::ExitTrap:
|
||||
case AuraType::EnterTrap:
|
||||
case AuraType::Totem:
|
||||
trap = true;
|
||||
break;
|
||||
default:
|
||||
trap = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CanSpawnAura(trap))
|
||||
return;
|
||||
|
||||
const auto base = database.LoadNPCTypesData(record.npc_type);
|
||||
if (base == nullptr) {
|
||||
Message(13, "Unable to load NPC data for aura %s", spells[spell_id].teleport_zone);
|
||||
Log(Logs::General, Logs::Error,
|
||||
"Unable to load NPC data for aura %s (NPC ID %d), check auras and npc_types tables.",
|
||||
spells[spell_id].teleport_zone, record.npc_type);
|
||||
return;
|
||||
}
|
||||
|
||||
auto npc_type = new NPCType;
|
||||
memcpy(npc_type, base, sizeof(NPCType));
|
||||
|
||||
strn0cpy(npc_type->name, record.name, 64);
|
||||
|
||||
auto npc = new Aura(npc_type, this, record);
|
||||
npc->SetAuraID(spell_id);
|
||||
entity_list.AddNPC(npc, false);
|
||||
|
||||
if (trap)
|
||||
AddTrap(npc, record);
|
||||
else
|
||||
AddAura(npc, record);
|
||||
}
|
||||
|
||||
bool ZoneDatabase::GetAuraEntry(uint16 spell_id, AuraRecord &record)
|
||||
{
|
||||
auto query = StringFormat("SELECT npc_type, name, spell_id, distance, aura_type, spawn_type, movement, "
|
||||
"duration, icon, cast_time FROM auras WHERE type='%d'",
|
||||
spell_id);
|
||||
|
||||
auto results = QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return false;
|
||||
|
||||
if (results.RowCount() != 1)
|
||||
return false;
|
||||
|
||||
auto row = results.begin();
|
||||
|
||||
record.npc_type = atoi(row[0]);
|
||||
strn0cpy(record.name, row[1], 64);
|
||||
record.spell_id = atoi(row[2]);
|
||||
record.distance = atoi(row[3]);
|
||||
record.distance *= record.distance; // so we can avoid sqrt
|
||||
record.aura_type = atoi(row[4]);
|
||||
record.spawn_type = atoi(row[5]);
|
||||
record.movement = atoi(row[6]);
|
||||
record.duration = atoi(row[7]) * 1000; // DB is in seconds
|
||||
record.icon = atoi(row[8]);
|
||||
record.cast_time = atoi(row[9]) * 1000; // DB is in seconds
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mob::AddAura(Aura *aura, AuraRecord &record)
|
||||
{
|
||||
// this is called only when it's safe
|
||||
assert(aura != nullptr);
|
||||
strn0cpy(aura_mgr.auras[aura_mgr.count].name, aura->GetCleanName(), 64);
|
||||
aura_mgr.auras[aura_mgr.count].spawn_id = aura->GetID();
|
||||
aura_mgr.auras[aura_mgr.count].aura = aura;
|
||||
if (record.icon == -1)
|
||||
aura_mgr.auras[aura_mgr.count].icon = spells[record.spell_id].new_icon;
|
||||
else
|
||||
aura_mgr.auras[aura_mgr.count].icon = record.icon;
|
||||
if (IsClient()) {
|
||||
auto outapp = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraCreate_Struct));
|
||||
auto aura_create = (AuraCreate_Struct *)outapp->pBuffer;
|
||||
aura_create->action = 0;
|
||||
aura_create->type = 1; // this can be 0 sometimes too
|
||||
strn0cpy(aura_create->aura_name, aura_mgr.auras[aura_mgr.count].name, 64);
|
||||
aura_create->entity_id = aura_mgr.auras[aura_mgr.count].spawn_id;
|
||||
aura_create->icon = aura_mgr.auras[aura_mgr.count].icon;
|
||||
CastToClient()->FastQueuePacket(&outapp);
|
||||
}
|
||||
// we can increment this now
|
||||
aura_mgr.count++;
|
||||
}
|
||||
|
||||
void Mob::AddTrap(Aura *aura, AuraRecord &record)
|
||||
{
|
||||
// this is called only when it's safe
|
||||
assert(aura != nullptr);
|
||||
strn0cpy(trap_mgr.auras[trap_mgr.count].name, aura->GetCleanName(), 64);
|
||||
trap_mgr.auras[trap_mgr.count].spawn_id = aura->GetID();
|
||||
trap_mgr.auras[trap_mgr.count].aura = aura;
|
||||
if (record.icon == -1)
|
||||
trap_mgr.auras[trap_mgr.count].icon = spells[record.spell_id].new_icon;
|
||||
else
|
||||
trap_mgr.auras[trap_mgr.count].icon = record.icon;
|
||||
// doesn't send to client
|
||||
trap_mgr.count++;
|
||||
}
|
||||
|
||||
bool Mob::CanSpawnAura(bool trap)
|
||||
{
|
||||
if (trap && !HasFreeTrapSlots()) {
|
||||
Message_StringID(MT_SpellFailure, NO_MORE_TRAPS);
|
||||
return false;
|
||||
} else if (!trap && !HasFreeAuraSlots()) {
|
||||
Message_StringID(MT_SpellFailure, NO_MORE_AURAS);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mob::RemoveAllAuras()
|
||||
{
|
||||
if (IsClient()) {
|
||||
database.SaveAuras(CastToClient());
|
||||
EQApplicationPacket outapp(OP_UpdateAura, 4);
|
||||
outapp.WriteUInt32(2);
|
||||
CastToClient()->QueuePacket(&outapp);
|
||||
}
|
||||
|
||||
// this is sent on camp/zone, so it just despawns?
|
||||
if (aura_mgr.count) {
|
||||
for (auto &e : aura_mgr.auras) {
|
||||
if (e.aura)
|
||||
e.aura->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
aura_mgr.count = 0;
|
||||
|
||||
if (trap_mgr.count) {
|
||||
for (auto &e : trap_mgr.auras) {
|
||||
if (e.aura)
|
||||
e.aura->Depop();
|
||||
}
|
||||
}
|
||||
|
||||
trap_mgr.count = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Mob::RemoveAura(int spawn_id, bool skip_strip, bool expired)
|
||||
{
|
||||
for (int i = 0; i < aura_mgr.count; ++i) {
|
||||
auto &aura = aura_mgr.auras[i];
|
||||
if (aura.spawn_id == spawn_id) {
|
||||
if (aura.aura)
|
||||
aura.aura->Depop(skip_strip);
|
||||
if (expired && IsClient()) {
|
||||
CastToClient()->SendColoredText(
|
||||
CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color
|
||||
// need to update client UI too
|
||||
auto app = new EQApplicationPacket(OP_UpdateAura, sizeof(AuraDestory_Struct));
|
||||
auto ads = (AuraDestory_Struct *)app->pBuffer;
|
||||
ads->action = 1; // delete
|
||||
ads->entity_id = spawn_id;
|
||||
CastToClient()->QueuePacket(app);
|
||||
safe_delete(app);
|
||||
}
|
||||
while (aura_mgr.count - 1 > i) {
|
||||
i++;
|
||||
aura.spawn_id = aura_mgr.auras[i].spawn_id;
|
||||
aura.icon = aura_mgr.auras[i].icon;
|
||||
aura.aura = aura_mgr.auras[i].aura;
|
||||
aura_mgr.auras[i].aura = nullptr;
|
||||
strn0cpy(aura.name, aura_mgr.auras[i].name, 64);
|
||||
}
|
||||
aura_mgr.count--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < trap_mgr.count; ++i) {
|
||||
auto &aura = trap_mgr.auras[i];
|
||||
if (aura.spawn_id == spawn_id) {
|
||||
if (aura.aura)
|
||||
aura.aura->Depop(skip_strip);
|
||||
if (expired && IsClient())
|
||||
CastToClient()->SendColoredText(
|
||||
CC_Yellow, StringFormat("%s has expired.", aura.name)); // TODO: verify color
|
||||
while (trap_mgr.count - 1 > i) {
|
||||
i++;
|
||||
aura.spawn_id = trap_mgr.auras[i].spawn_id;
|
||||
aura.icon = trap_mgr.auras[i].icon;
|
||||
aura.aura = trap_mgr.auras[i].aura;
|
||||
trap_mgr.auras[i].aura = nullptr;
|
||||
strn0cpy(aura.name, trap_mgr.auras[i].name, 64);
|
||||
}
|
||||
trap_mgr.count--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
#ifndef AURA_H
|
||||
#define AURA_H
|
||||
|
||||
#include <functional>
|
||||
#include <set>
|
||||
|
||||
#include "mob.h"
|
||||
#include "npc.h"
|
||||
#include "../common/types.h"
|
||||
#include "../common/timer.h"
|
||||
|
||||
class Group;
|
||||
class Raid;
|
||||
class Mob;
|
||||
struct NPCType;
|
||||
|
||||
enum class AuraType {
|
||||
OnAllFriendlies, // AE PC/Pet basically (ex. Circle of Power)
|
||||
OnAllGroupMembers, // Normal buffing aura (ex. Champion's Aura)
|
||||
OnGroupMembersPets, // Hits just pets (ex. Rathe's Strength)
|
||||
Totem, // Starts pulsing on a timer when an enemy enters (ex. Idol of Malos)
|
||||
EnterTrap, // Casts once when an enemy enters (ex. Fire Rune)
|
||||
ExitTrap, // Casts when they start to flee (ex. Poison Spikes Trap)
|
||||
FullyScripted, // We just call script function not a predefined
|
||||
Max
|
||||
};
|
||||
|
||||
enum class AuraSpawns {
|
||||
GroupMembers, // most auras use this
|
||||
Everyone, // this is like traps and clickies who cast on everyone
|
||||
Noone, // custom!
|
||||
Max
|
||||
};
|
||||
|
||||
enum class AuraMovement {
|
||||
Follow, // follows caster
|
||||
Stationary,
|
||||
Pathing, // some sorted pathing TODO: implement
|
||||
Max
|
||||
};
|
||||
|
||||
class Aura : public NPC
|
||||
{
|
||||
// NOTE: We may have to override more virtual functions if they're causing issues
|
||||
public:
|
||||
Aura(NPCType *type_data, Mob *owner, AuraRecord &record);
|
||||
~Aura() { };
|
||||
|
||||
bool IsAura() const { return true; }
|
||||
bool Process();
|
||||
void Depop(bool skip_strip = false);
|
||||
Mob *GetOwner();
|
||||
|
||||
void ProcessOnAllFriendlies(Mob *owner);
|
||||
void ProcessOnAllGroupMembers(Mob *owner);
|
||||
void ProcessOnGroupMembersPets(Mob *owner);
|
||||
void ProcessTotem(Mob *owner);
|
||||
void ProcessEnterTrap(Mob *owner);
|
||||
void ProcessExitTrap(Mob *owner);
|
||||
void ProcessSpawns();
|
||||
|
||||
// we only save auras that follow you, and player casted
|
||||
inline bool AuraZones() { return movement_type == AuraMovement::Follow && aura_id > -1; }
|
||||
inline int GetSpellID() { return spell_id; }
|
||||
inline int GetAuraID() { return aura_id; }
|
||||
inline void SetAuraID(int in) { aura_id = in; }
|
||||
|
||||
bool ShouldISpawnFor(Client *c);
|
||||
// so when we join a group, we need to spawn not already spawned auras
|
||||
// This is only possible when spawn type is GroupMembers
|
||||
inline bool JoinGroupSpawnCheck() { return spawn_type == AuraSpawns::GroupMembers; }
|
||||
private:
|
||||
int m_owner;
|
||||
int aura_id; // spell ID of the aura spell -1 if aura isn't from a casted spell
|
||||
int spell_id; // spell we cast
|
||||
int distance; // distance we remove
|
||||
Timer remove_timer; // when we depop
|
||||
Timer process_timer; // rate limit process calls
|
||||
Timer cast_timer; // some auras pulse
|
||||
Timer movement_timer; // rate limit movement updates
|
||||
AuraType type;
|
||||
AuraSpawns spawn_type;
|
||||
AuraMovement movement_type;
|
||||
|
||||
std::function<void(Aura &, Mob *)> process_func;
|
||||
std::set<int> casted_on; // we keep track of the other entities we've casted on
|
||||
std::set<int> spawned_for;
|
||||
};
|
||||
|
||||
#endif /* !AURA_H */
|
||||
|
||||
@@ -1460,6 +1460,14 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
|
||||
newbon->FeignedMinionChance = base1;
|
||||
break;
|
||||
|
||||
case SE_AdditionalAura:
|
||||
newbon->aura_slots += base1;
|
||||
break;
|
||||
|
||||
case SE_IncreaseTrapCount:
|
||||
newbon->trap_slots += base1;
|
||||
break;
|
||||
|
||||
// to do
|
||||
case SE_PetDiscipline:
|
||||
break;
|
||||
@@ -3201,6 +3209,16 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
|
||||
if (new_bonus->FeignedCastOnChance < effect_value)
|
||||
new_bonus->FeignedCastOnChance = effect_value;
|
||||
break;
|
||||
|
||||
case SE_AdditionalAura:
|
||||
if (new_bonus->aura_slots < effect_value)
|
||||
new_bonus->aura_slots = effect_value;
|
||||
break;
|
||||
|
||||
case SE_IncreaseTrapCount:
|
||||
if (new_bonus->trap_slots < effect_value)
|
||||
new_bonus->trap_slots = effect_value;
|
||||
break;
|
||||
|
||||
//Special custom cases for loading effects on to NPC from 'npc_spels_effects' table
|
||||
if (IsAISpellEffect) {
|
||||
|
||||
@@ -2929,6 +2929,7 @@ void Bot::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) {
|
||||
ns->spawn.light = m_Light.Type[EQEmu::lightsource::LightActive];
|
||||
ns->spawn.helm = helmtexture; //(GetShowHelm() ? helmtexture : 0); //0xFF;
|
||||
ns->spawn.equip_chest2 = texture; //0xFF;
|
||||
ns->spawn.show_name = true;
|
||||
const EQEmu::ItemData* item = nullptr;
|
||||
const EQEmu::ItemInstance* inst = nullptr;
|
||||
uint32 spawnedbotid = 0;
|
||||
|
||||
@@ -299,6 +299,7 @@ public:
|
||||
const char* GetBuyerWelcomeMessage() { return BuyerWelcomeMessage.c_str(); }
|
||||
|
||||
void FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho);
|
||||
bool ShouldISpawnFor(Client *c) { return !GMHideMe(c) && !IsHoveringForRespawn(); }
|
||||
virtual bool Process();
|
||||
void ProcessPackets();
|
||||
void LogMerchant(Client* player, Mob* merchant, uint32 quantity, uint32 price, const EQEmu::ItemData* item, bool buying);
|
||||
|
||||
@@ -323,6 +323,7 @@ void MapOpcodes()
|
||||
ConnectedOpcodes[OP_RecipesSearch] = &Client::Handle_OP_RecipesSearch;
|
||||
ConnectedOpcodes[OP_ReloadUI] = &Client::Handle_OP_ReloadUI;
|
||||
ConnectedOpcodes[OP_RemoveBlockedBuffs] = &Client::Handle_OP_RemoveBlockedBuffs;
|
||||
ConnectedOpcodes[OP_RemoveTrap] = &Client::Handle_OP_RemoveTrap;
|
||||
ConnectedOpcodes[OP_Report] = &Client::Handle_OP_Report;
|
||||
ConnectedOpcodes[OP_RequestDuel] = &Client::Handle_OP_RequestDuel;
|
||||
ConnectedOpcodes[OP_RequestTitles] = &Client::Handle_OP_RequestTitles;
|
||||
@@ -383,6 +384,7 @@ void MapOpcodes()
|
||||
ConnectedOpcodes[OP_TributeUpdate] = &Client::Handle_OP_TributeUpdate;
|
||||
ConnectedOpcodes[OP_VetClaimRequest] = &Client::Handle_OP_VetClaimRequest;
|
||||
ConnectedOpcodes[OP_VoiceMacroIn] = &Client::Handle_OP_VoiceMacroIn;
|
||||
ConnectedOpcodes[OP_UpdateAura] = &Client::Handle_OP_UpdateAura;;
|
||||
ConnectedOpcodes[OP_WearChange] = &Client::Handle_OP_WearChange;
|
||||
ConnectedOpcodes[OP_WhoAllRequest] = &Client::Handle_OP_WhoAllRequest;
|
||||
ConnectedOpcodes[OP_WorldUnknown001] = &Client::Handle_OP_Ignore;
|
||||
@@ -882,6 +884,8 @@ void Client::CompleteConnect()
|
||||
SetPetCommandState(PET_BUTTON_SPELLHOLD, 0);
|
||||
}
|
||||
|
||||
database.LoadAuras(this); // this ends up spawning them so probably safer to load this later (here)
|
||||
|
||||
entity_list.RefreshClientXTargets(this);
|
||||
|
||||
worldserver.RequestTellQueue(GetName());
|
||||
@@ -11776,6 +11780,28 @@ void Client::Handle_OP_RemoveBlockedBuffs(const EQApplicationPacket *app)
|
||||
}
|
||||
}
|
||||
|
||||
void Client::Handle_OP_RemoveTrap(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size != 4) {// just an int
|
||||
Log(Logs::General, Logs::None, "Size mismatch in OP_RemoveTrap expected 4 got %i", app->size);
|
||||
DumpPacket(app);
|
||||
return;
|
||||
}
|
||||
|
||||
auto id = app->ReadUInt32(0);
|
||||
bool good = false;
|
||||
for (int i = 0; i < trap_mgr.count; ++i) {
|
||||
if (trap_mgr.auras[i].spawn_id == id) {
|
||||
good = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (good)
|
||||
RemoveAura(id);
|
||||
else
|
||||
Message_StringID(MT_SpellFailure, NOT_YOUR_TRAP); // pretty sure this was red
|
||||
}
|
||||
|
||||
void Client::Handle_OP_Report(const EQApplicationPacket *app)
|
||||
{
|
||||
if (!CanUseReport)
|
||||
@@ -14294,6 +14320,24 @@ void Client::Handle_OP_VoiceMacroIn(const EQApplicationPacket *app)
|
||||
|
||||
}
|
||||
|
||||
void Client::Handle_OP_UpdateAura(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size != sizeof(AuraDestory_Struct)) {
|
||||
Log(Logs::General, Logs::None, "Size mismatch in OP_UpdateAura expected %i got %i",
|
||||
sizeof(AuraDestory_Struct), app->size);
|
||||
return;
|
||||
}
|
||||
|
||||
// client only sends this for removing
|
||||
auto aura = (AuraDestory_Struct *)app->pBuffer;
|
||||
if (aura->action != 1)
|
||||
return; // could log I guess, but should only ever get this action
|
||||
|
||||
RemoveAura(aura->entity_id);
|
||||
QueuePacket(app); // if we don't resend this, the client gets confused
|
||||
return;
|
||||
}
|
||||
|
||||
void Client::Handle_OP_WearChange(const EQApplicationPacket *app)
|
||||
{
|
||||
if (app->size != sizeof(WearChange_Struct)) {
|
||||
|
||||
@@ -236,6 +236,7 @@
|
||||
void Handle_OP_RecipesSearch(const EQApplicationPacket *app);
|
||||
void Handle_OP_ReloadUI(const EQApplicationPacket *app);
|
||||
void Handle_OP_RemoveBlockedBuffs(const EQApplicationPacket *app);
|
||||
void Handle_OP_RemoveTrap(const EQApplicationPacket *app);
|
||||
void Handle_OP_Report(const EQApplicationPacket *app);
|
||||
void Handle_OP_RequestDuel(const EQApplicationPacket *app);
|
||||
void Handle_OP_RequestTitles(const EQApplicationPacket *app);
|
||||
@@ -288,6 +289,7 @@
|
||||
void Handle_OP_TributeNPC(const EQApplicationPacket *app);
|
||||
void Handle_OP_TributeToggle(const EQApplicationPacket *app);
|
||||
void Handle_OP_TributeUpdate(const EQApplicationPacket *app);
|
||||
void Handle_OP_UpdateAura(const EQApplicationPacket *app);
|
||||
void Handle_OP_VetClaimRequest(const EQApplicationPacket *app);
|
||||
void Handle_OP_VoiceMacroIn(const EQApplicationPacket *app);
|
||||
void Handle_OP_WearChange(const EQApplicationPacket *app);
|
||||
|
||||
@@ -722,6 +722,8 @@ void Client::OnDisconnect(bool hard_disconnect) {
|
||||
}
|
||||
}
|
||||
|
||||
RemoveAllAuras();
|
||||
|
||||
Mob *Other = trade->With();
|
||||
if(Other)
|
||||
{
|
||||
|
||||
@@ -105,6 +105,8 @@
|
||||
#define PET_BUTTON_FOCUS 8
|
||||
#define PET_BUTTON_SPELLHOLD 9
|
||||
|
||||
#define AURA_HARDCAP 2
|
||||
|
||||
typedef enum { //focus types
|
||||
focusSpellHaste = 1,
|
||||
focusSpellDuration,
|
||||
@@ -205,6 +207,16 @@ typedef enum { //fear states
|
||||
|
||||
enum { FlyMode0 = 0, FlyMode1 = 1, Flymode2 = 2, FlyMode3 = 3 };
|
||||
|
||||
// This is actually FlyMode, from MQ2
|
||||
enum GravityBehavior {
|
||||
Ground,
|
||||
Flying,
|
||||
Levitating,
|
||||
Water,
|
||||
Floating, // boat
|
||||
LevitateWhileRunning
|
||||
};
|
||||
|
||||
struct TradeEntity;
|
||||
class Trade;
|
||||
enum TradeState {
|
||||
@@ -536,6 +548,8 @@ struct StatBonuses {
|
||||
int16 FeignedCastOnChance; // Percent Value
|
||||
bool PetCommands[PET_MAXCOMMANDS]; // SPA 267
|
||||
int FeignedMinionChance; // SPA 281 base1 = chance, just like normal FD
|
||||
int aura_slots;
|
||||
int trap_slots;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
|
||||
+7
-11
@@ -1246,12 +1246,9 @@ void EntityList::SendZoneSpawns(Client *client)
|
||||
auto it = mob_list.begin();
|
||||
while (it != mob_list.end()) {
|
||||
Mob *ent = it->second;
|
||||
if (!(ent->InZone()) || (ent->IsClient())) {
|
||||
if (ent->CastToClient()->GMHideMe(client) ||
|
||||
ent->CastToClient()->IsHoveringForRespawn()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
if (!ent->InZone() || !ent->ShouldISpawnFor(client)) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
app = new EQApplicationPacket;
|
||||
@@ -1279,17 +1276,16 @@ void EntityList::SendZoneSpawnsBulk(Client *client)
|
||||
for (auto it = mob_list.begin(); it != mob_list.end(); ++it) {
|
||||
spawn = it->second;
|
||||
if (spawn && spawn->GetID() > 0 && spawn->Spawned()) {
|
||||
if (spawn->IsClient() && (spawn->CastToClient()->GMHideMe(client) ||
|
||||
spawn->CastToClient()->IsHoveringForRespawn()))
|
||||
if (!spawn->ShouldISpawnFor(client))
|
||||
continue;
|
||||
|
||||
#if 1
|
||||
const glm::vec4& spos = spawn->GetPosition();
|
||||
|
||||
|
||||
delaypkt = false;
|
||||
if (DistanceSquared(cpos, spos) > dmax || (spawn->IsClient() && (spawn->GetRace() == MINOR_ILL_OBJ || spawn->GetRace() == TREE)))
|
||||
delaypkt = true;
|
||||
|
||||
|
||||
if (delaypkt) {
|
||||
app = new EQApplicationPacket;
|
||||
spawn->CreateSpawnPacket(app);
|
||||
@@ -2659,7 +2655,7 @@ void EntityList::SendPositionUpdates(Client *client, uint32 cLastUpdate, float u
|
||||
mob && !mob->IsCorpse()
|
||||
&& (it->second != client)
|
||||
&& (mob->IsClient() || iSendEvenIfNotChanged || (mob->LastChange() >= cLastUpdate))
|
||||
&& (!it->second->IsClient() || !it->second->CastToClient()->GMHideMe(client))
|
||||
&& (it->second->ShouldISpawnFor(client))
|
||||
) {
|
||||
if (
|
||||
update_range == 0
|
||||
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
virtual bool IsBeacon() const { return false; }
|
||||
virtual bool IsEncounter() const { return false; }
|
||||
virtual bool IsBot() const { return false; }
|
||||
virtual bool IsAura() const { return false; }
|
||||
|
||||
virtual bool Process() { return false; }
|
||||
virtual bool Save() { return true; }
|
||||
|
||||
+2
-2
@@ -142,7 +142,8 @@ void Mob::CalculateNewFearpoint()
|
||||
|
||||
int loop = 0;
|
||||
float ranx, rany, ranz;
|
||||
currently_fleeing = false;
|
||||
|
||||
currently_fleeing = true;
|
||||
while (loop < 100) //Max 100 tries
|
||||
{
|
||||
int ran = 250 - (loop*2);
|
||||
@@ -155,7 +156,6 @@ void Mob::CalculateNewFearpoint()
|
||||
float fdist = ranz - GetZ();
|
||||
if (fdist >= -12 && fdist <= 12 && CheckCoordLosNoZLeaps(GetX(),GetY(),GetZ(),ranx,rany,ranz))
|
||||
{
|
||||
currently_fleeing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1211,6 +1211,7 @@ void Merc::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho) {
|
||||
ns->spawn.flymode = 0;
|
||||
ns->spawn.NPC = 1; // 0=player,1=npc,2=pc corpse,3=npc corpse
|
||||
ns->spawn.IsMercenary = 1;
|
||||
ns->spawn.show_name = true;
|
||||
|
||||
UpdateActiveLight();
|
||||
ns->spawn.light = m_Light.Type[EQEmu::lightsource::LightActive];
|
||||
|
||||
+39
-1
@@ -45,6 +45,8 @@ class EQApplicationPacket;
|
||||
class Group;
|
||||
class NPC;
|
||||
class Raid;
|
||||
class Aura;
|
||||
struct AuraRecord;
|
||||
struct NewSpawn_Struct;
|
||||
struct PlayerPositionUpdateServer_Struct;
|
||||
|
||||
@@ -85,6 +87,23 @@ public:
|
||||
int params[MAX_SPECIAL_ATTACK_PARAMS];
|
||||
};
|
||||
|
||||
struct AuraInfo {
|
||||
char name[64];
|
||||
int spawn_id;
|
||||
int icon;
|
||||
Aura *aura;
|
||||
AuraInfo() : spawn_id(0), icon(0), aura(nullptr)
|
||||
{
|
||||
memset(name, 0, 64);
|
||||
}
|
||||
};
|
||||
|
||||
struct AuraMgr {
|
||||
int count; // active auras
|
||||
AuraInfo auras[AURA_HARDCAP];
|
||||
AuraMgr() : count(0) { }
|
||||
};
|
||||
|
||||
Mob(const char* in_name,
|
||||
const char* in_lastname,
|
||||
int32 in_cur_hp,
|
||||
@@ -308,6 +327,7 @@ public:
|
||||
void BuffProcess();
|
||||
virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr);
|
||||
void BuffFadeBySpellID(uint16 spell_id);
|
||||
void BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id);
|
||||
void BuffFadeByEffect(int effectid, int skipslot = -1);
|
||||
void BuffFadeAll();
|
||||
void BuffFadeNonPersistDeath();
|
||||
@@ -315,6 +335,7 @@ public:
|
||||
void BuffFadeBySlot(int slot, bool iRecalcBonuses = true);
|
||||
void BuffFadeDetrimentalByCaster(Mob *caster);
|
||||
void BuffFadeBySitModifier();
|
||||
bool IsAffectedByBuff(uint16 spell_id);
|
||||
void BuffModifyDurationBySpellID(uint16 spell_id, int32 newDuration);
|
||||
int AddBuff(Mob *caster, const uint16 spell_id, int duration = 0, int32 level_override = -1);
|
||||
int CanBuffStack(uint16 spellid, uint8 caster_level, bool iFailIfOverwrite = false);
|
||||
@@ -528,6 +549,7 @@ public:
|
||||
void SendPosition();
|
||||
void SetSpawned() { spawned = true; };
|
||||
bool Spawned() { return spawned; };
|
||||
virtual bool ShouldISpawnFor(Client *c) { return true; }
|
||||
void SetFlyMode(uint8 flymode);
|
||||
inline void Teleport(glm::vec3 NewPosition) { m_Position.x = NewPosition.x; m_Position.y = NewPosition.y;
|
||||
m_Position.z = NewPosition.z; };
|
||||
@@ -604,6 +626,19 @@ public:
|
||||
bool PlotPositionAroundTarget(Mob* target, float &x_dest, float &y_dest, float &z_dest,
|
||||
bool lookForAftArc = true);
|
||||
|
||||
// aura functions
|
||||
void MakeAura(uint16 spell_id);
|
||||
inline int GetAuraSlots() { return 1 + aabonuses.aura_slots + itembonuses.aura_slots + spellbonuses.aura_slots; }
|
||||
inline int GetTrapSlots() { return 1 + aabonuses.trap_slots + itembonuses.trap_slots + spellbonuses.trap_slots; }
|
||||
inline bool HasFreeAuraSlots() { return aura_mgr.count < GetAuraSlots(); }
|
||||
inline bool HasFreeTrapSlots() { return trap_mgr.count < GetTrapSlots(); }
|
||||
void AddAura(Aura *aura, AuraRecord &record);
|
||||
void AddTrap(Aura *aura, AuraRecord &record);
|
||||
bool CanSpawnAura(bool trap);
|
||||
void RemoveAura(int spawn_id, bool skip_strip = false, bool expired = false);
|
||||
void RemoveAllAuras();
|
||||
inline AuraMgr &GetAuraMgr() { return aura_mgr; } // mainly used for zone db loading/saving
|
||||
|
||||
//Procs
|
||||
void TriggerDefensiveProcs(Mob *on, uint16 hand = EQEmu::inventory::slotPrimary, bool FromSkillProc = false, int damage = 0);
|
||||
bool AddRangedProc(uint16 spell_id, uint16 iChance = 3, uint16 base_spell_id = SPELL_UNKNOWN);
|
||||
@@ -1090,7 +1125,7 @@ protected:
|
||||
int _GetWalkSpeed() const;
|
||||
int _GetRunSpeed() const;
|
||||
int _GetFearSpeed() const;
|
||||
virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ);
|
||||
virtual bool MakeNewPositionAndSendUpdate(float x, float y, float z, int speed);
|
||||
|
||||
virtual bool AI_EngagedCastCheck() { return(false); }
|
||||
virtual bool AI_PursueCastCheck() { return(false); }
|
||||
@@ -1455,6 +1490,9 @@ protected:
|
||||
|
||||
bool IsHorse;
|
||||
|
||||
AuraMgr aura_mgr;
|
||||
AuraMgr trap_mgr;
|
||||
|
||||
private:
|
||||
void _StopSong(); //this is not what you think it is
|
||||
Mob* target;
|
||||
|
||||
+5
-1
@@ -1532,7 +1532,11 @@ void NPC::AI_DoMovement() {
|
||||
|
||||
Log(Logs::Detail, Logs::AI, "Roam Box: d=%.3f (%.3f->%.3f,%.3f->%.3f): Go To (%.3f,%.3f)",
|
||||
roambox_distance, roambox_min_x, roambox_max_x, roambox_min_y, roambox_max_y, roambox_movingto_x, roambox_movingto_y);
|
||||
if (!CalculateNewPosition(roambox_movingto_x, roambox_movingto_y, GetZ(), walksp, true))
|
||||
|
||||
float new_z = this->FindGroundZ(m_Position.x, m_Position.y, 5);
|
||||
new_z += (this->GetSize() / 1.55);
|
||||
|
||||
if (!CalculateNewPosition2(roambox_movingto_x, roambox_movingto_y, new_z, walksp, true))
|
||||
{
|
||||
roambox_movingto_x = roambox_max_x + 1; // force update
|
||||
pLastFightingDelayMoving = Timer::GetCurrentTime() + RandomTimer(roambox_min_delay, roambox_delay);
|
||||
|
||||
@@ -90,6 +90,7 @@ class Client;
|
||||
class Group;
|
||||
class Raid;
|
||||
class Spawn2;
|
||||
class Aura;
|
||||
|
||||
namespace EQEmu
|
||||
{
|
||||
@@ -425,6 +426,7 @@ protected:
|
||||
NPCType* NPCTypedata_ours; //special case for npcs with uniquely created data.
|
||||
|
||||
friend class EntityList;
|
||||
friend class Aura;
|
||||
std::list<struct NPCFaction*> faction_list;
|
||||
uint32 copper;
|
||||
uint32 silver;
|
||||
|
||||
@@ -491,6 +491,14 @@ uint32 Raid::GetPlayerIndex(const char *name){
|
||||
return 0; //should never get to here if we do everything else right, set it to 0 so we never crash things that rely on it.
|
||||
}
|
||||
|
||||
uint32 Raid::GetPlayerIndex(Client *c)
|
||||
{
|
||||
for (int i = 0; i < MAX_RAID_MEMBERS; ++i)
|
||||
if (c == members[i].member)
|
||||
return i;
|
||||
return 0xFFFFFFFF; // return sentinel value, make sure you check it unlike the above function
|
||||
}
|
||||
|
||||
Client *Raid::GetClientByIndex(uint16 index)
|
||||
{
|
||||
if(index > MAX_RAID_MEMBERS)
|
||||
|
||||
@@ -142,6 +142,7 @@ public:
|
||||
//keeps me from having to keep iterating through the list
|
||||
//when I want lots of data from the same entry
|
||||
uint32 GetPlayerIndex(const char *name);
|
||||
uint32 GetPlayerIndex(Client *c);
|
||||
//for perl interface
|
||||
Client *GetClientByIndex(uint16 index);
|
||||
const char *GetClientNameByIndex(uint8 index);
|
||||
|
||||
@@ -2791,6 +2791,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
|
||||
break;
|
||||
}
|
||||
|
||||
case SE_PersistentEffect:
|
||||
MakeAura(spell_id);
|
||||
break;
|
||||
|
||||
// Handled Elsewhere
|
||||
case SE_ImmuneFleeing:
|
||||
case SE_NegateSpellEffect:
|
||||
@@ -5959,7 +5963,8 @@ bool Mob::TryDeathSave() {
|
||||
|
||||
bool Mob::AffectedBySpellExcludingSlot(int slot, int effect)
|
||||
{
|
||||
for (int i = 0; i <= EFFECT_COUNT; i++)
|
||||
int buff_count = GetMaxTotalSlots();
|
||||
for (int i = 0; i < buff_count; i++)
|
||||
{
|
||||
if (i == slot)
|
||||
continue;
|
||||
|
||||
+26
-1
@@ -3447,7 +3447,7 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob *spelltar, bool reflect, bool use_r
|
||||
if(spelltar->IsClient() && spelltar->CastToClient()->IsHoveringForRespawn())
|
||||
return false;
|
||||
|
||||
if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar) && !IsResurrectionEffects(spell_id)) {
|
||||
if(IsDetrimentalSpell(spell_id) && !IsAttackAllowed(spelltar, true) && !IsResurrectionEffects(spell_id)) {
|
||||
if(!IsClient() || !CastToClient()->GetGM()) {
|
||||
Message_StringID(MT_SpellFailure, SPELL_NO_HOLD);
|
||||
return false;
|
||||
@@ -4189,6 +4189,21 @@ void Mob::BuffFadeBySpellID(uint16 spell_id)
|
||||
CalcBonuses();
|
||||
}
|
||||
|
||||
void Mob::BuffFadeBySpellIDAndCaster(uint16 spell_id, uint16 caster_id)
|
||||
{
|
||||
bool recalc_bonus = false;
|
||||
auto buff_count = GetMaxTotalSlots();
|
||||
for (int i = 0; i < buff_count; ++i) {
|
||||
if (buffs[i].spellid == spell_id && buffs[i].casterid == caster_id) {
|
||||
BuffFadeBySlot(i, false);
|
||||
recalc_bonus = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (recalc_bonus)
|
||||
CalcBonuses();
|
||||
}
|
||||
|
||||
// removes buffs containing effectid, skipping skipslot
|
||||
void Mob::BuffFadeByEffect(int effectid, int skipslot)
|
||||
{
|
||||
@@ -4207,6 +4222,16 @@ void Mob::BuffFadeByEffect(int effectid, int skipslot)
|
||||
CalcBonuses();
|
||||
}
|
||||
|
||||
bool Mob::IsAffectedByBuff(uint16 spell_id)
|
||||
{
|
||||
int buff_count = GetMaxTotalSlots();
|
||||
for (int i = 0; i < buff_count; ++i)
|
||||
if (buffs[i].spellid == spell_id)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// checks if 'this' can be affected by spell_id from caster
|
||||
// returns true if the spell should fail, false otherwise
|
||||
bool Mob::IsImmuneToSpell(uint16 spell_id, Mob *caster)
|
||||
|
||||
@@ -286,6 +286,7 @@
|
||||
#define TRADESKILL_LEARN_RECIPE 3457 //You have learned the recipe %1!
|
||||
#define EXPEDITION_MIN_REMAIN 3551 //You only have %1 minutes remaining before this expedition comes to an end.
|
||||
#define LOOT_NOT_ALLOWED 3562 //You are not allowed to loot the item: %1.
|
||||
#define NOT_YOUR_TRAP 3671 //You cannot remove this, you are only allowed to remove traps you have set.
|
||||
#define NO_CAST_ON_PET 4045 //You cannot cast this spell on your pet.
|
||||
#define REWIND_WAIT 4059 //You must wait a bit longer before using the rewind command again.
|
||||
#define CORPSEDRAG_LIMIT 4061 //You are already dragging as much as you can!
|
||||
@@ -361,6 +362,7 @@
|
||||
#define GAIN_GROUP_LEADERSHIP_EXP 8788 //
|
||||
#define GAIN_RAID_LEADERSHIP_EXP 8789 //
|
||||
#define BUFF_MINUTES_REMAINING 8799 //%1 (%2 minutes remaining)
|
||||
#define NO_MORE_TRAPS 9002 //You have already placed your maximum number of traps.
|
||||
#define FEAR_TOO_HIGH 9035 //Your target is too high of a level for your fear spell.
|
||||
#define SLOW_MOSTLY_SUCCESSFUL 9029 //Your spell was mostly successful.
|
||||
#define SLOW_PARTIALLY_SUCCESSFUL 9030 // Your spell was partially successful.
|
||||
@@ -375,6 +377,7 @@
|
||||
#define SHAKE_OFF_STUN 9077 //You shake off the stun effect!
|
||||
#define STRIKETHROUGH_STRING 9078 //You strike through your opponent's defenses!
|
||||
#define SPELL_REFLECT 9082 //%1's spell has been reflected by %2.
|
||||
#define NO_MORE_AURAS 9160 //You do not have sufficient focus to maintain that ability.
|
||||
#define NEW_SPELLS_AVAIL 9149 //You have new spells available to you. Check the merchants near your guild master.
|
||||
#define FD_CAST_ON_NO_BREAK 9174 //The strength of your will allows you to resume feigning death.
|
||||
#define SNEAK_RESTRICT 9240 //You can not use this ability because you have not been hidden for long enough.
|
||||
|
||||
+4
-3
@@ -451,7 +451,7 @@ float Mob::CalculateHeadingToTarget(float in_x, float in_y) {
|
||||
return (256 * (360 - angle) / 360.0f);
|
||||
}
|
||||
|
||||
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, bool checkZ) {
|
||||
bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed) {
|
||||
if (GetID() == 0)
|
||||
return true;
|
||||
|
||||
@@ -495,7 +495,8 @@ bool Mob::MakeNewPositionAndSendUpdate(float x, float y, float z, int speed, boo
|
||||
m_Position.y = new_y;
|
||||
m_Position.z = new_z;
|
||||
|
||||
if(fix_z_timer.Check() && !this->IsEngaged())
|
||||
if(fix_z_timer.Check() &&
|
||||
(!this->IsEngaged() || flee_mode || currently_fleeing))
|
||||
this->FixZ();
|
||||
|
||||
tar_ndx++;
|
||||
@@ -1048,4 +1049,4 @@ void NPC::SaveGuardSpotCharm()
|
||||
void NPC::RestoreGuardSpotCharm()
|
||||
{
|
||||
m_GuardPoint = m_GuardPointSaved;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "merc.h"
|
||||
#include "zone.h"
|
||||
#include "zonedb.h"
|
||||
#include "aura.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
@@ -3152,6 +3153,37 @@ void ZoneDatabase::LoadBuffs(Client *client)
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneDatabase::SaveAuras(Client *c)
|
||||
{
|
||||
auto query = StringFormat("DELETE FROM `character_auras` WHERE `id` = %u", c->CharacterID());
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
const auto &auras = c->GetAuraMgr();
|
||||
for (int i = 0; i < auras.count; ++i) {
|
||||
auto aura = auras.auras[i].aura;
|
||||
if (aura && aura->AuraZones()) {
|
||||
query = StringFormat("INSERT INTO `character_auras` (id, slot, spell_id) VALUES(%u, %d, %d)",
|
||||
c->CharacterID(), i, aura->GetAuraID());
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneDatabase::LoadAuras(Client *c)
|
||||
{
|
||||
auto query = StringFormat("SELECT `spell_id` FROM `character_auras` WHERE `id` = %u ORDER BY `slot`", c->CharacterID());
|
||||
auto results = database.QueryDatabase(query);
|
||||
if (!results.Success())
|
||||
return;
|
||||
|
||||
for (auto row = results.begin(); row != results.end(); ++row)
|
||||
c->MakeAura(atoi(row[0]));
|
||||
}
|
||||
|
||||
void ZoneDatabase::SavePetInfo(Client *client)
|
||||
{
|
||||
PetInfo *petinfo = nullptr;
|
||||
|
||||
@@ -123,6 +123,19 @@ struct PetRecord {
|
||||
uint32 equipmentset; // default equipment for the pet
|
||||
};
|
||||
|
||||
struct AuraRecord {
|
||||
uint32 npc_type;
|
||||
char name[64]; // name shown in UI if shown and spawn name
|
||||
int spell_id;
|
||||
int distance;
|
||||
int aura_type;
|
||||
int spawn_type;
|
||||
int movement;
|
||||
int duration; // seconds some live for 90 mins (normal) others for 2 mins (traps)
|
||||
int icon; // -1 will use the buffs NEW_ICON
|
||||
int cast_time; // seconds some auras recast on a timer, most seem to be every 12 seconds
|
||||
};
|
||||
|
||||
// Actual pet info for a client.
|
||||
struct PetInfo {
|
||||
uint16 SpellID;
|
||||
@@ -260,6 +273,8 @@ public:
|
||||
|
||||
void SaveBuffs(Client *c);
|
||||
void LoadBuffs(Client *c);
|
||||
void SaveAuras(Client *c);
|
||||
void LoadAuras(Client *c);
|
||||
void LoadPetInfo(Client *c);
|
||||
void SavePetInfo(Client *c);
|
||||
void RemoveTempFactions(Client *c);
|
||||
@@ -404,6 +419,7 @@ public:
|
||||
void AddLootDropToNPC(NPC* npc, uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop);
|
||||
uint32 GetMaxNPCSpellsID();
|
||||
uint32 GetMaxNPCSpellsEffectsID();
|
||||
bool GetAuraEntry(uint16 spell_id, AuraRecord &record);
|
||||
|
||||
DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID);
|
||||
DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID);
|
||||
|
||||
Reference in New Issue
Block a user