#include #include "World.h" #include "InferredCellData.h" #include "Client.h" #include "WeenieObject.h" #include "PhysicsObj.h" #include "Monster.h" #include "Player.h" #include "ChatMsgs.h" #include "WorldLandBlock.h" #include "WClassID.h" #include "Database.h" #include "GameMode.h" #include "Config.h" #include "Server.h" #include "House.h" #include "HouseManager.h" #include "GameEventManager.h" CWorld::CWorld() { WINLOG(Temp, Normal, "Initializing World..\n"); SERVER_INFO << "Initializing World.."; // ZeroMemory(m_pBlocks, sizeof(m_pBlocks)); memset(m_pBlocks, 0, sizeof(m_pBlocks)); LoadDungeonsFile(); LoadMOTD(); EnumerateDungeonsFromCellData(); if (g_pHouseManager) g_pHouseManager->Load(); m_mAllObjects.reserve(10000000); m_mAllPlayers.reserve(5000); m_fLastSave = g_pGlobals->Time(); } void CWorld::SaveWorld() { SaveDungeonsFile(); if(g_pHouseManager) g_pHouseManager->Save(); } CWorld::~CWorld() { //if (m_pGameMode) //{ // delete m_pGameMode; // m_pGameMode = NULL; //} for (auto block : m_vBlocks) { if (block) { m_pBlocks[block->GetHeader()] = NULL; delete block; } } m_vBlocks.clear(); for (auto blockEntry : m_mDormantBlocks) { if (CWorldLandBlock *block = blockEntry.second) { m_pBlocks[block->GetHeader()] = NULL; delete block; } } m_mDormantBlocks.clear(); for (auto blockEntry : m_mUnloadedBlocks) { if (CWorldLandBlock *block = blockEntry.second) { m_pBlocks[block->GetHeader()] = NULL; delete block; } } m_mUnloadedBlocks.clear(); SaveWorld(); m_mDungeons.clear(); for (auto &dungeonDescEntry : m_mDungeonDescs) { DungeonDesc_t *pDungeonDesc = &dungeonDescEntry.second; SafeDeleteArray(pDungeonDesc->szDungeonName); SafeDeleteArray(pDungeonDesc->szDescription); SafeDeleteArray(pDungeonDesc->szAuthor); } m_mDungeonDescs.clear(); } void CWorld::Init() { std::list preload = g_pCellDataEx->GetAlwaysLoaded(); SERVER_INFO << "Preloading" << preload.size() << "blocks"; for (auto id : preload) ActivateBlock(id >> 16)->NeverGoDormant(); } Position CWorld::FindDungeonDrop() { if (m_mDungeons.empty()) { return Position(); } LocationMap::iterator dit = m_mDungeons.begin(); long index = Random::GenUInt(0, (long)m_mDungeons.size() - 1); while (index > 0) { index--; dit++; } return dit->second; } Position CWorld::FindDungeonDrop(WORD wBlockID) { LocationMap::iterator i = m_mDungeons.upper_bound(((uint32_t(wBlockID) << 16) + 0x100) - 1); if ((i == m_mDungeons.end()) || (BLOCK_WORD(i->second.objcell_id) != wBlockID)) { return Position(); } return i->second; } const char* CWorld::GetMOTD() { return m_strMOTD.c_str(); } void CWorld::LoadMOTD() { FILE *motd = g_pDB->DataFileOpen("motd.txt", "rt"); if (motd) { long lFileSize = fsize(motd); char* pcFileData = new char[lFileSize + 1]; long lEnd = (long)fread(pcFileData, sizeof(char), lFileSize, motd); pcFileData[lEnd] = 0; m_strMOTD = pcFileData; delete[] pcFileData; fclose(motd); } else m_strMOTD = "No MOTD set."; } void CWorld::LoadDungeonsFile() { FILE *wd = g_pDB->DataFileOpen("worlddesc"); if (wd) { long lSize = fsize(wd); BYTE* pbData = new BYTE[lSize]; long lRead = (long)fread(pbData, sizeof(BYTE), lSize, wd); BinaryReader input(pbData, lRead); uint32_t dwDungeonCount = input.ReadUInt32(); if (!input.GetLastError()) { for (uint32_t i = 0; i < dwDungeonCount; i++) { DungeonDesc_t dd; dd.wBlockID = input.ReadUInt16(); dd.szDungeonName = input.ReadString(); dd.szAuthor = input.ReadString(); dd.szDescription = input.ReadString(); dd.position.UnPack(&input); if (input.GetLastError()) break; // avoid using a buffer thats going to be deleted dd.szDungeonName = strdup(dd.szDungeonName); dd.szAuthor = strdup(dd.szAuthor); dd.szDescription = strdup(dd.szDescription); m_mDungeonDescs[dd.wBlockID] = dd; } } delete[] pbData; fclose(wd); } } void CWorld::SaveDungeonsFile() { FILE *wd = g_pDB->DataFileCreate("worlddesc"); if (wd) { BinaryWriter output; output.Write((uint32_t)m_mDungeonDescs.size()); for (DungeonDescMap::iterator i = m_mDungeonDescs.begin(); i != m_mDungeonDescs.end(); i++) { output.Write(i->second.wBlockID); output.WriteString(i->second.szDungeonName); output.WriteString(i->second.szAuthor); output.WriteString(i->second.szDescription); i->second.position.Pack(&output); } fwrite(output.GetData(), output.GetSize(), sizeof(BYTE), wd); fclose(wd); } else //MsgBox(MB_ICONHAND, "Error opening WorldDesc file! Close NOW to avoid corruption!"); SERVER_ERROR << "Error opening WorldDesc file! Close NOW to avoid corruption!"; } BOOL CWorld::DungeonExists(WORD wBlockID) { LocationMap::iterator i = m_mDungeons.upper_bound(((uint32_t(wBlockID) << 16) + 0x100) - 1); if ((i == m_mDungeons.end()) || (BLOCK_WORD(i->second.objcell_id) != wBlockID)) return FALSE; return TRUE; } LocationMap* CWorld::GetDungeons() { return &m_mDungeons; } DungeonDescMap* CWorld::GetDungeonDescs() { return &m_mDungeonDescs; } DungeonDesc_t* CWorld::GetDungeonDesc(const char* szDungeonName) { DungeonDescMap::iterator i = m_mDungeonDescs.begin(); DungeonDescMap::iterator iend = m_mDungeonDescs.end(); std::string dungeonName = ""; std::string searchName = szDungeonName; while (i != iend) { dungeonName = i->second.szDungeonName; std::transform(dungeonName.begin(), dungeonName.end(), dungeonName.begin(), ::tolower); std::transform(searchName.begin(), searchName.end(), searchName.begin(), ::tolower); if (dungeonName.find(searchName) != std::string::npos) return &i->second; i++; } return NULL; } DungeonDesc_t* CWorld::GetDungeonDesc(WORD wBlockID) { DungeonDescMap::iterator it = m_mDungeonDescs.find(wBlockID); if (it != m_mDungeonDescs.end()) return &it->second; else return NULL; } void CWorld::SetDungeonDesc(WORD wBlockID, const char* szDungeonName, const char* szAuthor, const char* szDescription, const Position &position) { DungeonDescMap::iterator it = m_mDungeonDescs.find(wBlockID); if (it != m_mDungeonDescs.end()) { DungeonDesc_t* pdd = &it->second; SafeDeleteArray(pdd->szDungeonName); SafeDeleteArray(pdd->szDescription); SafeDeleteArray(pdd->szAuthor); m_mDungeonDescs.erase(it); } DungeonDesc_t dd; dd.wBlockID = wBlockID; dd.szDungeonName = strdup(szDungeonName); dd.szAuthor = strdup(szAuthor); dd.szDescription = strdup(szDescription); dd.position = position; m_mDungeonDescs[wBlockID] = dd; } void CWorld::ClearAllSpawns() { for (uint32_t i = 0; i < (256 * 256); i++) { if (m_pBlocks[i]) m_pBlocks[i]->ClearSpawns(); } } CWorldLandBlock *CWorld::GetLandblock(WORD wHeader, bool bActivate) { CWorldLandBlock *pBlock = m_pBlocks[wHeader]; if (!pBlock && bActivate) { pBlock = ActivateBlock(wHeader); } return pBlock; } CWorldLandBlock *CWorld::ActivateBlock(WORD wHeader) { CWorldLandBlock **ppBlock = &m_pBlocks[wHeader]; CWorldLandBlock *pBlock; #if _DEBUG pBlock = *ppBlock; if (pBlock != NULL) { SERVER_INFO << "Landblock already active!"; return pBlock; } #endif // make sure this isn't already in our spawned blocked... pBlock = new CWorldLandBlock(this, wHeader); m_vSpawns.push_back(pBlock); *ppBlock = pBlock; pBlock->Init(); return pBlock; } bool CWorld::CreateEntity(CWeenieObject *pEntity, bool bMakeAware) { if (!pEntity) return false; CWeenieObject *pExistingWeenie = FindObject(pEntity->GetID()); if (pExistingWeenie) { if (pExistingWeenie == pEntity) { LOG_PRIVATE(World, Warning, "Trying to spawn weenie twice! ID 0x%08X\n"); return true; } else { //LOG_PRIVATE(World, Warning, "Trying to spawn second (different) weenie with existing ID 0x%08X! Deleting instead.\n", pEntity->GetID()); // Already exists. delete pEntity; } return false; } double createTime; if (!pEntity->m_Qualities.InqFloat(CREATION_TIMESTAMP_FLOAT, createTime)) pEntity->m_Qualities.SetFloat(CREATION_TIMESTAMP_FLOAT, Timer::cur_time); int lifespan = pEntity->m_Qualities.GetInt(LIFESPAN_INT, 0); if (lifespan && !pEntity->m_Qualities.GetInt(CREATION_TIMESTAMP_INT, 0)) { time_t t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); pEntity->m_Qualities.SetInt(CREATION_TIMESTAMP_INT, t); pEntity->_timeToRot = Timer::cur_time + lifespan; } if (!pEntity->GetID()) { pEntity->SetID(CreateWorldID(pEntity)); } if (pEntity->AsPlayer() && pEntity->AsPlayer()->GetClient()) { m_mAllPlayers[pEntity->GetID()] = pEntity->AsPlayer(); if (g_pConfig->ShowLogins() && pEntity->m_Qualities.id == W_HUMAN_CLASS) // Admins playing as creatures should not show as logging in { BroadcastGlobal(ServerBroadcast("System", csprintf("%s has logged in.", pEntity->GetName().c_str()), LTT_DEFAULT), PRIVATE_MSG, pEntity->GetID(), FALSE, TRUE); } } m_mAllObjects[pEntity->GetID()] = pEntity; //std::string eventString; //if (pEntity->m_Qualities.InqString(GENERATOR_EVENT_STRING, eventString)) //{ // _eventWeenies.insert(std::pair(eventString, pEntity->GetID())); // GameEventDef *ed = g_pGameEventManager->GetEvent(eventString.c_str()); // if (ed) // pEntity->CheckEventState(eventString, ed); //} pEntity->InitPhysicsObj(); pEntity->SetupWeenie(); pEntity->RecacheHasOwner(); if (pEntity->m_Position.objcell_id) { Position startPos = pEntity->m_Position; pEntity->enter_world(&startPos); if (!pEntity->m_Position.objcell_id) { LOG_PRIVATE(World, Warning, "Trying to spawn a weenie in an invalid position! Deleting instead.\n", pEntity->GetID()); RemoveEntity(pEntity); // Bad spawn position. // delete pEntity; return false; } if (!pEntity->CachedHasOwner()) { WORD wHeader = BLOCK_WORD(pEntity->GetLandcell()); CWorldLandBlock *pBlock = m_pBlocks[wHeader]; if (!pBlock) { pBlock = ActivateBlock(wHeader); } pEntity->PreSpawnCreate(); pBlock->Insert(pEntity, 0, TRUE, bMakeAware); } } else { pEntity->PreSpawnCreate(); } if (bMakeAware) pEntity->MakeAware(pEntity, true); std::string eventString; if (pEntity->m_Qualities.InqString(GENERATOR_EVENT_STRING, eventString)) { _eventWeenies.insert(std::pair(eventString, pEntity->GetID())); GameEventDef *ed = g_pGameEventManager->GetEvent(eventString.c_str()); if (ed) pEntity->CheckEventState(eventString, ed); } else pEntity->InitCreateGenerator(); pEntity->PostSpawn(); if (pEntity->cell) { assert(pEntity->GetLandcell() == pEntity->cell->id); } #ifdef _DEBUG SERVER_INFO << "Spawned ID" << pEntity->GetID() << "- " << pEntity->GetName().c_str() << "memory object @" << (uint64_t)pEntity; #endif return true; } uint32_t CWorld::CreateWorldID(CWeenieObject * pEntity) { bool useEphemeral = (pEntity->m_PhysicsState & MISSILE_PS) // player corpses are invisible at create time || (pEntity->IsCorpse() && pEntity->m_Qualities.GetBool(VISIBILITY_BOOL, true)) || pEntity->IsStuck() ; if (pEntity->GetWorldTopLevelOwner() && pEntity->GetWorldTopLevelOwner()->m_Qualities.GetInt(MERCHANDISE_ITEM_TYPES_INT, 0) > 0) useEphemeral = true; uint32_t isPlayerRange = 0; if(pEntity->m_Qualities.InqInstanceID(VICTIM_IID, isPlayerRange) && isPlayerRange >= 1342177280 && isPlayerRange <= 1610612735) useEphemeral = false; return GenerateGUID(useEphemeral ? eEphemeral : eDynamicGUID); } void CWorld::InsertTeleportLocation(TeleTownList_s l) { m_vTeleTown.push_back(l); } std::string CWorld::GetTeleportList() { std::string result; for (TeleTownList_s var : m_vTeleTown) { result.append(var.m_teleString).append(", "); } if (!result.empty() && result.back() == 0x20) { //Get out behind of bad formatting result.pop_back(); result.pop_back(); } return result; } TeleTownList_s CWorld::GetTeleportLocation(std::string location) { TeleTownList_s val; std::transform(location.begin(), location.end(), location.begin(), ::tolower); for (TeleTownList_s var : m_vTeleTown) { //Lets waste a bunch of time with this.. Hey, if its the first one on the list its O(1) std::string town = var.m_teleString; std::transform(town.begin(), town.end(), town.begin(), ::tolower); if (town[0] == location[0] && town.find(location) != std::string::npos) { val = var; break; } } return val; } void CWorld::InsertEntity(CWeenieObject *pEntity, BOOL bSilent) { uint32_t cell_id = pEntity->GetLandcell(); if (cell_id && !pEntity->HasOwner()) { WORD wHeader = BLOCK_WORD(cell_id); CWorldLandBlock *pBlock = m_pBlocks[wHeader]; if (!pBlock) { pBlock = ActivateBlock(wHeader); } if (bSilent) pBlock->Insert(pEntity, wHeader); else pBlock->Insert(pEntity); } // these should not be necessary... ? if (pEntity->AsPlayer()) { m_mAllPlayers[pEntity->GetID()] = pEntity->AsPlayer(); } m_mAllObjects[pEntity->GetID()] = pEntity; } void CWorld::JuggleEntity(WORD wOld, CWeenieObject* pEntity) { if (!pEntity->HasOwner()) { WORD wHeader = BLOCK_WORD(pEntity->GetLandcell()); CWorldLandBlock *pBlock = GetLandblock(wHeader); if (!pBlock) { pBlock = ActivateBlock(wHeader); } if (pEntity->_IsPlayer() || pBlock->IsTickingWithWorld()) { pBlock->Insert(pEntity, wOld); } else { if (pEntity->ShouldSave()) { pEntity->Save(); } EnsureRemoved(pEntity); uint32_t RemoveObject[3]; RemoveObject[0] = 0xF747; RemoveObject[1] = pEntity->GetID(); RemoveObject[2] = pEntity->_instance_timestamp; BroadcastPVS(pEntity->GetLandcell(), RemoveObject, sizeof(RemoveObject)); pEntity->exit_world(); pEntity->leave_world(); pEntity->unset_parent(); pEntity->unparent_children(); if (uint32_t generator_id = pEntity->InqIIDQuality(GENERATOR_IID, 0)) { CWeenieObject *target = g_pWorld->FindObject(generator_id); if (target) target->NotifyGeneratedDeath(pEntity); } DELETE_ENTITY(pEntity); } } } PlayerWeenieMap *CWorld::GetPlayers() { return &m_mAllPlayers; } uint32_t CWorld::GetNumPlayers() { return (uint32_t)m_mAllPlayers.size(); } CWeenieObject *CWorld::FindObject(uint32_t object_id, bool allowLandblockActivation, bool lockObject) { if (!object_id) return NULL; WeenieMap::iterator result = m_mAllObjects.find(object_id); if (result == m_mAllObjects.end() && allowLandblockActivation) { if (!g_pDBIO->IsWeenieInDatabase(object_id)) return NULL; //not in the database either CWeenieObject *weenie = CWeenieObject::Load(object_id); if (!weenie) return NULL; //try getting a valid cellId to search in Position pos; uint32_t cellId = weenie->m_Position.objcell_id; if (!cellId && weenie->m_Qualities.InqPosition(LOCATION_POSITION, pos)) cellId = pos.objcell_id; if (!cellId && weenie->m_Qualities.InqPosition(INSTANTIATION_POSITION, pos)) cellId = pos.objcell_id; if (cellId) { WORD header = (cellId >> 16); CWorldLandBlock *block = GetLandblock(header, false); if (!block) //we're not active { block = GetLandblock(header, true); if (block) //now we're active! { block->Think(); //force a tick so we spawn stuff and hopefully what we are looking for. result = m_mAllObjects.find(object_id); //try again now that we loaded the landblock that supposedly contains the weenie. } } } delete weenie; } if(result == m_mAllObjects.end()) return NULL; return result->second; } bool CWorld::FindObjectName(uint32_t object_id, std::string &name) { WeenieMap::iterator result = m_mAllObjects.find(object_id); if (result == m_mAllObjects.end()) return false; name = result->second->GetName(); return true; } //================================================== //Global, player search by GUID. //================================================== CPlayerWeenie *CWorld::FindPlayer(uint32_t dwGUID) { PlayerWeenieMap::iterator result = m_mAllPlayers.find(dwGUID); if (result == m_mAllPlayers.end()) return NULL; return result->second; } std::string CWorld::GetPlayerName(uint32_t playerId, bool allowOffline) { CPlayerWeenie *player = FindPlayer(playerId); if (player) return player->GetName(); if (allowOffline) return g_pDBIO->GetPlayerCharacterName(playerId); return ""; } uint32_t CWorld::GetPlayerId(const char *name, bool allowOffline) { if (*name == '+') name++; CPlayerWeenie *player = FindPlayer(name); if (player) return player->GetID(); if (allowOffline) return g_pDBIO->GetPlayerCharacterId(name); return 0; } //================================================== //Global, case insensitive player name search. //================================================== CPlayerWeenie *CWorld::FindPlayer(const char *target_name) { if (!target_name) return NULL; if (*target_name == '+') target_name++; for (auto entry : m_mAllPlayers) { CPlayerWeenie *player = entry.second; if (player) { std::string player_name_ = player->GetName(); const char *player_name = player_name_.c_str(); if (*player_name == '+') player_name++; if (!stricmp(target_name, player_name)) return player; } } return NULL; } void CWorld::SendNetMessage(CNetDeliveryTargets *target, void *data, uint32_t len, WORD group, uint32_t ignore_ent, BOOL game_event) { UNFINISHED(); for (uint32_t cell_id : target->_target_cell_pvs) { CWorldLandBlock *landBlock = GetLandblock(cell_id >> 16, false); if (!landBlock) continue; CObjCell *cell = landBlock->GetObjCell(cell_id & 0xFFFF); if (!cell) continue; // could use stab list, but sometimes messages are sent outside of stab list.. sigh, really need to use some form of voyeur tracking } } void CWorld::BroadcastPVS(CPhysicsObj *physobj, void *_data, uint32_t _len, WORD _group, uint32_t ignore_ent, BOOL _game_event, bool ephemeral) { if (!physobj) return; CPhysicsObj *topLevel = physobj->parent ? physobj->parent : physobj; uint32_t cell_id; cell_id = topLevel->m_Position.objcell_id; BroadcastPVS(cell_id, _data, _len, _group, ignore_ent, _game_event, ephemeral); uint32_t old_cell_id = topLevel->last_tick_cell_id; if (cell_id != old_cell_id && old_cell_id) { BroadcastPVS(old_cell_id, _data, _len, _group, ignore_ent, _game_event, ephemeral); } } void CWorld::BroadcastPVS(CWeenieObject *weenie, void *_data, uint32_t _len, WORD _group, uint32_t ignore_ent, BOOL _game_event, bool ephemeral) { if (!weenie) return; if (weenie->IsContained() || (weenie->IsEquipped() && weenie->InqIntQuality(PARENT_LOCATION_INT, 0) == 0)) //we're equipped but we dont have a physical presence in the world, make sure to include us here. { uint32_t topLevelID = weenie->GetTopLevelID(); if (topLevelID != weenie->GetID() && ignore_ent != topLevelID) { CWeenieObject *owner = FindObject(topLevelID); if (owner) { owner->SendNetMessage(_data, _len, _group, _game_event, ephemeral); if (owner->AsContainer() != NULL && owner->AsContainer()->_openedById != 0) { CWeenieObject *openedBy = FindObject(owner->AsContainer()->_openedById); if(openedBy) openedBy->SendNetMessage(_data, _len, _group, _game_event, ephemeral); } } } } else { BroadcastPVS((CPhysicsObj *)weenie, _data, _len, _group, ignore_ent, _game_event, ephemeral); } } void CWorld::BroadcastPVS(const Position &pos, void *_data, uint32_t _len, WORD _group, uint32_t ignore_ent, BOOL _game_event, bool ephemeral) { BroadcastPVS(pos.objcell_id, _data, _len, _group, ignore_ent, _game_event, ephemeral); } void CWorld::BroadcastPVS(uint32_t dwCell, void *_data, uint32_t _len, WORD _group, uint32_t ignore_ent, BOOL _game_event, bool ephemeral) { if (!dwCell) return; // Don't ask. WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) //indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); CWorldLandBlock *pBlock = m_pBlocks[block]; if ((pBlock && (dwCell & 0xFFFF) > 0x100) && !pBlock->PossiblyVisibleToOutdoors(dwCell)) //check if inside { // dungeons are usually contained in water blocks usually and we will only broadcast to them individually pBlock->Broadcast(_data, _len, _group, ignore_ent, _game_event, ephemeral); } else { for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) pBlock->Broadcast(_data, _len, _group, ignore_ent, _game_event, ephemeral); } } } } void CWorld::BroadcastGlobal(BinaryWriter *writer, WORD group, uint32_t ignore_ent, BOOL game_event, BOOL should_delete) { BroadcastGlobal(writer->GetData(), writer->GetSize(), group, ignore_ent, game_event); if (should_delete) { delete writer; } } void CWorld::BroadcastGlobal(void *_data, uint32_t _len, WORD _group, uint32_t ignore_ent, BOOL _game_event) { for (auto &playerEntry : m_mAllPlayers) { if (CPlayerWeenie *pPlayer = playerEntry.second) { if (!ignore_ent || (pPlayer->GetID() != ignore_ent)) { pPlayer->SendNetMessage(_data, _len, _group, _game_event); } } } } void CWorld::BroadcastLocal(uint32_t cellid, std::string text) { BinaryWriter *textMsg = ServerText(text.c_str(), LTT_DEFAULT); g_pWorld->BroadcastPVS(cellid, textMsg->GetData(), textMsg->GetSize(), PRIVATE_MSG, 0, false); delete textMsg; } void CWorld::BroadcastLocal(uint32_t cellid, std::string text, LogTextType channel) { BinaryWriter *textMsg = ServerText(text.c_str(), channel); g_pWorld->BroadcastPVS(cellid, textMsg->GetData(), textMsg->GetSize(), PRIVATE_MSG, 0, false); delete textMsg; } void CWorld::Test() { WINLOG(Temp, Normal, "\n"); WINLOG(Temp, Normal, "Portal: v%lu, %lu files.\n", g_pPortal->GetVersion(), g_pPortal->GetFileCount()); WINLOG(Temp, Normal, "Cell: v%lu, %u files.\n", g_pCell->GetVersion(), g_pCell->GetFileCount()); WINLOG(Temp, Normal, "%u objects", m_mAllObjects.size()); WINLOG(Temp, Normal, "%u players:\n", m_mAllPlayers.size()); for (PlayerWeenieMap::iterator pit = m_mAllPlayers.begin(); pit != m_mAllPlayers.end(); pit++) { CPlayerWeenie *pPlayer = pit->second; WINLOG(Temp, Normal, "%08X %s\n", pPlayer->GetID(), pPlayer->GetName().c_str()); } WINLOG(Temp, Normal, "%u active blocks:\n", m_vBlocks.size()); for (LandblockVector::iterator it = m_vBlocks.begin(); it != m_vBlocks.end(); it++) { CWorldLandBlock *pBlock = *it; WINLOG(Temp, Normal, "%04X %u players %u entities\n", pBlock->GetHeader(), pBlock->PlayerCount(), pBlock->LiveCount()); } WINLOG(Temp, Normal, "%u dormant blocks:\n", m_mDormantBlocks.size()); for (LandblockMap::iterator it = m_mDormantBlocks.begin(); it != m_mDormantBlocks.end(); it++) { CWorldLandBlock *pBlock = it->second; WINLOG(Temp, Normal, "%04X %u players %u entities\n", pBlock->GetHeader(), pBlock->PlayerCount(), pBlock->LiveCount()); } WINLOG(Temp, Normal, "%u unloaded blocks:\n", m_mUnloadedBlocks.size()); for (LandblockMap::iterator it = m_mUnloadedBlocks.begin(); it != m_mUnloadedBlocks.end(); it++) { CWorldLandBlock *pBlock = it->second; WINLOG(Temp, Normal, "%04X %u players %u entities\n", pBlock->GetHeader(), pBlock->PlayerCount(), pBlock->LiveCount()); } WINLOG(Temp, Normal, "\n"); } void CWorld::RemoveEntity(CWeenieObject *pEntity) { if (!pEntity) return; //if (m_pGameMode) // m_pGameMode->OnRemoveEntity(pEntity); if (pEntity->AsPlayer() && g_pConfig->ShowLogins() && pEntity->m_Qualities.id == W_HUMAN_CLASS) { BroadcastGlobal(ServerBroadcast("System", csprintf("%s has logged out.", pEntity->GetName().c_str()), LTT_DEFAULT), PRIVATE_MSG, pEntity->GetID(), FALSE, TRUE); } CWorldLandBlock *pBlock = pEntity->GetBlock(); if (!pBlock) { if (pEntity->ShouldSave()) pEntity->Save(); pEntity->exit_world(); pEntity->leave_world(); pEntity->unset_parent(); pEntity->unparent_children(); EnsureRemoved(pEntity); if (uint32_t generator_id = pEntity->InqIIDQuality(GENERATOR_IID, 0)) { CWeenieObject *target = g_pWorld->FindObject(generator_id); if (target) target->NotifyGeneratedDeath(pEntity); } DELETE_ENTITY(pEntity); } else { pBlock->Destroy(pEntity); } } void CWorld::EnsureRemoved(CWeenieObject *pEntity) { m_mAllPlayers.erase(pEntity->GetID()); m_mAllObjects.erase(pEntity->GetID()); std::string eventString; if (pEntity->m_Qualities.InqString(GENERATOR_EVENT_STRING, eventString)) { auto range_pair = _eventWeenies.equal_range(eventString); for (auto it = range_pair.first; it != range_pair.second; ++it) { if (it->second == pEntity->GetID()) { _eventWeenies.erase(it); break; } } } } void CWorld::CheckDormantLandblocks() { LandblockMap::iterator lit = m_mDormantBlocks.begin(); LandblockMap::iterator lend = m_mDormantBlocks.end(); unsigned int minNumToCleanup = 0; if (m_mDormantBlocks.size() >= g_pConfig->MaxDormantLandblocks()) minNumToCleanup = (uint32_t)(m_mDormantBlocks.size() - g_pConfig->MaxDormantLandblocks()); double dormantCleanupTime = g_pConfig->DormantLandblockCleanupTime(); while (lit != lend) { CWorldLandBlock *pBlock = lit->second; if (!minNumToCleanup && (pBlock->GetDormancyTime() + dormantCleanupTime) >= Timer::cur_time) { // theoretically these are sorted with the most recent last break; } else { lit = m_mDormantBlocks.erase(lit); lend = m_mDormantBlocks.end(); pBlock->UnloadSpawnsUntilNextTick(); m_mUnloadedBlocks[pBlock->GetHeader()] = pBlock; minNumToCleanup--; } } } void CWorld::Think() { if (!m_SendPerformanceInfoToPlayer) { LandblockVector::iterator lit = m_vBlocks.begin(); LandblockVector::iterator lend = m_vBlocks.end(); while (lit != lend) { CWorldLandBlock *pBlock = *lit; if (pBlock->Think() || pBlock->GetDormancyStatus() != LandblockDormancyStatus::Dormant) { lit++; } else { lit = m_vBlocks.erase(lit); lend = m_vBlocks.end(); m_mDormantBlocks[pBlock->GetHeader()] = pBlock; pBlock->SetIsTickingWithWorld(false); } } CheckDormantLandblocks(); } else { // Debug performance double fSlowestBlock = -1.0; CWorldLandBlock *pSlowestBlock = NULL; CStopWatch measureBlocks; LandblockVector::iterator lit = m_vBlocks.begin(); LandblockVector::iterator lend = m_vBlocks.end(); while (lit != lend) { CWorldLandBlock *pBlock = *lit; CStopWatch measureBlock; if (pBlock->Think()) { double elapsed = measureBlock.GetElapsed(); if (elapsed > fSlowestBlock) { fSlowestBlock = elapsed; pSlowestBlock = pBlock; } lit++; } else { lit++; // dead //lit = m_vBlocks.erase(lit); //lend = m_vBlocks.end(); //m_pBlocks[pBlock->GetHeader()] = NULL; //delete pBlock; } } double totalElapsed = measureBlocks.GetElapsed(); CPlayerWeenie *player = FindPlayer(m_SendPerformanceInfoToPlayer); if (player) { player->SendText(csprintf("FrameTime: %f", totalElapsed), LTT_DEFAULT); if (pSlowestBlock) { player->SendText(csprintf("Slowest block %04X took %f seconds (%u objects, %u players), avg block took. %f seconds", pSlowestBlock->GetHeader(), fSlowestBlock, pSlowestBlock->LiveCount(), pSlowestBlock->PlayerCount(), totalElapsed / (double)m_vBlocks.size()), LTT_DEFAULT); } } m_SendPerformanceInfoToPlayer = 0; } if (!m_vSpawns.empty()) { std::copy(m_vSpawns.begin(), m_vSpawns.end(), std::back_inserter(m_vBlocks)); m_vSpawns.clear(); } if ((m_fLastSave + 300.0f) <= g_pGlobals->Time()) { SaveWorld(); m_fLastSave = g_pGlobals->Time(); } //if (m_pGameMode) //{ // m_pGameMode->Think(); //} //#ifdef _DEBUG // for (auto worldObject : m_mAllObjects) // { // if (worldObject.second) // worldObject.second->DebugValidate(); // } //#endif } CGameMode *CWorld::GetGameMode() { return m_pGameMode; } void CWorld::SetNewGameMode(CGameMode *pGameMode) { return; if (pGameMode) { g_pWorld->BroadcastGlobal(ServerText(csprintf("Setting game mode to %s", pGameMode->GetName())), PRIVATE_MSG); } else { if (!pGameMode && m_pGameMode) { g_pWorld->BroadcastGlobal(ServerText(csprintf("Turning off game mode %s", m_pGameMode->GetName())), PRIVATE_MSG); } } if (m_pGameMode) { delete m_pGameMode; } m_pGameMode = pGameMode; } void CWorld::EnumNearby(const Position &position, float fRange, std::list *pResults) { // Enumerate nearby world objects uint32_t dwCell = position.objcell_id; WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); CWorldLandBlock *pBlock = m_pBlocks[block]; if ((pBlock && (dwCell & 0xFFFF) > 0x100) && !pBlock->PossiblyVisibleToOutdoors(dwCell)) //check if inside { pBlock->EnumNearby(position, fRange, pResults); } else { uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) // indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) { pBlock->EnumNearby(position, fRange, pResults); } } } } } void CWorld::EnumNearbyPlayers(const Position &position, float fRange, std::list *pResults) { // Enumerate nearby world objects uint32_t dwCell = position.objcell_id; WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); CWorldLandBlock *pBlock = m_pBlocks[block]; if ((pBlock && (dwCell & 0xFFFF) > 0x100) && !pBlock->PossiblyVisibleToOutdoors(dwCell)) //check if inside { pBlock->EnumNearbyPlayers(position, fRange, pResults); } else { uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) // indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) { pBlock->EnumNearbyPlayers(position, fRange, pResults); } } } } } void CWorld::EnsureBlockIsTicking(CWorldLandBlock *pBlock) { if (!pBlock->IsTickingWithWorld()) { LandblockMap::iterator entry; entry = m_mDormantBlocks.find(pBlock->GetHeader()); if (entry != m_mDormantBlocks.end()) { m_mDormantBlocks.erase(entry); m_vSpawns.push_back(pBlock); pBlock->SetIsTickingWithWorld(true); return; } entry = m_mUnloadedBlocks.find(pBlock->GetHeader()); if (entry != m_mUnloadedBlocks.end()) { m_mUnloadedBlocks.erase(entry); m_vSpawns.push_back(pBlock); pBlock->SetIsTickingWithWorld(true); return; } } } void CWorld::EnumNearby(CWeenieObject *pSource, float fRange, std::list *pResults) { // Enumerate nearby world objects if (pSource != NULL && !pSource->HasOwner()) { uint32_t dwCell = pSource->GetLandcell(); WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); CWorldLandBlock *pBlock = m_pBlocks[block]; if ((pBlock && (dwCell & 0xFFFF) > 0x100) && !pBlock->PossiblyVisibleToOutdoors(dwCell)) //check if inside { pBlock->EnumNearby(pSource, fRange, pResults); } else { uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) // indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) { pBlock->EnumNearby(pSource, fRange, pResults); } } } } } } void CWorld::EnumNearbyPlayers(CWeenieObject *pSource, float fRange, std::list *pResults) { // Enumerate nearby world objects if (pSource != NULL && !pSource->HasOwner()) { uint32_t dwCell = pSource->GetLandcell(); WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); CWorldLandBlock *pBlock = m_pBlocks[block]; if ((pBlock && (dwCell & 0xFFFF) > 0x100) && !pBlock->PossiblyVisibleToOutdoors(dwCell)) //check if inside { pBlock->EnumNearbyPlayers(pSource, fRange, pResults); } else { uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) // indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) { pBlock->EnumNearbyPlayers(pSource, fRange, pResults); } } } } } } CWeenieObject *CWorld::FindWithinPVS(CWeenieObject *source, uint32_t object_id) { if (!source || !object_id) return NULL; if (source->GetID() == object_id) return source; //Find nearby world objects. if (!source->HasOwner()) { uint32_t dwCell = source->GetLandcell(); WORD block = BLOCK_WORD(dwCell); WORD cell = CELL_WORD(dwCell); uint32_t basex = BASE_OFFSET(BLOCK_X(block), CELL_X(cell)); uint32_t basey = BASE_OFFSET(BLOCK_Y(block), CELL_Y(cell)); uint32_t minx = basex; uint32_t maxx = basex; uint32_t miny = basey; uint32_t maxy = basey; //if ( cell < 0xFF ) //indoor structure { if (minx >= (dwMinimumCellX + PVC_RANGE)) minx -= PVC_RANGE; else minx = dwMinimumCellX; if (maxx <= (dwMaximumCellX - PVC_RANGE)) maxx += PVC_RANGE; else maxx = dwMaximumCellX; if (miny >= (dwMinimumCellY + PVC_RANGE)) miny -= PVC_RANGE; else miny = dwMinimumCellY; if (maxy <= (dwMaximumCellY - PVC_RANGE)) maxy += PVC_RANGE; else maxy = dwMaximumCellY; } minx = BLOCK_OFFSET(minx) << 8; miny = BLOCK_OFFSET(miny); maxx = BLOCK_OFFSET(maxx) << 8; maxy = BLOCK_OFFSET(maxy); for (uint32_t xit = minx; xit <= maxx; xit += 0x100) { for (uint32_t yit = miny; yit <= maxy; yit += 1) { CWorldLandBlock *pBlock = m_pBlocks[xit | yit]; if (pBlock) { CWeenieObject *pEntity = pBlock->FindEntity(object_id); if (pEntity) return pEntity; } } } } if (CWeenieObject *externalObject = g_pWorld->FindObject(object_id)) { if (CContainerWeenie *externalContainer = externalObject->GetWorldTopLevelContainer()) { if (externalContainer->_openedById == source->GetID()) { return externalObject; } } } return source->FindContained(object_id); } void CWorld::EnumerateDungeonsFromCellData() { #ifndef QUICKSTART // FILE *dropsFile = fopen("d:\\temp\\dungeonDrops.txt", "wt"); // This creates a list of dungeons FILEMAP* pFiles = g_pCell->GetFiles(); FILEMAP::iterator i = pFiles->begin(); FILEMAP::iterator iend = pFiles->end(); while (i != iend) { uint32_t dwID = i->first; if (((dwID & 0xFFFF) >= 0xFFFE) || ((dwID & 0xFFFF) < 0x100)) { i++; continue; } CEnvCell *pEnvCell = CEnvCell::Get(dwID); if (!pEnvCell) { i++; continue; } Frame drop; bool bFoundDrop = false; for (uint32_t i = 0; i < pEnvCell->num_static_objects; i++) { uint32_t setupID = pEnvCell->static_object_ids[i]; if ((setupID >= 0x02000C39 && setupID <= 0x02000C48) || (setupID == 0x02000F4A)) { drop = pEnvCell->static_object_frames[i]; bFoundDrop = true; break; } } /* if (dropsFile) { for (uint32_t i = 0; i < pEnvCell->num_static_objects; i++) { uint32_t setupID = pEnvCell->static_object_ids[i]; if ((setupID >= 0x02000C39 && setupID <= 0x02000C48) || (setupID == 0x02000F4A)) { fprintf(dropsFile, "%08X %f %f %f %f %f %f %f\n", dwID, pEnvCell->static_object_frames[i].m_origin.x, pEnvCell->static_object_frames[i].m_origin.y, pEnvCell->static_object_frames[i].m_origin.z, pEnvCell->static_object_frames[i].m_angles.w, pEnvCell->static_object_frames[i].m_angles.x, pEnvCell->static_object_frames[i].m_angles.y, pEnvCell->static_object_frames[i].m_angles.z); break; } } } */ CEnvCell::Release(pEnvCell); if (bFoundDrop) { Position dropPos; dropPos.objcell_id = dwID; dropPos.frame = drop; m_mDungeons[dwID] = dropPos; if (dwID < (uint32_t)(0 - 0x10100)) i = pFiles->upper_bound(((dwID & 0xFFFF0000) + 0x10100) - 1); else break; } else { i++; } } #endif } void CWorld::BroadcastChatChannel(uint32_t channel_id, CPlayerWeenie *sender, const std::u16string &message) { uint32_t sender_monarch_id = 0; if (channel_id == Allegiance_ChatChannel) { sender_monarch_id = sender->InqIIDQuality(MONARCH_IID, 0); if (!sender_monarch_id) { return; } } BinaryWriter chatPayload; chatPayload.Write(channel_id); chatPayload.AppendWStringFromString(sender->GetName()); //chatPayload.AppendWStringFromString(message); chatPayload.Write(message); chatPayload.Write(12); // size of next data chatPayload.Write(sender->GetID()); chatPayload.Write(0); chatPayload.Write(2); BinaryWriter chatPackage; chatPackage.Write(0xF7DE); chatPackage.Write(chatPayload.GetSize() + 32); chatPackage.Write(1); // type chatPackage.Write(1); chatPackage.Write(1); chatPackage.Write(0xB0045); chatPackage.Write(1); chatPackage.Write(0xB0045); chatPackage.Write(0); chatPackage.Write(chatPayload.GetSize()); chatPackage.Write(&chatPayload); for (auto &entry : m_mAllPlayers) { CPlayerWeenie *player = entry.second; if (player && !player->IsPlayerSquelched(sender->GetID(), true)) { BOOL bShouldHear = FALSE; switch (channel_id) { case General_ChatChannel: bShouldHear = (player->GetCharacterOptions2() & HearGeneralChat_CharacterOptions2); break; case Trade_ChatChannel: bShouldHear = (player->GetCharacterOptions2() & HearTradeChat_CharacterOptions2); break; case LFG_ChatChannel: bShouldHear = (player->GetCharacterOptions2() & HearLFGChat_CharacterOptions2); break; case Roleplay_ChatChannel: bShouldHear = (player->GetCharacterOptions2() & HearRoleplayChat_CharacterOptions2); break; case Allegiance_ChatChannel: if (player->GetCharacterOptions() & HearAllegianceChat_CharacterOption) { if (sender_monarch_id && sender_monarch_id == player->InqIIDQuality(MONARCH_IID, 0)) bShouldHear = TRUE; if (player->IsAllegGagged()) bShouldHear = FALSE; } break; } if (bShouldHear) { player->SendNetMessage(&chatPackage, 4, FALSE, FALSE); } } } } std::string CWorld::GetServerStatus() { std::string status; extern CPhatServer *g_pPhatServer; uint32_t runTime = (uint32_t)(g_pGlobals->Time() - g_pPhatServer->GetStartupTime()); uint32_t runSeconds = runTime % 60; runTime /= 60; uint32_t runMinutes = runTime % 60; runTime /= 60; uint32_t runHours = runTime; status += csprintf("Server runtime: %uh %um %us\n", runHours, runMinutes, runSeconds); status += csprintf("%u MB used, %u / %u MB physical mem free.\n", (uint32_t)(GetProcessMemoryUsage() >> 20), (uint32_t)(GetFreeMemory() >> 20), (uint32_t)(GetTotalMemory() >> 20)); { // check bucket health uint32_t biggestBucket = 0; for (uint32_t i = 0; i < m_mAllPlayers.bucket_count(); i++) { uint32_t bucketSize = (uint32_t)m_mAllPlayers.bucket_size(i); if (bucketSize >= biggestBucket) biggestBucket = bucketSize; } status += csprintf("%u players (%u buckets [%u in biggest].)\n", m_mAllPlayers.size(), m_mAllPlayers.bucket_count(), biggestBucket); } { std::map sortedBlocks; unsigned int activeBlocks = 0; unsigned int dormantPendingBlocks = 0; unsigned int dormantBlocks = 0; uint32_t totalActiveObjects = 0; // find the top 5 most active blocks for (auto block : m_vBlocks) { if (!block) continue; uint32_t liveCount = block->LiveCount(); switch (block->GetDormancyStatus()) { case LandblockDormancyStatus::DoNotGoDormant: activeBlocks++; totalActiveObjects += liveCount; break; case LandblockDormancyStatus::WaitToGoDormant: dormantPendingBlocks++; totalActiveObjects += liveCount; break; case LandblockDormancyStatus::Dormant: dormantBlocks++; break; } liveCount = (liveCount << 16) | block->PlayerCount(); sortedBlocks[((0xFFFFFFFF - ((uint64_t)liveCount)) << 32) | (uint64_t)block->GetHeader()] = block; } { // check bucket health /* uint32_t biggestBucket = 0; for (uint32_t i = 0; i < m_mAllObjects.bucket_count(); i++) { uint32_t bucketSize = (uint32_t) m_mAllObjects.bucket_size(i); if (bucketSize >= biggestBucket) biggestBucket = bucketSize; } status += csprintf("%u objects (%u buckets [%u in biggest].)\n", m_mAllObjects.size(), m_mAllObjects.bucket_count(), biggestBucket); */ status += csprintf("%u active objects, %u total objects (%u buckets.)\n", totalActiveObjects, m_mAllObjects.size(), m_mAllObjects.bucket_count()); } status += csprintf("%u total blocks loaded. %u active. %u pending dormancy. %u dormant. %u unloaded.\n", m_vBlocks.size() + m_vSpawns.size(), activeBlocks, dormantPendingBlocks, (uint32_t)m_mDormantBlocks.size(), (uint32_t)m_mUnloadedBlocks.size()); unsigned int numBlocksToShow = 5; status += csprintf("Top %u most active blocks\n", numBlocksToShow); for (auto block : sortedBlocks) { uint32_t numObjects = (0xFFFFFFFF - (uint32_t)(block.first >> 32)); uint32_t blockID = (block.first & 0xFFFFFFFF); status += csprintf("%04X - %u objects %u players\n", blockID, HIWORD(numObjects), LOWORD(numObjects)); numBlocksToShow--; if (!numBlocksToShow) break; } } return status; } uint32_t CWorld::GenerateGUID(eGUIDClass type) { uint32_t new_iid = g_pObjectIDGen->GenerateGUID(type); //while (FindObject(new_iid)) //{ // new_iid = g_pObjectIDGen->GenerateGUID(type); //} return new_iid; } void CWorld::NotifyEventStarted(std::string eventName, GameEventDef *event) { auto result = _eventWeenies.equal_range(eventName); for (auto it = result.first; it != result.second; it++) { if (CWeenieObject *weenie = FindObject(it->second)) { weenie->CheckEventState(eventName, event); } } } void CWorld::NotifyEventStopped(std::string eventName, GameEventDef *event) { auto result = _eventWeenies.equal_range(eventName); for (auto it = result.first; it != result.second; it++) { if (CWeenieObject *weenie = FindObject(it->second)) { weenie->CheckEventState(eventName, event); } } } void CWorld::AddToUsedMergedItems(uint32_t item) { _stackableOnGround.try_emplace(item, false); } bool CWorld::IsItemInUse(uint32_t item) { std::map::const_iterator it = _stackableOnGround.find(item); if (it == _stackableOnGround.end()) return false; if (it->second == true) return true; _stackableOnGround[item] = true; return false; } void CWorld::RemoveMergedItem(uint32_t item) { _stackableOnGround.erase(item); }