/* EQEmu: EQEmulator
Copyright (C) 2001-2026 EQEmu Development Team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "entity.h"
#include "common/data_verification.h"
#include "common/features.h"
#include "common/guilds.h"
#include "zone/bot.h"
#include "zone/dialogue_window.h"
#include "zone/dynamic_zone.h"
#include "zone/guild_mgr.h"
#include "zone/npc_scale_manager.h"
#include "zone/petitions.h"
#include "zone/quest_parser_collection.h"
#include "zone/raids.h"
#include "zone/string_ids.h"
#include "zone/water_map.h"
#include "zone/worldserver.h"
#include
#include
#include
#include
extern Zone *zone;
extern volatile bool is_zone_loaded;
extern WorldServer worldserver;
extern uint32 numclients;
Entity::Entity()
{
id = 0;
initial_id = 0;
spawn_timestamp = time(nullptr);
}
Entity::~Entity()
{
}
Client *Entity::CastToClient()
{
if (this == 0x00) {
LogError("CastToClient error (nullptr)");
return 0;
}
#ifdef _EQDEBUG
if (!IsClient()) {
LogError("CastToClient error (not client)");
return 0;
}
#endif
return static_cast(this);
}
NPC *Entity::CastToNPC()
{
#ifdef _EQDEBUG
if (!IsNPC()) {
LogError("CastToNPC error (Not NPC)");
return 0;
}
#endif
return static_cast(this);
}
Mob *Entity::CastToMob()
{
#ifdef _EQDEBUG
if (!IsMob()) {
std::cout << "CastToMob error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
Merc *Entity::CastToMerc()
{
#ifdef _EQDEBUG
if (!IsMerc()) {
std::cout << "CastToMerc error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
Trap *Entity::CastToTrap()
{
#ifdef DEBUG
if (!IsTrap()) {
return 0;
}
#endif
return static_cast(this);
}
Corpse *Entity::CastToCorpse()
{
#ifdef _EQDEBUG
if (!IsCorpse()) {
std::cout << "CastToCorpse error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
Object *Entity::CastToObject()
{
#ifdef _EQDEBUG
if (!IsObject()) {
std::cout << "CastToObject error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
/*Group* Entity::CastToGroup() {
#ifdef _EQDEBUG
if(!IsGroup()) {
std::cout << "CastToGroup error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}*/
Doors *Entity::CastToDoors()
{
return static_cast(this);
}
Beacon *Entity::CastToBeacon()
{
return static_cast(this);
}
Encounter *Entity::CastToEncounter()
{
return static_cast(this);
}
const Client *Entity::CastToClient() const
{
if (this == 0x00) {
std::cout << "CastToClient error (nullptr)" << std::endl;
return 0;
}
#ifdef _EQDEBUG
if (!IsClient()) {
std::cout << "CastToClient error (not client?)" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const NPC *Entity::CastToNPC() const
{
#ifdef _EQDEBUG
if (!IsNPC()) {
std::cout << "CastToNPC error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const Mob *Entity::CastToMob() const
{
#ifdef _EQDEBUG
if (!IsMob()) {
std::cout << "CastToMob error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const Merc *Entity::CastToMerc() const
{
#ifdef _EQDEBUG
if (!IsMerc()) {
std::cout << "CastToMerc error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const Trap *Entity::CastToTrap() const
{
#ifdef DEBUG
if (!IsTrap()) {
return 0;
}
#endif
return static_cast(this);
}
const Corpse *Entity::CastToCorpse() const
{
#ifdef _EQDEBUG
if (!IsCorpse()) {
std::cout << "CastToCorpse error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const Object *Entity::CastToObject() const
{
#ifdef _EQDEBUG
if (!IsObject()) {
std::cout << "CastToObject error" << std::endl;
return 0;
}
#endif
return static_cast(this);
}
const Doors *Entity::CastToDoors() const
{
return static_cast(this);
}
const Beacon* Entity::CastToBeacon() const
{
return static_cast(this);
}
const Encounter* Entity::CastToEncounter() const
{
return static_cast(this);
}
Bot *Entity::CastToBot()
{
return static_cast(this);
}
const Bot *Entity::CastToBot() const
{
return static_cast(this);
}
EntityList::EntityList()
:
object_timer(5000),
door_timer(5000),
corpse_timer(2000),
group_timer(1000),
raid_timer(1000),
trap_timer(1000)
{
// set up ids between 1 and 1500
// neither client or server performs well if you have
// enough entities to exhaust this list
for (uint16 i = 1; i <= 1500; i++)
free_ids.push(i);
}
EntityList::~EntityList()
{
//must call this before the list is destroyed, or else it will try to
//delete the NPCs in the list, which it cannot do.
RemoveAllLocalities();
}
bool EntityList::CanAddHateForMob(Mob *p)
{
int count = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC *npc = it->second;
if (npc->IsOnHatelist(p))
count++;
// no need to continue if we already hit the limit
if (count > 3)
return false;
++it;
}
if (count <= 2)
return true;
return false;
}
void EntityList::AddClient(Client *client)
{
client->SetID(GetFreeID());
client_list.emplace(std::pair(client->GetID(), client));
mob_list.emplace(std::pair(client->GetID(), client));
}
void EntityList::TrapProcess()
{
if (numclients < 1)
return;
if (trap_list.empty()) {
trap_timer.Disable();
return;
}
auto it = trap_list.begin();
while (it != trap_list.end()) {
if (!it->second->Process()) {
safe_delete(it->second);
free_ids.push(it->first);
it = trap_list.erase(it);
} else {
++it;
}
}
}
// Debug function -- checks to see if group_list has any nullptr entries.
// Meant to be called after each group-related function, in order
// to track down bugs.
void EntityList::CheckGroupList (const char *fname, const int fline)
{
std::list::iterator it;
for (it = group_list.begin(); it != group_list.end(); ++it)
{
if (*it == nullptr)
{
LogError("nullptr group, [{}]:[{}]", fname, fline);
}
}
}
void EntityList::GroupProcess()
{
if (numclients < 1)
return;
if (group_list.empty()) {
group_timer.Disable();
return;
}
for (auto &group : group_list)
group->Process();
}
void EntityList::QueueToGroupsForNPCHealthAA(Mob *sender, const EQApplicationPacket *app)
{
for (auto &group : group_list)
group->QueueHPPacketsForNPCHealthAA(sender, app);
}
void EntityList::RaidProcess()
{
if (numclients < 1)
return;
if (raid_list.empty()) {
raid_timer.Disable();
return;
}
for (auto &raid : raid_list)
raid->Process();
}
void EntityList::DoorProcess()
{
if (zone && zone->IsIdleWhenEmpty()) {
if (numclients < 1) {
return;
}
}
if (door_list.empty()) {
door_timer.Disable();
return;
}
auto it = door_list.begin();
while (it != door_list.end()) {
if (!it->second->Process()) {
safe_delete(it->second);
free_ids.push(it->first);
it = door_list.erase(it);
}
++it;
}
}
void EntityList::ObjectProcess()
{
if (object_list.empty()) {
object_timer.Disable();
return;
}
auto it = object_list.begin();
while (it != object_list.end()) {
if (!it->second->Process()) {
safe_delete(it->second);
free_ids.push(it->first);
it = object_list.erase(it);
} else {
++it;
}
}
}
void EntityList::CorpseProcess()
{
if (corpse_list.empty()) {
corpse_timer.Disable(); // No corpses in list
return;
}
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (!it->second->Process()) {
safe_delete(it->second);
free_ids.push(it->first);
it = corpse_list.erase(it);
} else {
++it;
}
}
}
void EntityList::MobProcess()
{
bool mob_dead;
auto it = mob_list.begin();
while (it != mob_list.end()) {
uint16 id = it->first;
Mob *mob = it->second;
size_t sz = mob_list.size();
static int old_client_count = 0;
static Timer *mob_settle_timer = new Timer();
if (zone->IsIdleWhenEmpty()) {
if (
numclients == 0 &&
old_client_count > 0 &&
zone->GetSecondsBeforeIdle() > 0
) {
LogInfo(
"Zone will go into an idle state after [{}] second{}.",
zone->GetSecondsBeforeIdle(),
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
);
mob_settle_timer->Start(zone->GetSecondsBeforeIdle() * 1000);
}
if (numclients == 0 && mob_settle_timer->Check()) {
LogInfo(
"Zone has gone idle after [{}] second{}.",
zone->GetSecondsBeforeIdle(),
zone->GetSecondsBeforeIdle() != 1 ? "s" : ""
);
CheckToClearTraderAndBuyerTables();
mob_settle_timer->Disable();
}
// Disable settle timer if someone zones into empty zone
if (numclients > 0 || mob_settle_timer->Check()) {
if (mob_settle_timer->Enabled()) {
LogInfo("Zone is no longer scheduled to go idle.");
mob_settle_timer->Disable();
}
}
old_client_count = numclients;
Spawn2* s2 = mob->CastToNPC()->respawn2;
// Perform normal mob processing if any of these are true:
// -- zone is not empty
// -- the entity's spawn2 point is marked as path_while_zone_idle
// -- the zone is newly empty and we're allowing mobs to settle
if (
numclients > 0 || zone->quest_idle_override ||
(mob && s2 && s2->PathWhenZoneIdle()) ||
mob_settle_timer->Enabled()
) {
mob_dead = !mob->Process();
} else {
// spawn_events can cause spawns and deaths while zone empty.
// At the very least, process that.
mob_dead = mob->CastToNPC()->GetDepop();
}
} else {
mob_dead = !mob->Process();
}
size_t a_sz = mob_list.size();
if (a_sz > sz) {
//increased size can potentially screw with iterators so reset it to current value
//if buckets are re-orderered we may skip a process here and there but since
//process happens so often it shouldn't matter much
it = mob_list.find(id);
++it;
} else {
++it;
}
if (mob_dead) {
if (mob->IsMerc()) {
entity_list.RemoveMerc(id);
} else if (mob->IsBot()) {
entity_list.RemoveBot(id);
} else if (mob->IsNPC()) {
entity_list.RemoveNPC(id);
} else {
#ifdef _WINDOWS
struct in_addr in;
in.s_addr = mob->CastToClient()->GetIP();
LogInfo("Dropping client: Process=false, ip=[{}] port=[{}]", inet_ntoa(in), mob->CastToClient()->GetPort());
#endif
Group* g = GetGroupByMob(mob);
if (g) {
g->DelMember(mob);
}
Raid* r = entity_list.GetRaidByClient(mob->CastToClient());
if (r) {
r->MemberZoned(mob->CastToClient());
}
entity_list.RemoveClient(id);
}
entity_list.RemoveMob(id);
}
}
}
void EntityList::BeaconProcess()
{
auto it = beacon_list.begin();
while (it != beacon_list.end()) {
if (!it->second->Process()) {
safe_delete(it->second);
free_ids.push(it->first);
it = beacon_list.erase(it);
} else {
++it;
}
}
}
void EntityList::EncounterProcess()
{
auto it = encounter_list.begin();
while (it != encounter_list.end()) {
if (!it->second->Process()) {
// if Process is returning false here, we probably just got called from ReloadQuests .. oh well
parse->RemoveEncounter(it->second->GetEncounterName());
safe_delete(it->second);
free_ids.push(it->first);
it = encounter_list.erase(it);
}
else {
++it;
}
}
}
void EntityList::AddGroup(Group *group)
{
if (group == nullptr) //this seems to be happening somehow...
return;
uint32 gid = worldserver.NextGroupID();
if (gid == 0) {
LogError("Unable to get new group ID from world server. group is going to be broken");
return;
}
AddGroup(group, gid);
}
void EntityList::AddGroup(Group *group, uint32 gid)
{
group->SetID(gid);
group_list.push_back(group);
if (!group_timer.Enabled())
group_timer.Start();
#if EQDEBUG >= 5
CheckGroupList(__FILE__, __LINE__);
#endif
}
void EntityList::AddRaid(Raid *raid)
{
if (raid == nullptr)
return;
uint32 gid = worldserver.NextGroupID();
if (gid == 0) {
LogError("Unable to get new group ID from world server. group is going to be broken");
return;
}
AddRaid(raid, gid);
}
void EntityList::AddRaid(Raid *raid, uint32 gid)
{
raid->SetID(gid);
raid_list.push_back(raid);
if (!raid_timer.Enabled())
raid_timer.Start();
}
void EntityList::AddCorpse(Corpse *corpse, uint32 in_id)
{
if (corpse == 0)
return;
if (in_id == 0xFFFFFFFF)
corpse->SetID(GetFreeID());
else
corpse->SetID(in_id);
corpse->CalcCorpseName();
corpse_list.emplace(std::pair(corpse->GetID(), corpse));
if (!corpse_timer.Enabled())
corpse_timer.Start();
}
void EntityList::AddNPC(NPC *npc, bool send_spawn_packet, bool dont_queue)
{
npc->SetID(GetFreeID());
//If this is not set here we will despawn pets from new AC changes
auto owner_id = npc->GetOwnerID();
if (owner_id) {
auto owner = entity_list.GetMob(owner_id);
if (owner) {
owner->SetPetID(npc->GetID());
}
}
npc_list.emplace(std::pair(npc->GetID(), npc));
mob_list.emplace(std::pair(npc->GetID(), npc));
entity_list.ScanCloseMobs(npc);
if (parse->HasQuestSub(npc->GetNPCTypeID(), EVENT_SPAWN)) {
parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0);
}
const uint32 emote_id = npc->GetEmoteID();
if (emote_id) {
npc->DoNPCEmote(EQ::constants::EmoteEventTypes::OnSpawn, emote_id);
}
npc->SetSpawned();
if (send_spawn_packet) {
if (dont_queue) {
auto app = new EQApplicationPacket;
npc->CreateSpawnPacket(app, npc);
QueueClients(npc, app);
npc->SendArmorAppearance();
npc->SetAppearance(npc->GetGuardPointAnim(), false);
if (!npc->IsTargetable()) {
npc->SendTargetable(false);
}
safe_delete(app);
} else {
auto ns = new NewSpawn_Struct;
memset(ns, 0, sizeof(NewSpawn_Struct));
npc->FillSpawnStruct(ns, nullptr);
AddToSpawnQueue(npc->GetID(), &ns);
safe_delete(ns);
}
if (npc->IsFindable()) {
UpdateFindableNPCState(npc, false);
}
}
npc->SendPositionToClients();
if (parse->HasQuestSub(ZONE_CONTROLLER_NPC_ID, EVENT_SPAWN_ZONE)) {
npc->DispatchZoneControllerEvent(EVENT_SPAWN_ZONE, npc, "", 0, nullptr);
}
if (parse->ZoneHasQuestSub(EVENT_SPAWN_ZONE)) {
std::vector args = { npc };
parse->EventZone(EVENT_SPAWN_ZONE, zone, "", 0, &args);
}
if (zone->HasMap() && zone->HasWaterMap()) {
npc->SetSpawnedInWater(false);
if (zone->watermap->InLiquid(npc->GetPosition())) {
npc->SetSpawnedInWater(true);
}
}
}
void EntityList::AddMerc(Merc *merc, bool SendSpawnPacket, bool dontqueue)
{
if (merc)
{
merc->SetID(GetFreeID());
merc->SetSpawned();
if (SendSpawnPacket)
{
if (dontqueue) {
// Send immediately
auto outapp = new EQApplicationPacket();
merc->CreateSpawnPacket(outapp);
outapp->priority = 6;
QueueClients(merc, outapp, true);
safe_delete(outapp);
} else {
// Queue the packet
auto ns = new NewSpawn_Struct;
memset(ns, 0, sizeof(NewSpawn_Struct));
merc->FillSpawnStruct(ns, 0);
AddToSpawnQueue(merc->GetID(), &ns);
safe_delete(ns);
}
}
merc_list.emplace(std::pair(merc->GetID(), merc));
mob_list.emplace(std::pair(merc->GetID(), merc));
if (parse->MercHasQuestSub(EVENT_SPAWN)) {
parse->EventMerc(EVENT_SPAWN, merc, nullptr, "", 0);
}
}
}
void EntityList::AddObject(Object *obj, bool SendSpawnPacket)
{
obj->SetID(GetFreeID());
if (SendSpawnPacket) {
EQApplicationPacket app;
obj->CreateSpawnPacket(&app);
#if (EQDEBUG >= 6)
DumpPacket(&app);
#endif
QueueClients(0, &app,false);
}
object_list.emplace(std::pair(obj->GetID(), obj));
if (!object_timer.Enabled())
object_timer.Start();
}
void EntityList::AddDoor(Doors *door)
{
door->SetEntityID(GetFreeID());
door_list.emplace(std::pair(door->GetEntityID(), door));
if (!door_timer.Enabled())
door_timer.Start();
}
void EntityList::AddTrap(Trap *trap)
{
trap->SetID(GetFreeID());
trap_list.emplace(std::pair(trap->GetID(), trap));
if (!trap_timer.Enabled())
trap_timer.Start();
}
void EntityList::AddBeacon(Beacon *beacon)
{
beacon->SetID(GetFreeID());
beacon_list.emplace(std::pair(beacon->GetID(), beacon));
}
void EntityList::AddEncounter(Encounter *encounter)
{
encounter->SetID(GetFreeID());
encounter_list.emplace(std::pair(encounter->GetID(), encounter));
}
void EntityList::AddToSpawnQueue(uint16 entityid, NewSpawn_Struct **ns)
{
uint32 count;
if ((count = (client_list.size())) == 0)
return;
SpawnQueue.Append(*ns);
NumSpawnsOnQueue++;
if (tsFirstSpawnOnQueue == 0xFFFFFFFF)
tsFirstSpawnOnQueue = Timer::GetCurrentTime();
*ns = nullptr;
}
void EntityList::CheckSpawnQueue()
{
// Send the stuff if the oldest packet on the queue is older than 50ms -Quagmire
if (tsFirstSpawnOnQueue != 0xFFFFFFFF && (Timer::GetCurrentTime() - tsFirstSpawnOnQueue) > 50) {
LinkedListIterator iterator(SpawnQueue);
EQApplicationPacket *outapp = 0;
iterator.Reset();
NewSpawn_Struct *ns;
while(iterator.MoreElements()) {
outapp = new EQApplicationPacket;
ns = iterator.GetData();
Mob::CreateSpawnPacket(outapp, ns);
QueueClients(0, outapp);
auto it = npc_list.find(ns->spawn.spawnId);
if (it == npc_list.end()) {
// We must of despawned, hope that's the reason!
LogError("Error in EntityList::CheckSpawnQueue: Unable to find NPC for spawnId [{}]", ns->spawn.spawnId);
}
else {
NPC *pnpc = it->second;
pnpc->SendArmorAppearance();
pnpc->SetAppearance(pnpc->GetGuardPointAnim(), false);
if (!pnpc->IsTargetable()) {
pnpc->SendTargetable(false);
}
pnpc->SendPositionToClients();
}
safe_delete(outapp);
iterator.RemoveCurrent();
}
tsFirstSpawnOnQueue = 0xFFFFFFFF;
NumSpawnsOnQueue = 0;
}
}
Doors *EntityList::FindDoor(uint8 door_id)
{
if (door_id == 0 || door_list.empty())
return nullptr;
auto it = door_list.begin();
while (it != door_list.end()) {
if (it->second->GetDoorID() == door_id)
return it->second;
++it;
}
return nullptr;
}
Object *EntityList::FindObject(uint32 object_id)
{
if (object_id == 0 || object_list.empty())
return nullptr;
auto it = object_list.begin();
while (it != object_list.end()) {
if (it->second->GetDBID() == object_id)
return it->second;
++it;
}
return nullptr;
}
Object *EntityList::FindNearbyObject(float x, float y, float z, float radius)
{
if (object_list.empty())
return nullptr;
float ox;
float oy;
float oz;
auto it = object_list.begin();
while (it != object_list.end()) {
Object *object = it->second;
object->GetLocation(&ox, &oy, &oz);
ox = (x < ox) ? (ox - x) : (x - ox);
oy = (y < oy) ? (oy - y) : (y - oy);
oz = (z < oz) ? (oz - z) : (z - oz);
if ((ox <= radius) && (oy <= radius) && (oz <= radius))
return object;
++it;
}
return nullptr;
}
bool EntityList::MakeDoorSpawnPacket(EQApplicationPacket *app, Client *client)
{
if (door_list.empty())
return false;
uint32 mask_test = client->ClientVersionBit();
int count = 0;
auto it = door_list.begin();
while (it != door_list.end()) {
if ((it->second->GetClientVersionMask() & mask_test) &&
strlen(it->second->GetDoorName()) > 3)
count++;
++it;
}
if (count == 0 || count > 500)
return false;
uint32 length = count * sizeof(Door_Struct);
auto packet_buffer = new uchar[length];
memset(packet_buffer, 0, length);
uchar *ptr = packet_buffer;
Doors *door;
Door_Struct new_door;
it = door_list.begin();
while (it != door_list.end()) {
door = it->second;
if (door && (door->GetClientVersionMask() & mask_test) &&
strlen(door->GetDoorName()) > 3) {
memset(&new_door, 0, sizeof(new_door));
memcpy(new_door.name, door->GetDoorName(), 32);
auto position = door->GetPosition();
new_door.xPos = position.x;
new_door.yPos = position.y;
new_door.zPos = position.z;
new_door.heading = position.w;
new_door.incline = door->GetIncline();
new_door.size = door->GetSize();
new_door.doorId = door->GetDoorID();
new_door.opentype = door->GetOpenType();
Log(Logs::General, Logs::Doors, "Door timer_disable: %s door_id: %u is_open: %s invert_state: %i",
(door->GetDisableTimer() ? "true" : "false"),
door->GetDoorID(),
(door->IsDoorOpen() ? "true" : "false"),
door->GetInvertState()
);
new_door.state_at_spawn = (door->GetInvertState() ? !door->IsDoorOpen() : door->IsDoorOpen());
new_door.invert_state = door->GetInvertState();
new_door.door_param = door->GetDoorParam();
memcpy(ptr, &new_door, sizeof(new_door));
ptr += sizeof(new_door);
*(ptr - 1) = 0x01;
*(ptr - 3) = 0x01;
}
++it;
}
app->SetOpcode(OP_SpawnDoor);
app->size = length;
app->pBuffer = packet_buffer;
return true;
}
Entity *EntityList::GetEntityMob(uint16 id)
{
auto it = mob_list.find(id);
if (it != mob_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityMerc(uint16 id)
{
auto it = merc_list.find(id);
if (it != merc_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityMob(const char *name)
{
if (name == 0 || mob_list.empty())
return 0;
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (strcasecmp(it->second->GetName(), name) == 0)
return it->second;
++it;
}
return nullptr;
}
Entity *EntityList::GetEntityDoor(uint16 id)
{
auto it = door_list.find(id);
if (it != door_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityCorpse(uint16 id)
{
auto it = corpse_list.find(id);
if (it != corpse_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityCorpse(const char *name)
{
if (name == 0 || corpse_list.empty())
return nullptr;
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (strcasecmp(it->second->GetName(), name) == 0)
return it->second;
++it;
}
return nullptr;
}
Entity *EntityList::GetEntityTrap(uint16 id)
{
auto it = trap_list.find(id);
if (it != trap_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityObject(uint16 id)
{
auto it = object_list.find(id);
if (it != object_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityBeacon(uint16 id)
{
auto it = beacon_list.find(id);
if (it != beacon_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetEntityEncounter(uint16 id)
{
auto it = encounter_list.find(id);
if (it != encounter_list.end())
return it->second;
return nullptr;
}
Entity *EntityList::GetID(uint16 get_id)
{
Entity *ent = 0;
if ((ent = entity_list.GetEntityMob(get_id)) != 0)
return ent;
else if ((ent=entity_list.GetEntityDoor(get_id)) != 0)
return ent;
else if ((ent=entity_list.GetEntityCorpse(get_id)) != 0)
return ent;
else if ((ent=entity_list.GetEntityObject(get_id)) != 0)
return ent;
else if ((ent=entity_list.GetEntityTrap(get_id)) != 0)
return ent;
else if ((ent=entity_list.GetEntityBeacon(get_id)) != 0)
return ent;
else if ((ent = entity_list.GetEntityEncounter(get_id)) != 0)
return ent;
else
return 0;
}
NPC *EntityList::GetNPCByNPCTypeID(uint32 npc_id)
{
if (npc_id == 0 || npc_list.empty())
return nullptr;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->GetNPCTypeID() == npc_id)
return it->second;
++it;
}
return nullptr;
}
NPC *EntityList::GetNPCBySpawnID(uint32 spawn_id)
{
if (spawn_id == 0 || npc_list.empty()) {
return nullptr;
}
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->GetSpawnGroupId() == spawn_id) {
return it->second;
}
++it;
}
return nullptr;
}
Mob *EntityList::GetMob(uint16 get_id)
{
Entity *ent = nullptr;
if (get_id == 0)
return nullptr;
if ((ent = entity_list.GetEntityMob(get_id)))
return ent->CastToMob();
else if ((ent = entity_list.GetEntityCorpse(get_id)))
return ent->CastToMob();
return nullptr;
}
Mob *EntityList::GetMob(const char *name)
{
Entity* ent = nullptr;
if (name == 0)
return nullptr;
if ((ent = entity_list.GetEntityMob(name)))
return ent->CastToMob();
else if ((ent = entity_list.GetEntityCorpse(name)))
return ent->CastToMob();
return nullptr;
}
Mob *EntityList::GetMobByNpcTypeID(uint32 get_id)
{
if (get_id == 0 || mob_list.empty())
return 0;
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (it->second->GetNPCTypeID() == get_id)
return it->second;
++it;
}
return nullptr;
}
bool EntityList::IsMobSpawnedByNpcTypeID(uint32 get_id)
{
if (get_id == 0 || npc_list.empty())
return false;
auto it = npc_list.begin();
while (it != npc_list.end()) {
// Mobs will have a 0 as their GetID() if they're dead
if (it->second->GetNPCTypeID() == get_id && it->second->GetID() != 0)
return true;
++it;
}
return false;
}
bool EntityList::IsNPCSpawned(std::vector npc_ids)
{
return CountSpawnedNPCs(npc_ids) != 0;
}
uint32 EntityList::CountSpawnedNPCs(std::vector npc_ids)
{
uint32 npc_count = 0;
if (npc_list.empty() || npc_ids.empty()) {
return npc_count;
}
for (auto current_npc : npc_list) {
if (
std::find(
npc_ids.begin(),
npc_ids.end(),
current_npc.second->GetNPCTypeID()
) != npc_ids.end() &&
current_npc.second->GetID() != 0
) {
npc_count++;
}
}
return npc_count;
}
Object *EntityList::GetObjectByDBID(uint32 id)
{
if (id == 0 || object_list.empty())
return nullptr;
auto it = object_list.begin();
while (it != object_list.end()) {
if (it->second->GetDBID() == id)
return it->second;
++it;
}
return nullptr;
}
Doors *EntityList::GetDoorsByDBID(uint32 id)
{
if (id == 0 || door_list.empty())
return nullptr;
auto it = door_list.begin();
while (it != door_list.end()) {
if (it->second->GetDoorDBID() == id)
return it->second;
++it;
}
return nullptr;
}
Doors *EntityList::GetDoorsByDoorID(uint32 id)
{
if (id == 0 || door_list.empty())
return nullptr;
auto it = door_list.begin();
while (it != door_list.end()) {
if (it->second->CastToDoors()->GetDoorID() == id)
return it->second;
++it;
}
return nullptr;
}
uint16 EntityList::GetFreeID()
{
if (free_ids.empty()) { // hopefully this will never be true
// The client has a hard cap on entity count some where
// Neither the client or server performs well with a lot entities either
uint16 newid = 1500;
while (true) {
newid++;
if (GetID(newid) == nullptr)
return newid;
}
}
uint16 newid = free_ids.front();
free_ids.pop();
return newid;
}
// if no language skill is specified, sent with 100 skill
void EntityList::ChannelMessage(Mob *from, uint8 chan_num, uint8 language, const char *message, ...)
{
ChannelMessage(from, chan_num, language, Language::MaxValue, message);
}
void EntityList::ChannelMessage(Mob *from, uint8 chan_num, uint8 language,
uint8 lang_skill, const char *message, ...)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4096, message, argptr);
va_end(argptr);
auto it = client_list.begin();
while(it != client_list.end()) {
Client *client = it->second;
eqFilterType filter = FilterNone;
if (chan_num == ChatChannel_Shout) //shout
filter = FilterShouts;
else if (chan_num == ChatChannel_Auction) //auction
filter = FilterAuctions;
//
// Only say is limited in range
if (chan_num != ChatChannel_Say || Distance(client->GetPosition(), from->GetPosition()) < 200)
if (filter == FilterNone || client->GetFilter(filter) != FilterHide)
client->ChannelMessageSend(from->GetName(), 0, chan_num, language, lang_skill, buffer);
++it;
}
}
void EntityList::SendZoneSpawns(Client *client)
{
EQApplicationPacket *app;
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *ent = it->second;
if (!ent->InZone() || !ent->ShouldISpawnFor(client)) {
++it;
continue;
}
app = new EQApplicationPacket;
it->second->CastToMob()->CreateSpawnPacket(app); // TODO: Use zonespawns opcode instead
client->QueuePacket(app, true, Client::CLIENT_CONNECTED);
safe_delete(app);
++it;
}
}
void EntityList::SendZoneSpawnsBulk(Client *client)
{
NewSpawn_Struct ns{};
Mob *spawn;
EQApplicationPacket *app;
uint32 max_spawns = 100;
if (max_spawns > mob_list.size()) {
max_spawns = static_cast(mob_list.size());
}
auto bulk_zone_spawn_packet = new BulkZoneSpawnPacket(client, max_spawns);
const glm::vec4 &client_position = client->GetPosition();
const float distance_max = (600.0 * 600.0);
for (auto & it : mob_list) {
spawn = it.second;
if (spawn && spawn->GetID() > 0 && spawn->Spawned()) {
if (!spawn->ShouldISpawnFor(client)) {
continue;
}
const glm::vec4 &spawn_position = spawn->GetPosition();
bool is_delayed_packet = (
DistanceSquared(client_position, spawn_position) > distance_max ||
(spawn->IsClient() && (spawn->GetRace() == Race::MinorIllusion || spawn->GetRace() == Race::Tree))
);
if (is_delayed_packet) {
app = new EQApplicationPacket;
spawn->CreateSpawnPacket(app);
client->QueuePacket(app, true, Client::CLIENT_CONNECTED);
safe_delete(app);
}
else {
memset(&ns, 0, sizeof(NewSpawn_Struct));
spawn->FillSpawnStruct(&ns, client);
bulk_zone_spawn_packet->AddSpawn(&ns);
}
spawn->SendArmorAppearance(client);
/**
* Original code kept for spawn packet research
*
* int32 race = spawn->GetRace();
*
* Illusion races on PCs don't work as a mass spawn
* But they will work as an add_spawn AFTER CLIENT_CONNECTED.
* if (spawn->IsClient() && (race == Race::MinorIllusion || race == Race::Tree)) {
* app = new EQApplicationPacket;
* spawn->CreateSpawnPacket(app);
* client->QueuePacket(app, true, Client::CLIENT_CONNECTED);
* safe_delete(app);
* }
* else {
* memset(&ns, 0, sizeof(NewSpawn_Struct));
* spawn->FillSpawnStruct(&ns, client);
* bzsp->AddSpawn(&ns);
* }
*
* Despite being sent in the OP_ZoneSpawns packet, the client
* does not display worn armor correctly so display it.
* spawn->SendArmorAppearance(client);
*/
}
}
safe_delete(bulk_zone_spawn_packet);
}
//this is a hack to handle a broken spawn struct
void EntityList::SendZonePVPUpdates(Client *to)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
if(c->GetPVP())
c->SendAppearancePacket(AppearanceType::PVP, c->GetPVP(), true, false, to);
++it;
}
}
void EntityList::SendZoneCorpses(Client *client)
{
EQApplicationPacket *app;
for (auto it = corpse_list.begin(); it != corpse_list.end(); ++it) {
Corpse *ent = it->second;
app = new EQApplicationPacket;
ent->CreateSpawnPacket(app);
client->QueuePacket(app, true, Client::CLIENT_CONNECTED);
safe_delete(app);
}
}
void EntityList::SendZoneCorpsesBulk(Client *client)
{
NewSpawn_Struct ns;
Corpse *spawn;
uint32 maxspawns = 100;
auto bzsp = new BulkZoneSpawnPacket(client, maxspawns);
for (auto it = corpse_list.begin(); it != corpse_list.end(); ++it) {
spawn = it->second;
if (spawn && spawn->InZone()) {
memset(&ns, 0, sizeof(NewSpawn_Struct));
spawn->FillSpawnStruct(&ns, client);
bzsp->AddSpawn(&ns);
}
}
safe_delete(bzsp);
}
void EntityList::SendZoneObjects(Client *client)
{
auto it = object_list.begin();
while (it != object_list.end()) {
auto app = new EQApplicationPacket;
it->second->CreateSpawnPacket(app);
client->FastQueuePacket(&app);
++it;
}
}
void EntityList::Save()
{
auto it = client_list.begin();
while (it != client_list.end()) {
it->second->Save();
++it;
}
}
void EntityList::ReplaceWithTarget(Mob *pOldMob, Mob *pNewTarget)
{
if (!pNewTarget)
return;
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (it->second->IsAIControlled()) {
// replace the old mob with the new one
if (it->second->RemoveFromHateList(pOldMob))
it->second->AddToHateList(pNewTarget, 1, 0);
}
++it;
}
}
void EntityList::RemoveFromTargets(Mob *mob, bool RemoveFromXTargets)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *m = it->second;
++it;
if (!m) {
continue;
}
if (RemoveFromXTargets && mob) {
if (m->IsClient() && (mob->CheckAggro(m) || mob->IsOnFeignMemory(m))) {
m->CastToClient()->RemoveXTarget(mob, false);
// FadingMemories calls this function passing the client.
} else if (mob->IsClient() && (m->CheckAggro(mob) || m->IsOnFeignMemory(mob))) {
mob->CastToClient()->RemoveXTarget(m, false);
}
}
m->RemoveFromHateList(mob);
m->RemoveFromRampageList(mob);
}
}
void EntityList::RemoveFromTargetsFadingMemories(Mob *spell_target, bool RemoveFromXTargets, uint32 max_level)
{
for (auto &e : mob_list) {
auto &mob = e.second;
if (!mob) {
continue;
}
if (max_level && mob->GetLevel() > max_level) {
continue;
}
if (mob->GetSpecialAbility(SpecialAbility::MemoryFadeImmunity)) {
continue;
}
if (RemoveFromXTargets && spell_target) {
if (mob->IsClient() && (spell_target->CheckAggro(mob) || spell_target->IsOnFeignMemory(mob))) {
mob->CastToClient()->RemoveXTarget(spell_target, false);
} else if (spell_target->IsClient() && (mob->CheckAggro(spell_target) || mob->IsOnFeignMemory(spell_target))) {
spell_target->CastToClient()->RemoveXTarget(mob, false);
}
}
mob->RemoveFromHateList(spell_target);
mob->RemoveFromRampageList(spell_target);
}
}
void EntityList::RemoveFromXTargets(Mob *mob)
{
auto it = client_list.begin();
while (it != client_list.end()) {
it->second->RemoveXTarget(mob, false);
++it;
}
}
void EntityList::RemoveFromAutoXTargets(Mob *mob)
{
auto it = client_list.begin();
while (it != client_list.end()) {
it->second->RemoveXTarget(mob, true);
++it;
}
}
void EntityList::RefreshAutoXTargets(Client *c)
{
if (!c) {
return;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *m = it->second;
++it;
if (!m || m->GetHP() <= 0) {
continue;
}
if ((m->CheckAggro(c) || m->IsOnFeignMemory(c)) && !c->IsXTarget(m)) {
c->AddAutoXTarget(m, false); // we only call this before a bulk, so lets not send right away
break;
}
}
}
void EntityList::RefreshClientXTargets(Client *c)
{
if (!c) {
return;
}
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c2 = it->second;
++it;
if (!c2) {
continue;
}
if (c2->IsClientXTarget(c)) {
c2->UpdateClientXTarget(c);
}
}
}
void EntityList::QueueClientsByTarget(Mob *sender, const EQApplicationPacket *app,
bool iSendToSender, Mob *SkipThisMob, bool ackreq, bool HoTT, uint32 ClientVersionBits, bool inspect_buffs, bool clear_target_window)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
++it;
Mob *Target = c->GetTarget();
if (!Target)
continue;
Mob *TargetsTarget = nullptr;
TargetsTarget = Target->GetTarget();
bool Send = false;
if (c == SkipThisMob)
continue;
if (iSendToSender)
if (c == sender)
Send = true;
if (c != sender) {
if (Target == sender) {
if (inspect_buffs) { // if inspect_buffs is true we're sending a mob's buffs to those with the LAA
Send = clear_target_window;
if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) {
if (c->GetGM()) {
if (!c->EntityVariableExists(SEE_BUFFS_FLAG)) {
c->Message(Chat::White, "Your GM flag allows you to always see your targets' buffs.");
c->SetEntityVariable(SEE_BUFFS_FLAG, "1");
}
}
Send = !clear_target_window;
} else if (c->IsRaidGrouped()) {
Raid *raid = c->GetRaid();
if (raid) {
uint32 gid = raid->GetGroup(c);
if (gid < MAX_RAID_GROUPS && raid->GroupCount(gid) >= 3) {
if (raid->GetLeadershipAA(groupAAInspectBuffs, gid))
Send = !clear_target_window;
}
}
} else {
Group *group = c->GetGroup();
if (group && group->GroupCount() >= 3) {
if (group->GetLeadershipAA(groupAAInspectBuffs)) {
Send = !clear_target_window;
}
}
}
} else {
Send = true;
}
} else if (HoTT && TargetsTarget == sender) {
Send = true;
}
}
if (Send && (c->ClientVersionBit() & ClientVersionBits)) {
c->QueuePacket(app, ackreq);
}
}
}
void EntityList::QueueClientsByXTarget(Mob *sender, const EQApplicationPacket *app, bool iSendToSender, EQ::versions::ClientVersionBitmask client_version_bits)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
++it;
if (!c || ((c == sender) && !iSendToSender))
continue;
if ((c->ClientVersionBit() & client_version_bits) == 0)
continue;
if (!c->IsXTarget(sender))
continue;
c->QueuePacket(app);
}
}
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 (distance <= 0) {
distance = zone->GetClientUpdateRange();
}
float distance_squared = distance * distance;
for (auto &e : sender->GetCloseMobList(distance)) {
Mob *mob = e.second;
if (!mob) {
continue;
}
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);
}
}
}
}
//sender can be null
void EntityList::QueueClients(
Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, bool ackreq
)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *ent = it->second;
if ((!ignore_sender || ent != sender))
ent->QueuePacket(app, ackreq, Client::CLIENT_CONNECTED);
++it;
}
}
void EntityList::QueueClientsStatus(Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, uint8 minstatus, uint8 maxstatus)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if ((!ignore_sender || it->second != sender) &&
(it->second->Admin() >= minstatus && it->second->Admin() <= maxstatus))
it->second->QueuePacket(app);
++it;
}
}
void EntityList::DuelMessage(Mob *winner, Mob *loser, bool flee)
{
if (winner->GetLevelCon(winner->GetLevel(), loser->GetLevel()) > 2) {
if (parse->PlayerHasQuestSub(EVENT_DUEL_WIN)) {
std::vector args = { winner, loser };
parse->EventPlayer(EVENT_DUEL_WIN, winner->CastToClient(), loser->GetName(), loser->CastToClient()->CharacterID(), &args);
}
if (parse->PlayerHasQuestSub(EVENT_DUEL_LOSE)) {
std::vector args = { winner, loser };
parse->EventPlayer(EVENT_DUEL_LOSE, loser->CastToClient(), winner->GetName(), winner->CastToClient()->CharacterID(), &args);
}
}
auto it = client_list.begin();
while (it != client_list.end()) {
Client *cur = it->second;
//might want some sort of distance check in here?
if (cur != winner && cur != loser) {
if (flee)
cur->MessageString(Chat::Yellow, DUEL_FLED, winner->GetName(),loser->GetName(),loser->GetName());
else
cur->MessageString(Chat::Yellow, DUEL_FINISHED, winner->GetName(),loser->GetName());
}
++it;
}
}
Client *EntityList::GetClientByName(const char* name)
{
for (const auto& e : client_list) {
if (e.second && Strings::EqualFold(e.second->GetName(), name)) {
return e.second;
}
}
return nullptr;
}
Client *EntityList::GetClientByCharID(uint32 iCharID)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->CharacterID() == iCharID)
return it->second;
++it;
}
return nullptr;
}
Client *EntityList::GetClientByWID(uint32 iWID)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->GetWID() == iWID) {
return it->second;
}
++it;
}
return nullptr;
}
Client *EntityList::GetClientByLSID(uint32 iLSID)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->LSAccountID() == iLSID) {
return it->second;
}
++it;
}
return nullptr;
}
Bot* EntityList::GetRandomBot(const glm::vec3& location, float distance, Bot* exclude_bot)
{
auto is_whole_zone = false;
if (location.x == 0.0f && location.y == 0.0f) {
is_whole_zone = true;
}
auto distance_squared = (distance * distance);
std::vector bots_in_range;
for (const auto& b : bot_list) {
if (
b.second != exclude_bot &&
(
is_whole_zone ||
DistanceSquared(static_cast(b.second->GetPosition()), location) <= distance_squared
)
) {
bots_in_range.push_back(b.second);
}
}
if (bots_in_range.empty()) {
return nullptr;
}
return bots_in_range[zone->random.Int(0, bots_in_range.size() - 1)];
}
Client *EntityList::GetRandomClient(const glm::vec3& location, float distance, Client *exclude_client)
{
auto is_whole_zone = false;
if (location.x == 0.0f && location.y == 0.0f) {
is_whole_zone = true;
}
auto distance_squared = (distance * distance);
std::vector clients_in_range;
for (const auto& client : client_list) {
if (
client.second != exclude_client &&
(
is_whole_zone ||
DistanceSquared(static_cast(client.second->GetPosition()), location) <= distance_squared
)
) {
clients_in_range.push_back(client.second);
}
}
if (clients_in_range.empty()) {
return nullptr;
}
return clients_in_range[zone->random.Int(0, clients_in_range.size() - 1)];
}
NPC* EntityList::GetRandomNPC(const glm::vec3& location, float distance, NPC* exclude_npc)
{
auto is_whole_zone = false;
if (location.x == 0.0f && location.y == 0.0f) {
is_whole_zone = true;
}
auto distance_squared = (distance * distance);
std::vector npcs_in_range;
for (const auto& npc : npc_list) {
if (
npc.second != exclude_npc &&
(
is_whole_zone ||
DistanceSquared(static_cast(npc.second->GetPosition()), location) <= distance_squared
)
) {
npcs_in_range.push_back(npc.second);
}
}
if (npcs_in_range.empty()) {
return nullptr;
}
return npcs_in_range[zone->random.Int(0, npcs_in_range.size() - 1)];
}
Mob* EntityList::GetRandomMob(const glm::vec3& location, float distance, Mob* exclude_mob)
{
auto is_whole_zone = false;
if (location.x == 0.0f && location.y == 0.0f) {
is_whole_zone = true;
}
auto distance_squared = (distance * distance);
std::vector mobs_in_range;
for (const auto& mob : mob_list) {
if (
mob.second != exclude_mob &&
(
is_whole_zone ||
DistanceSquared(static_cast(mob.second->GetPosition()), location) <= distance_squared
)
) {
mobs_in_range.push_back(mob.second);
}
}
if (mobs_in_range.empty()) {
return nullptr;
}
return mobs_in_range[zone->random.Int(0, mobs_in_range.size() - 1)];
}
Corpse *EntityList::GetCorpseByOwner(Client *client)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->IsPlayerCorpse())
if (strcasecmp(it->second->GetOwnerName(), client->GetName()) == 0)
return it->second;
++it;
}
return nullptr;
}
Corpse *EntityList::GetCorpseByOwnerWithinRange(Client *client, Mob *center, int range)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->IsPlayerCorpse())
if (DistanceSquaredNoZ(center->GetPosition(), it->second->GetPosition()) < range &&
strcasecmp(it->second->GetOwnerName(), client->GetName()) == 0)
return it->second;
++it;
}
return nullptr;
}
Corpse *EntityList::GetCorpseByDBID(uint32 dbid)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->GetCorpseDBID() == dbid)
return it->second;
++it;
}
return nullptr;
}
Corpse *EntityList::GetCorpseByName(const char *name)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (strcmp(it->second->GetName(), name) == 0)
return it->second;
++it;
}
return nullptr;
}
Spawn2 *EntityList::GetSpawnByID(uint32 id)
{
if (!zone || !zone->IsLoaded())
return nullptr;
LinkedListIterator iterator(zone->spawn2_list);
iterator.Reset();
while(iterator.MoreElements())
{
if(iterator.GetData()->GetID() == id) {
return iterator.GetData();
}
iterator.Advance();
}
return nullptr;
}
void EntityList::RemoveAllCorpsesByCharID(uint32 charid)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->GetCharID() == charid) {
safe_delete(it->second);
free_ids.push(it->first);
it = corpse_list.erase(it);
} else {
++it;
}
}
}
void EntityList::RemoveCorpseByDBID(uint32 dbid)
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->GetCorpseDBID() == dbid) {
safe_delete(it->second);
free_ids.push(it->first);
it = corpse_list.erase(it);
} else {
++it;
}
}
}
int EntityList::RezzAllCorpsesByCharID(uint32 charid)
{
int RezzExp = 0;
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
if (it->second->GetCharID() == charid) {
RezzExp += it->second->GetRezExp();
it->second->IsRezzed(true);
it->second->CompleteResurrection();
}
++it;
}
return RezzExp;
}
Group *EntityList::GetGroupByMob(Mob *mob)
{
std::list::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->IsGroupMember(mob))
return *iterator;
++iterator;
}
return nullptr;
}
Group *EntityList::GetGroupByMobName(const char* name)
{
for (const auto& g : group_list) {
for (const auto& m : g->membername) {
if (strcmp(m, name) == 0) {
return g;
}
}
}
return nullptr;
}
Group *EntityList::GetGroupByLeaderName(const char *leader)
{
std::list::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if (!strcmp((*iterator)->GetLeaderName().c_str(), leader))
return *iterator;
++iterator;
}
return nullptr;
}
Group *EntityList::GetGroupByID(uint32 group_id)
{
std::list::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->GetID() == group_id)
return *iterator;
++iterator;
}
return nullptr;
}
bool EntityList::IsInSameGroupOrRaidGroup(Client *client1, Client *client2) {
Group* group = entity_list.GetGroupByClient(client1);
Raid* raid = entity_list.GetRaidByClient(client1);
return (group && group->IsGroupMember(client2))
|| (raid && raid->IsRaidMember(client2->GetName()) && raid->GetGroup(client1) == raid->GetGroup(client2));
}
Group *EntityList::GetGroupByClient(Client *client)
{
std::list ::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->IsGroupMember(client->CastToMob())) {
return *iterator;
}
++iterator;
}
return nullptr;
}
Raid *EntityList::GetRaidByID(uint32 id)
{
std::list::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
if ((*iterator)->GetID() == id)
return *iterator;
++iterator;
}
return nullptr;
}
Raid* EntityList::GetRaidByClient(Client* client)
{
if (client->p_raid_instance) {
return client->p_raid_instance;
}
std::list::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
for (const auto& member : (*iterator)->members) {
if (member.member && member.member == client) {
client->p_raid_instance = *iterator;
return *iterator;
}
}
++iterator;
}
return nullptr;
}
Raid* EntityList::GetRaidByBotName(const char* name)
{
for (const auto& r : raid_list) {
for (const auto& m : r->members) {
if (m.is_bot && strcmp(m.member_name, name) == 0) {
return r;
}
}
}
return nullptr;
}
Raid* EntityList::GetRaidByBot(Bot* bot)
{
if (bot->p_raid_instance) {
return bot->p_raid_instance;
}
std::list::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
for (const auto& member : (*iterator)->members) {
if (member.member && member.is_bot && member.member->CastToBot() == bot) {
bot->p_raid_instance = *iterator;
return *iterator;
}
}
++iterator;
}
return nullptr;
}
Raid* EntityList::GetRaidByName(const char* name)
{
for (const auto& r : raid_list) {
for (const auto& m : r->members) {
if (Strings::EqualFold(m.member_name, name)) {
return r;
}
}
}
return nullptr;
}
Client *EntityList::GetClientByAccID(uint32 accid)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->AccountID() == accid)
return it->second;
++it;
}
return nullptr;
}
void EntityList::ChannelMessageFromWorld(const char *from, const char *to,
uint8 chan_num, uint32 guild_id, uint8 language, uint8 lang_skill, const char *message)
{
for (auto it = client_list.begin(); it != client_list.end(); ++it) {
Client *client = it->second;
if (chan_num == ChatChannel_Guild) {
if (!client->IsInGuild(guild_id))
continue;
if (client->ClientVersion() >= EQ::versions::ClientVersion::RoF) {
if (!guild_mgr.CheckPermission(guild_id, client->GuildRank(), GUILD_ACTION_GUILD_CHAT_SEE))
continue;
}
if (client->GetFilter(FilterGuildChat) == FilterHide)
continue;
} else if (chan_num == ChatChannel_OOC) {
if (client->GetFilter(FilterOOC) == FilterHide)
continue;
}
client->ChannelMessageSend(from, to, chan_num, language, lang_skill, message);
}
}
void EntityList::Message(uint32 to_guilddbid, uint32 type, const char *message, ...)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4096, message, argptr);
va_end(argptr);
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if (to_guilddbid == 0 || client->IsInGuild(to_guilddbid))
client->Message(type, buffer);
++it;
}
}
void EntityList::QueueClientsGuild(const EQApplicationPacket *app, uint32 guild_id)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if (client->IsInGuild(guild_id))
client->QueuePacket(app, true);
++it;
}
}
void EntityList::QueueClientsGuildBankItemUpdate(GuildBankItemUpdate_Struct *gbius, uint32 guild_id)
{
auto outapp = std::make_unique(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct));
auto data = reinterpret_cast(outapp->pBuffer);
memcpy(data, gbius, sizeof(GuildBankItemUpdate_Struct));
for (auto const &[key, client]: client_list) {
if (client->IsInGuild(guild_id)) {
client->QueuePacket(outapp.get());
}
}
}
void EntityList::MessageStatus(uint32 to_guild_id, int to_minstatus, uint32 type, const char *message, ...)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4096, message, argptr);
va_end(argptr);
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if ((to_guild_id == 0 || client->IsInGuild(to_guild_id)) && client->Admin() >= to_minstatus) {
client->Message(type, buffer);
}
++it;
}
}
/**
* @param sender
* @param skipsender
* @param dist
* @param type
* @param string_id
* @param message1
* @param message2
* @param message3
* @param message4
* @param message5
* @param message6
* @param message7
* @param message8
* @param message9
*/
void EntityList::MessageCloseString(
Mob *sender,
bool skipsender,
float dist,
uint32 type,
uint32 string_id,
const char *message1,
const char *message2,
const char *message3,
const char *message4,
const char *message5,
const char *message6,
const char *message7,
const char *message8,
const char *message9
)
{
Client *c;
float dist2 = dist * dist;
for (auto & it : client_list) {
c = it.second;
if (c && DistanceSquared(c->GetPosition(), sender->GetPosition()) <= dist2 && (!skipsender || c != sender)) {
c->MessageString(
type,
string_id,
message1,
message2,
message3,
message4,
message5,
message6,
message7,
message8,
message9
);
}
}
}
/**
* @param sender
* @param skipsender
* @param dist
* @param type
* @param filter
* @param string_id
* @param message1
* @param message2
* @param message3
* @param message4
* @param message5
* @param message6
* @param message7
* @param message8
* @param message9
*/
void EntityList::FilteredMessageCloseString(
Mob *sender, bool skipsender,
float dist,
uint32 type,
eqFilterType filter,
uint32 string_id,
Mob *skip,
const char *message1,
const char *message2,
const char *message3,
const char *message4,
const char *message5,
const char *message6,
const char *message7,
const char *message8,
const char *message9
)
{
Client *c;
float dist2 = dist * dist;
for (auto & it : client_list) {
c = it.second;
if (c && c != skip && DistanceSquared(c->GetPosition(), sender->GetPosition()) <= dist2 && (!skipsender || c != sender)) {
c->FilteredMessageString(
sender, type, filter, string_id,
message1, message2, message3, message4, message5,
message6, message7, message8, message9
);
}
}
}
/**
*
* @param sender
* @param skipsender
* @param type
* @param string_id
* @param message1
* @param message2
* @param message3
* @param message4
* @param message5
* @param message6
* @param message7
* @param message8
* @param message9
*/
void EntityList::MessageString(
Mob *sender,
bool skipsender,
uint32 type,
uint32 string_id,
const char *message1,
const char *message2,
const char *message3,
const char *message4,
const char *message5,
const char *message6,
const char *message7,
const char *message8,
const char *message9
)
{
Client *c;
for (auto & it : client_list) {
c = it.second;
if (c && (!skipsender || c != sender)) {
c->MessageString(
type,
string_id,
message1,
message2,
message3,
message4,
message5,
message6,
message7,
message8,
message9
);
}
}
}
/**
*
* @param sender
* @param skipsender
* @param type
* @param filter
* @param string_id
* @param message1
* @param message2
* @param message3
* @param message4
* @param message5
* @param message6
* @param message7
* @param message8
* @param message9
*/
void EntityList::FilteredMessageString(
Mob *sender,
bool skipsender,
uint32 type,
eqFilterType filter,
uint32 string_id,
const char *message1,
const char *message2,
const char *message3,
const char *message4,
const char *message5,
const char *message6,
const char *message7,
const char *message8,
const char *message9
)
{
Client *c;
for (auto & it : client_list) {
c = it.second;
if (c && (!skipsender || c != sender)) {
c->FilteredMessageString(
sender,
type,
filter,
string_id,
message1,
message2,
message3,
message4,
message5,
message6,
message7,
message8,
message9
);
}
}
}
/**
* @param sender
* @param skipsender
* @param dist
* @param type
* @param message
* @param ...
*/
void EntityList::MessageClose(Mob *sender, bool skipsender, float dist, uint32 type, const char *message, ...)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4095, message, argptr);
va_end(argptr);
float dist2 = dist * dist;
auto it = client_list.begin();
while (it != client_list.end()) {
if (DistanceSquared(it->second->GetPosition(), sender->GetPosition()) <= dist2 &&
(!skipsender || it->second != sender)) {
it->second->Message(type, buffer);
}
++it;
}
}
void EntityList::FilteredMessageClose(
Mob *sender,
bool skipsender,
float dist,
uint32 type,
eqFilterType filter,
const char *message,
...
)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4095, message, argptr);
va_end(argptr);
float dist2 = dist * dist;
auto it = client_list.begin();
while (it != client_list.end()) {
if (DistanceSquared(it->second->GetPosition(), sender->GetPosition()) <= dist2 &&
(!skipsender || it->second != sender)) {
it->second->FilteredMessage(sender, type, filter, buffer);
}
++it;
}
}
void EntityList::RemoveAllMobs()
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (!it->second) {
++it;
continue;
}
safe_delete(it->second);
free_ids.push(it->first);
it = mob_list.erase(it);
}
}
void EntityList::RemoveAllClients()
{
// doesn't clear the data
client_list.clear();
}
void EntityList::RemoveAllNPCs()
{
// doesn't clear the data
npc_list.clear();
npc_limit_list.clear();
}
void EntityList::RemoveAllBots()
{
// doesn't clear the data
bot_list.clear();
}
void EntityList::RemoveAllMercs()
{
// doesn't clear the data
merc_list.clear();
}
void EntityList::RemoveAllGroups()
{
while (group_list.size()) {
auto group = group_list.front();
group_list.pop_front();
safe_delete(group);
}
}
void EntityList::RemoveAllRaids()
{
while (raid_list.size()) {
auto raid = raid_list.front();
raid_list.pop_front();
safe_delete(raid);
}
}
void EntityList::RemoveAllDoors()
{
auto it = door_list.begin();
while (it != door_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
it = door_list.erase(it);
}
DespawnAllDoors();
}
void EntityList::DespawnAllDoors()
{
auto outapp = new EQApplicationPacket(OP_RemoveAllDoors, 0);
for (auto it = client_list.begin(); it != client_list.end(); ++it) {
if (it->second) {
it->second->QueuePacket(outapp, true, Client::CLIENT_CONNECTED);
}
}
safe_delete(outapp);
}
void EntityList::RespawnAllDoors()
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second) {
auto outapp = new EQApplicationPacket();
MakeDoorSpawnPacket(outapp, it->second);
it->second->FastQueuePacket(&outapp, true, Client::CLIENT_CONNECTED);
}
++it;
}
}
void EntityList::RemoveAllCorpses()
{
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
it = corpse_list.erase(it);
}
}
void EntityList::RemoveAllObjects()
{
auto it = object_list.begin();
while (it != object_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
it = object_list.erase(it);
}
}
void EntityList::RemoveAllTraps()
{
auto it = trap_list.begin();
while (it != trap_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
it = trap_list.erase(it);
}
}
void EntityList::RemoveAllEncounters()
{
auto it = encounter_list.begin();
while (it != encounter_list.end()) {
parse->RemoveEncounter(it->second->GetEncounterName());
safe_delete(it->second);
free_ids.push(it->first);
it = encounter_list.erase(it);
}
}
/**
* @param delete_id
* @return
*/
bool EntityList::RemoveMob(uint16 delete_id)
{
if (delete_id == 0) {
return true;
}
auto it = mob_list.find(delete_id);
if (it != mob_list.end()) {
if (!it->second) {
return false;
}
if (npc_list.count(delete_id)) {
entity_list.RemoveNPC(delete_id);
}
else if (client_list.count(delete_id)) {
entity_list.RemoveClient(delete_id);
}
safe_delete(it->second);
if (!corpse_list.count(delete_id)) {
free_ids.push(it->first);
}
mob_list.erase(it);
return true;
}
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;
RemoveProximity(delete_id);
npc_list.erase(it);
if (npc_limit_list.count(delete_id)) {
npc_limit_list.erase(delete_id);
}
return true;
}
return false;
}
/**
* @param mob
* @return
*/
bool EntityList::RemoveMobFromCloseLists(Mob *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->m_close_mobs.erase(entity_id);
it->second->m_last_seen_mob_position.erase(entity_id);
++it;
}
return false;
}
/**
* @param mob
* @return
*/
void EntityList::RemoveAuraFromMobs(Mob *aura)
{
LogEntityManagement(
"Attempting to remove aura [{}] from mobs entity_id ({})",
aura->GetCleanName(),
aura->GetID()
);
for (auto &it : mob_list) {
auto mob = it.second;
mob->RemoveAura(aura->GetID());
}
}
// The purpose of this system is so that we cache relevant entities that are "close"
//
// In general; it becomes incredibly expensive to run zone-wide checks against every single mob in the zone when in reality
// we only care about entities closest to us
//
// A very simple example of where this is relevant is Aggro, the below example is skewed because the overall implementation
// of Aggro was also tweaked in conjunction with close lists. We also scan more aggressively when entities are moving (1-6 seconds)
// versus 60 seconds when idle. We also have entities that are moving add themselves to those closest to them so that their close
// lists remain always up to date
//
// Before: Aggro checks for NPC to Client aggro | (40 clients in zone) x (525 npcs) x 2 (times a second) = 2,520,000 checks a minute
// After: Aggro checks for NPC to Client aggro | (40 clients in zone) x (20-30 npcs) x 2 (times a second) = 144,000 checks a minute (This is // tually far less today)
//
// Places in the code where this logic makes a huge impact
//
// Aggro checks (zone wide -> close)
// Aura processing (zone wide -> close)
// AE Taunt (zone wide -> close)
// AOE Spells (zone wide -> close)
// Bard Pulse AOE (zone wide -> close)
// Mass Group Buff (zone wide -> close)
// AE Attack (zone wide -> close)
// Packet QueueCloseClients (zone wide -> close)
// Check Close Beneficial Spells (Buffs; should I heal other npcs) (zone wide -> close)
// AI Yell for Help (NPC Assist other NPCs) (zone wide -> close)
//
// All of the above makes a tremendous impact on the bottom line of cpu cycle performance because we run an order of magnitude
// less checks by focusing our hot path logic down to a very small subset of relevant entities instead of looping an entire
// entity list (zone wide)
BenchTimer g_scan_bench_timer;
void EntityList::ScanCloseMobs(Mob *scanning_mob)
{
if (!scanning_mob) {
return;
}
if (scanning_mob->GetID() <= 0 || scanning_mob->IsZoneController()) {
return;
}
g_scan_bench_timer.reset();
float scan_range = RuleI(Range, MobCloseScanDistance);
// Reserve memory in m_close_mobs to avoid frequent re-allocations if not already reserved.
// Assuming mob_list.size() as an upper bound for reservation.
if (scanning_mob->m_close_mobs.bucket_count() < mob_list.size()) {
scanning_mob->m_close_mobs.reserve(mob_list.size());
}
scanning_mob->m_close_mobs.clear();
for (auto &e : mob_list) {
auto mob = e.second;
if (mob && (mob->GetID() <= 0 || mob->IsZoneController())) {
continue;
}
float distance = Distance(scanning_mob->GetPosition(), mob->GetPosition());
if (distance <= scan_range || mob->GetAggroRange() >= scan_range) {
// add mob to scanning_mob's close list and vice versa
// check if the mob is already in the close mobs list before inserting
if (mob->m_close_mobs.find(scanning_mob->GetID()) == mob->m_close_mobs.end()) {
mob->m_close_mobs[scanning_mob->GetID()] = scanning_mob;
}
scanning_mob->m_close_mobs[mob->GetID()] = mob;
}
}
LogAIScanClose(
"[{}] Scanning close list > list_size [{}] moving [{}] elapsed [{}] us",
scanning_mob->GetCleanName(),
scanning_mob->m_close_mobs.size(),
scanning_mob->IsMoving() ? "true" : "false",
g_scan_bench_timer.elapsedMicroseconds()
);
}
bool EntityList::RemoveMerc(uint16 delete_id)
{
auto it = merc_list.find(delete_id);
if (it != merc_list.end()) {
merc_list.erase(it); // Already Deleted
return true;
}
return false;
}
bool EntityList::RemoveClient(uint16 delete_id)
{
auto it = client_list.find(delete_id);
if (it != client_list.end()) {
client_list.erase(it); // Already deleted
return true;
}
return false;
}
// If our ID was deleted already
bool EntityList::RemoveClient(Client *delete_client)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second == delete_client) {
client_list.erase(it);
return true;
}
++it;
}
return false;
}
bool EntityList::RemoveObject(uint16 delete_id)
{
auto it = object_list.find(delete_id);
if (it != object_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
object_list.erase(it);
return true;
}
return false;
}
bool EntityList::RemoveTrap(uint16 delete_id)
{
auto it = trap_list.find(delete_id);
if (it != trap_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
trap_list.erase(it);
return true;
}
return false;
}
bool EntityList::RemoveDoor(uint16 delete_id)
{
auto it = door_list.find(delete_id);
if (it != door_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
door_list.erase(it);
return true;
}
return false;
}
bool EntityList::RemoveCorpse(uint16 delete_id)
{
auto it = corpse_list.find(delete_id);
if (it != corpse_list.end()) {
safe_delete(it->second);
free_ids.push(it->first);
corpse_list.erase(it);
return true;
}
return false;
}
bool EntityList::RemoveGroup(uint32 delete_id)
{
auto it = std::find_if(group_list.begin(), group_list.end(),
[delete_id](const Group *a) { return a->GetID() == delete_id; });
if (it == group_list.end()) {
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return false;
}
auto group = *it;
group_list.erase(it);
safe_delete(group);
return true;
}
void EntityList::Clear()
{
RemoveAllClients();
entity_list.RemoveAllTraps(); //we can have child npcs so we go first
entity_list.RemoveAllMercs();
entity_list.RemoveAllBots();
entity_list.RemoveAllNPCs();
entity_list.RemoveAllMobs();
entity_list.RemoveAllCorpses();
entity_list.RemoveAllGroups();
entity_list.RemoveAllDoors();
entity_list.RemoveAllObjects();
entity_list.RemoveAllRaids();
entity_list.RemoveAllLocalities();
}
void EntityList::UpdateWho(bool iSendFullUpdate)
{
if ((!worldserver.Connected()) || !is_zone_loaded)
return;
uint32 tmpNumUpdates = numclients + 5;
ServerPacket* pack = 0;
ServerClientListKeepAlive_Struct* sclka = 0;
if (!iSendFullUpdate) {
pack = new ServerPacket(ServerOP_ClientListKA, sizeof(ServerClientListKeepAlive_Struct) + (tmpNumUpdates * 4));
sclka = (ServerClientListKeepAlive_Struct*) pack->pBuffer;
}
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->InZone()) {
if (iSendFullUpdate) {
it->second->UpdateWho();
} else {
if (sclka->numupdates >= tmpNumUpdates) {
tmpNumUpdates += 10;
uint8* tmp = pack->pBuffer;
pack->pBuffer = new uint8[sizeof(ServerClientListKeepAlive_Struct) + (tmpNumUpdates * 4)];
memset(pack->pBuffer, 0, sizeof(ServerClientListKeepAlive_Struct) + (tmpNumUpdates * 4));
memcpy(pack->pBuffer, tmp, pack->size);
pack->size = sizeof(ServerClientListKeepAlive_Struct) + (tmpNumUpdates * 4);
safe_delete_array(tmp);
sclka = (ServerClientListKeepAlive_Struct*) pack->pBuffer;
}
sclka->wid[sclka->numupdates] = it->second->GetWID();
sclka->numupdates++;
}
}
++it;
}
if (!iSendFullUpdate) {
pack->size = sizeof(ServerClientListKeepAlive_Struct) + (sclka->numupdates * 4);
worldserver.SendPacket(pack);
safe_delete(pack);
}
}
void EntityList::RemoveEntity(uint16 id)
{
if (id == 0)
return;
if (entity_list.RemoveMob(id))
return;
else if (entity_list.RemoveCorpse(id))
return;
else if (entity_list.RemoveDoor(id))
return;
else if (entity_list.RemoveGroup(id))
return;
else if (entity_list.RemoveTrap(id))
return;
else if (entity_list.RemoveMerc(id))
return;
else if (entity_list.RemoveBot(id))
return;
else
entity_list.RemoveObject(id);
}
void EntityList::Process()
{
CheckSpawnQueue();
}
void EntityList::Depop(bool StartSpawnTimer)
{
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
NPC *pnpc = it->second;
if (pnpc) {
Mob *own = pnpc->GetOwner();
//do not depop player/bot pets...
if (own && own->IsOfClientBot()) {
continue;
}
if (pnpc->IsHorse()) {
continue;
}
if (pnpc->IsFindable()) {
UpdateFindableNPCState(pnpc, true);
}
// Depop below will eventually remove this npc from the entity list
// but that can happen AFTER we've already tried to spawn its replacement.
// So go ahead and remove it from the limits so it doesn't count.
if (npc_limit_list.count(pnpc->GetID())) {
npc_limit_list.erase(pnpc->GetID());
}
pnpc->WipeHateList();
pnpc->Depop(StartSpawnTimer);
}
}
}
void EntityList::DepopAll(int NPCTypeID, bool StartSpawnTimer)
{
for (auto it = npc_list.begin(); it != npc_list.end(); ++it) {
NPC *pnpc = it->second;
if (pnpc && (pnpc->GetNPCTypeID() == (uint32)NPCTypeID))
pnpc->Depop(StartSpawnTimer);
}
}
void EntityList::SendTraders(Client *client)
{
Client *trader = nullptr;
auto it = client_list.begin();
while (it != client_list.end()) {
trader = it->second;
if (trader->IsTrader())
client->SendTraderPacket(trader);
if (trader->IsBuyer())
client->SendBuyerPacket(trader);
++it;
}
}
void EntityList::RemoveFromHateLists(Mob *mob, bool settoone)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(mob)) {
if (!settoone) {
it->second->RemoveFromHateList(mob);
it->second->RemoveFromRampageList(mob);
if (mob->IsClient()) {
mob->CastToClient()->RemoveXTarget(it->second, false); // gotta do book keeping
}
} else {
it->second->SetHateAmountOnEnt(mob, 1);
}
}
++it;
}
}
void EntityList::RemoveDebuffs(Mob *caster)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
it->second->BuffFadeDetrimentalByCaster(caster);
++it;
}
}
char *EntityList::MakeNameUnique(char *name)
{
bool used[300];
memset(used, 0, sizeof(used));
name[61] = 0; name[62] = 0; name[63] = 0;
int len = strlen(name);
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (it->second->IsMob()) {
if (strncasecmp(it->second->CastToMob()->GetName(), name, len) == 0) {
if (Seperator::IsNumber(&it->second->CastToMob()->GetName()[len])) {
used[Strings::ToInt(&it->second->CastToMob()->GetName()[len])] = true;
}
}
}
++it;
}
for (int i=0; i < 300; i++) {
if (!used[i]) {
#ifdef _WINDOWS
snprintf(name, 64, "%s%03d", name, i);
#else
//glibc clears destination of snprintf
//make a copy of name before snprintf--misanthropicfiend
char temp_name[64];
strn0cpy(temp_name, name, 64);
snprintf(name, 64, "%s%03d", temp_name, i);
#endif
return name;
}
}
LogError("Fatal error in EntityList::MakeNameUnique: Unable to find unique name for [{}]", name);
char tmp[64] = "!";
strn0cpy(&tmp[1], name, sizeof(tmp) - 1);
strcpy(name, tmp);
return MakeNameUnique(name);
}
char *EntityList::RemoveNumbers(char *name)
{
char tmp[64];
memset(tmp, 0, sizeof(tmp));
int k = 0;
for (unsigned int i=0; i '9')
tmp[k++] = name[i];
}
strn0cpy(name, tmp, sizeof(tmp));
return name;
}
void EntityList::ListNPCCorpses(Client *client)
{
uint32 corpse_count = 0;
for (const auto& corpse : corpse_list) {
uint32 corpse_number = (corpse_count + 1);
if (corpse.second->IsNPCCorpse()) {
client->Message(
Chat::White,
fmt::format(
"Corpse {} | Name: {} ({})",
corpse_number,
corpse.second->GetName(),
corpse.second->GetID()
).c_str()
);
corpse_count++;
}
}
if (corpse_count > 0) {
client->Message(
Chat::White,
fmt::format(
"{} NPC corpses listed.",
corpse_count
).c_str()
);
}
}
void EntityList::ListPlayerCorpses(Client *client)
{
uint32 corpse_count = 0;
for (const auto& corpse : corpse_list) {
uint32 corpse_number = (corpse_count + 1);
if (corpse.second->IsPlayerCorpse()) {
client->Message(
Chat::White,
fmt::format(
"Corpse {} | Name: {} ({})",
corpse_number,
corpse.second->GetName(),
corpse.second->GetID()
).c_str()
);
corpse_count++;
}
}
if (corpse_count > 0) {
client->Message(
Chat::White,
fmt::format(
"{} Player corpses listed.",
corpse_count
).c_str()
);
}
}
// returns the number of corpses deleted. A negative number indicates an error code.
uint32 EntityList::DeleteNPCCorpses()
{
uint32 corpse_count = 0;
for (const auto& corpse : corpse_list) {
if (corpse.second->IsNPCCorpse()) {
corpse.second->DepopNPCCorpse();
corpse_count++;
}
}
return corpse_count;
}
void EntityList::CorpseFix(Client* c)
{
uint32 fixed_count = 0;
for (const auto& e : corpse_list) {
auto cur = e.second;
if (cur->IsNPCCorpse()) {
if (DistanceNoZ(c->GetPosition(), cur->GetPosition()) < 100) {
c->Message(
Chat::White,
fmt::format(
"Attempting to fix {}.",
cur->GetCleanName()
).c_str()
);
cur->GMMove(
cur->GetX(),
cur->GetY(),
cur->GetFixedZ(c->GetPosition()),
c->GetHeading()
);
fixed_count++;
}
}
}
if (!fixed_count) {
c->Message(Chat::White, "There were no nearby NPC corpses to fix.");
return;
}
c->Message(
Chat::White,
fmt::format(
"Fixed {} nearby NPC corpse{}.",
fixed_count,
fixed_count != 1 ? "s" : ""
).c_str()
);
}
// returns the number of corpses deleted. A negative number indicates an error code.
uint32 EntityList::DeletePlayerCorpses()
{
uint32 corpse_count = 0;
for (const auto& corpse : corpse_list) {
if (corpse.second->IsPlayerCorpse()) {
corpse.second->Delete();
corpse_count++;
}
}
return corpse_count;
}
void EntityList::SendPetitionToAdmins()
{
auto outapp = new EQApplicationPacket(OP_PetitionUpdate, sizeof(PetitionUpdate_Struct));
PetitionUpdate_Struct *pcus = (PetitionUpdate_Struct*) outapp->pBuffer;
pcus->petnumber = 0; // Petition Number
pcus->color = 0;
pcus->status = 0xFFFFFFFF;
pcus->senttime = 0;
strcpy(pcus->accountid, "");
strcpy(pcus->gmsenttoo, "");
pcus->quetotal=0;
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->CastToClient()->Admin() >= AccountStatus::QuestTroupe)
it->second->CastToClient()->QueuePacket(outapp);
++it;
}
safe_delete(outapp);
}
void EntityList::SendPetitionToAdmins(Petition *pet)
{
auto outapp = new EQApplicationPacket(OP_PetitionUpdate, sizeof(PetitionUpdate_Struct));
PetitionUpdate_Struct *pcus = (PetitionUpdate_Struct*) outapp->pBuffer;
pcus->petnumber = pet->GetID(); // Petition Number
if (pet->CheckedOut()) {
pcus->color = 0x00;
pcus->status = 0xFFFFFFFF;
pcus->senttime = pet->GetSentTime();
strcpy(pcus->accountid, "");
strcpy(pcus->gmsenttoo, "");
} else {
pcus->color = pet->GetUrgency(); // 0x00 = green, 0x01 = yellow, 0x02 = red
pcus->status = pet->GetSentTime();
pcus->senttime = pet->GetSentTime(); // 4 has to be 0x1F
strcpy(pcus->accountid, pet->GetAccountName());
strcpy(pcus->charname, pet->GetCharName());
}
pcus->quetotal = PetitionList::Instance()->GetTotalPetitions();
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->CastToClient()->Admin() >= AccountStatus::QuestTroupe) {
if (pet->CheckedOut())
strcpy(pcus->gmsenttoo, "");
else
strcpy(pcus->gmsenttoo, it->second->CastToClient()->GetName());
it->second->CastToClient()->QueuePacket(outapp);
}
++it;
}
safe_delete(outapp);
}
void EntityList::ClearClientPetitionQueue()
{
auto outapp = new EQApplicationPacket(OP_PetitionUpdate, sizeof(PetitionUpdate_Struct));
PetitionUpdate_Struct *pet = (PetitionUpdate_Struct*) outapp->pBuffer;
pet->color = 0x00;
pet->status = 0xFFFFFFFF;
pet->senttime = 0;
strcpy(pet->accountid, "");
strcpy(pet->gmsenttoo, "");
strcpy(pet->charname, "");
pet->quetotal = PetitionList::Instance()->GetTotalPetitions();
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->CastToClient()->Admin() >= AccountStatus::GMAdmin) {
int x = 0;
for (x = 0; x < 64; x++) {
pet->petnumber = x;
it->second->CastToClient()->QueuePacket(outapp);
}
}
++it;
}
safe_delete(outapp);
return;
}
BulkZoneSpawnPacket::BulkZoneSpawnPacket(Client *iSendTo, uint32 iMaxSpawnsPerPacket)
{
data = nullptr;
pSendTo = iSendTo;
pMaxSpawnsPerPacket = iMaxSpawnsPerPacket;
}
BulkZoneSpawnPacket::~BulkZoneSpawnPacket()
{
SendBuffer();
safe_delete_array(data);
}
bool BulkZoneSpawnPacket::AddSpawn(NewSpawn_Struct *ns)
{
if (!data) {
data = new NewSpawn_Struct[pMaxSpawnsPerPacket];
memset(data, 0, sizeof(NewSpawn_Struct) * pMaxSpawnsPerPacket);
index = 0;
}
memcpy(&data[index], ns, sizeof(NewSpawn_Struct));
index++;
if (index >= pMaxSpawnsPerPacket) {
SendBuffer();
return true;
}
return false;
}
void BulkZoneSpawnPacket::SendBuffer()
{
if (!data)
return;
uint32 tmpBufSize = (index * sizeof(NewSpawn_Struct));
auto outapp = new EQApplicationPacket(OP_ZoneSpawns, (unsigned char *)data, tmpBufSize);
if (pSendTo) {
pSendTo->FastQueuePacket(&outapp);
} else {
entity_list.QueueClients(0, outapp);
safe_delete(outapp);
}
memset(data, 0, sizeof(NewSpawn_Struct) * pMaxSpawnsPerPacket);
index = 0;
}
void EntityList::DoubleAggro(Mob *who)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(who))
it->second->SetHateAmountOnEnt(who, it->second->CastToNPC()->GetHateAmount(who),
it->second->CastToNPC()->GetHateAmount(who) * 2);
++it;
}
}
void EntityList::HalveAggro(Mob *who)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CastToNPC()->CheckAggro(who))
it->second->CastToNPC()->SetHateAmountOnEnt(who, it->second->CastToNPC()->GetHateAmount(who) / 2);
++it;
}
}
//removes "targ" from all hate lists, including feigned, in the zone
void EntityList::ClearAggro(Mob* targ, bool clear_caster_id)
{
Client *c = nullptr;
if (targ->IsClient()) {
c = targ->CastToClient();
}
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (clear_caster_id) {
it->second->BuffDetachCaster(targ);
}
if (it->second->CheckAggro(targ)) {
if (c) {
c->RemoveXTarget(it->second, false);
}
it->second->RemoveFromHateList(targ);
it->second->RemoveFromRampageList(targ, true);
}
if (c && it->second->IsOnFeignMemory(c)) {
it->second->RemoveFromFeignMemory(c); //just in case we feigned
c->RemoveXTarget(it->second, false);
}
++it;
}
}
//removes "targ" from all hate lists of mobs that are water only.
void EntityList::ClearWaterAggro(Mob* targ)
{
Client *c = nullptr;
if (targ->IsClient()) {
c = targ->CastToClient();
}
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->IsUnderwaterOnly()) {
if (it->second->CheckAggro(targ)) {
if (c) {
c->RemoveXTarget(it->second, false);
}
it->second->RemoveFromHateList(targ);
it->second->RemoveFromRampageList(targ);
}
if (c && it->second->IsOnFeignMemory(c)) {
it->second->RemoveFromFeignMemory(c); //just in case we feigned
c->RemoveXTarget(it->second, false);
}
}
++it;
}
}
void EntityList::ClearFeignAggro(Mob *targ)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
// add Feign Memory check because sometimes weird stuff happens
if (it->second->CheckAggro(targ) || (targ->IsClient() && it->second->IsOnFeignMemory(targ))) {
if (it->second->GetSpecialAbility(SpecialAbility::FeignDeathImmunity)) {
++it;
continue;
}
if (targ->IsClient()) {
if (parse->PlayerHasQuestSub(EVENT_FEIGN_DEATH)) {
std::vector args = { it->second };
int i = parse->EventPlayer(EVENT_FEIGN_DEATH, targ->CastToClient(), "", 0, &args);
if (i != 0) {
++it;
continue;
}
}
if (it->second->IsNPC()) {
if (parse->HasQuestSub(it->second->GetNPCTypeID(), EVENT_FEIGN_DEATH)) {
int i = parse->EventNPC(EVENT_FEIGN_DEATH, it->second->CastToNPC(), targ, "", 0);
if (i != 0) {
++it;
continue;
}
}
}
}
it->second->RemoveFromHateList(targ);
if (it->second->GetSpecialAbility(SpecialAbility::Rampage)) {
it->second->RemoveFromRampageList(targ, true);
}
if (targ->IsClient()) {
if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) {
it->second->AddFeignMemory(targ);
}
else {
targ->CastToClient()->RemoveXTarget(it->second, false);
}
}
else if (targ->IsPet()){
if (it->second->GetLevel() >= 35 && zone->random.Roll(60)) {
it->second->AddFeignMemory(targ);
}
}
}
++it;
}
}
void EntityList::ClearZoneFeignAggro(Mob *targ)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
it->second->RemoveFromFeignMemory(targ);
if (targ && targ->IsClient()) {
targ->CastToClient()->RemoveXTarget(it->second, false);
}
++it;
}
}
void EntityList::AggroZone(Mob *who, int64 hate)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
it->second->AddToHateList(who, hate);
++it;
}
}
// Signal Quest command function
void EntityList::SignalMobsByNPCID(uint32 snpc, int signal_id)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC *pit = it->second;
if (pit->GetNPCTypeID() == snpc)
pit->SignalNPC(signal_id);
++it;
}
}
bool EntityList::MakeTrackPacket(Client *client)
{
std::list > tracking_list;
auto distance = static_cast(client->GetSkill(EQ::skills::SkillTracking) * client->GetClassTrackingDistanceMultiplier(client->GetClass()));
if (distance <= 0.0f) {
return false;
}
if (distance < 300.0f) {
distance = 300.0f;
}
for (auto it = mob_list.cbegin(); it != mob_list.cend(); ++it) {
if (
!it->second ||
it->second == client ||
!it->second->IsTrackable() ||
it->second->IsInvisible(client)
) {
continue;
}
const auto mob_distance = DistanceNoZ(it->second->GetPosition(), client->GetPosition());
if (mob_distance > distance) {
continue;
}
tracking_list.push_back(std::make_pair(it->second, mob_distance));
}
tracking_list.sort(
[](const std::pair &a, const std::pair &b) {
return a.first->GetSpawnTimeStamp() > b.first->GetSpawnTimeStamp();
}
);
auto outapp = new EQApplicationPacket(OP_Track, sizeof(Track_Struct) * tracking_list.size());
auto pack = (Tracking_Struct *) outapp->pBuffer;
outapp->priority = 6;
int index = 0;
for (auto it = tracking_list.cbegin(); it != tracking_list.cend(); ++it, ++index) {
pack->Entrys[index].entityid = static_cast(it->first->GetID());
pack->Entrys[index].distance = it->second;
pack->Entrys[index].level = it->first->GetLevel();
pack->Entrys[index].is_npc = !it->first->IsClient();
strn0cpy(pack->Entrys[index].name, it->first->GetName(), sizeof(pack->Entrys[index].name));
pack->Entrys[index].is_pet = it->first->IsPet();
pack->Entrys[index].is_merc = it->first->IsMerc();
}
client->QueuePacket(outapp);
safe_delete(outapp);
return true;
}
void EntityList::MessageGroup(Mob *sender, bool skipclose, uint32 type, const char *message, ...)
{
va_list argptr;
char buffer[4096];
va_start(argptr, message);
vsnprintf(buffer, 4095, message, argptr);
va_end(argptr);
float dist2 = 100;
if (skipclose)
dist2 = 0;
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second != sender &&
(Distance(it->second->GetPosition(), sender->GetPosition()) <= dist2 || it->second->GetGroup() == sender->CastToClient()->GetGroup())) {
it->second->Message(type, buffer);
}
++it;
}
}
bool EntityList::Fighting(Mob *targ)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(targ))
return true;
++it;
}
return false;
}
void EntityList::AddHealAggro(Mob *target, Mob *caster, uint16 hate)
{
if (hate == 0)
return;
for (auto &e : npc_list) {
auto &npc = e.second;
if (!npc->CheckAggro(target) || npc->IsFeared() || npc->IsPet())
continue;
if (zone->random.Roll(50)) // witness check -- place holder
// This is either a level check (con color check?) or a stat roll
continue;
if ((npc->IsMezzed() || npc->IsStunned()) && hate > 4) // patch notes say stunned/mezzed NPCs get a fraction of the hate
npc->AddToHateList(caster, hate / 4); // made up number
else
npc->AddToHateList(caster, hate);
}
}
void EntityList::OpenDoorsNear(Mob *who)
{
if (!who->CanOpenDoors()) {
return;
}
for (auto &it : door_list) {
Doors *door = it.second;
if (!door || door->IsDoorOpen()) {
continue;
}
auto diff = who->GetPosition() - door->GetPosition();
float distance = diff.x * diff.x + diff.y * diff.y;
if (diff.z * diff.z < 10 && distance <= 100) {
door->Open(who);
}
}
}
void EntityList::SendAlarm(Trap *trap, Mob *currenttarget, uint8 kos)
{
float preSquareDistance = trap->effectvalue * trap->effectvalue;
for (auto it = npc_list.begin();it != npc_list.end(); ++it) {
NPC *cur = it->second;
auto diff = glm::vec3(cur->GetPosition()) - trap->m_Position;
float curdist = diff.x * diff.x + diff.y * diff.y + diff.z * diff.z;
if (cur->GetOwner() || cur->IsEngaged() || curdist > preSquareDistance )
continue;
if (kos) {
uint8 factioncon = currenttarget->GetReverseFactionCon(cur);
if (factioncon == FACTION_THREATENINGLY || factioncon == FACTION_SCOWLS) {
cur->AddToHateList(currenttarget,1);
}
}
else
cur->AddToHateList(currenttarget,1);
}
}
void EntityList::AddProximity(NPC *proximity_for)
{
RemoveProximity(proximity_for->GetID());
proximity_list.push_back(proximity_for);
proximity_for->proximity = new NPCProximity; // deleted in NPC::~NPC
}
bool EntityList::RemoveProximity(uint16 delete_npc_id)
{
auto it = std::find_if(proximity_list.begin(), proximity_list.end(),
[delete_npc_id](const NPC *a) { return a->GetID() == delete_npc_id; });
if (it == proximity_list.end())
return false;
proximity_list.erase(it);
return true;
}
void EntityList::RemoveAllLocalities()
{
proximity_list.clear();
}
struct quest_proximity_event {
QuestEventID event_id;
Client *client;
NPC *npc;
int area_id;
int area_type;
};
void EntityList::ProcessMove(Client *c, const glm::vec3& location)
{
float last_x = c->ProximityX();
float last_y = c->ProximityY();
float last_z = c->ProximityZ();
std::list events;
for (auto iter = proximity_list.begin(); iter != proximity_list.end(); ++iter) {
NPC *d = (*iter);
NPCProximity *l = d->proximity;
if (l == nullptr)
continue;
//check both bounding boxes, if either coords pairs
//cross a boundary, send the event.
bool old_in = true;
bool new_in = true;
if (last_x < l->min_x || last_x > l->max_x ||
last_y < l->min_y || last_y > l->max_y ||
last_z < l->min_z || last_z > l->max_z) {
old_in = false;
}
if (location.x < l->min_x || location.x > l->max_x ||
location.y < l->min_y || location.y > l->max_y ||
location.z < l->min_z || location.z > l->max_z) {
new_in = false;
}
if (old_in && !new_in) {
quest_proximity_event evt;
evt.event_id = EVENT_EXIT;
evt.client = c;
evt.npc = d;
evt.area_id = 0;
evt.area_type = 0;
events.push_back(evt);
} else if (new_in && !old_in) {
quest_proximity_event evt;
evt.event_id = EVENT_ENTER;
evt.client = c;
evt.npc = d;
evt.area_id = 0;
evt.area_type = 0;
events.push_back(evt);
}
}
for (auto iter = area_list.begin(); iter != area_list.end(); ++iter) {
Area& a = (*iter);
bool old_in = true;
bool new_in = true;
if (last_x < a.min_x || last_x > a.max_x ||
last_y < a.min_y || last_y > a.max_y ||
last_z < a.min_z || last_z > a.max_z) {
old_in = false;
}
if (location.x < a.min_x || location.x > a.max_x ||
location.y < a.min_y || location.y > a.max_y ||
location.z < a.min_z || location.z > a.max_z ) {
new_in = false;
}
if (old_in && !new_in) {
//were in but are no longer.
quest_proximity_event evt;
evt.event_id = EVENT_LEAVE_AREA;
evt.client = c;
evt.npc = nullptr;
evt.area_id = a.id;
evt.area_type = a.type;
events.push_back(evt);
} else if (!old_in && new_in) {
//were not in but now are
quest_proximity_event evt;
evt.event_id = EVENT_ENTER_AREA;
evt.client = c;
evt.npc = nullptr;
evt.area_id = a.id;
evt.area_type = a.type;
events.push_back(evt);
}
}
for (auto iter = events.begin(); iter != events.end(); ++iter) {
quest_proximity_event& evt = (*iter);
std::vector args = { &evt.area_id, &evt.area_type };
if (evt.npc) {
if (evt.event_id == EVENT_ENTER) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_ENTER)) {
parse->EventNPC(EVENT_ENTER, evt.npc, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_EXIT) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_EXIT)) {
parse->EventNPC(EVENT_EXIT, evt.npc, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_ENTER_AREA) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_ENTER_AREA)) {
parse->EventNPC(EVENT_ENTER_AREA, evt.npc, evt.client, "", 0, &args);
}
} else if (evt.event_id == EVENT_LEAVE_AREA) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_LEAVE_AREA)) {
parse->EventNPC(EVENT_LEAVE_AREA, evt.npc, evt.client, "", 0, &args);
}
}
} else {
if (evt.event_id == EVENT_ENTER) {
if (parse->PlayerHasQuestSub(EVENT_ENTER)) {
parse->EventPlayer(EVENT_ENTER, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_EXIT) {
if (parse->PlayerHasQuestSub(EVENT_EXIT)) {
parse->EventPlayer(EVENT_EXIT, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_ENTER_AREA) {
if (parse->PlayerHasQuestSub(EVENT_ENTER_AREA)) {
parse->EventPlayer(EVENT_ENTER_AREA, evt.client, "", 0, &args);
}
} else if (evt.event_id == EVENT_LEAVE_AREA) {
if (parse->PlayerHasQuestSub(EVENT_LEAVE_AREA)) {
parse->EventPlayer(EVENT_LEAVE_AREA, evt.client, "", 0, &args);
}
}
}
}
}
void EntityList::ProcessMove(NPC *n, float x, float y, float z) {
float last_x = n->GetX();
float last_y = n->GetY();
float last_z = n->GetZ();
std::list events;
for (const auto& a : area_list) {
bool old_in = true;
bool new_in = true;
if (
!EQ::ValueWithin(last_x, a.min_x, a.max_x) ||
!EQ::ValueWithin(last_y, a.min_y, a.max_y) ||
!EQ::ValueWithin(last_z, a.min_z, a.max_z)
) {
old_in = false;
}
if (
!EQ::ValueWithin(x, a.min_x, a.max_x) ||
!EQ::ValueWithin(y, a.min_y, a.max_y) ||
!EQ::ValueWithin(z, a.min_z, a.max_z)
) {
new_in = false;
}
if (old_in && !new_in) {
//were in but are no longer.
quest_proximity_event evt;
evt.event_id = EVENT_LEAVE_AREA;
evt.client = nullptr;
evt.npc = n;
evt.area_id = a.id;
evt.area_type = a.type;
events.emplace_back(evt);
}
else if (!old_in && new_in) {
//were not in but now are
quest_proximity_event evt;
evt.event_id = EVENT_ENTER_AREA;
evt.client = nullptr;
evt.npc = n;
evt.area_id = a.id;
evt.area_type = a.type;
events.emplace_back(evt);
}
}
for (const auto& evt : events) {
std::vector args = { &evt.area_id, &evt.area_type };
if (evt.event_id == EVENT_ENTER) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_ENTER)) {
parse->EventNPC(EVENT_ENTER, evt.npc, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_EXIT) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_EXIT)) {
parse->EventNPC(EVENT_EXIT, evt.npc, evt.client, "", 0);
}
} else if (evt.event_id == EVENT_ENTER_AREA) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_ENTER_AREA)) {
parse->EventNPC(EVENT_ENTER_AREA, evt.npc, evt.client, "", 0, &args);
}
} else if (evt.event_id == EVENT_LEAVE_AREA) {
if (parse->HasQuestSub(evt.npc->GetNPCTypeID(), EVENT_LEAVE_AREA)) {
parse->EventNPC(EVENT_LEAVE_AREA, evt.npc, evt.client, "", 0, &args);
}
}
}
}
void EntityList::AddArea(int id, int type, float min_x, float max_x, float min_y,
float max_y, float min_z, float max_z)
{
RemoveArea(id);
Area a;
a.id = id;
a.type = type;
if (min_x > max_x) {
a.min_x = max_x;
a.max_x = min_x;
} else {
a.min_x = min_x;
a.max_x = max_x;
}
if (min_y > max_y) {
a.min_y = max_y;
a.max_y = min_y;
} else {
a.min_y = min_y;
a.max_y = max_y;
}
if (min_z > max_z) {
a.min_z = max_z;
a.max_z = min_z;
} else {
a.min_z = min_z;
a.max_z = max_z;
}
area_list.push_back(a);
}
void EntityList::RemoveArea(int id)
{
auto it = std::find_if(area_list.begin(), area_list.end(),
[id](const Area &a) { return a.id == id; });
if (it == area_list.end())
return;
area_list.erase(it);
}
void EntityList::ClearAreas()
{
area_list.clear();
}
void EntityList::ProcessProximitySay(const char *message, Client *c, uint8 language)
{
if (!message || !c) {
return;
}
for (const auto& n : proximity_list) {
auto* p = n->proximity;
if (!p || !p->say) {
continue;
}
if (!parse->HasQuestSub(n->GetNPCTypeID(), EVENT_PROXIMITY_SAY)) {
continue;
}
if (
!EQ::ValueWithin(c->GetX(), p->min_x, p->max_x) ||
!EQ::ValueWithin(c->GetY(), p->min_y, p->max_y) ||
!EQ::ValueWithin(c->GetZ(), p->min_z, p->max_z)
) {
continue;
}
parse->EventNPC(EVENT_PROXIMITY_SAY, n, c, message, language);
}
}
void EntityList::SaveAllClientsTaskState()
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if (client->IsTaskStateLoaded()) {
client->SaveTaskState();
}
++it;
}
}
void EntityList::ReloadAllClientsTaskState(int task_id)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if (client->IsTaskStateLoaded()) {
// If we have been passed a TaskID, only reload the client state if they have
// that Task active.
if ((!task_id) || (task_id && client->IsTaskActive(task_id))) {
Log(Logs::General, Logs::Tasks, "[CLIENTLOAD] Reloading Task State For Client %s", client->GetName());
client->RemoveClientTaskState();
client->LoadClientTaskState();
TaskManager::Instance()->SendActiveTasksToClient(client);
}
}
++it;
}
}
bool EntityList::IsMobInZone(Mob *who)
{
//We don't use mob_list.find(who) because this code needs to be able to handle dangling pointers for the quest code.
auto it = mob_list.begin();
while(it != mob_list.end()) {
if(it->second == who) {
return true;
}
++it;
}
auto enc_it = encounter_list.begin();
while (enc_it != encounter_list.end()) {
if (enc_it->second == who) {
return true;
}
++enc_it;
}
return false;
}
/*
Code to limit the amount of certain NPCs in a given zone.
Primarily used to make a named mob unique within the zone, but written
to be more generic allowing limits larger than 1.
Maintain this stuff in a seperate list since the number
of limited NPCs will most likely be much smaller than the number
of NPCs in the entire zone.
*/
void EntityList::LimitAddNPC(NPC *npc)
{
if (!npc)
return;
SpawnLimitRecord r;
uint16 eid = npc->GetID();
r.spawngroup_id = npc->GetSpawnGroupId();
r.npc_type = npc->GetNPCTypeID();
npc_limit_list[eid] = r;
}
void EntityList::LimitRemoveNPC(NPC *npc)
{
if (!npc)
return;
uint16 eid = npc->GetID();
npc_limit_list.erase(eid);
}
//check a limit over the entire zone.
//returns true if the limit has not been reached
bool EntityList::LimitCheckType(uint32 npc_type, int count)
{
if (count < 1)
return true;
std::map::iterator cur,end;
cur = npc_limit_list.begin();
end = npc_limit_list.end();
for (; cur != end; ++cur) {
if (cur->second.npc_type == npc_type) {
count--;
if (count == 0) {
return false;
}
}
}
return true;
}
//check limits on an npc type in a given spawn group.
//returns true if the limit has not been reached
bool EntityList::LimitCheckGroup(uint32 spawngroup_id, int count)
{
if (count < 1)
return true;
std::map::iterator cur,end;
cur = npc_limit_list.begin();
end = npc_limit_list.end();
for (; cur != end; ++cur) {
if (cur->second.spawngroup_id == spawngroup_id) {
count--;
if (count == 0) {
return false;
}
}
}
return true;
}
bool EntityList::LimitCheckName(const char *npc_name)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC *npc = it->second;
if (npc) {
if (strcasecmp(npc_name, npc->GetRawNPCTypeName()) == 0) {
return false;
}
}
++it;
}
return true;
}
void EntityList::UpdateHoTT(Mob *target)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
if (c->GetTarget() == target) {
if (target->GetTarget())
c->SetHoTT(target->GetTarget()->GetID());
else
c->SetHoTT(0);
c->UpdateXTargetType(TargetsTarget, target->GetTarget());
}
++it;
}
}
void EntityList::DestroyTempPets(Mob *owner)
{
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC* n = it->second;
if (n->GetSwarmInfo()) {
if (n->GetSwarmInfo()->owner_id == owner->GetID()) {
n->Depop();
}
}
++it;
}
}
void EntityList::AddTempPetsToHateList(Mob *owner, Mob* other, bool bFrenzy)
{
if (!other || !owner)
return;
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC* n = it->second;
if (n && n->GetSwarmInfo()) {
if (n->GetSwarmInfo()->owner_id == owner->GetID()) {
if (
!n->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
!(other->IsBot() && n->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
!(other->IsClient() && n->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
!(other->IsNPC() && n->GetSpecialAbility(SpecialAbility::NPCAggroImmunity))
) {
n->hate_list.AddEntToHateList(other, 0, 0, bFrenzy);
}
}
}
++it;
}
}
void EntityList::AddTempPetsToHateListOnOwnerDamage(Mob *owner, Mob* attacker, int32 spell_id)
{
if (!attacker || !owner)
return;
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC* n = it->second;
if (n && n->GetSwarmInfo()) {
if (n->GetSwarmInfo()->owner_id == owner->GetID()) {
if (
attacker &&
attacker != n &&
!n->IsEngaged() &&
!n->GetSpecialAbility(SpecialAbility::AggroImmunity) &&
!(attacker->IsBot() && n->GetSpecialAbility(SpecialAbility::BotAggroImmunity)) &&
!(attacker->IsClient() && n->GetSpecialAbility(SpecialAbility::ClientAggroImmunity)) &&
!(attacker->IsNPC() && n->GetSpecialAbility(SpecialAbility::NPCAggroImmunity)) &&
!attacker->IsTrap() &&
!attacker->IsCorpse()
) {
n->AddToHateList(attacker, 1, 0, true, false, false, spell_id);
n->SetTarget(attacker);
}
}
}
++it;
}
}
void EntityList::QuestJournalledSayClose(
Mob *sender, float dist, const char *mobname, const char *message,
Journal::Options &opts
)
{
SerializeBuffer buf(sizeof(SpecialMesgHeader_Struct) + 12 + 64 + 64);
buf.WriteInt8(static_cast(opts.speak_mode));
buf.WriteInt8(static_cast(opts.journal_mode));
buf.WriteInt8(opts.language);
buf.WriteInt32(opts.message_type);
buf.WriteInt32(opts.target_spawn_id);
buf.WriteString(mobname);
buf.WriteInt32(0); // location, client doesn't seem to do anything with this
buf.WriteInt32(0);
buf.WriteInt32(0);
if (RuleB(Chat, QuestDialogueUsesDialogueWindow)) {
for (auto &e : sender->GetCloseMobList(dist)) {
Mob *mob = e.second;
if (!mob) {
continue;
}
if (!mob->IsClient()) {
continue;
}
Client *client = mob->CastToClient();
if (client->GetTarget() && client->GetTarget()->IsMob() && client->GetTarget()->CastToMob() == sender) {
std::string window_markdown = message;
DialogueWindow::Render(client, window_markdown);
}
}
return;
}
else if (RuleB(Chat, AutoInjectSaylinksToSay)) {
std::string new_message = EQ::SayLinkEngine::InjectSaylinksIfNotExist(message);
buf.WriteString(new_message);
}
else {
buf.WriteString(message);
}
auto outapp = new EQApplicationPacket(OP_SpecialMesg, buf);
// client only bothers logging if target spawn ID matches, safe to send to everyone
QueueCloseClients(sender, outapp, false, dist);
delete outapp;
}
bool Entity::CheckCoordLosNoZLeaps(float cur_x, float cur_y, float cur_z,
float trg_x, float trg_y, float trg_z, float perwalk)
{
if (zone->zonemap == nullptr) {
return true;
}
glm::vec3 myloc;
glm::vec3 oloc;
glm::vec3 hit;
myloc.x = cur_x;
myloc.y = cur_y;
myloc.z = cur_z+5;
oloc.x = trg_x;
oloc.y = trg_y;
oloc.z = trg_z+5;
if (myloc.x == oloc.x && myloc.y == oloc.y && myloc.z == oloc.z) {
return true;
}
if (!zone->zonemap->LineIntersectsZoneNoZLeaps(myloc,oloc,perwalk,&hit)) {
return true;
}
return false;
}
Corpse *EntityList::GetClosestCorpse(Mob *sender, const char *Name)
{
if (!sender)
return nullptr;
uint32 CurrentDistance, ClosestDistance = 4294967295u;
Corpse *CurrentCorpse, *ClosestCorpse = nullptr;
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
CurrentCorpse = it->second;
++it;
if (Name && strcasecmp(CurrentCorpse->GetOwnerName(), Name))
continue;
CurrentDistance = ((CurrentCorpse->GetY() - sender->GetY()) * (CurrentCorpse->GetY() - sender->GetY())) +
((CurrentCorpse->GetX() - sender->GetX()) * (CurrentCorpse->GetX() - sender->GetX()));
if (CurrentDistance < ClosestDistance) {
ClosestDistance = CurrentDistance;
ClosestCorpse = CurrentCorpse;
}
}
return ClosestCorpse;
}
void EntityList::TryWakeTheDead(Mob *sender, Mob *target, int32 spell_id, uint32 max_distance, uint32 duration, uint32 amount_pets)
{
if (!sender) {
return;
}
std::vector used_corpse_list;
for (int i = 0; i < amount_pets; i++)
{
uint32 CurrentDistance, ClosestDistance = 4294967295u;
Corpse *CurrentCorpse, *ClosestCorpse = nullptr;
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
CurrentCorpse = it->second;
++it;
bool corpse_already_used = false;
for (auto itr = used_corpse_list.begin(); itr != used_corpse_list.end(); ++itr) {
if ((*itr) && (*itr) == CurrentCorpse->GetID()) {
corpse_already_used = true;
continue;
}
}
if (corpse_already_used) {
continue;
}
CurrentDistance = static_cast(sender->CalculateDistance(CurrentCorpse->GetX(), CurrentCorpse->GetY(), CurrentCorpse->GetZ()));
if (max_distance && CurrentDistance > max_distance) {
continue;
}
if (CurrentDistance < ClosestDistance) {
ClosestDistance = CurrentDistance;
ClosestCorpse = CurrentCorpse;
}
}
if (ClosestCorpse) {
sender->WakeTheDead(spell_id, ClosestCorpse, target, duration);
used_corpse_list.push_back(ClosestCorpse->GetID());
}
}
}
void EntityList::ForceGroupUpdate(uint32 gid)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second){
Group *g = nullptr;
g = it->second->GetGroup();
if (g) {
if (g->GetID() == gid) {
database.RefreshGroupFromDB(it->second);
}
}
}
++it;
}
}
void EntityList::SendGroupLeave(uint32 gid, const char *name)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
if (c) {
Group *g = nullptr;
g = c->GetGroup();
if (g) {
if (g->GetID() == gid) {
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer;
strcpy(gj->membername, name);
gj->action = groupActLeave;
strcpy(gj->yourname, c->GetName());
Mob *Leader = g->GetLeader();
if (Leader)
Leader->CastToClient()->GetGroupAAs(&gj->leader_aas);
c->QueuePacket(outapp);
safe_delete(outapp);
g->DelMemberOOZ(name);
if (g->IsLeader(c) && c->IsLFP())
c->UpdateLFP();
}
}
}
++it;
}
}
void EntityList::SendGroupJoin(uint32 gid, const char *name)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second){
Group *g = nullptr;
g = it->second->GetGroup();
if (g) {
if (g->GetID() == gid) {
auto outapp = new EQApplicationPacket(OP_GroupUpdate, sizeof(GroupJoin_Struct));
GroupJoin_Struct* gj = (GroupJoin_Struct*) outapp->pBuffer;
strcpy(gj->membername, name);
gj->action = groupActJoin;
strcpy(gj->yourname, it->second->GetName());
Mob *Leader = g->GetLeader();
if (Leader)
Leader->CastToClient()->GetGroupAAs(&gj->leader_aas);
it->second->QueuePacket(outapp);
safe_delete(outapp);
}
}
}
++it;
}
}
void EntityList::GroupMessage(uint32 gid, const char *from, const char *message)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second) {
Group *g = nullptr;
g = it->second->GetGroup();
if (g) {
if (g->GetID() == gid)
it->second->ChannelMessageSend(from, it->second->GetName(), ChatChannel_Group, Language::CommonTongue, Language::MaxValue, message);
}
}
++it;
}
}
uint16 EntityList::CreateGroundObject(uint32 item_id, const glm::vec4& position, uint32 decay_time)
{
const auto is = database.GetItem(item_id);
if (!is) {
return 0;
}
auto inst = new EQ::ItemInstance(is, is->MaxCharges);
if (!inst) {
return 0;
}
auto object = new Object(inst, position.x, position.y, position.z, position.w, decay_time);
entity_list.AddObject(object, true);
safe_delete(inst);
if (!object) {
return 0;
}
return object->GetID();
}
uint16 EntityList::CreateGroundObjectFromModel(const char *model, const glm::vec4& position, uint8 type, uint32 decay_time)
{
if (!model) {
return 0;
}
auto object = new Object(model, position.x, position.y, position.z, position.w, type);
entity_list.AddObject(object, true);
if (!object) {
return 0;
}
return object->GetID();
}
uint16 EntityList::CreateDoor(const char *model, const glm::vec4& position, uint8 opentype, uint16 size)
{
if (!model)
return 0; // fell through everything, this is bad/incomplete from perl
auto door = new Doors(model, position, opentype, size);
RemoveAllDoors();
zone->LoadZoneDoors();
entity_list.AddDoor(door);
entity_list.RespawnAllDoors();
if (door)
return door->GetEntityID();
return 0; // fell through everything, this is bad/incomplete from perl
}
Mob *EntityList::GetTargetForMez(Mob *caster)
{
if (!caster)
return nullptr;
auto it = mob_list.begin();
//TODO: make this smarter and not mez targets being damaged by dots
while (it != mob_list.end()) {
Mob *d = it->second;
if (d) {
if (d == caster) { //caster can't pick himself
++it;
continue;
}
if (caster->GetTarget() == d) { //caster can't pick his target
++it;
continue;
}
if (!caster->CheckAggro(d)) { //caster can't pick targets that aren't aggroed on himself
++it;
continue;
}
if (DistanceSquared(caster->GetPosition(), d->GetPosition()) > 22250) { //only pick targets within 150 range
++it;
continue;
}
if (!caster->CheckLosFN(d)) { //this is wasteful but can't really think of another way to do it
++it; //that wont have us trying to los the same target every time
continue; //it's only in combat so it's impact should be minimal.. but stil.
}
return d;
}
++it;
}
return nullptr;
}
void EntityList::SendZoneAppearance(Client *c)
{
if (!c) {
return;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *cur = it->second;
if (cur) {
if (cur == c) {
++it;
continue;
}
if (cur->GetAppearance() != eaStanding) {
cur->SendAppearancePacket(AppearanceType::Animation, cur->GetAppearanceValue(cur->GetAppearance()), false, true, c);
}
if (cur->GetSize() != cur->GetBaseSize()) {
cur->SendAppearancePacket(AppearanceType::Size, (uint32) cur->GetSize(), false, true, c);
}
}
++it;
}
}
void EntityList::SendNimbusEffects(Client *c)
{
if (!c) {
return;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *cur = it->second;
if (cur) {
if (cur == c) {
++it;
continue;
}
if (cur->GetNimbusEffect1() != 0) {
cur->SendSpellEffect(cur->GetNimbusEffect1(), 1000, 0, 1, 3000, false, c);
}
if (cur->GetNimbusEffect2() != 0) {
cur->SendSpellEffect(cur->GetNimbusEffect2(), 2000, 0, 1, 3000, false, c);
}
if (cur->GetNimbusEffect3() != 0) {
cur->SendSpellEffect(cur->GetNimbusEffect3(), 3000, 0, 1, 3000, false, c);
}
}
++it;
}
}
void EntityList::SendUntargetable(Client *c)
{
if (!c) {
return;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *cur = it->second;
if (cur) {
if (cur == c) {
++it;
continue;
}
if (!cur->IsTargetable()) {
cur->SendTargetable(false, c);
}
}
++it;
}
}
void EntityList::SendAppearanceEffects(Client *c)
{
if (!c) {
return;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *cur = it->second;
if (cur) {
if (cur == c) {
++it;
continue;
}
cur->SendSavedAppearanceEffects(c);
}
++it;
}
}
void EntityList::SendIllusionWearChange(Client *c)
{
if (!c) {
return;
}
for (auto &e : mob_list) {
auto &mob = e.second;
if (mob) {
if (mob == c) {
continue;
}
mob->SendIllusionWearChange(c);
}
}
}
void EntityList::ZoneWho(Client *c, Who_All_Struct *Who)
{
// This is only called for SoF clients, as regular /who is now handled server-side for that client.
uint32 PacketLength = 0;
uint32 Entries = 0;
uint8 WhomLength = strlen(Who->whom);
std::list client_sub_list;
auto it = client_list.begin();
while (it != client_list.end()) {
Client *ClientEntry = it->second;
++it;
if (ClientEntry) {
if (ClientEntry->GMHideMe(c))
continue;
if ((Who->wrace != 0xFFFFFFFF) && (ClientEntry->GetRace() != Who->wrace))
continue;
if ((Who->wclass != 0xFFFFFFFF) && (ClientEntry->GetClass() != Who->wclass))
continue;
if ((Who->lvllow != 0xFFFFFFFF) && (ClientEntry->GetLevel() < Who->lvllow))
continue;
if ((Who->lvlhigh != 0xFFFFFFFF) && (ClientEntry->GetLevel() > Who->lvlhigh))
continue;
if (Who->guildid != 0xFFFFFFFF) {
if ((Who->guildid == 0xFFFFFFFC) && !ClientEntry->IsTrader())
continue;
if ((Who->guildid == 0xFFFFFFFB) && !ClientEntry->IsBuyer())
continue;
if (Who->guildid != ClientEntry->GuildID())
continue;
}
if (WhomLength && strncasecmp(Who->whom, ClientEntry->GetName(), WhomLength) &&
strncasecmp(guild_mgr.GetGuildName(ClientEntry->GuildID()), Who->whom, WhomLength))
continue;
Entries++;
client_sub_list.push_back(ClientEntry);
PacketLength = PacketLength + strlen(ClientEntry->GetName());
if (strlen(guild_mgr.GetGuildName(ClientEntry->GuildID())) > 0)
PacketLength = PacketLength + strlen(guild_mgr.GetGuildName(ClientEntry->GuildID())) + 2;
}
}
PacketLength = PacketLength + sizeof(WhoAllReturnStruct) + (47 * Entries);
auto outapp = new EQApplicationPacket(OP_WhoAllResponse, PacketLength);
char *Buffer = (char *)outapp->pBuffer;
WhoAllReturnStruct *WARS = (WhoAllReturnStruct *)Buffer;
WARS->id = 0;
WARS->playerineqstring = 5001;
strncpy(WARS->line, "---------------------------", sizeof(WARS->line));
WARS->unknown35 = 0x0a;
WARS->unknown36 = 0;
switch(Entries) {
case 0:
WARS->playersinzonestring = 5029;
break;
case 1:
WARS->playersinzonestring = 5028; // 5028 There is %1 player in EverQuest.
break;
default:
WARS->playersinzonestring = 5036; // 5036 There are %1 players in EverQuest.
}
WARS->unknown44[0] = 0;
WARS->unknown44[1] = 0;
WARS->unknown52 = Entries;
WARS->unknown56 = Entries;
WARS->playercount = Entries;
Buffer += sizeof(WhoAllReturnStruct);
auto sit = client_sub_list.begin();
while (sit != client_sub_list.end()) {
Client *ClientEntry = *sit;
++sit;
if (ClientEntry) {
if (ClientEntry->GMHideMe(c))
continue;
if ((Who->wrace != 0xFFFFFFFF) && (ClientEntry->GetRace() != Who->wrace))
continue;
if ((Who->wclass != 0xFFFFFFFF) && (ClientEntry->GetClass() != Who->wclass))
continue;
if ((Who->lvllow != 0xFFFFFFFF) && (ClientEntry->GetLevel() < Who->lvllow))
continue;
if ((Who->lvlhigh != 0xFFFFFFFF) && (ClientEntry->GetLevel() > Who->lvlhigh))
continue;
if (Who->guildid != 0xFFFFFFFF) {
if ((Who->guildid == 0xFFFFFFFC) && !ClientEntry->IsTrader())
continue;
if ((Who->guildid == 0xFFFFFFFB) && !ClientEntry->IsBuyer())
continue;
if (Who->guildid != ClientEntry->GuildID())
continue;
}
if (WhomLength && strncasecmp(Who->whom, ClientEntry->GetName(), WhomLength) &&
strncasecmp(guild_mgr.GetGuildName(ClientEntry->GuildID()), Who->whom, WhomLength))
continue;
std::string GuildName;
if ((ClientEntry->GuildID() != GUILD_NONE) && (ClientEntry->GuildID() > 0)) {
GuildName = "<";
GuildName += guild_mgr.GetGuildName(ClientEntry->GuildID());
GuildName += ">";
}
uint32 FormatMSGID = 5025; // 5025 %T1[%2 %3] %4 (%5) %6 %7 %8 %9
if (ClientEntry->GetAnon() == 1)
FormatMSGID = 5024; // 5024 %T1[ANONYMOUS] %2 %3
else if (ClientEntry->GetAnon() == 2)
FormatMSGID = 5023; // 5023 %T1[ANONYMOUS] %2 %3 %4
uint32 PlayerClass = Class::None;
uint32 PlayerLevel = 0;
uint32 PlayerRace = Race::Doug;
uint32 ZoneMSGID = 0xFFFFFFFF;
if (ClientEntry->GetAnon()==0) {
PlayerClass = ClientEntry->GetClass();
PlayerLevel = ClientEntry->GetLevel();
PlayerRace = ClientEntry->GetRace();
}
WhoAllPlayerPart1* WAPP1 = (WhoAllPlayerPart1*)Buffer;
WAPP1->FormatMSGID = FormatMSGID;
WAPP1->PIDMSGID = 0xFFFFFFFF;
strcpy(WAPP1->Name, ClientEntry->GetName());
Buffer += sizeof(WhoAllPlayerPart1) + strlen(WAPP1->Name);
WhoAllPlayerPart2* WAPP2 = (WhoAllPlayerPart2*)Buffer;
if (ClientEntry->IsTrader())
WAPP2->RankMSGID = 12315;
else if (ClientEntry->IsBuyer())
WAPP2->RankMSGID = 6056;
else if (ClientEntry->Admin() >= AccountStatus::Steward && ClientEntry->GetGM())
WAPP2->RankMSGID = 12312;
else
WAPP2->RankMSGID = 0xFFFFFFFF;
strcpy(WAPP2->Guild, GuildName.c_str());
Buffer += sizeof(WhoAllPlayerPart2) + strlen(WAPP2->Guild);
WhoAllPlayerPart3* WAPP3 = (WhoAllPlayerPart3*)Buffer;
WAPP3->Unknown80[0] = 0xFFFFFFFF;
if (ClientEntry->IsLD())
WAPP3->Unknown80[1] = 12313; // LinkDead
else
WAPP3->Unknown80[1] = 0xFFFFFFFF;
WAPP3->ZoneMSGID = ZoneMSGID;
WAPP3->Zone = 0;
WAPP3->Class_ = PlayerClass;
WAPP3->Level = PlayerLevel;
WAPP3->Race = PlayerRace;
WAPP3->Account[0] = 0;
Buffer += sizeof(WhoAllPlayerPart3);
WhoAllPlayerPart4* WAPP4 = (WhoAllPlayerPart4*)Buffer;
WAPP4->Unknown100 = 0;
Buffer += sizeof(WhoAllPlayerPart4);
}
}
c->QueuePacket(outapp);
safe_delete(outapp);
}
void EntityList::UnMarkNPC(uint16 ID)
{
// Designed to be called from the Mob destructor, this method calls Group::UnMarkNPC for
// each group to remove the dead mobs entity ID from the groups list of NPCs marked via the
// Group Leadership AA Mark NPC ability.
//
auto it = group_list.begin();
while (it != group_list.end()) {
if (*it)
(*it)->UnMarkNPC(ID);
++it;
}
}
uint32 EntityList::CheckNPCsClose(Mob *center)
{
uint32 count = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC *cur = it->second;
if (!cur || cur == center || cur->IsPet() || cur->GetClass() == Class::LDoNTreasure ||
cur->GetBodyType() == BodyType::NoTarget || cur->GetBodyType() == BodyType::Special) {
++it;
continue;
}
float xDiff = cur->GetX() - center->GetX();
float yDiff = cur->GetY() - center->GetY();
float zDiff = cur->GetZ() - center->GetZ();
float dist = ((xDiff * xDiff) + (yDiff * yDiff) + (zDiff * zDiff));
if (dist <= RuleR(Adventure, DistanceForRescueAccept))
count++;
++it;
}
return count;
}
void EntityList::SignalAllClients(int signal_id)
{
for (const auto& c : client_list) {
if (c.second) {
c.second->Signal(signal_id);
}
}
}
void EntityList::GetMobList(std::list &m_list)
{
m_list.clear();
auto it = mob_list.begin();
while (it != mob_list.end()) {
m_list.push_back(it->second);
++it;
}
}
void EntityList::GetNPCList(std::list &n_list)
{
n_list.clear();
auto it = npc_list.begin();
while (it != npc_list.end()) {
n_list.push_back(it->second);
++it;
}
}
void EntityList::GetClientList(std::list &c_list)
{
c_list.clear();
auto it = client_list.begin();
while (it != client_list.end()) {
c_list.push_back(it->second);
++it;
}
}
void EntityList::GetBotList(std::list &b_list)
{
b_list.clear();
auto it = bot_list.begin();
while (it != bot_list.end()) {
b_list.push_back(it->second);
++it;
}
}
std::vector EntityList::GetBotListByCharacterID(uint32 character_id, uint8 class_id)
{
std::vector client_bot_list;
if (!character_id) {
return client_bot_list;
}
auto it = bot_list.begin();
while (it != bot_list.end()) {
if (it->second->GetOwner() && it->second->GetBotOwnerCharacterID() == character_id && (!class_id || it->second->GetClass() == class_id)) {
client_bot_list.push_back(it->second);
}
++it;
}
return client_bot_list;
}
std::vector EntityList::GetBotListByClientName(std::string client_name, uint8 class_id)
{
std::vector client_bot_list;
if (client_name.empty()) {
return client_bot_list;
}
auto it = bot_list.begin();
while (it != bot_list.end()) {
if (it->second->GetOwner() && Strings::ToLower(it->second->GetOwner()->GetCleanName()) == Strings::ToLower(client_name) && (!class_id || it->second->GetClass() == class_id)) {
client_bot_list.push_back(it->second);
}
++it;
}
return client_bot_list;
}
void EntityList::SignalAllBotsByOwnerCharacterID(uint32 character_id, int signal_id)
{
auto client_bot_list = GetBotListByCharacterID(character_id);
if (client_bot_list.empty()) {
return;
}
for (const auto& b : client_bot_list) {
b->Signal(signal_id);
}
}
void EntityList::SignalAllBotsByOwnerName(std::string owner_name, int signal_id)
{
auto client_bot_list = GetBotListByClientName(owner_name);
if (client_bot_list.empty()) {
return;
}
for (const auto& b : client_bot_list) {
b->Signal(signal_id);
}
}
void EntityList::SignalBotByBotID(uint32 bot_id, int signal_id)
{
auto b = GetBotByBotID(bot_id);
if (b) {
b->Signal(signal_id);
}
}
void EntityList::SignalBotByBotName(std::string bot_name, int signal_id)
{
auto b = GetBotByBotName(bot_name);
if (b) {
b->Signal(signal_id);
}
}
void EntityList::GetCorpseList(std::list &c_list)
{
c_list.clear();
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
c_list.push_back(it->second);
++it;
}
}
void EntityList::GetObjectList(std::list &o_list)
{
o_list.clear();
auto it = object_list.begin();
while (it != object_list.end()) {
o_list.push_back(it->second);
++it;
}
}
void EntityList::GetDoorsList(std::list &o_list)
{
o_list.clear();
auto it = door_list.begin();
while (it != door_list.end()) {
o_list.push_back(it->second);
++it;
}
}
void EntityList::GetSpawnList(std::list &o_list)
{
o_list.clear();
if(zone) {
LinkedListIterator iterator(zone->spawn2_list);
iterator.Reset();
while(iterator.MoreElements())
{
Spawn2 *ent = iterator.GetData();
o_list.push_back(ent);
iterator.Advance();
}
}
}
void EntityList::UpdateQGlobal(uint32 qid, QGlobal newGlobal)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *ent = it->second;
if (ent->IsClient()) {
QGlobalCache *qgc = ent->CastToClient()->GetQGlobals();
if (qgc) {
uint32 char_id = ent->CastToClient()->CharacterID();
if (newGlobal.char_id == char_id && newGlobal.npc_id == 0)
qgc->AddGlobal(qid, newGlobal);
}
} else if (ent->IsNPC()) {
QGlobalCache *qgc = ent->CastToNPC()->GetQGlobals();
if (qgc) {
uint32 npc_id = ent->GetNPCTypeID();
if (newGlobal.npc_id == npc_id)
qgc->AddGlobal(qid, newGlobal);
}
}
++it;
}
}
void EntityList::DeleteQGlobal(std::string name, uint32 npcID, uint32 charID, uint32 zoneID)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *ent = it->second;
if (ent->IsClient()) {
QGlobalCache *qgc = ent->CastToClient()->GetQGlobals();
if (qgc)
qgc->RemoveGlobal(name, npcID, charID, zoneID);
} else if (ent->IsNPC()) {
QGlobalCache *qgc = ent->CastToNPC()->GetQGlobals();
if (qgc)
qgc->RemoveGlobal(name, npcID, charID, zoneID);
}
++it;
}
}
void EntityList::SendFindableNPCList(Client *c)
{
if (!c) {
return;
}
static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
auto b = (FindableNPC_Struct*) p.pBuffer;
b->Unknown109 = 0x16;
b->Unknown110 = 0x06;
b->Unknown111 = 0x24;
b->Action = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second) {
NPC *n = it->second;
if (n->IsFindable()) {
b->EntityID = n->GetID();
strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name));
strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName));
b->Race = n->GetRace();
b->Class = n->GetClass();
c->QueuePacket(&p);
}
}
++it;
}
}
void EntityList::UpdateFindableNPCState(NPC *n, bool Remove)
{
if (!n || !n->IsFindable()) {
return;
}
static EQApplicationPacket p(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
auto b = (FindableNPC_Struct *) p.pBuffer;
b->Unknown109 = 0x16;
b->Unknown110 = 0x06;
b->Unknown111 = 0x24;
b->Action = Remove ? 1 : 0;
b->EntityID = n->GetID();
strn0cpy(b->Name, n->GetCleanName(), sizeof(b->Name));
strn0cpy(b->LastName, n->GetLastName(), sizeof(b->LastName));
b->Race = n->GetRace();
b->Class = n->GetClass();
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
if (c && (c->ClientVersion() >= EQ::versions::ClientVersion::SoD)) {
c->QueuePacket(&p);
}
++it;
}
}
void EntityList::HideCorpses(Client *c, uint8 CurrentMode, uint8 NewMode)
{
if (!c) {
return;
}
if (NewMode == HideCorpseNone) {
SendZoneCorpses(c);
return;
}
Group *g = nullptr;
if (NewMode == HideCorpseAllButGroup) {
g = c->GetGroup();
if (!g) {
NewMode = HideCorpseAll;
}
}
auto it = corpse_list.begin();
while (it != corpse_list.end()) {
Corpse *b = it->second;
if (b && (b->GetCharID() != c->CharacterID())) {
if ((NewMode == HideCorpseAll) || ((NewMode == HideCorpseNPC) && (b->IsNPCCorpse()))) {
EQApplicationPacket outapp;
b->CreateDespawnPacket(&outapp, false);
c->QueuePacket(&outapp);
} else if(NewMode == HideCorpseAllButGroup) {
if (!g->IsGroupMember(b->GetOwnerName())) {
EQApplicationPacket outapp;
b->CreateDespawnPacket(&outapp, false);
c->QueuePacket(&outapp);
} else if((CurrentMode == HideCorpseAll)) {
EQApplicationPacket outapp;
b->CreateSpawnPacket(&outapp);
c->QueuePacket(&outapp);
}
}
}
++it;
}
}
void EntityList::AddLootToNPCS(uint32 item_id, uint32 count)
{
if (count == 0)
return;
int npc_count = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (!it->second->IsPet()
&& it->second->GetClass() != Class::LDoNTreasure
&& it->second->GetBodyType() != BodyType::NoTarget
&& it->second->GetBodyType() != BodyType::NoTarget2
&& it->second->GetBodyType() != BodyType::Special)
npc_count++;
++it;
}
if (npc_count == 0)
return;
auto npcs = new NPC *[npc_count];
auto counts = new int[npc_count];
auto marked = new bool[npc_count];
memset(counts, 0, sizeof(int) * npc_count);
memset(marked, 0, sizeof(bool) * npc_count);
int i = 0;
it = npc_list.begin();
while (it != npc_list.end()) {
if (!it->second->IsPet()
&& it->second->GetClass() != Class::LDoNTreasure
&& it->second->GetBodyType() != BodyType::NoTarget
&& it->second->GetBodyType() != BodyType::NoTarget2
&& it->second->GetBodyType() != BodyType::Special)
npcs[i++] = it->second;
++it;
}
while (count > 0) {
std::vector selection;
selection.reserve(npc_count);
for (int j = 0; j < npc_count; ++j)
selection.push_back(j);
while (!selection.empty() && count > 0) {
int k = zone->random.Int(0, selection.size() - 1);
counts[selection[k]]++;
count--;
selection.erase(selection.begin() + k);
}
}
for (int j = 0; j < npc_count; ++j)
if (counts[j] > 0)
for (int k = 0; k < counts[j]; ++k)
npcs[j]->AddItem(item_id, 1);
safe_delete_array(npcs);
safe_delete_array(counts);
safe_delete_array(marked);
}
void EntityList::CameraEffect(uint32 duration, float intensity)
{
auto outapp = new EQApplicationPacket(OP_CameraEffect, sizeof(Camera_Struct));
Camera_Struct* cs = (Camera_Struct*) outapp->pBuffer;
cs->duration = duration; // Duration in milliseconds
cs->intensity = intensity;
entity_list.QueueClients(0, outapp);
safe_delete(outapp);
}
NPC *EntityList::GetClosestBanker(Mob *sender, uint32 &distance)
{
if (!sender)
return nullptr;
distance = 4294967295u;
NPC *nc = nullptr;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->GetClass() == Class::Banker) {
uint32 nd = ((it->second->GetY() - sender->GetY()) * (it->second->GetY() - sender->GetY())) +
((it->second->GetX() - sender->GetX()) * (it->second->GetX() - sender->GetX()));
if (nd < distance){
distance = nd;
nc = it->second;
}
}
++it;
}
return nc;
}
void EntityList::ExpeditionWarning(uint32 minutes_left)
{
auto outapp = new EQApplicationPacket(OP_DzExpeditionEndsWarning, sizeof(ExpeditionExpireWarning));
ExpeditionExpireWarning *ew = (ExpeditionExpireWarning*)outapp->pBuffer;
ew->minutes_remaining = minutes_left;
auto it = client_list.begin();
while (it != client_list.end()) {
it->second->MessageString(Chat::Yellow, DZ_MINUTES_REMAIN, itoa((int)minutes_left));
it->second->QueuePacket(outapp);
++it;
}
safe_delete(outapp);
}
Mob *EntityList::GetClosestMobByBodyType(Mob *sender, uint8 BodyType, bool skip_client_pets)
{
if (!sender)
return nullptr;
uint32 CurrentDistance, ClosestDistance = 4294967295u;
Mob *CurrentMob, *ClosestMob = nullptr;
auto it = mob_list.begin();
while (it != mob_list.end()) {
CurrentMob = it->second;
++it;
if (CurrentMob->GetBodyType() != BodyType)
continue;
// Do not detect client pets
if (skip_client_pets && CurrentMob->IsPet() && CurrentMob->IsPetOwnerOfClientBot())
continue;
CurrentDistance = ((CurrentMob->GetY() - sender->GetY()) * (CurrentMob->GetY() - sender->GetY())) +
((CurrentMob->GetX() - sender->GetX()) * (CurrentMob->GetX() - sender->GetX()));
if (CurrentDistance < ClosestDistance) {
ClosestDistance = CurrentDistance;
ClosestMob = CurrentMob;
}
}
return ClosestMob;
}
void EntityList::GetTargetsForConeArea(Mob *start, float min_radius, float radius, float height, int pcnpc, std::list &m_list)
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
Mob *ptr = it->second;
if (ptr == start) {
++it;
continue;
}
// check PC/NPC only flag 1 = PCs, 2 = NPCs
if (pcnpc == 1 && !ptr->IsClient() && !ptr->IsMerc() && !ptr->IsBot()) {
++it;
continue;
} else if (pcnpc == 2 && (ptr->IsClient() || ptr->IsMerc() || ptr->IsBot())) {
++it;
continue;
}
if (ptr->IsClient() && !ptr->CastToClient()->ClientFinishedLoading()) {
++it;
continue;
}
if (ptr->IsAura() || ptr->IsTrap()) {
++it;
continue;
}
float x_diff = ptr->GetX() - start->GetX();
float y_diff = ptr->GetY() - start->GetY();
float z_diff = ptr->GetZ() - start->GetZ();
x_diff *= x_diff;
y_diff *= y_diff;
z_diff *= z_diff;
if ((x_diff + y_diff) <= (radius * radius) && (x_diff + y_diff) >= (min_radius * min_radius))
if(z_diff <= (height * height))
m_list.push_back(ptr);
++it;
}
}
Client *EntityList::FindCorpseDragger(uint16 CorpseID)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (it->second->IsDraggingCorpse(CorpseID))
return it->second;
++it;
}
return nullptr;
}
std::vector EntityList::GetTargetsForVirusEffect(Mob *spreader, Mob *original_caster, int range, int pcnpc, int32 spell_id)
{
/*
Live Mechanics
Virus spreader does NOT need LOS
There is no max target limit
*/
if (!spreader) {
return {};
}
std::vector spreader_list = {};
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
for (auto &it : spreader->GetCloseMobList(range)) {
Mob *mob = it.second;
if (!mob) {
continue;
}
if (mob == spreader) {
continue;
}
// check PC/NPC only flag 1 = PCs, 2 = NPCs
if (pcnpc == 1 && !mob->IsClient() && !mob->IsMerc() && !mob->IsBot()) {
continue;
}
else if (pcnpc == 2 && (mob->IsClient() || mob->IsMerc() || mob->IsBot())) {
continue;
}
if (mob->IsClient() && !mob->CastToClient()->ClientFinishedLoading()) {
continue;
}
if (mob->IsAura() || mob->IsTrap()) {
continue;
}
// Make sure the target is in range
if (mob->CalculateDistance(spreader->GetX(), spreader->GetY(), spreader->GetZ()) <= range) {
if (!original_caster) {
continue;
}
//Do not allow detrimental spread to anything the original caster couldn't normally attack.
if (is_detrimental_spell && !original_caster->IsAttackAllowed(mob, true)) {
continue;
}
//For non-NPCs, do not allow beneficial spread to anything the original caster could normally attack.
if (!is_detrimental_spell && !original_caster->IsNPC() && original_caster->IsAttackAllowed(mob, true)) {
continue;
}
// If the spreader is an npc and NOT a PET, then spread to other npc controlled mobs that are not pets
if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && !mob->IsPet() && !mob->IsTempPet()) {
spreader_list.push_back(mob);
}
// If the spreader is an npc and NOT a PET, then spread to npc controlled pet
else if (spreader->IsNPC() && !spreader->IsPet() && !spreader->IsTempPet() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
// If the spreader is an npc controlled PET it can spread to any other npc or an npc controlled pet
else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && spreader->IsPetOwnerNPC()) {
if (mob->IsNPC() && (!mob->IsPet() || !mob->IsTempPet())) {
spreader_list.push_back(mob);
}
else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
}
// if the spreader is anything else(bot, pet, etc) then it should spread to everything but non client controlled npcs
else if (!spreader->IsNPC() && !mob->IsNPC()) {
spreader_list.push_back(mob);
}
// if spreader is not an NPC, and Target is an NPC, then spread to non-NPC controlled pets
else if (!spreader->IsNPC() && mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
// if spreader is a non-NPC controlled pet we need to determine appropriate targets(pet to client, pet to pet, pet to bot, etc)
else if (spreader->IsNPC() && (spreader->IsPet() || spreader->IsTempPet()) && !spreader->IsPetOwnerNPC()) {
//Spread to non-NPCs
if (!mob->IsNPC()) {
spreader_list.push_back(mob);
}
//Spread to other non-NPC Pets
else if (mob->IsNPC() && (mob->IsPet() || mob->IsTempPet()) && !mob->IsPetOwnerNPC()) {
spreader_list.push_back(mob);
}
}
}
}
return spreader_list;
}
void EntityList::StopMobAI()
{
for (auto &mob : mob_list) {
mob.second->AI_Stop();
mob.second->AI_ShutDown();
}
}
void EntityList::SendAlternateAdvancementStats() {
for (auto &c : client_list) {
c.second->Message(Chat::White, "Reloading AA");
c.second->ReloadExpansionProfileSetting();
if (!database.LoadAlternateAdvancement(c.second)) {
c.second->Message(Chat::Red, "Error loading alternate advancement character data");
}
c.second->SendClearPlayerAA();
c.second->SendAlternateAdvancementTable();
c.second->SendAlternateAdvancementStats();
c.second->SendAlternateAdvancementPoints();
}
}
void EntityList::ReloadMerchants() {
for (auto it = npc_list.begin();it != npc_list.end(); ++it) {
NPC *cur = it->second;
if (cur->MerchantType != 0) {
zone->LoadNewMerchantData(cur->MerchantType);
}
}
}
/**
* If we have a distance requested that is greater than our scanning distance
* then we return the full list
*
* See comments @EntityList::ScanCloseMobs for system explanation
*/
std::unordered_map &EntityList::GetCloseMobList(Mob *mob, float distance)
{
if (distance <= RuleI(Range, MobCloseScanDistance)) {
return mob->m_close_mobs;
}
return mob_list;
}
void EntityList::GateAllClientsToSafeReturn()
{
DynamicZone* dz = zone ? zone->GetDynamicZone() : nullptr;
for (const auto& client_list_iter : client_list)
{
if (client_list_iter.second)
{
// falls back to gating clients to bind if dz invalid
client_list_iter.second->GoToDzSafeReturnOrBind(dz);
}
}
}
int EntityList::MovePlayerCorpsesToGraveyard(bool force_move_from_instance)
{
if (!zone)
{
return 0;
}
int moved_count = 0;
for (auto it = corpse_list.begin(); it != corpse_list.end();)
{
bool moved = false;
if (it->second && it->second->IsPlayerCorpse())
{
if (zone->HasGraveyard())
{
moved = it->second->MovePlayerCorpseToGraveyard();
}
else if (force_move_from_instance && zone->GetInstanceID() != 0)
{
moved = it->second->MovePlayerCorpseToNonInstance();
}
}
if (moved)
{
safe_delete(it->second);
free_ids.push(it->first);
it = corpse_list.erase(it);
++moved_count;
}
else
{
++it;
}
}
return moved_count;
}
void EntityList::DespawnGridNodes(int32 grid_id) {
for (auto m : mob_list) {
Mob *mob = m.second;
if (
mob->IsNPC() &&
mob->GetRace() == Race::Node &&
mob->EntityVariableExists("grid_id") &&
Strings::ToInt(mob->GetEntityVariable("grid_id")) == grid_id)
{
mob->Depop();
}
}
}
void EntityList::Marquee(uint32 type, std::string message, uint32 duration) {
for (const auto& c : client_list) {
if (c.second) {
c.second->SendMarqueeMessage(type, message, duration);
}
}
}
void EntityList::Marquee(
uint32 type,
uint32 priority,
uint32 fade_in,
uint32 fade_out,
uint32 duration,
std::string message
) {
for (const auto& c : client_list) {
if (c.second) {
c.second->SendMarqueeMessage(type, priority, fade_in, fade_out, duration, message);
}
}
}
std::vector EntityList::GetFilteredEntityList(Mob* sender, uint32 distance, EntityFilterType filter_type)
{
std::vector l;
if (!sender) {
return l;
}
const auto squared_distance = (distance * distance);
const auto position = sender->GetPosition();
for (auto &m: mob_list) {
if (!m.second) {
continue;
}
if (m.second == sender) {
continue;
}
if (
distance &&
DistanceSquaredNoZ(
position,
m.second->GetPosition()
) > squared_distance
) {
continue;
}
if (
(filter_type == EntityFilterType::Bots && !m.second->IsBot()) ||
(filter_type == EntityFilterType::Clients && !m.second->IsClient()) ||
(filter_type == EntityFilterType::NPCs && !m.second->IsNPC())
) {
continue;
}
l.push_back(m.second);
}
return l;
}
void EntityList::DamageArea(
Mob* sender,
int64 damage,
uint32 distance,
EntityFilterType filter_type,
bool is_percentage
) {
if (!sender) {
return;
}
if (damage <= 0) {
return;
}
const auto& l = GetFilteredEntityList(sender, distance, filter_type);
for (const auto& e : l) {
if (is_percentage) {
const auto damage_percentage = EQ::Clamp(damage, static_cast(1), static_cast(100));
const auto total_damage = (e->GetMaxHP() / 100) * damage_percentage;
e->Damage(sender, total_damage, SPELL_UNKNOWN, EQ::skills::SkillEagleStrike);
} else {
e->Damage(sender, damage, SPELL_UNKNOWN, EQ::skills::SkillEagleStrike);
}
}
}
std::vector EntityList::GetNPCsByIDs(std::vector npc_ids)
{
std::vector v;
for (const auto& e : GetNPCList()) {
const auto& n = std::find(npc_ids.begin(), npc_ids.end(), e.second->GetNPCTypeID());
if (e.second) {
if (n != npc_ids.end()) {
continue;
}
v.emplace_back(e.second);
}
}
return v;
}
std::vector EntityList::GetExcludedNPCsByIDs(std::vector npc_ids)
{
std::vector v;
for (const auto& e : GetNPCList()) {
const auto& n = std::find(npc_ids.begin(), npc_ids.end(), e.second->GetNPCTypeID());
if (e.second) {
if (n == npc_ids.end()) {
continue;
}
v.emplace_back(e.second);
}
}
return v;
}
void EntityList::SendMerchantEnd(Mob* merchant)
{
for (const auto& e : client_list) {
Client* c = e.second;
if (!c) {
continue;
}
if (c->GetMerchantSessionEntityID() == merchant->GetID()) {
c->SendMerchantEnd();
}
}
}
void EntityList::SendMerchantInventory(Mob* m, int32 slot_id, bool is_delete)
{
if (!m || !m->IsNPC()) {
return;
}
for (const auto& e : client_list) {
Client* c = e.second;
if (!c) {
continue;
}
if (c->GetMerchantSessionEntityID() == m->GetID()) {
if (!is_delete) {
c->BulkSendMerchantInventory(m->CastToNPC()->MerchantType, m->GetNPCTypeID());
} else {
auto app = new EQApplicationPacket(OP_ShopDelItem, sizeof(Merchant_DelItem_Struct));
auto d = (Merchant_DelItem_Struct*)app->pBuffer;
d->itemslot = slot_id;
d->npcid = m->GetID();
d->playerid = c->GetID();
app->priority = 6;
c->QueuePacket(app);
safe_delete(app);
}
}
}
return;
}
void EntityList::RestoreCorpse(NPC *npc, uint32_t decay_time)
{
uint16 corpse_id = npc->GetID();
npc->Death(npc, npc->GetHP() + 1, SPELL_UNKNOWN, EQ::skills::SkillHandtoHand);
auto c = entity_list.GetCorpseByID(corpse_id);
if (c) {
c->UnLock();
c->SetDecayTimer(decay_time);
}
}
void EntityList::CheckToClearTraderAndBuyerTables()
{
if (zone->GetZoneID() == Zones::BAZAAR) {
TraderRepository::DeleteWhere(
database,
fmt::format(
"`char_zone_id` = {} AND `char_zone_instance_id` = {}", zone->GetZoneID(), zone->GetInstanceID()
)
);
BuyerRepository::DeleteBuyers(database, zone->GetZoneID(), zone->GetInstanceID());
LogTradingDetail(
"Removed trader and buyer entries for Zone ID [{}] and Instance ID [{}]",
zone->GetZoneID(),
zone->GetInstanceID()
);
}
}