eqemu-server/zone/aura.cpp
2018-03-07 15:23:03 -05:00

940 lines
28 KiB
C++

#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);
}
}
safe_delete(app);
}
// 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);
if (trap)
npc->TryMoveAlong(5.0f, 0.0f, false); // try to place 5 units in front
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;
}