DarkflameServer/dGame/EntityManager.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

607 lines
17 KiB
C++
Raw Permalink Normal View History

#include "EntityManager.h"
#include "RakNetTypes.h"
#include "Game.h"
#include "User.h"
#include "ObjectIDManager.h"
#include "Character.h"
#include "GeneralUtils.h"
#include "dServer.h"
#include "Spawner.h"
#include "SkillComponent.h"
#include "SwitchComponent.h"
#include "UserManager.h"
#include "Metrics.hpp"
#include "dZoneManager.h"
#include "MissionComponent.h"
#include "Game.h"
#include "Logger.h"
#include "MessageIdentifiers.h"
#include "dConfig.h"
#include "eTriggerEventType.h"
#include "eObjectBits.h"
#include "eGameMasterLevel.h"
#include "eReplicaComponentType.h"
#include "eReplicaPacketType.h"
#include "PlayerManager.h"
#include "GhostComponent.h"
#include <ranges>
// Configure which zones have ghosting disabled, mostly small worlds.
std::vector<LWOMAPID> EntityManager::m_GhostingExcludedZones = {
// Small zones
1000,
// Racing zones
1203,
1261,
1303,
1403,
// Property zones
1150,
1151,
1250,
1251,
1350,
1450
};
// Configure some exceptions for ghosting, nessesary for some special objects.
std::vector<LOT> EntityManager::m_GhostingExcludedLOTs = {
Implementing and Fixing All Racing Achievements (#366) * Grammatical changes in comments * Grammatical fixes in comments Small grammatical fixes found in comments throughout the code. * Added descriptions to functions Added descriptions to functions that didn't have them to keep the code well documented * Created RacingTaskParam.h Created RacingTaskParam so eliminate magic numbers in the original implementation of completing racing missions. * Updated magic numbers in Mission.cpp Updated magic numbers in Mission.cpp to a meaningful name. * Implemented racing smashable task progression Previously, races did not progress tasks for smashing Entities. Now all achievements tracking smashables track them correctly. This has been implemented in the three Entities that can be smashed in a race (imagination boxes, track specific smashables, Forbidden Valley dragon eggs). * Updated race imagination task progression Race imagination now no longer uses a magic number when passed to missionComponent. Instead we use a number defined in an enum located in RacingTaskParam.h * Updated Race task checks Racing tasks for completing races without smashing now no longer auto complete the whole chain of missions. Tasks that track placing on tracks and races overall now properly complete. Tasks that count how many missions in a zone are completed now function. Tasks that track race completions in multiple areas now function. * Updated RacingControlComponent.cpp Fixed any tasks that required 3 players to now require 3 or more players in a race to progress. This restriction is ignored if the world config opted in for solo racing to allow progression in solo worlds. Updated magic numbers sent into missionComponent->Progress to an enum created in this PR. Fixed some indentation. * Fixed a grammatical error in variable name Fixed a grammatical error in the enum for task params
2022-02-05 11:28:17 +00:00
// AG - Footrace
4967
};
void EntityManager::Initialize() {
// Check if this zone has ghosting enabled
m_GhostingEnabled = std::find(
m_GhostingExcludedZones.begin(),
m_GhostingExcludedZones.end(),
Game::zoneManager->GetZoneID().GetMapID()
) == m_GhostingExcludedZones.end();
// grab hardcore mode settings and load them with sane defaults
auto hcmode = Game::config->GetValue("hardcore_mode");
m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1");
auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent");
m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent);
auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier");
m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult);
auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death");
m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1");
// If cloneID is not zero, then hardcore mode is disabled
// aka minigames and props
if (Game::zoneManager->GetZoneID().GetCloneID() != 0) m_HardcoreMode = false;
}
Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentEntity, const bool controller, const LWOOBJID explicitId) {
// Determine the objectID for the new entity
LWOOBJID id;
// If an explicit ID was provided, use it
if (explicitId != LWOOBJID_EMPTY) {
id = explicitId;
}
// For non player entites, we'll generate a new ID or set the appropiate flags
else if (user == nullptr || info.lot != 1) {
// Entities with no ID already set, often spawned entities, we'll generate a new sequencial ID
if (info.id == 0) {
id = ObjectIDManager::GenerateObjectID();
}
// Entities with an ID already set, often level entities, we'll use that ID as a base
else {
id = info.id;
}
// Exclude the zone control object from any flags
if (!controller && info.lot != 14) {
// The client flags means the client should render the entity
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
// Spawned entities require the spawned flag to render
if (info.spawnerID != 0) {
GeneralUtils::SetBit(id, eObjectBits::SPAWNED);
}
}
}
// For players, we'll use the persistent ID for that character
else {
id = user->GetLastUsedChar()->GetObjectID();
}
info.id = id;
Entity* entity = new Entity(id, info, user, parentEntity);
// Initialize the entity
entity->Initialize();
// Add the entity to the entity map
m_Entities.insert_or_assign(id, entity);
// Set the zone control entity if the entity is a zone control object, this should only happen once
if (controller) {
m_ZoneControlEntity = entity;
}
// Check if this entity is a respawn point, if so add it to the registry
const auto& spawnName = entity->GetVar<std::u16string>(u"respawnname");
if (!spawnName.empty()) {
m_SpawnPoints.insert_or_assign(GeneralUtils::UTF16ToWTF8(spawnName), entity->GetObjectID());
}
return entity;
}
void EntityManager::DestroyEntity(const LWOOBJID& objectID) {
DestroyEntity(GetEntity(objectID));
}
void EntityManager::DestroyEntity(Entity* entity) {
if (!entity) return;
entity->TriggerEvent(eTriggerEventType::DESTROY, entity);
const auto id = entity->GetObjectID();
if (std::count(m_EntitiesToDelete.begin(), m_EntitiesToDelete.end(), id)) {
return;
}
// Destruct networked entities
if (entity->GetNetworkId() != 0) {
DestructEntity(entity);
}
// Delete this entity at the end of the frame
ScheduleForDeletion(id);
}
void EntityManager::SerializeEntities() {
for (size_t i = 0; i < m_EntitiesToSerialize.size(); i++) {
const LWOOBJID toSerialize = m_EntitiesToSerialize[i];
auto* entity = GetEntity(toSerialize);
if (!entity) continue;
m_SerializationCounter++;
RakNet::BitStream stream;
stream.Write<char>(ID_REPLICA_MANAGER_SERIALIZE);
stream.Write<unsigned short>(entity->GetNetworkId());
entity->WriteBaseReplicaData(stream, eReplicaPacketType::SERIALIZATION);
entity->WriteComponents(stream, eReplicaPacketType::SERIALIZATION);
if (entity->GetIsGhostingCandidate()) {
for (auto* player : PlayerManager::GetAllPlayers()) {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (ghostComponent && ghostComponent->IsObserved(toSerialize)) {
Game::server->Send(stream, player->GetSystemAddress(), false);
}
}
} else {
Game::server->Send(stream, UNASSIGNED_SYSTEM_ADDRESS, true);
}
}
m_EntitiesToSerialize.clear();
}
void EntityManager::KillEntities() {
for (size_t i = 0; i < m_EntitiesToKill.size(); i++) {
const LWOOBJID toKill = m_EntitiesToKill[i];
auto* entity = GetEntity(toKill);
if (!entity) {
LOG("Attempting to kill null entity %llu", toKill);
continue;
}
if (entity->GetScheduledKiller()) {
entity->Smash(entity->GetScheduledKiller()->GetObjectID(), eKillType::SILENT);
} else {
entity->Smash(LWOOBJID_EMPTY, eKillType::SILENT);
}
}
m_EntitiesToKill.clear();
}
void EntityManager::DeleteEntities() {
for (size_t i = 0; i < m_EntitiesToDelete.size(); i++) {
const LWOOBJID toDelete = m_EntitiesToDelete[i];
auto entityToDelete = GetEntity(toDelete);
if (entityToDelete) {
// Get all this info first before we delete the player.
auto networkIdToErase = entityToDelete->GetNetworkId();
const auto& ghostingToDelete = std::find(m_EntitiesToGhost.begin(), m_EntitiesToGhost.end(), entityToDelete);
delete entityToDelete;
entityToDelete = nullptr;
if (networkIdToErase != 0) m_LostNetworkIds.push(networkIdToErase);
if (ghostingToDelete != m_EntitiesToGhost.end()) m_EntitiesToGhost.erase(ghostingToDelete);
} else {
LOG("Attempted to delete non-existent entity %llu", toDelete);
}
m_Entities.erase(toDelete);
}
m_EntitiesToDelete.clear();
}
void EntityManager::UpdateEntities(const float deltaTime) {
for (auto* entity : m_Entities | std::views::values) {
entity->Update(deltaTime);
}
SerializeEntities();
KillEntities();
DeleteEntities();
}
Entity* EntityManager::GetEntity(const LWOOBJID& objectId) const {
const auto& index = m_Entities.find(objectId);
if (index == m_Entities.end()) {
return nullptr;
}
return index->second;
}
std::vector<Entity*> EntityManager::GetEntitiesInGroup(const std::string& group) {
std::vector<Entity*> entitiesInGroup;
for (auto* entity : m_Entities | std::views::values) {
for (const auto& entityGroup : entity->GetGroups()) {
if (entityGroup == group) {
entitiesInGroup.push_back(entity);
}
}
}
return entitiesInGroup;
}
std::vector<Entity*> EntityManager::GetEntitiesByComponent(const eReplicaComponentType componentType) const {
std::vector<Entity*> withComp;
if (componentType != eReplicaComponentType::INVALID) {
for (auto* entity : m_Entities | std::views::values) {
if (!entity->HasComponent(componentType)) continue;
withComp.push_back(entity);
}
}
return withComp;
}
std::vector<Entity*> EntityManager::GetEntitiesByLOT(const LOT& lot) const {
std::vector<Entity*> entities;
for (auto* entity : m_Entities | std::views::values) {
if (entity->GetLOT() == lot) entities.push_back(entity);
}
return entities;
}
std::vector<Entity*> EntityManager::GetEntitiesByProximity(NiPoint3 reference, float radius) const {
std::vector<Entity*> entities;
if (radius <= 1000.0f) { // The client has a 1000 unit limit on this same logic, so we'll use the same limit
for (auto* entity : m_Entities | std::views::values) {
if (NiPoint3::Distance(reference, entity->GetPosition()) <= radius) entities.push_back(entity);
}
}
return entities;
}
Entity* EntityManager::GetZoneControlEntity() const {
return m_ZoneControlEntity;
}
Entity* EntityManager::GetSpawnPointEntity(const std::string& spawnName) const {
// Lookup the spawn point entity in the map
const auto& spawnPoint = m_SpawnPoints.find(spawnName);
// Check if the spawn point entity is valid just in case
return spawnPoint == m_SpawnPoints.end() ? nullptr : GetEntity(spawnPoint->second);
}
const std::unordered_map<std::string, LWOOBJID>& EntityManager::GetSpawnPointEntities() const {
return m_SpawnPoints;
}
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr, const bool skipChecks) {
if (!entity) {
LOG("Attempted to construct null entity");
return;
}
if (entity->GetNetworkId() == 0) {
uint16_t networkId;
if (!m_LostNetworkIds.empty()) {
networkId = m_LostNetworkIds.top();
m_LostNetworkIds.pop();
} else {
networkId = ++m_NetworkIdCounter;
}
entity->SetNetworkId(networkId);
}
if (entity->GetIsGhostingCandidate()) {
if (std::find(m_EntitiesToGhost.begin(), m_EntitiesToGhost.end(), entity) == m_EntitiesToGhost.end()) {
m_EntitiesToGhost.push_back(entity);
}
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
CheckGhosting(entity);
return;
}
}
m_SerializationCounter++;
RakNet::BitStream stream;
stream.Write<uint8_t>(ID_REPLICA_MANAGER_CONSTRUCTION);
stream.Write(true);
stream.Write<uint16_t>(entity->GetNetworkId());
entity->WriteBaseReplicaData(stream, eReplicaPacketType::CONSTRUCTION);
entity->WriteComponents(stream, eReplicaPacketType::CONSTRUCTION);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
if (skipChecks) {
Game::server->Send(stream, UNASSIGNED_SYSTEM_ADDRESS, true);
} else {
for (auto* player : PlayerManager::GetAllPlayers()) {
if (player->GetPlayerReadyForUpdates()) {
Game::server->Send(stream, player->GetSystemAddress(), false);
} else {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
}
}
}
} else {
Game::server->Send(stream, sysAddr, false);
}
if (entity->IsPlayer()) {
if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) {
GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, sysAddr);
}
}
}
void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
//ZoneControl is special:
ConstructEntity(m_ZoneControlEntity, sysAddr);
for (auto* entity : m_Entities | std::views::values) {
if (entity && (entity->GetSpawnerID() != 0 || entity->GetLOT() == 1) && !entity->GetIsGhostingCandidate()) {
ConstructEntity(entity, sysAddr);
}
}
UpdateGhosting(PlayerManager::GetPlayer(sysAddr));
}
void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) {
if (!entity || entity->GetNetworkId() == 0) return;
RakNet::BitStream stream;
stream.Write<uint8_t>(ID_REPLICA_MANAGER_DESTRUCTION);
stream.Write<uint16_t>(entity->GetNetworkId());
Game::server->Send(stream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
for (auto* player : PlayerManager::GetAllPlayers()) {
if (!player->GetPlayerReadyForUpdates()) {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (ghostComponent) ghostComponent->RemoveLimboConstruction(entity->GetObjectID());
}
}
}
void EntityManager::SerializeEntity(Entity* entity) {
if (!entity) return;
EntityManager::SerializeEntity(*entity);
}
void EntityManager::SerializeEntity(const Entity& entity) {
if (entity.GetNetworkId() == 0) return;
if (std::find(m_EntitiesToSerialize.cbegin(), m_EntitiesToSerialize.cend(), entity.GetObjectID()) == m_EntitiesToSerialize.cend()) {
m_EntitiesToSerialize.push_back(entity.GetObjectID());
}
}
void EntityManager::DestructAllEntities(const SystemAddress& sysAddr) {
for (auto* entity : m_Entities | std::views::values) {
DestructEntity(entity, sysAddr);
}
}
void EntityManager::SetGhostDistanceMax(float value) {
m_GhostDistanceMaxSquared = value * value;
}
void EntityManager::SetGhostDistanceMin(float value) {
m_GhostDistanceMinSqaured = value * value;
}
void EntityManager::QueueGhostUpdate(LWOOBJID playerID) {
if (std::find(m_PlayersToUpdateGhosting.begin(), m_PlayersToUpdateGhosting.end(), playerID) == m_PlayersToUpdateGhosting.end()) {
m_PlayersToUpdateGhosting.push_back(playerID);
}
}
void EntityManager::UpdateGhosting() {
for (const auto playerID : m_PlayersToUpdateGhosting) {
auto* player = PlayerManager::GetPlayer(playerID);
if (player == nullptr) {
continue;
}
UpdateGhosting(player);
}
m_PlayersToUpdateGhosting.clear();
}
void EntityManager::UpdateGhosting(Entity* player) {
if (!player) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (!missionComponent || !ghostComponent) return;
const auto& referencePoint = ghostComponent->GetGhostReferencePoint();
const auto isOverride = ghostComponent->GetGhostOverride();
for (auto* entity : m_EntitiesToGhost) {
const auto& entityPoint = entity->GetPosition();
const auto id = entity->GetObjectID();
const auto observed = ghostComponent->IsObserved(id);
const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint);
auto ghostingDistanceMax = m_GhostDistanceMaxSquared;
auto ghostingDistanceMin = m_GhostDistanceMinSqaured;
const auto isAudioEmitter = entity->GetLOT() == 6368; // https://explorer.lu/objects/6368
if (isAudioEmitter) {
ghostingDistanceMax = ghostingDistanceMin;
}
if (observed && distance > ghostingDistanceMax && !isOverride) {
ghostComponent->GhostEntity(id);
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
} else if (!observed && ghostingDistanceMin > distance) {
// Check collectables, don't construct if it has been collected
uint32_t collectionId = entity->GetCollectibleID();
if (collectionId != 0) {
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
if (missionComponent->HasCollectible(collectionId)) {
continue;
}
}
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1);
}
}
}
void EntityManager::CheckGhosting(Entity* entity) {
if (entity == nullptr) {
return;
}
const auto& referencePoint = entity->GetPosition();
for (auto* player : PlayerManager::GetAllPlayers()) {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (!ghostComponent) continue;
const auto& entityPoint = ghostComponent->GetGhostReferencePoint();
const auto id = entity->GetObjectID();
const auto observed = ghostComponent->IsObserved(id);
const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint);
if (observed && distance > m_GhostDistanceMaxSquared) {
ghostComponent->GhostEntity(id);
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
} else if (!observed && m_GhostDistanceMinSqaured > distance) {
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1);
}
}
}
Entity* EntityManager::GetGhostCandidate(LWOOBJID id) const {
for (auto* entity : m_EntitiesToGhost) {
if (entity->GetObjectID() == id) {
return entity;
}
}
return nullptr;
}
bool EntityManager::GetGhostingEnabled() const {
return m_GhostingEnabled;
}
void EntityManager::ScheduleForKill(Entity* entity) {
// Deactivate switches if they die
if (!entity)
return;
SwitchComponent* switchComp = entity->GetComponent<SwitchComponent>();
if (switchComp) {
entity->TriggerEvent(eTriggerEventType::DEACTIVATED, entity);
}
const auto objectId = entity->GetObjectID();
if (std::find(m_EntitiesToKill.begin(), m_EntitiesToKill.end(), objectId) == m_EntitiesToKill.end()) {
m_EntitiesToKill.push_back(objectId);
}
}
void EntityManager::ScheduleForDeletion(LWOOBJID entity) {
if (std::find(m_EntitiesToDelete.begin(), m_EntitiesToDelete.end(), entity) == m_EntitiesToDelete.end()) {
m_EntitiesToDelete.push_back(entity);
}
}
void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
for (const auto entity : m_Entities | std::views::values) {
if (entity) {
entity->OnFireEventServerSide(origin, args);
}
}
}
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}