DarkflameServer/dGame/dEntity/Entity.cpp
David Markowitz 891648288a Organize Entity header
Probably the third or fourth pass of this darn header...  Just keep making it better every time
Rename some functions to make more sense to a reader
Use different method for Observing/subscribing to component events
Get rid of abomination of overloading GetParentUser
2023-06-16 01:56:02 -07:00

1386 lines
48 KiB
C++

#include "dCommonVars.h"
#include "Entity.h"
#include "CDClientManager.h"
#include "Game.h"
#include "dLogger.h"
#include "dServer.h"
#include "GameMessages.h"
#include "EntityManager.h"
#include "dZoneManager.h"
#include "Zone.h"
#include "Spawner.h"
#include "Player.h"
#include "LUTriggers.h"
#include "User.h"
#include "EntityTimer.h"
#include "EntityCallbackTimer.h"
#include "Loot.h"
#include "eMissionTaskType.h"
#include "eTriggerEventType.h"
#include "eObjectBits.h"
//Component includes:
#include "Component.h"
#include "ControllablePhysicsComponent.h"
#include "RenderComponent.h"
#include "MultiZoneEntranceComponent.h"
#include "CharacterComponent.h"
#include "DestroyableComponent.h"
#include "BuffComponent.h"
#include "BouncerComponent.h"
#include "InventoryComponent.h"
#include "LevelProgressionComponent.h"
#include "PlayerForcedMovementComponent.h"
#include "ScriptComponent.h"
#include "SkillComponent.h"
#include "SimplePhysicsComponent.h"
#include "SwitchComponent.h"
#include "PhantomPhysicsComponent.h"
#include "RigidbodyPhantomPhysicsComponent.h"
#include "MovingPlatformComponent.h"
#include "MissionComponent.h"
#include "MissionOfferComponent.h"
#include "QuickBuildComponent.h"
#include "BuildBorderComponent.h"
#include "MovementAIComponent.h"
#include "VendorComponent.h"
#include "RocketLaunchpadControlComponent.h"
#include "PropertyComponent.h"
#include "CollectibleComponent.h"
#include "BaseCombatAIComponent.h"
#include "PropertyManagementComponent.h"
#include "PropertyVendorComponent.h"
#include "ProximityMonitorComponent.h"
#include "PropertyEntranceComponent.h"
#include "ModelBehaviorComponent.h"
#include "PetComponent.h"
#include "HavokVehiclePhysicsComponent.h"
#include "PossessableComponent.h"
#include "PossessorComponent.h"
#include "ModuleAssemblyComponent.h"
#include "RacingControlComponent.h"
#include "SoundTriggerComponent.h"
#include "ShootingGalleryComponent.h"
#include "RailActivatorComponent.h"
#include "LUPExhibitComponent.h"
#include "TriggerComponent.h"
#include "eGameMasterLevel.h"
#include "eReplicaComponentType.h"
#include "eReplicaPacketType.h"
#include "RacingStatsComponent.h"
#include "MinigameControlComponent.h"
#include "ItemComponent.h"
// Table includes
#include "CDComponentsRegistryTable.h"
#include "CDObjectSkillsTable.h"
#include "CDObjectsTable.h"
#include "CDSkillBehaviorTable.h"
const std::vector<ComponentWhitelist> Entity::m_ComponentWhitelists = {
{ // Unknown use case
eReplicaComponentType::CONTROLLABLE_PHYSICS,
eReplicaComponentType::SIMPLE_PHYSICS,
eReplicaComponentType::RENDER
},
{ // Used for BBB
eReplicaComponentType::RENDER,
eReplicaComponentType::DESTROYABLE,
eReplicaComponentType::ITEM,
eReplicaComponentType::BLUEPRINT,
eReplicaComponentType::MODEL_BEHAVIOR,
eReplicaComponentType::CONTROLLABLE_PHYSICS,
eReplicaComponentType::SIMPLE_PHYSICS,
eReplicaComponentType::SPAWN
},
{ // Unknown use case
eReplicaComponentType::RENDER,
eReplicaComponentType::ITEM,
eReplicaComponentType::BLUEPRINT,
},
{ // Used for Pets
eReplicaComponentType::PET,
eReplicaComponentType::SKILL,
eReplicaComponentType::DESTROYABLE,
eReplicaComponentType::RENDER,
eReplicaComponentType::CONTROLLABLE_PHYSICS
},
{ // Unknown use case
eReplicaComponentType::CONTROLLABLE_PHYSICS,
eReplicaComponentType::SIMPLE_PHYSICS,
eReplicaComponentType::RENDER,
},
};
Entity::Entity(const LWOOBJID& objectID, EntityInfo info, Entity* parentEntity) {
m_ObjectID = objectID;
m_TemplateID = info.lot;
m_ParentEntity = parentEntity;
m_Character = nullptr;
m_GMLevel = eGameMasterLevel::CIVILIAN;
m_NetworkID = 0;
m_Observers = 0;
m_OwnerOverride = LWOOBJID_EMPTY;
m_Groups.clear();
m_Timers.clear();
m_ChildEntities.clear();
m_TargetsInPhantom.clear();
m_DieCallbacks.clear();
m_PhantomCollisionCallbacks.clear();
m_ScheduleKiller = nullptr;
m_IsParentChildDirty = true;
m_IsGhostingCandidate = false;
m_PlayerIsReadyForUpdates = false;
m_ShouldDestroyAfterUpdate = false;
m_Settings = info.settings;
m_NetworkSettings = info.networkSettings;
m_DefaultPosition = info.pos;
m_DefaultRotation = info.rot;
m_Scale = info.scale;
m_Spawner = info.spawner;
m_SpawnerID = info.spawnerID;
m_HasSpawnerNodeID = info.hasSpawnerNodeID;
m_SpawnerNodeID = info.spawnerNodeID;
if (info.lot != 1) m_PlayerIsReadyForUpdates = true;
}
Entity::~Entity() {
if (m_Character) m_Character->SaveXMLToDatabase();
CancelAllTimers();
CancelCallbackTimers();
for (auto child : m_ChildEntities) if (child) child->RemoveParent();
if (m_ParentEntity) m_ParentEntity->RemoveChild(this);
}
void Entity::ApplyComponentWhitelist(TemplateComponents& components) const {
const auto whitelistIndex = GetVar<int32_t>(u"componentWhitelist");
if (whitelistIndex < 0 || whitelistIndex >= m_ComponentWhitelists.size()) return;
const auto& whitelist = m_ComponentWhitelists.at(whitelistIndex);
const auto endRange = std::remove_if(components.begin(), components.end(), [&whitelist](const auto& componentCandidate) {
return std::find(whitelist.begin(), whitelist.end(), componentCandidate.first) == whitelist.end();
});
components.erase(endRange, components.end());
}
void Entity::ApplyComponentBlacklist(TemplateComponents& components) const {
bool hasPetComponent = std::find_if(components.begin(), components.end(), [](const auto& componentCandidate) {
return componentCandidate.first == eReplicaComponentType::PET;
}) != components.end();
if (hasPetComponent) {
auto toRemove = std::remove_if(components.begin(), components.end(), [](const auto& componentCandidate) {
return componentCandidate.first == eReplicaComponentType::MODEL_BEHAVIOR || componentCandidate.first == eReplicaComponentType::ITEM;
});
components.erase(toRemove, components.end());
}
}
void Entity::ApplyComponentConfig(TemplateComponents& components) const {
if (GetVar<bool>(u"markedAsPhantom")) {
auto toRemove = std::remove_if(components.begin(), components.end(), [](const auto& componentCandidate) {
return componentCandidate.first == eReplicaComponentType::SIMPLE_PHYSICS ||
componentCandidate.first == eReplicaComponentType::PHANTOM_PHYSICS; // Just make sure we dont have phantom physics already
});
components.erase(toRemove, components.end());
components.emplace_back(eReplicaComponentType::PHANTOM_PHYSICS, 0U);
}
}
void Entity::AddPathComponent(TemplateComponents& components) const {
const Path* path = dZoneManager::Instance()->GetZone()->GetPath(GetVarAsString(u"attached_path"));
//Check to see if we have an attached path and add the appropiate component to handle it:
if (path) {
// if we have a moving platform path, then we need a moving platform component
if (path->pathType == PathType::MovingPlatform) {
bool hasMovingPlatform = std::count_if(components.begin(), components.end(), [](const auto& componentCandidate) {
return componentCandidate.first == eReplicaComponentType::MOVING_PLATFORM;
}) > 0;
if (!hasMovingPlatform) components.emplace_back(eReplicaComponentType::MOVING_PLATFORM, 0U);
} else if (path->pathType == PathType::Movement) {
bool hasMovementAi = std::count_if(components.begin(), components.end(), [](const auto& componentCandidate) {
return componentCandidate.first == eReplicaComponentType::MOVEMENT_AI;
}) > 0;
if (!hasMovementAi) {
components.emplace_back(eReplicaComponentType::MOVEMENT_AI, 0U);
}
} else {
Game::logger->Log("Entity", "Unsupported path type %i provided for lot %i.", path->pathType, GetLOT());
}
}
}
void Entity::Initialize() {
// A few edge cases to tackle first
const auto triggerInfo = GetVarAsString(u"trigger_id");
if (!triggerInfo.empty()) AddComponent<TriggerComponent>(triggerInfo);
const auto groupIDs = GetVarAsString(u"groupID");
if (!groupIDs.empty()) {
m_Groups = GeneralUtils::SplitString(groupIDs, ';');
if (m_Groups.back().empty()) m_Groups.erase(m_Groups.end() - 1);
}
if (m_ParentEntity) m_ParentEntity->AddChild(this);
auto* componentsRegistry = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
TemplateComponents components = componentsRegistry->GetTemplateComponents(m_TemplateID);
ApplyComponentWhitelist(components);
ApplyComponentBlacklist(components);
AddPathComponent(components);
// Brick-by-Brick models use custom physics depending on _something_ but the client uses 4246 as the simple
// physics component id and 4247 for phantom physics. We'll just use the simple physics component for now
// since we dont know what the phantom physics are for at the moment.
if (GetLOT() == LOT_MODEL_IN_WORLD) components.emplace_back(eReplicaComponentType::SIMPLE_PHYSICS, 4246U);
for (const auto& [componentTemplate, componentId] : components) {
switch (componentTemplate) {
case eReplicaComponentType::CONTROLLABLE_PHYSICS:
AddComponent<ControllablePhysicsComponent>();
break;
case eReplicaComponentType::RENDER:
AddComponent<RenderComponent>();
break;
case eReplicaComponentType::SIMPLE_PHYSICS:
AddComponent<SimplePhysicsComponent>(componentId);
break;
case eReplicaComponentType::CHARACTER:
AddComponent<CharacterComponent>(m_Character);
AddComponent<MissionComponent>();
AddComponent<PossessorComponent>();
AddComponent<LevelProgressionComponent>();
AddComponent<PlayerForcedMovementComponent>();
break;
case eReplicaComponentType::SCRIPT: {
std::string script;
if (m_TemplateID == LOT_ZONE_CONTROL) {
script = ScriptComponent::GetZoneScriptName(componentId);
} else {
script = ScriptComponent::GetScriptName(this, componentId);
}
AddComponent<ScriptComponent>(script); // Technically this should check for if the script name is empty and not create a component if it is.
break;
}
case eReplicaComponentType::BOUNCER:
AddComponent<BouncerComponent>();
break;
case eReplicaComponentType::DESTROYABLE:
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) AddComponent<DestroyableComponent>(componentId);
break;
case eReplicaComponentType::SKILL:
AddComponent<SkillComponent>();
break;
case eReplicaComponentType::ITEM:
AddComponent<ItemComponent>();
break;
case eReplicaComponentType::VENDOR:
AddComponent<VendorComponent>();
break;
case eReplicaComponentType::INVENTORY:
AddComponent<InventoryComponent>();
break;
case eReplicaComponentType::SHOOTING_GALLERY:
AddComponent<ShootingGalleryComponent>();
break;
case eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS:
AddComponent<RigidbodyPhantomPhysicsComponent>();
break;
case eReplicaComponentType::COLLECTIBLE:
AddComponent<CollectibleComponent>();
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) AddComponent<DestroyableComponent>(componentId);
break;
case eReplicaComponentType::MOVING_PLATFORM:
AddComponent<MovingPlatformComponent>(GetVarAsString(u"attached_path"));
break;
case eReplicaComponentType::PET:
AddComponent<PetComponent>(componentId);
AddComponent<MovementAIComponent>();
break;
case eReplicaComponentType::HAVOK_VEHICLE_PHYSICS: {
auto* havokVehiclePhysicsComponent = AddComponent<HavokVehiclePhysicsComponent>();
if (havokVehiclePhysicsComponent) {
havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition);
havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation);
}
break;
}
case eReplicaComponentType::MOVEMENT_AI:
AddComponent<MovementAIComponent>();
break;
case eReplicaComponentType::PROPERTY:
AddComponent<PropertyComponent>();
break;
case eReplicaComponentType::SCRIPTED_ACTIVITY:
AddComponent<ScriptedActivityComponent>(componentId);
break;
case eReplicaComponentType::PHANTOM_PHYSICS: {
auto* phantomPhysicsComponent = AddComponent<PhantomPhysicsComponent>();
if (phantomPhysicsComponent) phantomPhysicsComponent->SetPhysicsEffectActive(false);
break;
}
case eReplicaComponentType::MODEL_BEHAVIOR: {
AddComponent<ModelBehaviorComponent>();
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) {
auto* destroyableComponent = AddComponent<DestroyableComponent>(componentId);
if (destroyableComponent) {
destroyableComponent->SetHealth(1);
destroyableComponent->SetMaxHealth(1.0f);
destroyableComponent->SetFaction(-1, true);
destroyableComponent->SetIsSmashable(true);
}
}
break;
}
case eReplicaComponentType::PROPERTY_ENTRANCE:
AddComponent<PropertyEntranceComponent>(componentId);
break;
case eReplicaComponentType::PROPERTY_MANAGEMENT:
AddComponent<PropertyManagementComponent>();
break;
case eReplicaComponentType::QUICK_BUILD:
AddComponent<QuickBuildComponent>(componentId);
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) AddComponent<DestroyableComponent>(componentId);
break;
case eReplicaComponentType::SWITCH:
AddComponent<SwitchComponent>();
break;
case eReplicaComponentType::MINIGAME_CONTROL:
AddComponent<MinigameControlComponent>();
break;
case eReplicaComponentType::BASE_COMBAT_AI: {
auto* baseCombatAiComponent = AddComponent<BaseCombatAIComponent>(componentId);
if (baseCombatAiComponent && baseCombatAiComponent->GetTetherSpeed() > 0.0f) {
auto* movementAiComponent = AddComponent<MovementAIComponent>();
if (!movementAiComponent) break;
MovementAIInfo movementAiInfo{};
movementAiInfo.movementType = "";
movementAiInfo.wanderChance = 0;
movementAiInfo.wanderRadius = 16;
movementAiInfo.wanderSpeed = 2.5f;
movementAiInfo.wanderDelayMax = 5;
movementAiInfo.wanderDelayMin = 2;
movementAiComponent->SetMoveInfo(movementAiInfo);
}
break;
}
case eReplicaComponentType::MODULE_ASSEMBLY:
AddComponent<ModuleAssemblyComponent>();
break;
case eReplicaComponentType::PROPERTY_VENDOR:
AddComponent<PropertyVendorComponent>();
break;
case eReplicaComponentType::ROCKET_LAUNCHPAD_CONTROL:
AddComponent<RocketLaunchpadControlComponent>(componentId);
break;
case eReplicaComponentType::RACING_CONTROL:
AddComponent<RacingControlComponent>();
break;
case eReplicaComponentType::MISSION_OFFER:
AddComponent<MissionOfferComponent>(GetLOT());
break;
case eReplicaComponentType::RACING_STATS:
AddComponent<RacingStatsComponent>();
break;
case eReplicaComponentType::LUP_EXHIBIT:
AddComponent<LUPExhibitComponent>();
break;
case eReplicaComponentType::SOUND_TRIGGER:
AddComponent<SoundTriggerComponent>();
break;
case eReplicaComponentType::PROXIMITY_MONITOR:
AddComponent<ProximityMonitorComponent>();
break;
case eReplicaComponentType::MULTI_ZONE_ENTRANCE:
AddComponent<MultiZoneEntranceComponent>();
break;
case eReplicaComponentType::BUFF:
AddComponent<BuffComponent>();
break;
case eReplicaComponentType::RAIL_ACTIVATOR:
AddComponent<RailActivatorComponent>(componentId);
break;
case eReplicaComponentType::POSSESSABLE:
AddComponent<PossessableComponent>(componentId);
break;
case eReplicaComponentType::BUILD_BORDER:
AddComponent<BuildBorderComponent>();
break;
case eReplicaComponentType::GHOST:
case eReplicaComponentType::SPAWN:
case eReplicaComponentType::MODULAR_BUILD:
case eReplicaComponentType::BUILD_CONTROLLER:
case eReplicaComponentType::BUILD_ACTIVATOR:
case eReplicaComponentType::ICON_ONLY:
case eReplicaComponentType::PROJECTILE_PHYSICS:
case eReplicaComponentType::DROP_EFFECT:
case eReplicaComponentType::CHEST:
case eReplicaComponentType::BLUEPRINT:
case eReplicaComponentType::PLATFORM_BOUNDARY:
case eReplicaComponentType::MODULE:
case eReplicaComponentType::JETPACKPAD:
case eReplicaComponentType::EXHIBIT:
case eReplicaComponentType::OVERHEAD_ICON:
case eReplicaComponentType::PET_CONTROL:
case eReplicaComponentType::MINIFIG:
case eReplicaComponentType::PET_CREATOR:
case eReplicaComponentType::MODEL_BUILDER:
case eReplicaComponentType::SPRINGPAD:
case eReplicaComponentType::FX:
case eReplicaComponentType::VEHICLE_PHYSICS:
case eReplicaComponentType::PHYSICS_SYSTEM:
case eReplicaComponentType::CHANGLING_BUILD:
case eReplicaComponentType::CHOICE_BUILD:
case eReplicaComponentType::PACKAGE:
case eReplicaComponentType::SOUND_REPEATER:
case eReplicaComponentType::SOUND_AMBIENT_2D:
case eReplicaComponentType::SOUND_AMBIENT_3D:
case eReplicaComponentType::PRECONDITION:
case eReplicaComponentType::FLAG:
case eReplicaComponentType::CUSTOM_BUILD_ASSEMBLY:
case eReplicaComponentType::SHOWCASE_MODEL_HANDLER:
case eReplicaComponentType::RACING_MODULE:
case eReplicaComponentType::GENERIC_ACTIVATOR:
case eReplicaComponentType::HF_LIGHT_DIRECTION_GADGET:
case eReplicaComponentType::ROCKET_ANIMATION_CONTROL:
case eReplicaComponentType::TRIGGER:
case eReplicaComponentType::DROPPED_LOOT:
case eReplicaComponentType::FACTION_TRIGGER:
case eReplicaComponentType::BBB:
case eReplicaComponentType::RACING_SOUND_TRIGGER:
case eReplicaComponentType::CHAT_BUBBLE:
case eReplicaComponentType::FRIENDS_LIST:
case eReplicaComponentType::GUILD:
case eReplicaComponentType::LOCAL_SYSTEM:
case eReplicaComponentType::MISSION:
case eReplicaComponentType::MUTABLE_MODEL_BEHAVIORS:
case eReplicaComponentType::PATHFINDING:
case eReplicaComponentType::PET_TAMING_CONTROL:
case eReplicaComponentType::PROPERTY_EDITOR:
case eReplicaComponentType::SKINNED_RENDER:
case eReplicaComponentType::SLASH_COMMAND:
case eReplicaComponentType::STATUS_EFFECT:
case eReplicaComponentType::TEAMS:
case eReplicaComponentType::TEXT_EFFECT:
case eReplicaComponentType::TRADE:
case eReplicaComponentType::USER_CONTROL:
case eReplicaComponentType::IGNORE_LIST:
case eReplicaComponentType::INTERACTION_MANAGER:
case eReplicaComponentType::DONATION_VENDOR:
case eReplicaComponentType::COMBAT_MEDIATOR:
case eReplicaComponentType::ACHIEVEMENT_VENDOR:
case eReplicaComponentType::GATE_RUSH_CONTROL:
case eReplicaComponentType::ROLLER:
case eReplicaComponentType::PLAYER_FORCED_MOVEMENT:
case eReplicaComponentType::CRAFTING:
case eReplicaComponentType::LEVEL_PROGRESSION:
case eReplicaComponentType::POSSESSOR:
case eReplicaComponentType::MOUNT_CONTROL:
case eReplicaComponentType::UNKNOWN_112:
case eReplicaComponentType::PROPERTY_PLAQUE:
case eReplicaComponentType::UNKNOWN_115:
case eReplicaComponentType::CULLING_PLANE:
case eReplicaComponentType::NUMBER_OF_COMPONENTS:
case eReplicaComponentType::INVALID:
default:
Game::logger->Log("Entity", "Attempted to create component %i for lot %i but no creator exists. Component will not be created.", componentId, m_TemplateID);
}
}
AddCallbackTimer(0.0f, [this]() {
GetScript()->OnStartup(this);
});
// Load data specific to this LOT first. These act as defaults for the components.
std::for_each(m_Components.begin(), m_Components.end(), [this](auto& component) {
component.second->LoadTemplateData();
});
// Then load data specific to this Entity. This will vary on an Entity to Entity basis.
// If there is data you want to override the LOT default value, generally you would attach it via LDF
// and it would get loaded and overridden here.
std::for_each(m_Components.begin(), m_Components.end(), [this](auto& component) {
component.second->LoadConfigData();
});
/**
* Startup all the components. Some components need or want data from other components so
* we want to ensure that
* A) Most if not all components are newed and ready to be accessed.
* B) All components have their personal data loaded and ready to be used.
*/
std::for_each(m_Components.begin(), m_Components.end(), [this](auto& component) {
component.second->Startup();
});
/**
* Load the player save data from XML. Ideally we do this after all initialization so the player
* save data overrides any defaults that may be applied.
*/
if (!IsPlayer()) std::for_each(m_Components.begin(), m_Components.end(), [this](auto& component) {
component.second->LoadFromXml(m_Character->GetXMLDoc());
});
TriggerEvent(eTriggerEventType::CREATE, this);
if (!m_Character && EntityManager::Instance()->GetGhostingEnabled()) IsGhosted();
}
// Invert this check and build it into the component initialization. The ghosting property is an intrinsic property of which components the Entity has.
// Keep the first check since that is a special case for large scene elements like Brig Rock as a whole.
void Entity::IsGhosted() {
// Don't ghost what is likely large scene elements
if (HasComponent(eReplicaComponentType::SIMPLE_PHYSICS) && HasComponent(eReplicaComponentType::RENDER) && (m_Components.size() == 2 || (HasComponent(eReplicaComponentType::TRIGGER) && m_Components.size() == 3))) {
return;
}
/* Filter for ghosting candidates.
*
* Don't ghost moving platforms, until we've got proper syncing for those.
* Don't ghost big phantom physics triggers, as putting those to sleep might prevent interactions.
* Don't ghost property related objects, as the client expects those to always be loaded.
*/
if (!EntityManager::IsExcludedFromGhosting(GetLOT()) &&
!HasComponent(eReplicaComponentType::SCRIPTED_ACTIVITY) &&
!HasComponent(eReplicaComponentType::MOVING_PLATFORM) &&
!HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) &&
!HasComponent(eReplicaComponentType::PROPERTY) &&
!HasComponent(eReplicaComponentType::RACING_CONTROL) &&
!HasComponent(eReplicaComponentType::VEHICLE_PHYSICS)) {
m_IsGhostingCandidate = true;
}
if (GetLOT() == LOT_3D_AMBIENT_SOUND) m_IsGhostingCandidate = true;
// Special case for collectibles in Ninjago
if (HasComponent(eReplicaComponentType::COLLECTIBLE) && Game::server->GetZoneID() == 2000) {
m_IsGhostingCandidate = true;
}
}
// Move to header
bool Entity::operator==(const Entity& other) const {
return other.m_ObjectID == m_ObjectID;
}
// Move to header
bool Entity::operator!=(const Entity& other) const {
return !(other.m_ObjectID == m_ObjectID);
}
// Move to header
bool Entity::HasComponent(const eReplicaComponentType componentId) const {
return m_Components.find(componentId) != m_Components.end();
}
// Fine
void Entity::Subscribe(CppScripts::Script* scriptToAdd, const std::string& notificationName) {
Game::logger->Log("Entity", "Subscribing a script with notification %s", notificationName.c_str());
if (notificationName == "HitOrHealResult" || notificationName == "Hit") {
auto* destroyableComponent = GetComponent<DestroyableComponent>();
if (destroyableComponent) destroyableComponent->Subscribe(scriptToAdd);
}
}
// Fine
void Entity::Unsubscribe(CppScripts::Script* scriptToRemove, const std::string& notificationName) {
Game::logger->Log("Entity", "Unsubscribing a script with notification %s", notificationName.c_str());
if (notificationName == "HitOrHealResult" || notificationName == "Hit") {
auto* destroyableComponent = GetComponent<DestroyableComponent>();
if (destroyableComponent) destroyableComponent->Unsubscribe(scriptToRemove);
}
}
// Fine
void Entity::SetProximityRadius(const float proxRadius, const std::string& name) {
auto* proximityMonitorComponent = AddComponent<ProximityMonitorComponent>();
if (proximityMonitorComponent) proximityMonitorComponent->SetProximityRadius(proxRadius, name);
}
// Remove in favor of a square constructor
void Entity::SetProximityRadius(dpEntity* entity, const std::string& name) {
auto* proximityMonitorComponent = AddComponent<ProximityMonitorComponent>();
if (proximityMonitorComponent) proximityMonitorComponent->SetProximityRadius(entity, name);
}
void Entity::SetGMLevel(eGameMasterLevel value) {
m_GMLevel = value;
// User m_Character?
if (GetParentUser()) {
Character* character = GetParentUser()->GetLastUsedChar();
if (character) {
character->SetGMLevel(value);
}
}
auto* character = GetComponent<CharacterComponent>();
if (character) character->SetGMLevel(value);
GameMessages::SendGMLevelBroadcast(m_ObjectID, value);
}
void Entity::WriteBaseReplicaData(RakNet::BitStream* outBitStream, const eReplicaPacketType packetType) {
if (packetType == eReplicaPacketType::CONSTRUCTION) {
outBitStream->Write(m_ObjectID);
outBitStream->Write(m_TemplateID);
if (IsPlayer()) {
std::string name = m_Character != nullptr ? m_Character->GetName() : "Invalid";
outBitStream->Write<uint8_t>(uint8_t(name.size()));
for (size_t i = 0; i < name.size(); ++i) {
outBitStream->Write<uint16_t>(name[i]);
}
} else {
const auto& name = GetVar<std::string>(u"npcName");
outBitStream->Write<uint8_t>(uint8_t(name.size()));
for (size_t i = 0; i < name.size(); ++i) {
outBitStream->Write<uint16_t>(name[i]);
}
}
outBitStream->Write<uint32_t>(0); //Time since created on server
const auto& syncLDF = GetVar<std::vector<std::u16string>>(u"syncLDF");
// Only sync for models.
// PetComponent check un-needed since we should be removing the component during construction.
if (m_Settings.size() > 0 && (GetComponent<ModelBehaviorComponent>() && !GetComponent<PetComponent>())) {
outBitStream->Write1(); //ldf data
RakNet::BitStream settingStream;
settingStream.Write<uint32_t>(m_Settings.size());
for (LDFBaseData* data : m_Settings) {
if (data) {
data->WriteToPacket(&settingStream);
}
}
outBitStream->Write(settingStream.GetNumberOfBytesUsed() + 1);
outBitStream->Write<uint8_t>(0); //no compression used
outBitStream->Write(settingStream);
} else if (!syncLDF.empty()) {
std::vector<LDFBaseData*> ldfData;
for (const auto& data : syncLDF) {
ldfData.push_back(GetVarData(data));
}
outBitStream->Write1(); //ldf data
RakNet::BitStream settingStream;
settingStream.Write<uint32_t>(ldfData.size());
for (LDFBaseData* data : ldfData) {
if (data) {
data->WriteToPacket(&settingStream);
}
}
outBitStream->Write(settingStream.GetNumberOfBytesUsed() + 1);
outBitStream->Write<uint8_t>(0); //no compression used
outBitStream->Write(settingStream);
} else {
outBitStream->Write0(); //No ldf data
}
auto* triggerComponent = GetComponent<TriggerComponent>();
if (triggerComponent) {
// has trigger component, check to see if we have events to handle
auto* trigger = triggerComponent->GetTrigger();
outBitStream->Write<bool>(trigger && trigger->events.size() > 0);
} else { // no trigger componenet, so definitely no triggers
outBitStream->Write0();
}
outBitStream->Write<bool>(m_ParentEntity != nullptr || m_SpawnerID != 0);
if (m_ParentEntity != nullptr || m_SpawnerID != 0) {
if (m_ParentEntity != nullptr) outBitStream->Write(GeneralUtils::SetBit(m_ParentEntity->GetObjectID(), static_cast<uint32_t>(eObjectBits::CLIENT)));
else if (m_Spawner != nullptr && m_Spawner->m_Info.isNetwork) outBitStream->Write(m_SpawnerID);
else outBitStream->Write(GeneralUtils::SetBit(m_SpawnerID, static_cast<uint32_t>(eObjectBits::CLIENT)));
}
outBitStream->Write(m_HasSpawnerNodeID);
if (m_HasSpawnerNodeID) outBitStream->Write(m_SpawnerNodeID);
//outBitStream->Write0(); //Spawner node id
outBitStream->Write<bool>(m_Scale != 1.0f || m_Scale != 0.0f);
if (m_Scale != 1.0f || m_Scale != 0.0f) outBitStream->Write(m_Scale);
outBitStream->Write0(); //ObjectWorldState
outBitStream->Write(m_GMLevel != eGameMasterLevel::CIVILIAN);
if (m_GMLevel != eGameMasterLevel::CIVILIAN) {
outBitStream->Write(m_GMLevel);
}
}
// Only serialize parent / child info should the info be dirty (changed) or if this is the construction of the entity.
outBitStream->Write(m_IsParentChildDirty || packetType == eReplicaPacketType::CONSTRUCTION);
if (m_IsParentChildDirty || packetType == eReplicaPacketType::CONSTRUCTION) {
m_IsParentChildDirty = false;
outBitStream->Write(m_ParentEntity != nullptr);
if (m_ParentEntity) {
outBitStream->Write(m_ParentEntity->GetObjectID());
outBitStream->Write0();
}
outBitStream->Write(m_ChildEntities.size() > 0);
if (m_ChildEntities.size() > 0) {
outBitStream->Write<uint16_t>(m_ChildEntities.size());
for (Entity* child : m_ChildEntities) {
outBitStream->Write<LWOOBJID>(child->GetObjectID());
}
}
}
}
// uh
void Entity::WriteComponents(RakNet::BitStream* outBitStream, const eReplicaPacketType packetType) {
}
// We should be able to use this at some point
void Entity::ResetFlags() {
// Unused
}
// std::for_each
void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) {
DluAssert(doc != nullptr);
for (const auto& pair : m_Components) {
if (pair.second == nullptr) continue;
pair.second->UpdateXml(doc);
}
}
CppScripts::Script* Entity::GetScript() const {
auto* scriptComponent = GetComponent<ScriptComponent>();
if (!scriptComponent) return CppScripts::invalidScript.get();
auto* script = scriptComponent->GetScript();
DluAssert(script != nullptr);
return script;
}
void Entity::Update(const float deltaTime) {
auto namedTimerItr = std::remove_if(m_Timers.begin(), m_Timers.end(), [this, &deltaTime](const std::unique_ptr<EntityTimer>& timer) {
timer->Update(deltaTime);
if (timer->GetTime() <= 0) {
GetScript()->OnTimerDone(this, timer->GetName());
TriggerEvent(eTriggerEventType::TIMER_DONE, this);
return true;
}
return false;
});
m_Timers.erase(namedTimerItr, m_Timers.end());
auto callbackTimerItr = std::remove_if(m_CallbackTimers.begin(), m_CallbackTimers.end(), [this, &deltaTime](const std::unique_ptr<EntityCallbackTimer>& timer) {
timer->Update(deltaTime);
if (timer->GetTime() <= 0) {
timer->ExecuteCallback();
return true;
}
return false;
});
m_CallbackTimers.erase(callbackTimerItr, m_CallbackTimers.end());
// Add pending timers to the list of timers so they start next tick.
if (!m_PendingTimers.empty()) {
this->m_Timers.reserve(m_Timers.size() + m_PendingTimers.size());
for (auto& timer : m_PendingTimers) {
this->m_Timers.push_back(std::move(timer));
}
m_PendingTimers.clear();
}
if (IsSleeping()) {
Sleep();
return;
}
Wake();
GetScript()->OnUpdate(this);
for (const auto& [componentId, component] : m_Components) {
if (component) component->Update(deltaTime);
}
if (m_ShouldDestroyAfterUpdate) EntityManager::Instance()->DestroyEntity(this);
}
void Entity::OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxName, const std::string& status) {
auto* other = EntityManager::Instance()->GetEntity(otherEntity);
if (!other) return;
GetScript()->OnProximityUpdate(this, other, proxName, status);
auto* rocketLaunchpadControlComponent = GetComponent<RocketLaunchpadControlComponent>();
if (!rocketLaunchpadControlComponent) return;
rocketLaunchpadControlComponent->OnProximityUpdate(other, proxName, status);
}
void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
auto* other = EntityManager::Instance()->GetEntity(otherEntity);
if (!other) return;
GetScript()->OnCollisionPhantom(this, other);
std::for_each(m_PhantomCollisionCallbacks.begin(), m_PhantomCollisionCallbacks.end(), [other](const auto& callback) {
callback(other);
});
auto* switchComponent = GetComponent<SwitchComponent>();
if (switchComponent) switchComponent->EntityEnter(other);
TriggerEvent(eTriggerEventType::ENTER, other);
// POI system
const auto& poi = GetVar<std::u16string>(u"POI");
if (!poi.empty()) {
auto* missionComponent = other->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::EXPLORE, 0, 0, GeneralUtils::UTF16ToWTF8(poi));
}
}
if (!other->IsDead()) {
auto* combat = GetComponent<BaseCombatAIComponent>();
if (combat != nullptr) {
const auto index = std::find(m_TargetsInPhantom.begin(), m_TargetsInPhantom.end(), otherEntity);
if (index != m_TargetsInPhantom.end()) return;
const auto valid = combat->IsEnemy(otherEntity);
if (!valid) return;
m_TargetsInPhantom.push_back(otherEntity);
}
}
}
void Entity::OnCollisionLeavePhantom(const LWOOBJID otherEntity) {
auto* other = EntityManager::Instance()->GetEntity(otherEntity);
if (!other) return;
GetScript()->OnOffCollisionPhantom(this, other);
TriggerEvent(eTriggerEventType::EXIT, other);
auto* switchComponent = GetComponent<SwitchComponent>();
if (switchComponent) switchComponent->EntityLeave(other);
const auto index = std::find(m_TargetsInPhantom.begin(), m_TargetsInPhantom.end(), otherEntity);
if (index == m_TargetsInPhantom.end()) return;
m_TargetsInPhantom.erase(index);
}
void Entity::OnFireEventServerSide(Entity* sender, std::string args, int32_t param1, int32_t param2, int32_t param3) {
GetScript()->OnFireEventServerSide(this, sender, args, param1, param2, param3);
}
void Entity::OnActivityStateChangeRequest(LWOOBJID senderID, int32_t value1, int32_t value2, const std::u16string& stringValue) {
GetScript()->OnActivityStateChangeRequest(this, senderID, value1, value2, stringValue);
}
void Entity::OnCinematicUpdate(Entity* self, Entity* sender, eCinematicEvent event, const std::u16string& pathName,
float_t pathTime, float_t totalTime, int32_t waypoint) {
GetScript()->OnCinematicUpdate(self, sender, event, pathName, pathTime, totalTime, waypoint);
}
void Entity::CancelCallbackTimers() {
m_CallbackTimers.clear();
}
void Entity::NotifyObject(Entity* sender, const std::u16string& name, int32_t param1, int32_t param2) {
GameMessages::SendNotifyObject(GetObjectID(), sender->GetObjectID(), name, UNASSIGNED_SYSTEM_ADDRESS, param1, param2);
GetScript()->OnNotifyObject(this, sender, name, param1, param2);
}
void Entity::OnEmoteReceived(const int32_t emote, Entity* target) {
GetScript()->OnEmoteReceived(this, emote, target);
}
void Entity::OnUse(Entity* originator) {
TriggerEvent(eTriggerEventType::INTERACT, originator);
GetScript()->OnUse(this, originator);
for (const auto& [componentId, component] : m_Components) {
if (component) component->OnUse(originator);
}
}
void Entity::OnHitOrHealResult(Entity* attacker, int32_t damage) {
GetScript()->OnHitOrHealResult(this, attacker, damage);
}
void Entity::OnHit(Entity* attacker) {
TriggerEvent(eTriggerEventType::HIT, attacker);
GetScript()->OnHit(this, attacker);
}
void Entity::OnZonePropertyEditBegin() {
GetScript()->OnZonePropertyEditBegin(this);
}
void Entity::OnZonePropertyEditEnd() {
GetScript()->OnZonePropertyEditEnd(this);
}
void Entity::OnZonePropertyModelEquipped() {
GetScript()->OnZonePropertyModelEquipped(this);
}
void Entity::OnZonePropertyModelPlaced(Entity* player) {
GetScript()->OnZonePropertyModelPlaced(this, player);
}
void Entity::OnZonePropertyModelPickedUp(Entity* player) {
GetScript()->OnZonePropertyModelPickedUp(this, player);
}
void Entity::OnZonePropertyModelRemoved(Entity* player) {
GetScript()->OnZonePropertyModelRemoved(this, player);
}
void Entity::OnZonePropertyModelRemovedWhileEquipped(Entity* player) {
GetScript()->OnZonePropertyModelRemovedWhileEquipped(this, player);
}
void Entity::OnZonePropertyModelRotated(Entity* player) {
GetScript()->OnZonePropertyModelRotated(this, player);
}
void Entity::OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData) {
GetScript()->OnMessageBoxResponse(this, sender, button, identifier, userData);
}
void Entity::OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier) {
GetScript()->OnChoiceBoxResponse(this, sender, button, buttonIdentifier, identifier);
}
void Entity::RequestActivityExit(Entity* sender, const LWOOBJID& player, const bool canceled) {
GetScript()->OnRequestActivityExit(sender, player, canceled);
}
void Entity::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType) {
if (!m_PlayerIsReadyForUpdates) return;
auto* destroyableComponent = GetComponent<DestroyableComponent>();
if (!destroyableComponent) {
Kill(EntityManager::Instance()->GetEntity(source));
return;
}
auto* possessorComponent = GetComponent<PossessorComponent>();
if (possessorComponent) {
if (possessorComponent->GetPossessable() != LWOOBJID_EMPTY) {
auto* mount = EntityManager::Instance()->GetEntity(possessorComponent->GetPossessable());
if (mount) possessorComponent->Dismount(mount, true);
}
}
destroyableComponent->Smash(source, killType, deathType);
}
void Entity::Kill(Entity* murderer) {
if (!m_PlayerIsReadyForUpdates) return;
for (const auto& cb : m_DieCallbacks) cb();
m_DieCallbacks.clear();
//OMAI WA MOU, SHINDERIU
GetScript()->OnDie(this, murderer);
if (m_Spawner) m_Spawner->NotifyOfEntityDeath(m_ObjectID);
if (!IsPlayer()) EntityManager::Instance()->DestroyEntity(this);
const auto& grpNameQBShowBricks = GetVar<std::string>(u"grpNameQBShowBricks");
if (!grpNameQBShowBricks.empty()) {
auto spawners = dZoneManager::Instance()->GetSpawnersByName(grpNameQBShowBricks);
Spawner* spawner = nullptr;
if (!spawners.empty()) {
spawner = spawners.front();
} else {
spawners = dZoneManager::Instance()->GetSpawnersInGroup(grpNameQBShowBricks);
if (!spawners.empty()) spawner = spawners.front();
}
if (spawner) spawner->Spawn();
}
// Track a player being smashed
auto* characterComponent = GetComponent<CharacterComponent>();
if (characterComponent) {
characterComponent->UpdatePlayerStatistic(TimesSmashed);
}
// Track a player smashing something else
if (!murderer) return;
auto* murdererCharacterComponent = murderer->GetComponent<CharacterComponent>();
if (murdererCharacterComponent) {
murdererCharacterComponent->UpdatePlayerStatistic(SmashablesSmashed);
}
}
void Entity::AddRebuildCompleteCallback(const std::function<void(Entity* user)>& callback) const {
auto* quickBuildComponent = GetComponent<QuickBuildComponent>();
if (quickBuildComponent) quickBuildComponent->AddRebuildCompleteCallback(callback);
}
bool Entity::IsDead() const {
auto* dest = GetComponent<DestroyableComponent>();
return dest && dest->GetArmor() == 0 && dest->GetHealth() == 0;
}
void Entity::AddLootItem(const Loot::Info& info) {
if (!IsPlayer()) return;
auto* player = dynamic_cast<Player*>(this);
if (player) player->GetDroppedLoot().insert(std::make_pair(info.id, info)); // Move this to Player
}
// Replace static_cast with dynamic_cast
void Entity::PickupItem(const LWOOBJID& objectID) {
if (!IsPlayer()) return;
auto* inventoryComponent = GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
auto* objectsTable = CDClientManager::Instance().GetTable<CDObjectsTable>();
auto* skillsTable = CDClientManager::Instance().GetTable<CDObjectSkillsTable>();
auto* player = dynamic_cast<Player*>(this);
if (!player) return;
auto& droppedLoot = player->GetDroppedLoot();
auto lootIterator = droppedLoot.find(objectID);
if (lootIterator == droppedLoot.end()) return;
const auto& [objId, loot] = *lootIterator;
auto* characterComponent = GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->TrackLOTCollection(loot.lot);
const CDObjects& object = objectsTable->GetByID(loot.lot);
if (object.id != 0 && object.type == "Powerup") {
const auto lootLot = loot.lot;
auto skills = skillsTable->Query([lootLot](CDObjectSkills entry) { return (entry.objectTemplate == lootLot); });
auto* skillBehaviorTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
for (const auto& skill : skills) {
auto behaviorData = skillBehaviorTable->GetSkillByID(skill.skillID);
// This should take a skillID, not a behaviorID.
SkillComponent::HandleUnmanaged(behaviorData.behaviorID, GetObjectID());
auto* missionComponent = GetComponent<MissionComponent>();
if (missionComponent) missionComponent->Progress(eMissionTaskType::POWERUP, skill.skillID);
}
} else {
inventoryComponent->AddItem(loot.lot, loot.count, eLootSourceType::PICKUP, eInventoryType::INVALID, {}, LWOOBJID_EMPTY, true, false, LWOOBJID_EMPTY, eInventoryType::INVALID, 1);
}
droppedLoot.erase(lootIterator);
}
// This functions name is misleading and should not modify the number of dropped coins.
// A separate function, PickupCoins should modify that.
// Replace static_cast with dynamic_cast
bool Entity::CanPickupCoins(const uint64_t& count) const {
if (!IsPlayer()) return false;
const auto* player = dynamic_cast<const Player*>(this);
return count <= player->GetDroppedCoins();
}
void Entity::PickupCoins(const uint64_t& count) {
if (!IsPlayer()) return;
auto* player = dynamic_cast<Player*>(this);
if (!player) return;
const auto droppedCoins = player->GetDroppedCoins();
if (count <= droppedCoins) player->SetDroppedCoins(droppedCoins - count);
}
void Entity::RegisterCoinDrop(const uint64_t& coinsDropped) {
if (!IsPlayer()) return;
auto* player = dynamic_cast<Player*>(this);
if (!player) return;
player->SetDroppedCoins(player->GetDroppedCoins() + coinsDropped);
}
void Entity::AddChild(Entity* child) {
m_IsParentChildDirty = true;
if (std::find(m_ChildEntities.begin(), m_ChildEntities.end(), child) != m_ChildEntities.end()) return;
m_ChildEntities.push_back(child);
}
void Entity::RemoveChild(Entity* child) {
if (!child) return;
uint32_t entityPosition = 0;
auto toRemove = std::remove(m_ChildEntities.begin(), m_ChildEntities.end(), child);
if (toRemove != m_ChildEntities.end()) {
m_ChildEntities.erase(toRemove, m_ChildEntities.end());
m_IsParentChildDirty = true;
}
}
void Entity::RemoveParent() {
if (m_ParentEntity) m_IsParentChildDirty = true;
else Game::logger->Log("Entity", "WARNING: Attempted to remove parent from(objid:lot) (%llu:%i) when no parent existed", GetObjectID(), GetLOT());
this->m_ParentEntity = nullptr;
}
void Entity::AddTimer(const std::string& name, float time) {
m_PendingTimers.emplace_back(std::make_unique<EntityTimer>(name, time));
}
void Entity::AddCallbackTimer(const float time, const std::function<void()>& callback) {
m_CallbackTimers.emplace_back(std::make_unique<EntityCallbackTimer>(time, callback));
}
bool Entity::HasTimer(const std::string& name) {
auto possibleTimer = std::find_if(m_Timers.begin(), m_Timers.end(), [name](const std::unique_ptr<EntityTimer>& timer) {
return timer->GetName() == name;
});
return possibleTimer != m_Timers.end();
}
void Entity::ScheduleKillAfterUpdate(Entity* murderer) {
EntityManager::Instance()->ScheduleForKill(this);
if (murderer) m_ScheduleKiller = murderer;
}
void Entity::CancelTimer(const std::string& name) {
auto toErase = std::remove_if(m_Timers.begin(), m_Timers.end(), [&name](const std::unique_ptr<EntityTimer>& timer) {
return timer->GetName() == name;
});
m_Timers.erase(m_Timers.begin(), toErase);
}
void Entity::CancelAllTimers() {
m_Timers.clear();
CancelCallbackTimers();
}
bool Entity::IsPlayer() const {
return m_TemplateID == 1 && GetSystemAddress() != UNASSIGNED_SYSTEM_ADDRESS;
}
void Entity::TriggerEvent(eTriggerEventType event, Entity* optionalTarget) {
auto* triggerComponent = GetComponent<TriggerComponent>();
if (triggerComponent) triggerComponent->TriggerEvent(event, optionalTarget);
}
Entity* Entity::GetOwner() const {
if (m_OwnerOverride != LWOOBJID_EMPTY) {
auto* other = EntityManager::Instance()->GetEntity(m_OwnerOverride);
if (other != nullptr) {
return other->GetOwner();
}
}
return const_cast<Entity*>(this);
}
void Entity::SetObservers(int8_t value) {
if (value < 0) value = 0;
m_Observers = value;
}
void Entity::Sleep() {
auto* baseCombatAIComponent = GetComponent<BaseCombatAIComponent>();
if (baseCombatAIComponent) baseCombatAIComponent->Sleep();
}
void Entity::Wake() {
auto* baseCombatAIComponent = GetComponent<BaseCombatAIComponent>();
if (baseCombatAIComponent) baseCombatAIComponent->Wake();
}
bool Entity::IsSleeping() const {
return m_IsGhostingCandidate && m_Observers == 0;
}
// The following 3 should share a base component class so we can else if it.
const NiPoint3& Entity::GetPosition() const {
auto* controllablePhysicsComponent = GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent != nullptr) {
return controllablePhysicsComponent->GetPosition();
}
auto* phantomPhysicsComponent = GetComponent<PhantomPhysicsComponent>();
if (phantomPhysicsComponent != nullptr) {
return phantomPhysicsComponent->GetPosition();
}
auto* simplePhysicsComponent = GetComponent<SimplePhysicsComponent>();
if (simplePhysicsComponent != nullptr) {
return simplePhysicsComponent->GetPosition();
}
auto* vehiclePhysicsComponent = GetComponent<HavokVehiclePhysicsComponent>();
if (vehiclePhysicsComponent != nullptr) {
return vehiclePhysicsComponent->GetPosition();
}
return NiPoint3::ZERO;
}
const NiQuaternion& Entity::GetRotation() const {
auto* controllablePhysicsComponent = GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent != nullptr) {
return controllablePhysicsComponent->GetRotation();
}
auto* phantomPhysicsComponent = GetComponent<PhantomPhysicsComponent>();
if (phantomPhysicsComponent != nullptr) {
return phantomPhysicsComponent->GetRotation();
}
auto* simplePhysicsComponent = GetComponent<SimplePhysicsComponent>();
if (simplePhysicsComponent != nullptr) {
return simplePhysicsComponent->GetRotation();
}
auto* vehiclePhysicsComponent = GetComponent<HavokVehiclePhysicsComponent>();
if (vehiclePhysicsComponent != nullptr) {
return vehiclePhysicsComponent->GetRotation();
}
return NiQuaternion::IDENTITY;
}
void Entity::SetPosition(const NiPoint3& position) {
auto* controllablePhysicsComponent = GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent != nullptr) {
controllablePhysicsComponent->SetPosition(position);
}
auto* phantomPhysicsComponent = GetComponent<PhantomPhysicsComponent>();
if (phantomPhysicsComponent != nullptr) {
phantomPhysicsComponent->SetPosition(position);
}
auto* simplePhysicsComponent = GetComponent<SimplePhysicsComponent>();
if (simplePhysicsComponent != nullptr) {
simplePhysicsComponent->SetPosition(position);
}
auto* vehiclePhysicsComponent = GetComponent<HavokVehiclePhysicsComponent>();
if (vehiclePhysicsComponent != nullptr) {
vehiclePhysicsComponent->SetPosition(position);
}
EntityManager::Instance()->SerializeEntity(this);
}
void Entity::SetRotation(const NiQuaternion& rotation) {
auto* controllablePhysicsComponent = GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent != nullptr) {
controllablePhysicsComponent->SetRotation(rotation);
}
auto* phantomPhysicsComponent = GetComponent<PhantomPhysicsComponent>();
if (phantomPhysicsComponent != nullptr) {
phantomPhysicsComponent->SetRotation(rotation);
}
auto* simplePhysicsComponent = GetComponent<SimplePhysicsComponent>();
if (simplePhysicsComponent != nullptr) {
simplePhysicsComponent->SetRotation(rotation);
}
auto* vehiclePhysicsComponent = GetComponent<HavokVehiclePhysicsComponent>();
if (vehiclePhysicsComponent != nullptr) {
vehiclePhysicsComponent->SetRotation(rotation);
}
EntityManager::Instance()->SerializeEntity(this);
}
bool Entity::HasVar(const std::u16string& name) const {
auto hasVar = std::find_if(m_Settings.begin(), m_Settings.end(), [&name](const LDFBaseData* data) {
return data->GetKey() == name;
});
return hasVar != m_Settings.end();
}
std::vector<LWOOBJID>& Entity::GetTargetsInPhantom() {
auto toRemove = std::remove_if(m_TargetsInPhantom.begin(), m_TargetsInPhantom.end(), [this](const LWOOBJID& id) {
return EntityManager::Instance()->GetEntity(id) == nullptr;
});
m_TargetsInPhantom.erase(toRemove, m_TargetsInPhantom.end());
return m_TargetsInPhantom;
}
void Entity::SendNetworkVar(const std::string& data, const SystemAddress& sysAddr) {
GameMessages::SendSetNetworkScriptVar(this, sysAddr, data);
}
LDFBaseData* Entity::GetVarData(const std::u16string& name) const {
for (auto* data : m_Settings) {
if (data && data->GetKey() == name) return data;
}
return nullptr;
}
std::string Entity::GetVarAsString(const std::u16string& name) const {
auto* data = GetVarData(name);
return data ? data->GetValueAsString() : "";
}
void Entity::Resurrect() {
if (IsPlayer()) GameMessages::SendResurrect(this);
}
void Entity::AddToGroups(const std::string& group) {
if (std::find(m_Groups.begin(), m_Groups.end(), group) == m_Groups.end()) {
m_Groups.push_back(group);
}
}
void Entity::RetroactiveVaultSize() {
auto* inventoryComponent = GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
auto itemsVault = inventoryComponent->GetInventory(eInventoryType::VAULT_ITEMS);
auto modelVault = inventoryComponent->GetInventory(eInventoryType::VAULT_MODELS);
if (itemsVault->GetSize() == modelVault->GetSize()) return;
modelVault->SetSize(itemsVault->GetSize());
}