diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index f65366bce..ef991136e 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -99,6 +99,7 @@ #include "PetCreatorComponent.h" #include "HFLightDirectionGadgetComponent.h" #include "SpawnerComponent.h" +#include "ExhibitComponent.h" #include "PathfindingComponent.h" #include "PhysicsSystemComponent.h" #include "VehiclePhysicsComponent.h" @@ -911,6 +912,13 @@ void Entity::DoObjectComponentLoad(ObjectLoadState& state) { if (id > -1 && !HasComponent(eReplicaComponentType::PLAYER_FORCED_MOVEMENT)) AddComponent(id); break; } + case eReplicaComponentType::EXHIBIT: { + // 1.10.64's loader has this case but makes nothing (dead code; showcases were cut); DLU makes the exhibit, + // which sends nothing in a construction, so unchanged clients are unaffected + const auto id = state.GetComponentID(eReplicaComponentType::EXHIBIT, -1); + if (id > -1) AddComponent(id); + break; + } case eReplicaComponentType::ROCKET_LANDING: { // LWORocketAnimationControlComponent: the client makes it from the registry const auto id = state.GetComponentID(eReplicaComponentType::ROCKET_LANDING, -1); diff --git a/dGame/dComponents/CMakeLists.txt b/dGame/dComponents/CMakeLists.txt index 45da3fc7b..62818a3ae 100644 --- a/dGame/dComponents/CMakeLists.txt +++ b/dGame/dComponents/CMakeLists.txt @@ -42,6 +42,7 @@ set(DGAME_DCOMPONENTS_SOURCES "Physics/SimplePhysicsComponent.cpp" "SkillComponent.cpp" "Sound/SoundTriggerComponent.cpp" + "ExhibitComponent.cpp" "SpawnerComponent.cpp" "SwitchComponent.cpp" "TriggerComponent.cpp" diff --git a/dGame/dComponents/ExhibitComponent.cpp b/dGame/dComponents/ExhibitComponent.cpp new file mode 100644 index 000000000..eee2c8e51 --- /dev/null +++ b/dGame/dComponents/ExhibitComponent.cpp @@ -0,0 +1,51 @@ +#include "ExhibitComponent.h" + +#include "CDClientDatabase.h" +#include "Entity.h" +#include "Logger.h" + +// LWOExhibitComponent::LoadDataFromTemplate 0x00c9ce40: the ExhibitComponent row +void ExhibitComponent::LoadDataFromTemplate() { + auto query = CDClientDatabase::CreatePreppedStmt( + "SELECT offsetX, offsetY, offsetZ, width, height, length, fReputationSizeMultiplier, fImaginationCost FROM ExhibitComponent WHERE id = ?;"); + query.bind(1, m_ComponentID); + auto result = query.execQuery(); + if (result.eof()) { + LOG("Object LOT %i couldn't find ExhibitComponent %i in database", m_Parent->GetLOT(), m_ComponentID); + return; + } + m_Offset = NiPoint3(result.getFloatField(0), result.getFloatField(1), result.getFloatField(2)); + m_Size = NiPoint3(result.getFloatField(3), result.getFloatField(4), result.getFloatField(5)) * 0.8f; + m_ReputationSizeMultiplier = result.getFloatField(6); + m_ImaginationCost = result.getFloatField(7); +} + +// LWOExhibitComponent::LoadConfigData 0x00c9c950: the spawn component's config, then the exhibit_* keys +void ExhibitComponent::LoadConfigData() { + SpawnerComponent::LoadConfigData(); + const auto text = [this](const std::u16string& key, const size_t max) { + auto value = m_Parent->GetVar(key); + if (value.size() > max) value.resize(max); + return value; + }; + m_ResetTime = m_Parent->GetVar(u"exhibit_reset_time"); + m_InstanceName = text(u"exhibit_instance_name", 32); + m_VotingAllowed = m_Parent->GetVar(u"exhibit_voting_allowed"); + m_AllowUnmoderated = m_Parent->GetVar(u"exhibit_allow_unmoderated"); + m_AcceptsDroppedModels = m_Parent->GetVar(u"exhibit_accepts_dropped_models"); + m_RequiredNumberOfBricks = m_Parent->GetVar(u"exhibit_required_number_of_bricks"); + m_MustUseBrickLots = text(u"exhibit_must_use_brick_lots", 250); + m_MustNotUseBrickLots = text(u"exhibit_must_not_use_brick_lots", 250); + m_MustUseBehaviorLots = text(u"exhibit_must_use_behavior_lots", 250); + m_MustNotUseBehaviorLots = text(u"exhibit_must_not_use_behavior_lots", 250); + m_InstanceDescription = text(u"exhibit_instance_description", 250); + m_IncludeModelsMine = m_Parent->GetVar(u"exhibit_include_models_mine"); + m_IncludeModelsBffs = m_Parent->GetVar(u"exhibit_include_models_bffs"); + m_IncludeModelsFriends = m_Parent->GetVar(u"exhibit_include_models_friends"); + m_IncludeModelsGuild = m_Parent->GetVar(u"exhibit_include_models_guild"); + m_IncludeModelsPublic = m_Parent->GetVar(u"exhibit_include_models_public"); + m_DisplayRangeByRank = m_Parent->GetVar(u"exhibit_display_range_by_rank"); + m_TargetTopModelsInstance = text(u"exhibit_target_top_models_instance", 32); + m_IsAnimatedPedestal = m_Parent->GetVar(u"exhibit_is_animated_pedestal"); + m_AnimatedPedestalActiveBone = m_Parent->GetVar(u"exhibit_animated_ped_active_bone"); +} diff --git a/dGame/dComponents/ExhibitComponent.h b/dGame/dComponents/ExhibitComponent.h new file mode 100644 index 000000000..6695b696e --- /dev/null +++ b/dGame/dComponents/ExhibitComponent.h @@ -0,0 +1,59 @@ +#ifndef EXHIBITCOMPONENT_H +#define EXHIBITCOMPONENT_H + +#include + +#include "NiPoint3.h" +#include "SpawnerComponent.h" + +/** + * An exhibit (showcase pedestal, 32), as the client's LWOExhibitComponent: a spawn component that shows a player's + * model. The client sends and reads nothing for it in a construction (its Deserialize is empty); it spawns the shown + * model itself from the exhibit messages. 1.10.64's loader has the case but makes nothing (dead code); DLU makes it. + */ +class ExhibitComponent final : public SpawnerComponent { +public: + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::EXHIBIT; + + ExhibitComponent(Entity* parent, const int32_t componentID) : SpawnerComponent(parent, componentID) {} + + void LoadDataFromTemplate() override; + void LoadConfigData() override; + + const NiPoint3& GetOffset() const { return m_Offset; } + const NiPoint3& GetSize() const { return m_Size; } + const std::u16string& GetInstanceName() const { return m_InstanceName; } + bool GetVotingAllowed() const { return m_VotingAllowed; } + +private: + // The ExhibitComponent row: where the model sits on the pedestal and how big it may be (the client scales the + // row's size by 0.8) + NiPoint3 m_Offset{}; + NiPoint3 m_Size{}; + float m_ReputationSizeMultiplier = 1.0f; + float m_ImaginationCost = 0.0f; + + // The level config (LWOExhibitComponent::LoadConfigData 0x00c9c950) + uint32_t m_ResetTime = 0; + std::u16string m_InstanceName; // at most 32 characters + bool m_VotingAllowed = false; + bool m_AllowUnmoderated = false; + bool m_AcceptsDroppedModels = false; + uint32_t m_RequiredNumberOfBricks = 0; + std::u16string m_MustUseBrickLots; // at most 250 characters each + std::u16string m_MustNotUseBrickLots; + std::u16string m_MustUseBehaviorLots; + std::u16string m_MustNotUseBehaviorLots; + std::u16string m_InstanceDescription; + bool m_IncludeModelsMine = false; + bool m_IncludeModelsBffs = false; + bool m_IncludeModelsFriends = false; + bool m_IncludeModelsGuild = false; + bool m_IncludeModelsPublic = false; + uint32_t m_DisplayRangeByRank = 0; + std::u16string m_TargetTopModelsInstance; // at most 32 characters + bool m_IsAnimatedPedestal = false; + uint32_t m_AnimatedPedestalActiveBone = 0; +}; + +#endif // EXHIBITCOMPONENT_H diff --git a/dGame/dComponents/SpawnerComponent.h b/dGame/dComponents/SpawnerComponent.h index 7558ae30c..8b42aa107 100644 --- a/dGame/dComponents/SpawnerComponent.h +++ b/dGame/dComponents/SpawnerComponent.h @@ -12,7 +12,7 @@ class Spawner; * The spawner component (10) of a spawner object (LOT 176), made from the registry as the client makes it. It owns the * spawner (its nodes, what it spawned and its timers); the zone manager indexes spawners by the object's ID. */ -class SpawnerComponent final : public Component { +class SpawnerComponent : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SPAWNER;