eqemu-server/zone/entity.cpp
Kinglykrab 7ea77ee027
[Bots] Add Quest API Support for Limits. (#2522)
* [Bots] Add Quest API Support for Limits.

# Perl
- Add `$client->GetBotCreationLimit()` to Perl.
- Add `$client->GetBotCreationLimit(class_id)` to Perl.
- Add `$client->GetBotRequiredLevel()` to Perl.
- Add `$client->GetBotRequiredLevel(class_id)` to Perl.
- Add `$client->GetBotSpawnLimit()` to Perl.
- Add `$client->GetBotSpawnLimit(class_id)` to Perl.
- Add `$client->SetBotCreationLimit(creation_limit)` to Perl.
- Add `$client->SetBotCreationLimit(creation_limit, class_id)` to Perl.
- Add `$client->SetBotRequiredLevel(required_level)` to Perl.
- Add `$client->SetBotRequiredLevel(required_level, class_id)` to Perl.
- Add `$client->SetBotSpawnLimit(spawn_limit)` to Perl.
- Add `$client->SetBotSpawnLimit(spawn_limit, class_id)` to Perl.
- Add `$entity_list->GetBotListByCharacterID(character_id, class_id)` to Perl.

# Lua
- Add `client:GetBotCreationLimit()` to Lua.
- Add `client:GetBotCreationLimit(class_id)` to Lua.
- Add `client:GetBotRequiredLevel()` to Lua.
- Add `client:GetBotRequiredLevel(class_id)` to Lua.
- Add `client:GetBotSpawnLimit()` to Lua.
- Add `client:GetBotSpawnLimit(class_id)` to Lua.
- Add `client:SetBotCreationLimit(creation_limit)` to Lua.
- Add `client:SetBotCreationLimit(creation_limit, class_id)` to Lua.
- Add `client:SetBotRequiredLevel(required_level)` to Lua.
- Add `client:SetBotRequiredLevel(required_level, class_id)` to Lua.
- Add `client:SetBotSpawnLimit(spawn_limit)` to Lua.
- Add `client:SetBotSpawnLimit(spawn_limit, class_id)` to Lua.
- Add `entity_list:GetBotListByCharacterID(character_id, class_id)` to Lua.

# Notes
- Allows operators to set creation and spawn limits based on class, as well as required level.
- Using the class-inspecific methods sets the global limit or required level.
- Global limits are checked prior to class-specific limits and if they are not met, creation or spawn is disallowed.
- Modified preexisting Quest API to make use of this new stuff under the hood.

* Update bot_command.cpp

* Add client bot file.
2022-11-16 19:51:13 -05:00

5833 lines
132 KiB
C++

/* EQEMu: Everquest Server Emulator
Copyright (C) 2001-2003 EQEMu Development Team (http://eqemulator.net)
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; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY except by those people which sell it, which
are required to give you total support for your newly bought product;
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "../common/global_define.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <iostream>
#ifdef _WINDOWS
#include <process.h>
#else
#include <pthread.h>
#include "../common/unix.h"
#endif
#include "../common/features.h"
#include "../common/guilds.h"
#include "entity.h"
#include "dynamic_zone.h"
#include "guild_mgr.h"
#include "petitions.h"
#include "quest_parser_collection.h"
#include "raids.h"
#include "string_ids.h"
#include "worldserver.h"
#include "water_map.h"
#include "npc_scale_manager.h"
#include "../common/say_link.h"
#include "dialogue_window.h"
#ifdef _WINDOWS
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
#ifdef BOTS
#include "bot.h"
#endif
extern Zone *zone;
extern volatile bool is_zone_loaded;
extern WorldServer worldserver;
extern uint32 numclients;
extern PetitionList petition_list;
extern char errorname[32];
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<Client *>(this);
}
NPC *Entity::CastToNPC()
{
#ifdef _EQDEBUG
if (!IsNPC()) {
LogError("CastToNPC error (Not NPC)");
return 0;
}
#endif
return static_cast<NPC *>(this);
}
Mob *Entity::CastToMob()
{
#ifdef _EQDEBUG
if (!IsMob()) {
std::cout << "CastToMob error" << std::endl;
return 0;
}
#endif
return static_cast<Mob *>(this);
}
Merc *Entity::CastToMerc()
{
#ifdef _EQDEBUG
if (!IsMerc()) {
std::cout << "CastToMerc error" << std::endl;
return 0;
}
#endif
return static_cast<Merc *>(this);
}
Trap *Entity::CastToTrap()
{
#ifdef DEBUG
if (!IsTrap()) {
return 0;
}
#endif
return static_cast<Trap *>(this);
}
Corpse *Entity::CastToCorpse()
{
#ifdef _EQDEBUG
if (!IsCorpse()) {
std::cout << "CastToCorpse error" << std::endl;
return 0;
}
#endif
return static_cast<Corpse *>(this);
}
Object *Entity::CastToObject()
{
#ifdef _EQDEBUG
if (!IsObject()) {
std::cout << "CastToObject error" << std::endl;
return 0;
}
#endif
return static_cast<Object *>(this);
}
/*Group* Entity::CastToGroup() {
#ifdef _EQDEBUG
if(!IsGroup()) {
std::cout << "CastToGroup error" << std::endl;
return 0;
}
#endif
return static_cast<Group*>(this);
}*/
Doors *Entity::CastToDoors()
{
return static_cast<Doors *>(this);
}
Beacon *Entity::CastToBeacon()
{
return static_cast<Beacon *>(this);
}
Encounter *Entity::CastToEncounter()
{
return static_cast<Encounter *>(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<const Client *>(this);
}
const NPC *Entity::CastToNPC() const
{
#ifdef _EQDEBUG
if (!IsNPC()) {
std::cout << "CastToNPC error" << std::endl;
return 0;
}
#endif
return static_cast<const NPC *>(this);
}
const Mob *Entity::CastToMob() const
{
#ifdef _EQDEBUG
if (!IsMob()) {
std::cout << "CastToMob error" << std::endl;
return 0;
}
#endif
return static_cast<const Mob *>(this);
}
const Merc *Entity::CastToMerc() const
{
#ifdef _EQDEBUG
if (!IsMerc()) {
std::cout << "CastToMerc error" << std::endl;
return 0;
}
#endif
return static_cast<const Merc *>(this);
}
const Trap *Entity::CastToTrap() const
{
#ifdef DEBUG
if (!IsTrap()) {
return 0;
}
#endif
return static_cast<const Trap *>(this);
}
const Corpse *Entity::CastToCorpse() const
{
#ifdef _EQDEBUG
if (!IsCorpse()) {
std::cout << "CastToCorpse error" << std::endl;
return 0;
}
#endif
return static_cast<const Corpse *>(this);
}
const Object *Entity::CastToObject() const
{
#ifdef _EQDEBUG
if (!IsObject()) {
std::cout << "CastToObject error" << std::endl;
return 0;
}
#endif
return static_cast<const Object *>(this);
}
const Doors *Entity::CastToDoors() const
{
return static_cast<const Doors *>(this);
}
const Beacon* Entity::CastToBeacon() const
{
return static_cast<const Beacon *>(this);
}
const Encounter* Entity::CastToEncounter() const
{
return static_cast<const Encounter *>(this);
}
#ifdef BOTS
Bot *Entity::CastToBot()
{
#ifdef _EQDEBUG
if (!IsBot()) {
std::cout << "CastToBot error" << std::endl;
return 0;
}
#endif
return static_cast<Bot *>(this);
}
const Bot *Entity::CastToBot() const
{
#ifdef _EQDEBUG
if (!IsBot()) {
std::cout << "CastToBot error" << std::endl;
return 0;
}
#endif
return static_cast<const Bot *>(this);
}
#endif
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.insert(std::pair<uint16, Client *>(client->GetID(), client));
mob_list.insert(std::pair<uint16, Mob *>(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<Group *>::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();
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
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()
{
#ifdef IDLE_WHEN_EMPTY
if (numclients < 1)
return;
#endif
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();
#ifdef IDLE_WHEN_EMPTY
static int old_client_count=0;
static Timer *mob_settle_timer = new Timer();
if (numclients == 0 && old_client_count > 0 &&
RuleI(Zone, SecondsBeforeIdle) > 0) {
// Start Timer to allow any mobs that chased chars from zone
// to return home.
mob_settle_timer->Start(RuleI(Zone, SecondsBeforeIdle) * 1000);
}
old_client_count = numclients;
// Disable settle timer if someone zones into empty zone
if (numclients > 0 || mob_settle_timer->Check()) {
mob_settle_timer->Disable();
}
Spawn2* s2 = mob->CastToNPC()->respawn2;
// Perform normal mob processing if any of these are true:
// -- zone is not empty
// -- a quest has turned it on for this zone while zone is idle
// -- 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 (zone->process_mobs_while_empty || numclients > 0 ||
(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();
#endif
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);
}
#ifdef BOTS
else if(mob->IsBot()) {
entity_list.RemoveBot(id);
}
#endif
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) {
LogError("About to delete a client still in a group");
g->DelMember(mob);
}
Raid *r = entity_list.GetRaidByClient(mob->CastToClient());
if(r) {
LogError("About to delete a client still in a raid");
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);
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
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.insert(std::pair<uint16, Corpse *>(corpse->GetID(), corpse));
if (!corpse_timer.Enabled())
corpse_timer.Start();
}
void EntityList::AddNPC(NPC *npc, bool SendSpawnPacket, bool dontqueue)
{
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());
}
}
parse->EventNPC(EVENT_SPAWN, npc, nullptr, "", 0);
uint32 emoteid = npc->GetEmoteID();
if (emoteid != 0)
npc->DoNPCEmote(EQ::constants::EmoteEventTypes::OnSpawn, emoteid);
npc->SetSpawned();
if (SendSpawnPacket) {
if (dontqueue) { // aka, SEND IT NOW BITCH!
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); // Not working on player newspawns, so it's safe to use a ForWho of 0
AddToSpawnQueue(npc->GetID(), &ns);
safe_delete(ns);
}
if (npc->IsFindable())
UpdateFindableNPCState(npc, false);
}
npc_list.insert(std::pair<uint16, NPC *>(npc->GetID(), npc));
mob_list.insert(std::pair<uint16, Mob *>(npc->GetID(), npc));
entity_list.ScanCloseMobs(npc->close_mobs, npc, true);
/* Zone controller process EVENT_SPAWN_ZONE */
npc->DispatchZoneControllerEvent(EVENT_SPAWN_ZONE, npc, "", 0, nullptr);
/**
* Set whether NPC was spawned in or out of water
*/
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.insert(std::pair<uint16, Merc *>(merc->GetID(), merc));
mob_list.insert(std::pair<uint16, Mob *>(merc->GetID(), merc));
}
}
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.insert(std::pair<uint16, Object *>(obj->GetID(), obj));
if (!object_timer.Enabled())
object_timer.Start();
}
void EntityList::AddDoor(Doors *door)
{
door->SetEntityID(GetFreeID());
door_list.insert(std::pair<uint16, Doors *>(door->GetEntityID(), door));
if (!door_timer.Enabled())
door_timer.Start();
}
void EntityList::AddTrap(Trap *trap)
{
trap->SetID(GetFreeID());
trap_list.insert(std::pair<uint16, Trap *>(trap->GetID(), trap));
if (!trap_timer.Enabled())
trap_timer.Start();
}
void EntityList::AddBeacon(Beacon *beacon)
{
beacon->SetID(GetFreeID());
beacon_list.insert(std::pair<uint16, Beacon *>(beacon->GetID(), beacon));
}
void EntityList::AddEncounter(Encounter *encounter)
{
encounter->SetID(GetFreeID());
encounter_list.insert(std::pair<uint16, Encounter *>(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<NewSpawn_Struct *> 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);
}
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<uint32> npc_ids)
{
return CountSpawnedNPCs(npc_ids) != 0;
}
uint32 EntityList::CountSpawnedNPCs(std::vector<uint32> 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, 100, 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<uint32>(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() == MINOR_ILL_OBJ || spawn->GetRace() == 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 == MINOR_ILL_OBJ || 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(AT_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);
}
}
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(IMMUNE_FADING_MEMORIES))
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);
}
}
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)
{
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
if (c->GetGM() || RuleB(Spells, AlwaysSendTargetsBuffs)) {
Send = true;
} else if (c->IsRaidGrouped()) {
Raid *raid = c->GetRaid();
if (!raid)
continue;
uint32 gid = raid->GetGroup(c);
if (gid > 11 || raid->GroupCount(gid) < 3)
continue;
if (raid->GetLeadershipAA(groupAAInspectBuffs, gid))
Send = true;
} else {
Group *group = c->GetGroup();
if (!group || group->GroupCount() < 3)
continue;
if (group->GetLeadershipAA(groupAAInspectBuffs))
Send = true;
}
} 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);
}
}
/**
* @param sender
* @param app
* @param ignore_sender
* @param distance
* @param skipped_mob
* @param is_ack_required
* @param filter
*/
void EntityList::QueueCloseClients(
Mob *sender,
const EQApplicationPacket *app,
bool ignore_sender,
float distance,
Mob *skipped_mob,
bool is_ack_required,
eqFilterType filter
)
{
if (sender == nullptr) {
QueueClients(sender, app, ignore_sender);
return;
}
if (distance <= 0) {
distance = 600;
}
float distance_squared = distance * distance;
for (auto &e : GetCloseMobList(sender, 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::QueueManaged(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) {
std::vector<std::any> args;
args.push_back(winner);
args.push_back(loser);
parse->EventPlayer(EVENT_DUEL_WIN, winner->CastToClient(), loser->GetName(), loser->CastToClient()->CharacterID(), &args);
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 *checkname)
{
auto it = client_list.begin();
while (it != client_list.end()) {
if (strcasecmp(it->second->GetName(), checkname) == 0)
return it->second;
++it;
}
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;
}
#ifdef BOTS
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<Bot*> bots_in_range;
for (const auto& b : bot_list) {
if (
b != exclude_bot &&
(
is_whole_zone ||
DistanceSquared(static_cast<glm::vec3>(b->GetPosition()), location) <= distance_squared
)
) {
bots_in_range.push_back(b);
}
}
if (bots_in_range.empty()) {
return nullptr;
}
return bots_in_range[zone->random.Int(0, bots_in_range.size() - 1)];
}
#endif
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<Client*> clients_in_range;
for (const auto& client : client_list) {
if (
client.second != exclude_client &&
(
is_whole_zone ||
DistanceSquared(static_cast<glm::vec3>(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<NPC*> npcs_in_range;
for (const auto& npc : npc_list) {
if (
npc.second != exclude_npc &&
(
is_whole_zone ||
DistanceSquared(static_cast<glm::vec3>(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<Mob*> mobs_in_range;
for (const auto& mob : mob_list) {
if (
mob.second != exclude_mob &&
(
is_whole_zone ||
DistanceSquared(static_cast<glm::vec3>(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<Spawn2 *> 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<Group *>::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->IsGroupMember(mob))
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
Group *EntityList::GetGroupByLeaderName(const char *leader)
{
std::list<Group *>::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if (!strcmp((*iterator)->GetLeaderName(), leader))
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
Group *EntityList::GetGroupByID(uint32 group_id)
{
std::list<Group *>::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->GetID() == group_id)
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
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 <Group *>::iterator iterator;
iterator = group_list.begin();
while (iterator != group_list.end()) {
if ((*iterator)->IsGroupMember(client->CastToMob()))
return *iterator;
++iterator;
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
return nullptr;
}
Raid *EntityList::GetRaidByLeaderName(const char *leader)
{
std::list<Raid *>::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
if ((*iterator)->GetLeader())
if(strcmp((*iterator)->GetLeader()->GetName(), leader) == 0)
return *iterator;
++iterator;
}
return nullptr;
}
Raid *EntityList::GetRaidByID(uint32 id)
{
std::list<Raid *>::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<Raid *>::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
for (auto &member : (*iterator)->members) {
if (member.member) {
if (member.member == client) {
client->p_raid_instance = *iterator;
return *iterator;
}
}
}
++iterator;
}
return nullptr;
}
Raid *EntityList::GetRaidByMob(Mob *mob)
{
std::list<Raid *>::iterator iterator;
iterator = raid_list.begin();
while (iterator != raid_list.end()) {
for(int x = 0; x < MAX_RAID_MEMBERS; x++) {
// TODO: Implement support for Mob objects in Raid class
/*if((*iterator)->members[x].member){
if((*iterator)->members[x].member == mob)
return *iterator;
}*/
}
++iterator;
}
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 (!guild_mgr.CheckPermission(guild_id, client->GuildRank(), GUILD_HEAR))
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(Mob *sender, const EQApplicationPacket *app,
bool ignore_sender, 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);
++it;
}
}
void EntityList::QueueClientsGuildBankItemUpdate(const GuildBankItemUpdate_Struct *gbius, uint32 GuildID)
{
auto outapp = new EQApplicationPacket(OP_GuildBank, sizeof(GuildBankItemUpdate_Struct));
GuildBankItemUpdate_Struct *outgbius = (GuildBankItemUpdate_Struct*)outapp->pBuffer;
memcpy(outgbius, gbius, sizeof(GuildBankItemUpdate_Struct));
const EQ::ItemData *Item = database.GetItem(gbius->ItemID);
auto it = client_list.begin();
while (it != client_list.end()) {
Client *client = it->second;
if (client->IsInGuild(GuildID)) {
if (Item && (gbius->Permissions == GuildBankPublicIfUsable))
outgbius->Useable = Item->IsEquipable(client->GetBaseRace(), client->GetBaseClass());
client->QueuePacket(outapp);
}
++it;
}
safe_delete(outapp);
}
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()) {
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::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);
}
#if EQDEBUG >= 5
CheckGroupList (__FILE__, __LINE__);
#endif
}
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 (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_mob
* @return
*/
bool EntityList::RemoveMob(Mob *delete_mob)
{
if (delete_mob == 0) {
return true;
}
auto it = mob_list.begin();
while (it != mob_list.end()) {
if (it->second == delete_mob) {
safe_delete(it->second);
if (!corpse_list.count(it->first)) {
free_ids.push(it->first);
}
mob_list.erase(it);
return true;
}
++it;
}
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->close_mobs.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 actually 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)
*
* @param close_mobs
* @param scanning_mob
*/
void EntityList::ScanCloseMobs(
std::unordered_map<uint16, Mob *> &close_mobs,
Mob *scanning_mob,
bool add_self_to_other_lists
)
{
float scan_range = RuleI(Range, MobCloseScanDistance) * RuleI(Range, MobCloseScanDistance);
close_mobs.clear();
for (auto &e : mob_list) {
auto mob = e.second;
if (!mob->IsNPC() && !mob->IsClient()) {
continue;
}
if (mob->GetID() <= 0) {
continue;
}
float distance = DistanceSquared(scanning_mob->GetPosition(), mob->GetPosition());
if (distance <= scan_range || mob->GetAggroRange() >= scan_range) {
close_mobs.insert(std::pair<uint16, Mob *>(mob->GetID(), mob));
if (add_self_to_other_lists && scanning_mob->GetID() > 0) {
bool has_mob = false;
for (auto &cm: mob->close_mobs) {
if (scanning_mob->GetID() == cm.first) {
has_mob = true;
break;
}
}
if (!has_mob) {
mob->close_mobs.insert(std::pair<uint16, Mob *>(scanning_mob->GetID(), scanning_mob));
}
}
}
}
LogAIScanClose(
"[{}] Scanning Close List | list_size [{}] moving [{}]",
scanning_mob->GetCleanName(),
close_mobs.size(),
scanning_mob->IsMoving() ? "true" : "false"
);
}
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;
}
bool EntityList::RemoveRaid(uint32 delete_id)
{
auto it = std::find_if(raid_list.begin(), raid_list.end(),
[delete_id](const Raid *a) { return a->GetID() == delete_id; });
if (it == raid_list.end())
return false;
auto raid = *it;
raid_list.erase(it);
safe_delete(raid);
return true;
}
void EntityList::Clear()
{
RemoveAllClients();
entity_list.RemoveAllTraps(); //we can have child npcs so we go first
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;
#ifdef BOTS
// This block of code is necessary to clean up bot objects
else if (entity_list.RemoveBot(id))
return;
#endif //BOTS
else
entity_list.RemoveObject(id);
}
void EntityList::Process()
{
CheckSpawnQueue();
}
void EntityList::CountNPC(uint32 *NPCCount, uint32 *NPCLootCount, uint32 *gmspawntype_count)
{
*NPCCount = 0;
*NPCLootCount = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
(*NPCCount)++;
(*NPCLootCount) += it->second->CountLoot();
if (it->second->GetNPCTypeID() == 0)
(*gmspawntype_count)++;
++it;
}
}
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's pets...
if (own && own->IsClient())
continue;
if (pnpc->IsHorse())
continue;
if (pnpc->IsFindable())
UpdateFindableNPCState(pnpc, true);
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);
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[atoi(&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<strlen(name) && i<sizeof(tmp); i++) {
if (name[i] < '0' || name[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 = petition_list.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 = petition_list.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;
}
void EntityList::WriteEntityIDs()
{
auto it = mob_list.begin();
while (it != mob_list.end()) {
std::cout << "ID: " << it->first << " Name: " << it->second->GetName() << std::endl;
++it;
}
}
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;
}
}
void EntityList::Evade(Mob *who)
{
uint32 flatval = who->GetLevel() * 13;
int amt = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CastToNPC()->CheckAggro(who)) {
amt = it->second->CastToNPC()->GetHateAmount(who);
amt -= flatval;
if (amt > 0)
it->second->CastToNPC()->SetHateAmountOnEnt(who, amt);
else
it->second->CastToNPC()->SetHateAmountOnEnt(who, 0);
}
++it;
}
}
//removes "targ" from all hate lists, including feigned, in the zone
void EntityList::ClearAggro(Mob* targ)
{
Client *c = nullptr;
if (targ->IsClient()) {
c = targ->CastToClient();
}
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second->CheckAggro(targ)) {
if (c) {
c->RemoveXTarget(it->second, false);
}
it->second->RemoveFromHateList(targ);
}
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);
}
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(IMMUNE_FEIGN_DEATH)) {
++it;
continue;
}
if (targ->IsClient()) {
std::vector<std::any> args;
args.push_back(it->second);
int i = parse->EventPlayer(EVENT_FEIGN_DEATH, targ->CastToClient(), "", 0, &args);
if (i != 0) {
++it;
continue;
}
if (it->second->IsNPC()) {
int i = parse->EventNPC(EVENT_FEIGN_DEATH, it->second->CastToNPC(), targ, "", 0);
if (i != 0) {
++it;
continue;
}
}
}
it->second->RemoveFromHateList(targ);
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<std::pair<Mob *, float> > tracking_list;
auto distance = static_cast<float>(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<Mob *, float> &a, const std::pair<Mob *, float> &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<uint32>(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<quest_proximity_event> 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);
if (evt.npc) {
std::vector<std::any> args;
parse->EventNPC(evt.event_id, evt.npc, evt.client, "", 0, &args);
} else {
std::vector<std::any> args;
args.push_back(&evt.area_id);
args.push_back(&evt.area_type);
parse->EventPlayer(evt.event_id, 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<quest_proximity_event> events;
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 (x < a.min_x || x > a.max_x ||
y < a.min_y || y > a.max_y ||
z < a.min_z || 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.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 = nullptr;
evt.npc = n;
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<std::any> args;
args.push_back(&evt.area_id);
args.push_back(&evt.area_type);
parse->EventNPC(evt.event_id, 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;
auto iter = proximity_list.begin();
for (; iter != proximity_list.end(); ++iter) {
NPC *d = (*iter);
NPCProximity *l = d->proximity;
if (l == nullptr || !l->say)
continue;
if (c->GetX() < l->min_x || c->GetX() > l->max_x
|| c->GetY() < l->min_y || c->GetY() > l->max_y
|| c->GetZ() < l->min_z || c->GetZ() > l->max_z)
continue;
parse->EventNPC(EVENT_PROXIMITY_SAY, d, c, Message, language);
}
}
void EntityList::SaveAllClientsTaskState()
{
if (!task_manager) {
return;
}
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)
{
if (!task_manager)
return;
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();
task_manager->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<uint16, SpawnLimitRecord>::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<uint16, SpawnLimitRecord>::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;
}
//check limits on an npc type in a given spawn group, and
//checks limits on the entire zone in one pass.
//returns true if neither limit has been reached
bool EntityList::LimitCheckBoth(uint32 npc_type, uint32 spawngroup_id, int group_count, int type_count)
{
if (group_count < 1 && type_count < 1)
return true;
std::map<uint16, SpawnLimitRecord>::iterator cur,end;
cur = npc_limit_list.begin();
end = npc_limit_list.end();
for (; cur != end; ++cur) {
if (cur->second.npc_type == npc_type) {
type_count--;
if (type_count == 0) {
return false;
}
}
if (cur->second.spawngroup_id == spawngroup_id) {
group_count--;
if (group_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;
}
}
int16 EntityList::CountTempPets(Mob *owner)
{
int16 count = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
NPC* n = it->second;
if (n->GetSwarmInfo()) {
if (n->GetSwarmInfo()->owner_id == owner->GetID()) {
count++;
}
}
++it;
}
owner->SetTempPetCount(count);
return count;
}
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(IMMUNE_AGGRO) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && other->IsClient()) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && other->IsNPC())
) {
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(IMMUNE_AGGRO) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_CLIENT) && attacker->IsClient()) &&
!(n->GetSpecialAbility(IMMUNE_AGGRO_NPC) && attacker->IsNPC()) &&
!attacker->IsTrap() &&
!attacker->IsCorpse()
) {
n->AddToHateList(attacker, 1, 0, true, false, false, spell_id);
n->SetTarget(attacker);
}
}
}
++it;
}
}
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;
}
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<int8>(opts.speak_mode));
buf.WriteInt8(static_cast<int8>(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 : GetCloseMobList(sender, (dist * 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;
}
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<int> 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<uint32>(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, 0, 100, message);
}
}
++it;
}
}
uint16 EntityList::CreateGroundObject(uint32 itemid, const glm::vec4& position, uint32 decay_time)
{
const EQ::ItemData *is = database.GetItem(itemid);
if (!is)
return 0;
auto i = new EQ::ItemInstance(is, is->MaxCharges);
if (!i)
return 0;
auto object = new Object(i, position.x, position.y, position.z, position.w, decay_time);
entity_list.AddObject(object, true);
safe_delete(i);
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(AT_Anim, cur->GetAppearanceValue(cur->GetAppearance()), false, true, c);
}
if (cur->GetSize() != cur->GetBaseSize()) {
cur->SendAppearancePacket(AT_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->SendSavedAppearenceEffects(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 *> 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 = 0;
uint32 PlayerLevel = 0;
uint32 PlayerRace = 0;
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() == LDON_TREASURE ||
cur->GetBodyType() == BT_NoTarget || cur->GetBodyType() == BT_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::GateAllClients()
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *c = it->second;
if (c)
c->GoToBind();
++it;
}
}
void EntityList::SignalAllClients(uint32 data)
{
auto it = client_list.begin();
while (it != client_list.end()) {
Client *ent = it->second;
if (ent)
ent->Signal(data);
++it;
}
}
uint16 EntityList::GetClientCount(){
uint16 ClientCount = 0;
std::list<Client*> client_list;
entity_list.GetClientList(client_list);
auto iter = client_list.begin();
while (iter != client_list.end()) {
Client *entry = (*iter);
entry->GetCleanName();
ClientCount++;
iter++;
}
return ClientCount;
}
void EntityList::GetMobList(std::list<Mob *> &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<NPC *> &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<Client *> &c_list)
{
c_list.clear();
auto it = client_list.begin();
while (it != client_list.end()) {
c_list.push_back(it->second);
++it;
}
}
#ifdef BOTS
void EntityList::GetBotList(std::list<Bot *> &b_list)
{
b_list.clear();
for (auto bot : bot_list) {
b_list.push_back(bot);
}
}
std::vector<Bot *> EntityList::GetBotListByCharacterID(uint32 character_id, uint8 class_id)
{
std::vector<Bot *> client_bot_list;
if (!character_id) {
return client_bot_list;
}
for (auto bot : bot_list) {
if (
bot->GetOwner() &&
bot->GetBotOwnerCharacterID() == character_id &&
(
!class_id ||
bot->GetClass() == class_id
)
) {
client_bot_list.push_back(bot);
}
}
return client_bot_list;
}
std::vector<Bot *> EntityList::GetBotListByClientName(std::string client_name)
{
std::vector<Bot *> client_bot_list;
if (client_name.empty()) {
return client_bot_list;
}
for (auto bot : bot_list) {
if (bot->GetOwner() && Strings::ToLower(bot->GetOwner()->GetCleanName()) == Strings::ToLower(client_name)) {
client_bot_list.push_back(bot);
}
}
return client_bot_list;
}
#endif
void EntityList::GetCorpseList(std::list<Corpse *> &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<Object *> &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<Doors*> &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<Spawn2*> &o_list)
{
o_list.clear();
if(zone) {
LinkedListIterator<Spawn2*> 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;
auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer;
fnpcs->Unknown109 = 0x16;
fnpcs->Unknown110 = 0x06;
fnpcs->Unknown111 = 0x24;
fnpcs->Action = 0;
auto it = npc_list.begin();
while (it != npc_list.end()) {
if (it->second) {
NPC *n = it->second;
if (n->IsFindable()) {
fnpcs->EntityID = n->GetID();
strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name));
strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName));
fnpcs->Race = n->GetRace();
fnpcs->Class = n->GetClass();
c->QueuePacket(outapp);
}
}
++it;
}
safe_delete(outapp);
}
void EntityList::UpdateFindableNPCState(NPC *n, bool Remove)
{
if (!n || !n->IsFindable())
return;
auto outapp = new EQApplicationPacket(OP_SendFindableNPCs, sizeof(FindableNPC_Struct));
FindableNPC_Struct *fnpcs = (FindableNPC_Struct *)outapp->pBuffer;
fnpcs->Unknown109 = 0x16;
fnpcs->Unknown110 = 0x06;
fnpcs->Unknown111 = 0x24;
fnpcs->Action = Remove ? 1: 0;
fnpcs->EntityID = n->GetID();
strn0cpy(fnpcs->Name, n->GetCleanName(), sizeof(fnpcs->Name));
strn0cpy(fnpcs->LastName, n->GetLastName(), sizeof(fnpcs->LastName));
fnpcs->Race = n->GetRace();
fnpcs->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(outapp);
++it;
}
safe_delete(outapp);
}
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() != LDON_TREASURE
&& it->second->GetBodyType() != BT_NoTarget
&& it->second->GetBodyType() != BT_NoTarget2
&& it->second->GetBodyType() != BT_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() != LDON_TREASURE
&& it->second->GetBodyType() != BT_NoTarget
&& it->second->GetBodyType() != BT_NoTarget2
&& it->second->GetBodyType() != BT_Special)
npcs[i++] = it->second;
++it;
}
while (count > 0) {
std::vector<int> 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() == 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, EXPEDITION_MIN_REMAIN, itoa((int)minutes_left));
it->second->QueuePacket(outapp);
++it;
}
safe_delete(outapp);
}
Mob *EntityList::GetClosestMobByBodyType(Mob *sender, bodyType 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->IsPetOwnerClient())
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<Mob*> &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<Mob*> 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<Mob *> spreader_list = {};
bool is_detrimental_spell = IsDetrimentalSpell(spell_id);
for (auto &it : entity_list.GetCloseMobList(spreader, 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->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
*
* @param mob
* @param distance
* @return
*/
std::unordered_map<uint16, Mob *> &EntityList::GetCloseMobList(Mob *mob, float distance)
{
if (distance <= RuleI(Range, MobCloseScanDistance)) {
return mob->close_mobs;
}
return mob_list;
}
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 mob_iterator : mob_list) {
Mob *mob = mob_iterator.second;
if (mob->IsNPC() && mob->GetRace() == 2254 && mob->EntityVariableExists("grid_id") && atoi(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);
}
}
}