#include "Contents/World/WorldProximity.h" #include "Contents/World/World.h" #include "Client/Mode/CGameMode.h" #include "Client/Mode/ModeMgr.h" #include "Client/Session.h" #include "Contents/Actors/GameActor.h" #include "Contents/Actors/Pc.h" #include "Contents/Actors/RenderObject.h" #include "Network/Connection/Connection.h" #include "Network/Packets/PacketId.h" #include "Network/Packets/PacketLengths.h" #include "Resource/World/TerrainResource.h" #include #include #include #include #include "Contents/GuildInfo.h" #pragma pack(push, 1) struct PACKET_CZ_APPROXIMATE_ACTOR { /* 0000 */ std::int16_t packetType; /* 0002 */ std::uint32_t accountId; /* 0006 */ std::uint16_t unknown_0006; /* 0008 */ std::uint8_t unknown_0008; }; #pragma pack(pop) #if defined(_WIN32) && !defined(_WIN64) static_assert(sizeof(PACKET_CZ_APPROXIMATE_ACTOR) == 9); static_assert(offsetof(PACKET_CZ_APPROXIMATE_ACTOR, accountId) == 2); static_assert(offsetof(PACKET_CZ_APPROXIMATE_ACTOR, unknown_0006) == 6); #endif // INLINED void UpdateWorldProximityNotification( unsigned char& notified, std::function nearby, CConnection& connection, const PacketLengths& lengths, const std::uint32_t& accountId ) { if (notified == 1) { if (!nearby()) { notified = 0; } return; } if (nearby()) { unsigned char packet[9]; const std::uint16_t id = 0x0BB0; const std::uint16_t enabled = 1; std::memcpy(packet, &id, sizeof(id)); std::memcpy(packet + 2, &accountId, sizeof(accountId)); std::memcpy(packet + 6, &enabled, sizeof(enabled)); packet[8] = 1; connection.Send(lengths.FixedLength(id), packet); notified = 1; } } // OFFSET: 0x00AC93C0 // FUNCTION: fn_17683 void CWorld::UpdateGuildMasterApproach(std::function isNearby) { if (this->unknown_00F4 == true) { if (!isNearby()) { this->unknown_00F4 = false; } return; } if (isNearby() == true) { PACKET_CZ_APPROXIMATE_ACTOR packet; packet.packetType = ID_CZ_APPROXIMATE_ACTOR; packet.accountId = g_guildInfo.unknown_00D0; packet.unknown_0006 = 1; packet.unknown_0008 = 1; GetRagConnection().Send(reinterpret_cast(GetRagConnection()).FixedLength(packet.packetType), &packet); this->unknown_00F4 = true; } } // OFFSET: 0x00AC8A40 // FUNCTION: fn_17678 __declspec(noinline) bool CWorld::FindActorInCell(const float* position, std::function predicate) const { const float key = static_cast(this->terrain->width) * position[1] + position[0]; const auto found = this->actorCells.find(key); if (found == this->actorCells.end()) { return false; } const std::list& ids = found->second; return std::find_if(ids.begin(), ids.end(), predicate) != ids.end(); } // OFFSET: 0x00AC8BA0 // FUNCTION: fn_17679 bool CWorld::IsGuildMaster(std::uint32_t aid) { for (CRenderObject* actor : this->actors) { if (actor->render.accountId == aid) { CGameActor* gameActor = static_cast(actor); if (!gameActor) { return false; } return gameActor->Slot42() == static_cast(g_guildInfo.unknown_00D0); } } return false; } // OFFSET: 0x00AC8750 // FUNCTION: fn_17677 bool CWorld::IsExistGuildMasterNearby(const float& x, const float& z) { if (!this->player || this->player->Slot50() < 1) { return false; } if (!this->actorCells.empty()) { for (float cellX = x - static_cast(this->nearbyRange); cellX <= static_cast(this->nearbyRange) + x; cellX += 1.0f) { for (float cellZ = z - static_cast(this->nearbyRange); cellZ <= static_cast(this->nearbyRange) + z; cellZ += 1.0f) { const float position[2] = {cellX, cellZ}; if (this->FindActorInCell(position, [this](std::uint32_t aid) { return this->IsGuildMaster(aid); }) == true) { return true; } } } } for (std::uint32_t aid : this->unindexedActorIds) { if (!this->IsGuildMaster(aid)) { continue; } CRenderObject* actor = g_session.FindGameActor(aid); if (actor->render.unknown_0070 == 1) { float centerX = 0.0f; float centerZ = 0.0f; int column = 0; int row = 0; CGameMode* mode = static_cast(g_modeMgr.GetGameMode()); mode->WorldCell(actor->render.position.x + 0.5f, actor->render.position.z + 0.5f, column, row); mode = static_cast(g_modeMgr.GetGameMode()); mode->CellCenter(column, row, centerX, centerZ); return std::fabs(centerX - x) <= static_cast(this->nearbyRange) && std::fabs(centerZ - z) <= static_cast(this->nearbyRange); } return std::fabs(actor->render.position.x - x) <= static_cast(this->nearbyRange) && std::fabs(actor->render.position.z - z) <= static_cast(this->nearbyRange); } return false; } // OFFSET: 0x00ACA180 // FUNCTION: fn_17689 void CWorld::ClearActorCells() { this->actorCells.clear(); this->unindexedActorIds.clear(); } // OFFSET: 0x00ACA670 // FUNCTION: fn_17695 void CWorld::RemovePcPos(std::uint32_t aid, bool notify) { for (auto cell = this->actorCells.begin(); cell != this->actorCells.end(); ++cell) { std::list& ids = cell->second; for (auto entry = ids.begin(); entry != ids.end(); ++entry) { if (*entry != aid) { continue; } if (notify && this->unknown_00F4) { if (this->IsGuildMaster(aid)) { this->UpdateGuildMasterApproach([this]() { return false; }); } } ids.erase(entry); return; } } } // OFFSET: 0x00ACA7B0 // FUNCTION: fn_17696 void CWorld::RemovePcPosForMove(std::uint32_t aid, bool notify) { for (auto entry = this->unindexedActorIds.begin(); entry != this->unindexedActorIds.end(); ++entry) { if (*entry != aid) { continue; } if (notify && this->unknown_00F4) { if (this->IsGuildMaster(aid)) { this->UpdateGuildMasterApproach([this]() { return false; }); } } this->unindexedActorIds.erase(entry); return; } } struct WorldProximityGuildMasterCallback { void* unknown_0000; CWorld* world; bool Invoke(const std::uint32_t& aid); }; // OFFSET: 0x00ACBA10 // FUNCTION: fn_17717 bool WorldProximityRejectA() { return false; } // OFFSET: 0x00ACBA20 // FUNCTION: fn_17718 bool WorldProximityRejectB() { return false; } // OFFSET: 0x00ACBA30 // FUNCTION: fn_17719 bool WorldProximityGuildMasterCallback::Invoke(const std::uint32_t& aidRef) { const std::uint32_t aid = aidRef; for (CRenderObject* actor : this->world->actors) { if (actor->render.accountId != aid) { continue; } if (actor == nullptr) { return false; } return static_cast(actor)->Slot42() == static_cast(g_guildInfo.unknown_00D0); } return false; } struct WorldProximityRangeCallback { void* unknown_0000; int range; float playerX; float playerZ; float cellX; float cellZ; bool Invoke(); }; // OFFSET: 0x00ACBA80 // FUNCTION: fn_17720 bool WorldProximityRangeCallback::Invoke() { return std::fabs(this->cellX - this->playerX) <= static_cast(this->range) && std::fabs(this->cellZ - this->playerZ) <= static_cast(this->range); } // OFFSET: 0x00AC9D50 // FUNCTION: fn_17686 __declspec(noinline) void CWorld::PcApproachToPlayer(std::uint32_t aid, const CellFloatPos& pos) { if (this->player && this->player->Slot50() > 0 && this->IsGuildMaster(aid)) { auto isNearby = [](const CellFloatPos& cell, const CellFloatPos& player, int range) { return std::fabs(cell.x - player.x) <= static_cast(range) && std::fabs(cell.z - player.z) <= static_cast(range); }; const CellFloatPos player = {this->player->render.position.x, this->player->render.position.z}; this->UpdateGuildMasterApproach(std::bind(isNearby, pos, player, this->nearbyRange)); } } // OFFSET: 0x00AC9E70 // FUNCTION: fn_17687 void CWorld::PlayerMove(const float& x, const float& z) { auto isNearby = [this](const float& cellX, const float& cellZ) { return this->IsExistGuildMasterNearby(cellX, cellZ); }; this->UpdateGuildMasterApproach(std::bind(isNearby, x, z)); } // OFFSET: 0x00ACA9F0 // FUNCTION: fn_17699 __declspec(noinline) void CWorld::AddPcPos(std::uint32_t aid, const CellFloatPos& cell) { this->RemovePcPos(aid, false); for (auto entry = this->unindexedActorIds.begin(); entry != this->unindexedActorIds.end(); ++entry) { if (*entry != aid) { continue; } this->unindexedActorIds.erase(entry); break; } const float key = static_cast(this->terrain->width) * cell.z + cell.x; this->actorCells[key].emplace_back(aid); this->PcApproachToPlayer(aid, cell); } // OFFSET: 0x00ACAB90 // FUNCTION: fn_17700 void CWorld::AddPcPosFromCell(std::uint32_t aid, const int* cell) { CellFloatPos pos; pos.x = 0.0f; pos.z = 0.0f; static_cast(g_modeMgr.GetGameMode())->CellCenter(cell[0], cell[1], pos.x, pos.z); this->AddPcPos(aid, pos); } // OFFSET: 0x00ACABE0 // FUNCTION: fn_17701 void CWorld::AddPcPosUnindexed(std::uint32_t aid, const CellFloatPos& cell) { this->RemovePcPos(aid, false); for (auto entry = this->unindexedActorIds.begin(); entry != this->unindexedActorIds.end(); ++entry) { if (*entry != aid) { continue; } this->unindexedActorIds.erase(entry); break; } this->unindexedActorIds.push_back(aid); this->PcApproachToPlayer(aid, cell); }