From 9e56725cffd3bb2b15ee27a13e1b35c1864dfdb3 Mon Sep 17 00:00:00 2001 From: wincent Date: Sun, 22 Oct 2023 17:36:08 +0200 Subject: [PATCH] Initial changes. * Recorder to recall player actions. * Server precondtions to manage entity visiblity. --- CMakeVariables.txt | 4 +- dGame/EntityManager.cpp | 13 +- dGame/Player.cpp | 44 +- dGame/Player.h | 8 +- dGame/dGameMessages/GameMessages.cpp | 42 +- dGame/dUtilities/CMakeLists.txt | 2 + dGame/dUtilities/Recorder.cpp | 474 ++++++++++++++++++ dGame/dUtilities/Recorder.h | 170 +++++++ dGame/dUtilities/ServerPreconditions.cpp | 113 +++++ dGame/dUtilities/ServerPreconditions.hpp | 38 ++ dGame/dUtilities/SlashCommandHandler.cpp | 110 ++++ dGame/dUtilities/VanityUtilities.cpp | 37 +- dNet/ClientPackets.cpp | 21 + dScripts/02_server/DLU/CMakeLists.txt | 1 + .../DLU/DukeDialogueGlowingBrick.cpp | 72 +++ .../02_server/DLU/DukeDialogueGlowingBrick.h | 11 + dScripts/CppScripts.cpp | 3 + dWorldServer/WorldServer.cpp | 4 + 18 files changed, 1101 insertions(+), 66 deletions(-) create mode 100644 dGame/dUtilities/Recorder.cpp create mode 100644 dGame/dUtilities/Recorder.h create mode 100644 dGame/dUtilities/ServerPreconditions.cpp create mode 100644 dGame/dUtilities/ServerPreconditions.hpp create mode 100644 dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp create mode 100644 dScripts/02_server/DLU/DukeDialogueGlowingBrick.h diff --git a/CMakeVariables.txt b/CMakeVariables.txt index abfe9e15..8ddb3215 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -7,9 +7,9 @@ LICENSE=AGPL-3.0 # Set __dynamic to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs. __dynamic=1 # Set __ggdb to 1 to enable the -ggdb flag for the linker, including more debug info. -# __ggdb=1 +__ggdb=1 # Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs. -# __include_backtrace__=1 +__include_backtrace__=1 # Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries. # __compile_backtrace__=1 # Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with. diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 4018aba8..317c49cb 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -24,6 +24,7 @@ #include "eGameMasterLevel.h" #include "eReplicaComponentType.h" #include "eReplicaPacketType.h" +#include "ServerPreconditions.hpp" // Configure which zones have ghosting disabled, mostly small worlds. std::vector EntityManager::m_GhostingExcludedZones = { @@ -515,13 +516,15 @@ void EntityManager::UpdateGhosting(Player* player) { ghostingDistanceMax = ghostingDistanceMin; } - if (observed && distance > ghostingDistanceMax && !isOverride) { + auto condition = ServerPreconditions::CheckPreconditions(entity, player); + + if (observed && ((distance > ghostingDistanceMax && !isOverride) || !condition)) { player->GhostEntity(id); DestructEntity(entity, player->GetSystemAddress()); entity->SetObservers(entity->GetObservers() - 1); - } else if (!observed && ghostingDistanceMin > distance) { + } else if (!observed && ghostingDistanceMin > distance && condition) { // Check collectables, don't construct if it has been collected uint32_t collectionId = entity->GetCollectibleID(); @@ -563,13 +566,15 @@ void EntityManager::CheckGhosting(Entity* entity) { const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint); - if (observed && distance > ghostingDistanceMax) { + const auto precondition = ServerPreconditions::CheckPreconditions(entity, player); + + if (observed && (distance > ghostingDistanceMax || !precondition)) { player->GhostEntity(id); DestructEntity(entity, player->GetSystemAddress()); entity->SetObservers(entity->GetObservers() - 1); - } else if (!observed && ghostingDistanceMin > distance) { + } else if (!observed && (ghostingDistanceMin > distance && precondition)) { player->ObserveEntity(id); ConstructEntity(entity, player->GetSystemAddress()); diff --git a/dGame/Player.cpp b/dGame/Player.cpp index 48b983aa..c68324b9 100644 --- a/dGame/Player.cpp +++ b/dGame/Player.cpp @@ -32,9 +32,6 @@ Player::Player(const LWOOBJID& objectID, const EntityInfo info, User* user, Enti m_GhostReferencePoint = NiPoint3::ZERO; m_GhostOverridePoint = NiPoint3::ZERO; m_GhostOverride = false; - m_ObservedEntitiesLength = 256; - m_ObservedEntitiesUsed = 0; - m_ObservedEntities.resize(m_ObservedEntitiesLength); m_Character->SetEntity(this); @@ -180,41 +177,21 @@ bool Player::GetGhostOverride() const { } void Player::ObserveEntity(int32_t id) { - for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) { - if (m_ObservedEntities[i] == 0 || m_ObservedEntities[i] == id) { - m_ObservedEntities[i] = id; - - return; - } - } - - const auto index = m_ObservedEntitiesUsed++; - - if (m_ObservedEntitiesUsed > m_ObservedEntitiesLength) { - m_ObservedEntities.resize(m_ObservedEntitiesLength + m_ObservedEntitiesLength); - - m_ObservedEntitiesLength = m_ObservedEntitiesLength + m_ObservedEntitiesLength; - } - - m_ObservedEntities[index] = id; + m_ObservedEntities.emplace(id); } bool Player::IsObserved(int32_t id) { - for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) { - if (m_ObservedEntities[i] == id) { - return true; - } - } - - return false; + return m_ObservedEntities.find(id) != m_ObservedEntities.end(); } void Player::GhostEntity(int32_t id) { - for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) { - if (m_ObservedEntities[i] == id) { - m_ObservedEntities[i] = 0; - } + const auto& iter = m_ObservedEntities.find(id); + + if (iter == m_ObservedEntities.end()) { + return; } + + m_ObservedEntities.erase(iter); } Player* Player::GetPlayer(const SystemAddress& sysAddr) { @@ -262,9 +239,8 @@ void Player::SetDroppedCoins(uint64_t value) { Player::~Player() { Game::logger->Log("Player", "Deleted player"); - for (int32_t i = 0; i < m_ObservedEntitiesUsed; i++) { - const auto id = m_ObservedEntities[i]; - + for (const auto& id : m_ObservedEntities) + { if (id == 0) { continue; } diff --git a/dGame/Player.h b/dGame/Player.h index 287ee613..f41ff4a5 100644 --- a/dGame/Player.h +++ b/dGame/Player.h @@ -2,6 +2,8 @@ #include "Entity.h" +#include + /** * Extended Entity for player data and behavior. * @@ -120,11 +122,7 @@ private: bool m_GhostOverride; - std::vector m_ObservedEntities; - - int32_t m_ObservedEntitiesLength; - - int32_t m_ObservedEntitiesUsed; + std::unordered_set m_ObservedEntities; std::vector m_LimboConstructions; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 1e0fe2d8..64f89842 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -99,6 +99,8 @@ #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" +#include "Recorder.h" + void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; CMSGHEADER; @@ -189,6 +191,12 @@ void GameMessages::SendPlayAnimation(Entity* entity, const std::u16string& anima if (fScale != 1.0f) bitStream.Write(fScale); SEND_PACKET_BROADCAST; + + auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (recorder != nullptr) { + recorder->AddRecord(new Recording::AnimationRecord(GeneralUtils::UTF16ToWTF8(animationName))); + } } void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) { @@ -5166,6 +5174,17 @@ void GameMessages::HandleRespondToMission(RakNet::BitStream* inStream, Entity* e inStream->Read(isDefaultReward); if (isDefaultReward) inStream->Read(reward); + Entity* offerer = Game::entityManager->GetEntity(receiverID); + + if (offerer == nullptr) { + Game::logger->Log("GameMessages", "Unable to get receiver entity %llu for RespondToMission", receiverID); + return; + } + + for (CppScripts::Script* script : CppScripts::GetEntityScripts(offerer)) { + script->OnRespondToMission(offerer, missionID, Game::entityManager->GetEntity(playerID), reward); + } + MissionComponent* missionComponent = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); if (!missionComponent) { Game::logger->Log("GameMessages", "Unable to get mission component for entity %llu to handle RespondToMission", playerID); @@ -5178,17 +5197,6 @@ void GameMessages::HandleRespondToMission(RakNet::BitStream* inStream, Entity* e } else { Game::logger->Log("GameMessages", "Unable to get mission %i for entity %llu to update reward in RespondToMission", missionID, playerID); } - - Entity* offerer = Game::entityManager->GetEntity(receiverID); - - if (offerer == nullptr) { - Game::logger->Log("GameMessages", "Unable to get receiver entity %llu for RespondToMission", receiverID); - return; - } - - for (CppScripts::Script* script : CppScripts::GetEntityScripts(offerer)) { - script->OnRespondToMission(offerer, missionID, Game::entityManager->GetEntity(playerID), reward); - } } void GameMessages::HandleMissionDialogOK(RakNet::BitStream* inStream, Entity* entity) { @@ -5359,6 +5367,12 @@ void GameMessages::HandleEquipItem(RakNet::BitStream* inStream, Entity* entity) item->Equip(); Game::entityManager->SerializeEntity(entity); + + auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (recorder != nullptr) { + recorder->AddRecord(new Recording::EquipRecord(item->GetLot())); + } } void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity) { @@ -5379,6 +5393,12 @@ void GameMessages::HandleUnequipItem(RakNet::BitStream* inStream, Entity* entity item->UnEquip(); Game::entityManager->SerializeEntity(entity); + + auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (recorder != nullptr) { + recorder->AddRecord(new Recording::UnequipRecord(item->GetLot())); + } } void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 639f9cf4..c770d596 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -5,4 +5,6 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" "Mail.cpp" "Preconditions.cpp" "SlashCommandHandler.cpp" + "Recorder.cpp" + "ServerPreconditions.cpp" "VanityUtilities.cpp" PARENT_SCOPE) diff --git a/dGame/dUtilities/Recorder.cpp b/dGame/dUtilities/Recorder.cpp new file mode 100644 index 00000000..e420ffb8 --- /dev/null +++ b/dGame/dUtilities/Recorder.cpp @@ -0,0 +1,474 @@ +#include "Recorder.h" + +#include "ControllablePhysicsComponent.h" +#include "GameMessages.h" +#include "InventoryComponent.h" +#include "../dWorldServer/ObjectIDManager.h" +#include "ChatPackets.h" +#include "EntityManager.h" +#include "EntityInfo.h" +#include "ServerPreconditions.hpp" + +using namespace Recording; + +std::unordered_map m_Recorders = {}; + +Recorder::Recorder() { + this->m_StartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + this->m_IsRecording = false; +} + +Recorder::~Recorder() { +} + +void Recorder::AddRecord(Record* record) +{ + if (!this->m_IsRecording) { + return; + } + + Game::logger->Log("Recorder", "Adding record"); + + // Time since start of recording + record->m_Timestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - this->m_StartTime; + + this->m_MovementRecords.push_back(record); +} + +void Recorder::Act(Entity* actor) { + Game::logger->Log("Recorder", "Acting %d steps", m_MovementRecords.size()); + + // Loop through all records + for (auto* record : m_MovementRecords) { + record->Act(actor); + } +} + +Entity* Recording::Recorder::ActFor(Entity* actorTemplate, Entity* player) { + EntityInfo info; + info.lot = actorTemplate->GetLOT(); + info.pos = actorTemplate->GetPosition(); + info.rot = actorTemplate->GetRotation(); + info.scale = 1; + info.spawner = nullptr; + info.spawnerID = player->GetObjectID(); + info.spawnerNodeID = 0; + info.settings = { + new LDFData>(u"syncLDF", { u"custom_script_client" }), + new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") + }; + + // Spawn it + auto* actor = Game::entityManager->CreateEntity(info); + + // Hide the template from the player + ServerPreconditions::AddExcludeFor(player->GetObjectID(), actorTemplate->GetObjectID()); + + // Solo act for the player + ServerPreconditions::AddSoloActor(actor->GetObjectID(), player->GetObjectID()); + + Game::entityManager->ConstructEntity(actor); + + Act(actor); + + return actor; +} + +void Recording::Recorder::StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID) { + // Remove the exclude for the player + ServerPreconditions::RemoveExcludeFor(playerID, actorTemplate->GetObjectID()); + + Game::entityManager->DestroyEntity(actor); +} + +bool Recorder::IsRecording() const { + return this->m_IsRecording; +} + +void Recorder::StartRecording(LWOOBJID actorID) { + const auto& it = m_Recorders.find(actorID); + + // Delete the old recorder if it exists + if (it != m_Recorders.end()) { + delete it->second; + m_Recorders.erase(it); + } + + Recorder* recorder = new Recorder(); + m_Recorders.insert_or_assign(actorID, recorder); + recorder->m_IsRecording = true; +} + +void Recorder::StopRecording(LWOOBJID actorID) { + auto iter = m_Recorders.find(actorID); + if (iter == m_Recorders.end()) { + return; + } + + iter->second->m_IsRecording = false; +} + +Recorder* Recorder::GetRecorder(LWOOBJID actorID) { + auto iter = m_Recorders.find(actorID); + if (iter == m_Recorders.end()) { + return nullptr; + } + + return iter->second; +} + +Recording::MovementRecord::MovementRecord(const NiPoint3& position, const NiQuaternion& rotation, const NiPoint3& velocity, const NiPoint3& angularVelocity, bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity) { + this->position = position; + this->rotation = rotation; + this->velocity = velocity; + this->angularVelocity = angularVelocity; + this->onGround = onGround; + this->dirtyVelocity = dirtyVelocity; + this->dirtyAngularVelocity = dirtyAngularVelocity; +} + +void Recording::MovementRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + auto* controllableComponent = actor->GetComponent(); + + if (controllableComponent) { + controllableComponent->SetPosition(r.position); + controllableComponent->SetRotation(r.rotation); + controllableComponent->SetVelocity(r.velocity); + controllableComponent->SetAngularVelocity(r.angularVelocity); + controllableComponent->SetIsOnGround(r.onGround); + controllableComponent->SetDirtyVelocity(r.dirtyVelocity); + controllableComponent->SetDirtyAngularVelocity(r.dirtyAngularVelocity); + } + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::MovementRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("MovementRecord"); + + element->SetAttribute("x", position.x); + element->SetAttribute("y", position.y); + element->SetAttribute("z", position.z); + + element->SetAttribute("qx", rotation.x); + element->SetAttribute("qy", rotation.y); + element->SetAttribute("qz", rotation.z); + element->SetAttribute("qw", rotation.w); + + element->SetAttribute("vx", velocity.x); + element->SetAttribute("vy", velocity.y); + element->SetAttribute("vz", velocity.z); + + element->SetAttribute("avx", angularVelocity.x); + element->SetAttribute("avy", angularVelocity.y); + element->SetAttribute("avz", angularVelocity.z); + + element->SetAttribute("g", onGround); + + element->SetAttribute("dv", dirtyVelocity); + + element->SetAttribute("dav", dirtyAngularVelocity); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::MovementRecord::Deserialize(tinyxml2::XMLElement* element) { + position.x = element->FloatAttribute("x"); + position.y = element->FloatAttribute("y"); + position.z = element->FloatAttribute("z"); + + rotation.x = element->FloatAttribute("qx"); + rotation.y = element->FloatAttribute("qy"); + rotation.z = element->FloatAttribute("qz"); + rotation.w = element->FloatAttribute("qw"); + + velocity.x = element->FloatAttribute("vx"); + velocity.y = element->FloatAttribute("vy"); + velocity.z = element->FloatAttribute("vz"); + + angularVelocity.x = element->FloatAttribute("avx"); + angularVelocity.y = element->FloatAttribute("avy"); + angularVelocity.z = element->FloatAttribute("avz"); + + onGround = element->BoolAttribute("g"); + + dirtyVelocity = element->BoolAttribute("dv"); + + dirtyAngularVelocity = element->BoolAttribute("dav"); + + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +Recording::SpeakRecord::SpeakRecord(const std::string& text) { + this->text = text; +} + +void Recording::SpeakRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + GameMessages::SendNotifyClientZoneObject( + actor->GetObjectID(), u"sendToclient_bubble", 0, 0, actor->GetObjectID(), r.text, UNASSIGNED_SYSTEM_ADDRESS); + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::SpeakRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("SpeakRecord"); + + element->SetAttribute("text", text.c_str()); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::SpeakRecord::Deserialize(tinyxml2::XMLElement* element) { + text = element->Attribute("text"); + + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +Recording::AnimationRecord::AnimationRecord(const std::string& animation) { + this->animation = animation; +} + +void Recording::AnimationRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + GameMessages::SendPlayAnimation(actor, GeneralUtils::ASCIIToUTF16(r.animation)); + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::AnimationRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("AnimationRecord"); + + element->SetAttribute("animation", animation.c_str()); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::AnimationRecord::Deserialize(tinyxml2::XMLElement* element) { + animation = element->Attribute("animation"); + + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +Recording::EquipRecord::EquipRecord(LOT item) { + this->item = item; +} + +void Recording::EquipRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + auto* inventoryComponent = actor->GetComponent(); + + const LWOOBJID id = ObjectIDManager::Instance()->GenerateObjectID(); + + const auto& info = Inventory::FindItemComponent(r.item); + + if (inventoryComponent) { + inventoryComponent->UpdateSlot(info.equipLocation, { id, r.item, 1, 0 }); + } + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::EquipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("EquipRecord"); + + element->SetAttribute("item", item); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::EquipRecord::Deserialize(tinyxml2::XMLElement* element) { + item = element->IntAttribute("item"); + + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +Recording::UnequipRecord::UnequipRecord(LOT item) { + this->item = item; +} + +void Recording::UnequipRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + auto* inventoryComponent = actor->GetComponent(); + + const auto& info = Inventory::FindItemComponent(r.item); + + if (inventoryComponent) { + inventoryComponent->RemoveSlot(info.equipLocation); + } + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::UnequipRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("UnequipRecord"); + + element->SetAttribute("item", item); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::UnequipRecord::Deserialize(tinyxml2::XMLElement* element) { + item = element->IntAttribute("item"); + + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +void Recording::ClearEquippedRecord::Act(Entity* actor) { + // Calculate the amount of seconds (as a float) since the start of the recording + float time = m_Timestamp.count() / 1000.0f; + + auto r = *this; + + actor->AddCallbackTimer(time, [actor, r] () { + auto* inventoryComponent = actor->GetComponent(); + + if (inventoryComponent) { + auto equipped = inventoryComponent->GetEquippedItems(); + + for (auto entry : equipped) { + inventoryComponent->RemoveSlot(entry.first); + } + } + + Game::entityManager->SerializeEntity(actor); + }); +} + +void Recording::ClearEquippedRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) { + auto* element = document.NewElement("ClearEquippedRecord"); + + element->SetAttribute("t", m_Timestamp.count()); + + parent->InsertEndChild(element); +} + +void Recording::ClearEquippedRecord::Deserialize(tinyxml2::XMLElement* element) { + m_Timestamp = std::chrono::milliseconds(element->Int64Attribute("t")); +} + +void Recording::Recorder::SaveToFile(const std::string& filename) { + tinyxml2::XMLDocument document; + + auto* root = document.NewElement("Recorder"); + + for (auto* record : m_MovementRecords) { + record->Serialize(document, root); + } + + document.InsertFirstChild(root); + + document.SaveFile(filename.c_str()); +} + +float Recording::Recorder::GetDuration() const { + // Return the highest timestamp + float duration = 0.0f; + + for (auto* record : m_MovementRecords) { + duration = std::max(duration, record->m_Timestamp.count() / 1000.0f); + } + + return duration; +} + +Recorder* Recording::Recorder::LoadFromFile(const std::string& filename) { + tinyxml2::XMLDocument document; + + if (document.LoadFile(filename.c_str()) != tinyxml2::XML_SUCCESS) { + return nullptr; + } + + auto* root = document.FirstChildElement("Recorder"); + + if (!root) { + return nullptr; + } + + Recorder* recorder = new Recorder(); + + for (auto* element = root->FirstChildElement(); element; element = element->NextSiblingElement()) { + const std::string name = element->Name(); + + if (name == "MovementRecord") { + MovementRecord* record = new MovementRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } else if (name == "SpeakRecord") { + SpeakRecord* record = new SpeakRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } else if (name == "AnimationRecord") { + AnimationRecord* record = new AnimationRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } else if (name == "EquipRecord") { + EquipRecord* record = new EquipRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } else if (name == "UnequipRecord") { + UnequipRecord* record = new UnequipRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } else if (name == "ClearEquippedRecord") { + ClearEquippedRecord* record = new ClearEquippedRecord(); + record->Deserialize(element); + recorder->m_MovementRecords.push_back(record); + } + } + + return recorder; +} + +void Recording::Recorder::AddRecording(LWOOBJID actorID, Recorder* recorder) { + const auto& it = m_Recorders.find(actorID); + + // Delete the old recorder if it exists + if (it != m_Recorders.end()) { + delete it->second; + m_Recorders.erase(it); + } + + m_Recorders.insert_or_assign(actorID, recorder); +} diff --git a/dGame/dUtilities/Recorder.h b/dGame/dUtilities/Recorder.h new file mode 100644 index 00000000..fd739146 --- /dev/null +++ b/dGame/dUtilities/Recorder.h @@ -0,0 +1,170 @@ +#pragma once + +#include "Player.h" +#include "Game.h" +#include "EntityManager.h" +#include "tinyxml2.h" + +#include + +namespace Recording +{ + +class Record +{ +public: + virtual void Act(Entity* actor) = 0; + + virtual void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) = 0; + + virtual void Deserialize(tinyxml2::XMLElement* element) = 0; + + std::chrono::milliseconds m_Timestamp; +}; + + +class MovementRecord : public Record +{ +public: + NiPoint3 position; + NiQuaternion rotation; + NiPoint3 velocity; + NiPoint3 angularVelocity; + bool onGround; + bool dirtyVelocity; + bool dirtyAngularVelocity; + + MovementRecord() = default; + + MovementRecord( + const NiPoint3& position, + const NiQuaternion& rotation, + const NiPoint3& velocity, + const NiPoint3& angularVelocity, + bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity + ); + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + +class SpeakRecord : public Record +{ +public: + std::string text; + + SpeakRecord() = default; + + SpeakRecord(const std::string& text); + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + +class AnimationRecord : public Record +{ +public: + std::string animation; + + AnimationRecord() = default; + + AnimationRecord(const std::string& animation); + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + +class EquipRecord : public Record +{ +public: + LOT item; + + EquipRecord() = default; + + EquipRecord(LOT item); + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + +class UnequipRecord : public Record +{ +public: + LOT item; + + UnequipRecord() = default; + + UnequipRecord(LOT item); + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + +class ClearEquippedRecord : public Record +{ +public: + ClearEquippedRecord() = default; + + void Act(Entity* actor) override; + + void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override; + + void Deserialize(tinyxml2::XMLElement* element) override; +}; + + +class Recorder +{ +public: + Recorder(); + + ~Recorder(); + + void AddRecord(Record* record); + + void Act(Entity* actor); + + Entity* ActFor(Entity* actorTemplate, Entity* player); + + void StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID); + + bool IsRecording() const; + + void SaveToFile(const std::string& filename); + + float GetDuration() const; + + static Recorder* LoadFromFile(const std::string& filename); + + static void AddRecording(LWOOBJID actorID, Recorder* recorder); + + static void StartRecording(LWOOBJID actorID); + + static void StopRecording(LWOOBJID actorID); + + static Recorder* GetRecorder(LWOOBJID actorID); + +private: + std::vector m_MovementRecords; + + bool m_IsRecording; + + std::chrono::milliseconds m_StartTime; +}; + +} \ No newline at end of file diff --git a/dGame/dUtilities/ServerPreconditions.cpp b/dGame/dUtilities/ServerPreconditions.cpp new file mode 100644 index 00000000..16934026 --- /dev/null +++ b/dGame/dUtilities/ServerPreconditions.cpp @@ -0,0 +1,113 @@ +#include "ServerPreconditions.hpp" + +#include "tinyxml2.h" + +using namespace ServerPreconditions; + +std::unordered_map>> ServerPreconditions::m_Preconditions; + +std::unordered_map ServerPreconditions::m_SoloActors; + +std::unordered_map> ServerPreconditions::m_ExcludeForPlayer; + +void ServerPreconditions::LoadPreconditions(std::string file) { + tinyxml2::XMLDocument doc; + doc.LoadFile(file.c_str()); + + tinyxml2::XMLElement* root = doc.FirstChildElement("Preconditions"); + if (!root) { + return; + } + + for (tinyxml2::XMLElement* element = root->FirstChildElement("Entity"); element; element = element->NextSiblingElement("Entity")) { + LOT lot = element->UnsignedAttribute("lot"); + std::vector> preconditions; + + for (tinyxml2::XMLElement* precondition = element->FirstChildElement("Precondition"); precondition; precondition = precondition->NextSiblingElement("Precondition")) { + const auto condition = Preconditions::CreateExpression(precondition->GetText()); + + int64_t inverted; + + if (precondition->QueryInt64Attribute("not", &inverted) == tinyxml2::XML_SUCCESS) { + preconditions.push_back(std::make_pair(inverted > 0, condition)); + } + else { + preconditions.push_back(std::make_pair(false, condition)); + } + } + + m_Preconditions[lot] = preconditions; + } +} + +bool ServerPreconditions::CheckPreconditions(Entity* target, Entity* entity) { + if (IsExcludedFor(entity->GetObjectID(), target->GetObjectID())) { + return false; + } + + if (IsSoloActor(target->GetObjectID())) { + return IsActingFor(target->GetObjectID(), entity->GetObjectID()); + } + + if (m_Preconditions.find(target->GetLOT()) == m_Preconditions.end()) { + return true; + } + + for (const auto& [inverse, precondition] : m_Preconditions[target->GetLOT()]) { + if (precondition.Check(entity) == inverse) { + return false; + } + } + + return true; +} + +bool ServerPreconditions::IsSoloActor(LWOOBJID actor) { + return m_SoloActors.find(actor) != m_SoloActors.end(); +} + +bool ServerPreconditions::IsActingFor(LWOOBJID actor, LWOOBJID target) { + return m_SoloActors.find(actor) != m_SoloActors.end() && m_SoloActors[actor] == target; +} + +void ServerPreconditions::AddSoloActor(LWOOBJID actor, LWOOBJID target) { + m_SoloActors[actor] = target; +} + +void ServerPreconditions::RemoveSoloActor(LWOOBJID actor) { + m_SoloActors.erase(actor); +} + +void ServerPreconditions::AddExcludeFor(LWOOBJID player, LWOOBJID target) { + const auto& it = m_ExcludeForPlayer.find(player); + + if (it == m_ExcludeForPlayer.end()) { + m_ExcludeForPlayer[player] = std::unordered_set(); + } + + m_ExcludeForPlayer[player].insert(target); +} + +void ServerPreconditions::RemoveExcludeFor(LWOOBJID player, LWOOBJID target) { + const auto& it = m_ExcludeForPlayer.find(player); + + if (it == m_ExcludeForPlayer.end()) { + return; + } + + m_ExcludeForPlayer[player].erase(target); + + if (m_ExcludeForPlayer[player].empty()) { + m_ExcludeForPlayer.erase(player); + } +} + +bool ServerPreconditions::IsExcludedFor(LWOOBJID player, LWOOBJID target) { + const auto& it = m_ExcludeForPlayer.find(player); + + if (it == m_ExcludeForPlayer.end()) { + return false; + } + + return it->second.find(target) != it->second.end(); +} diff --git a/dGame/dUtilities/ServerPreconditions.hpp b/dGame/dUtilities/ServerPreconditions.hpp new file mode 100644 index 00000000..222f71c3 --- /dev/null +++ b/dGame/dUtilities/ServerPreconditions.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include "dCommonVars.h" +#include "Preconditions.h" + +class Entity; + +namespace ServerPreconditions +{ + +extern std::unordered_map>> m_Preconditions; + +extern std::unordered_map m_SoloActors; + +extern std::unordered_map> m_ExcludeForPlayer; + +void LoadPreconditions(std::string file); + +bool CheckPreconditions(Entity* target, Entity* entity); + +bool IsSoloActor(LWOOBJID actor); + +bool IsActingFor(LWOOBJID actor, LWOOBJID target); + +void AddSoloActor(LWOOBJID actor, LWOOBJID target); + +void RemoveSoloActor(LWOOBJID actor); + +void AddExcludeFor(LWOOBJID player, LWOOBJID target); + +void RemoveExcludeFor(LWOOBJID player, LWOOBJID target); + +bool IsExcludedFor(LWOOBJID player, LWOOBJID target); + +} \ No newline at end of file diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 17fd980c..deaf85be 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -86,6 +86,9 @@ #include "CDObjectsTable.h" #include "CDZoneTableTable.h" +#include "Recorder.h" +#include "ServerPreconditions.hpp" + void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { auto commandCopy = command; // Sanity check that a command was given @@ -711,6 +714,33 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + if (chatCommand == "removemission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + if (args.size() == 0) return; + + uint32_t missionID; + + if (!GeneralUtils::TryParse(args[0], missionID)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); + return; + } + + auto* comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); + + if (comp == nullptr) { + return; + } + + auto* mission = comp->GetMission(missionID); + + if (mission == nullptr) { + return; + } + + comp->RemoveMission(missionID); + + return; + } + if (chatCommand == "playeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { int32_t effectID = 0; @@ -883,6 +913,86 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); } + if (chatCommand == "record-act" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + EntityInfo info; + info.lot = 2097253; + info.pos = entity->GetPosition(); + info.rot = entity->GetRotation(); + info.scale = 1; + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + info.settings = { + new LDFData>(u"syncLDF", { u"custom_script_client" }), + new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") + }; + + // If there is an argument, set the lot + if (args.size() > 0) { + if (!GeneralUtils::TryParse(args[0], info.lot)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + } + + // Spawn it + auto* actor = Game::entityManager->CreateEntity(info); + + // If there is an argument, set the actors name + if (args.size() > 1) { + actor->SetVar(u"npcName", args[1]); + } + + // Construct it + Game::entityManager->ConstructEntity(actor); + + auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (record) { + record->Act(actor); + } else { + Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID()); + } + } + + if (chatCommand == "record-start" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + Recording::Recorder::StartRecording(entity->GetObjectID()); + + auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (record) { + if (args.size() > 0 && args[0] == "clear") { + record->AddRecord(new Recording::ClearEquippedRecord()); + } + } else { + Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID()); + } + } + + if (chatCommand == "record-stop" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + Recording::Recorder::StopRecording(entity->GetObjectID()); + } + + if (chatCommand == "record-save" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { + auto* record = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (record) { + record->SaveToFile(args[0]); + } else { + Game::logger->Log("SlashCommandHandler", "Failed to get recorder for objectID: %llu", entity->GetObjectID()); + } + } + + if (chatCommand == "record-load" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { + auto* record = Recording::Recorder::LoadFromFile(args[0]); + + if (record) { + Recording::Recorder::AddRecording(entity->GetObjectID(), record); + } else { + Game::logger->Log("SlashCommandHandler", "Failed to load recording from file: %s", args[0].c_str()); + } + } + if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { NiPoint3 pos{}; if (args.size() == 3) { diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index eb182e04..d47f9f43 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -117,18 +117,23 @@ void VanityUtilities::SpawnVanity() { if (!npc.m_Phrases.empty()){ npcEntity->SetVar>(u"chats", npc.m_Phrases); - auto* scriptComponent = npcEntity->GetComponent(); - - if (scriptComponent && !npc.m_Script.empty()) { - scriptComponent->SetScript(npc.m_Script); - scriptComponent->SetSerialized(false); - - for (const auto& npc : npc.m_Flags) { - npcEntity->SetVar(GeneralUtils::ASCIIToUTF16(npc.first), npc.second); - } - } SetupNPCTalk(npcEntity); } + + auto* scriptComponent = npcEntity->GetComponent(); + + Game::logger->Log("VanityUtilities", "Script: %s", npc.m_Script.c_str()); + + if (scriptComponent && !npc.m_Script.empty()) { + scriptComponent->SetScript(npc.m_Script); + scriptComponent->SetSerialized(false); + + Game::logger->Log("VanityUtilities", "Setting script to %s", npc.m_Script.c_str()); + + for (const auto& npc : npc.m_Flags) { + npcEntity->SetVar(GeneralUtils::ASCIIToUTF16(npc.first), npc.second); + } + } } if (zoneID == 1200) { @@ -162,6 +167,9 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin entity->SetVar(u"npcName", name); if (entity->GetVar(u"noGhosting")) entity->SetIsGhostingCandidate(false); + // Debug print + Game::logger->Log("VanityUtilities", "Spawning NPC %s (%i) at %f, %f, %f", name.c_str(), lot, position.x, position.y, position.z); + auto* inventoryComponent = entity->GetComponent(); if (inventoryComponent && !inventory.empty()) { @@ -176,6 +184,15 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin destroyableComponent->SetHealth(0); } + auto* scriptComponent = entity->GetComponent(); + + if (scriptComponent == nullptr) + { + entity->AddComponent(eReplicaComponentType::SCRIPT, new ScriptComponent(entity, "", false)); + } + + Game::logger->Log("VanityUtilities", "NPC has script component? %s", (entity->GetComponent() != nullptr) ? "true" : "false"); + Game::entityManager->ConstructEntity(entity); return entity; diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index e797ea21..6cd688f1 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -34,6 +34,7 @@ #include "eGameMasterLevel.h" #include "eReplicaComponentType.h" #include "CheatDetection.h" +#include "Recorder.h" void ClientPackets::HandleChatMessage(const SystemAddress& sysAddr, Packet* packet) { User* user = UserManager::Instance()->GetUser(sysAddr); @@ -82,6 +83,12 @@ void ClientPackets::HandleChatMessage(const SystemAddress& sysAddr, Packet* pack std::string sMessage = GeneralUtils::UTF16ToWTF8(message); Game::logger->Log("Chat", "%s: %s", playerName.c_str(), sMessage.c_str()); ChatPackets::SendChatMessage(sysAddr, chatChannel, playerName, user->GetLoggedInChar(), isMythran, message); + + auto* recorder = Recording::Recorder::GetRecorder(user->GetLoggedInChar()); + + if (recorder != nullptr) { + recorder->AddRecord(new Recording::SpeakRecord(sMessage)); + } } void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Packet* packet) { @@ -236,6 +243,20 @@ void ClientPackets::HandleClientPositionUpdate(const SystemAddress& sysAddr, Pac comp->SetAngularVelocity(angVelocity); comp->SetDirtyAngularVelocity(angVelocityFlag); + auto* recorder = Recording::Recorder::GetRecorder(entity->GetObjectID()); + + if (recorder != nullptr) { + recorder->AddRecord(new Recording::MovementRecord( + position, + rotation, + velocity, + angVelocity, + onGround, + velocityFlag, + angVelocityFlag + )); + } + auto* player = static_cast(entity); player->SetGhostReferencePoint(position); Game::entityManager->QueueGhostUpdate(player->GetObjectID()); diff --git a/dScripts/02_server/DLU/CMakeLists.txt b/dScripts/02_server/DLU/CMakeLists.txt index 64d4cbbd..60edc5f4 100644 --- a/dScripts/02_server/DLU/CMakeLists.txt +++ b/dScripts/02_server/DLU/CMakeLists.txt @@ -1,3 +1,4 @@ set(DSCRIPTS_SOURCES_02_SERVER_DLU "DLUVanityNPC.cpp" + "DukeDialogueGlowingBrick.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp b/dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp new file mode 100644 index 00000000..d963fe1e --- /dev/null +++ b/dScripts/02_server/DLU/DukeDialogueGlowingBrick.cpp @@ -0,0 +1,72 @@ +#include "DukeDialogueGlowingBrick.h" + +#include "eMissionState.h" + +#include "Recorder.h" +#include "MissionComponent.h" +#include "InventoryComponent.h" + +void DukeDialogueGlowingBrick::OnStartup(Entity* self) { + Game::logger->Log("DukeDialogueGlowingBrick", "OnStartup"); +} + +void DukeDialogueGlowingBrick::OnTimerDone(Entity* self, std::string timerName) { +} + +void DukeDialogueGlowingBrick::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) { + if (missionID != 201453) { + return; + } + + if (missionState != eMissionState::AVAILABLE) { + return; + } + + auto* recorder = Recording::Recorder::LoadFromFile("DukeGlowing.xml"); + + if (recorder == nullptr) { + return; + } + + auto* actor = recorder->ActFor(self, target); + + if (actor == nullptr) { + return; + } + + const auto targetID = target->GetObjectID(); + const auto actorID = actor->GetObjectID(); + + self->AddCallbackTimer(3.0f, [targetID] () { + auto* target = Game::entityManager->GetEntity(targetID); + + if (target == nullptr) { + return; + } + + auto* missionComponent = target->GetComponent(); + + if (missionComponent == nullptr) { + return; + } + + missionComponent->CompleteMission(201453); + }); + + self->AddCallbackTimer(recorder->GetDuration() + 10.0f, [recorder, self, actorID, targetID] () { + auto* target = Game::entityManager->GetEntity(targetID); + auto* actor = Game::entityManager->GetEntity(actorID); + + if (target == nullptr || actor == nullptr) { + return; + } + + recorder->StopActingFor(actor, self, targetID); + + delete recorder; + }); +} + +void DukeDialogueGlowingBrick::OnRespondToMission(Entity* self, int missionID, Entity* player, int reward) { + +} diff --git a/dScripts/02_server/DLU/DukeDialogueGlowingBrick.h b/dScripts/02_server/DLU/DukeDialogueGlowingBrick.h new file mode 100644 index 00000000..668b5807 --- /dev/null +++ b/dScripts/02_server/DLU/DukeDialogueGlowingBrick.h @@ -0,0 +1,11 @@ +#pragma once +#include "CppScripts.h" + +class DukeDialogueGlowingBrick : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + void OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) override; + void OnRespondToMission(Entity* self, int missionID, Entity* player, int reward) override; +}; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 065248c5..16762fd1 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -212,6 +212,7 @@ // DLU Scripts #include "DLUVanityNPC.h" +#include "DukeDialogueGlowingBrick.h" // AM Scripts #include "AmConsoleTeleportServer.h" @@ -819,6 +820,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr //DLU: else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityNPC.lua") script = new DLUVanityNPC(); + else if (scriptName == "scripts\\02_server\\DLU\\DukeGlowing.lua") + script = new DukeDialogueGlowingBrick(); // Survival minigame else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua") diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 0803bbf7..c5f5cc22 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -75,6 +75,8 @@ #include "EntityManager.h" #include "CheatDetection.h" +#include "ServerPreconditions.hpp" + namespace Game { dLogger* logger = nullptr; dServer* server = nullptr; @@ -255,6 +257,8 @@ int main(int argc, char** argv) { PerformanceManager::SelectProfile(zoneID); + ServerPreconditions::LoadPreconditions("vanity/preconditions.xml"); + Game::entityManager = new EntityManager(); Game::zoneManager = new dZoneManager(); //Load our level: