From 192c8cf974b46b03b5604457274797733a73d330 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 25 Feb 2024 16:59:10 -0600 Subject: [PATCH] feat: refactor vanity (#1477) * feat: refactor vanity cleanup code to be generalized for objects remove unused party feature add fallback to data to text Allow for better organizing data in multiple files remove special case flag values in favor of config data general cleanup and fixes * newline at eof's --- CMakeLists.txt | 2 +- dGame/Entity.cpp | 6 + dGame/Entity.h | 2 + dGame/dUtilities/VanityUtilities.cpp | 559 +++++++----------- dGame/dUtilities/VanityUtilities.h | 43 +- dScripts/02_server/DLU/CMakeLists.txt | 2 +- ...NPC.cpp => DLUVanityTeleportingObject.cpp} | 26 +- ...nityNPC.h => DLUVanityTeleportingObject.h} | 7 +- dScripts/CppScripts.cpp | 6 +- vanity/atm.xml | 23 + vanity/{NPC.xml => dev-tribute.xml} | 345 +++++------ vanity/root.xml | 4 + 12 files changed, 414 insertions(+), 611 deletions(-) rename dScripts/02_server/DLU/{DLUVanityNPC.cpp => DLUVanityTeleportingObject.cpp} (51%) rename dScripts/02_server/DLU/{DLUVanityNPC.h => DLUVanityTeleportingObject.h} (54%) create mode 100644 vanity/atm.xml rename vanity/{NPC.xml => dev-tribute.xml} (65%) create mode 100644 vanity/root.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index e085bfe7..b36bdb29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,7 +179,7 @@ file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PRO file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip) # Copy vanity files on first build -set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml") +set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "root.xml" "dev-tribute.xml" "atm.xml") foreach(file ${VANITY_FILES}) configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index bb932991..dd15f69d 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2197,3 +2197,9 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) { auto* characterComponent = GetComponent(); if (characterComponent) characterComponent->SetRespawnRot(rotation); } + +void Entity::SetScale(const float scale) { + if (scale == m_Scale) return; + m_Scale = scale; + Game::entityManager->SerializeEntity(this); +} \ No newline at end of file diff --git a/dGame/Entity.h b/dGame/Entity.h index 6546e458..7d5e24c9 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -295,6 +295,8 @@ public: void ProcessPositionUpdate(PositionUpdate& update); + void SetScale(const float scale); + protected: LWOOBJID m_ObjectID; diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index fa1a3eac..3e93f830 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -22,143 +22,13 @@ #include -std::vector VanityUtilities::m_NPCs = {}; -std::vector VanityUtilities::m_Parties = {}; -std::vector VanityUtilities::m_PartyPhrases = {}; +std::vector VanityUtilities::m_Objects = {}; +std::set VanityUtilities::m_LoadedFiles = {}; + void VanityUtilities::SpawnVanity() { - if (Game::config->GetValue("disable_vanity") == "1") { - return; - } - const uint32_t zoneID = Game::server->GetZoneID(); - for (const auto& npc : m_NPCs) { - if (npc.m_ID == LWOOBJID_EMPTY) continue; - if (npc.m_LOT == 176){ - Game::zoneManager->RemoveSpawner(npc.m_ID); - } else{ - auto* entity = Game::entityManager->GetEntity(npc.m_ID); - if (!entity) continue; - entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); - } - } - - m_NPCs.clear(); - m_Parties.clear(); - m_PartyPhrases.clear(); - - ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); - - // Loop through all parties - for (const auto& party : m_Parties) { - const auto chance = party.m_Chance; - const auto zone = party.m_Zone; - - if (zone != Game::server->GetZoneID()) { - continue; - } - - float rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (chance < rate) { - continue; - } - - // Copy m_NPCs into a new vector - std::vector npcList = m_NPCs; - std::vector taken = {}; - - LOG("Spawning party with %i locations", party.m_Locations.size()); - - // Loop through all locations - for (const auto& location : party.m_Locations) { - rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (0.75f < rate) { - continue; - } - - // Get a random NPC - auto npcIndex = GeneralUtils::GenerateRandomNumber(0, npcList.size() - 1); - - while (std::find(taken.begin(), taken.end(), npcIndex) != taken.end()) { - npcIndex = GeneralUtils::GenerateRandomNumber(0, npcList.size() - 1); - } - - auto& npc = npcList[npcIndex]; - // Skip spawners - if (npc.m_LOT == 176) continue; - - taken.push_back(npcIndex); - - LOG("ldf size is %i", npc.ldf.size()); - if (npc.ldf.empty()) { - npc.ldf = { - new LDFData>(u"syncLDF", { u"custom_script_client" }), - new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") - }; - } - - // Spawn the NPC - if (npc.m_LOT == 176){ - npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); - } else { - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); - if (!npc.m_Phrases.empty()) { - npcEntity->SetVar>(u"chats", m_PartyPhrases); - SetupNPCTalk(npcEntity); - } - } - } - return; - } - - // Loop through all NPCs - for (auto& npc : m_NPCs) { - if (npc.m_Locations.find(Game::server->GetZoneID()) == npc.m_Locations.end()) - continue; - - const std::vector& locations = npc.m_Locations.at(Game::server->GetZoneID()); - - // Pick a random location - const auto& location = locations[GeneralUtils::GenerateRandomNumber( - static_cast(0), static_cast(locations.size() - 1))]; - - float rate = GeneralUtils::GenerateRandomNumber(0, 1); - if (location.m_Chance < rate) { - continue; - } - - if (npc.ldf.empty()) { - npc.ldf = { - new LDFData>(u"syncLDF", { u"custom_script_client" }), - new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") - }; - } - if (npc.m_LOT == 176){ - npc.m_ID = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); - } else { - // Spawn the NPC - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); - if (!npcEntity) continue; - npc.m_ID = npcEntity->GetObjectID(); - 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); - } - } - } - if (zoneID == 1200) { { EntityInfo info; @@ -175,38 +45,99 @@ void VanityUtilities::SpawnVanity() { Game::entityManager->ConstructEntity(entity); } } + + if (Game::config->GetValue("disable_vanity") == "1") { + return; + } + + for (const auto& npc : m_Objects) { + if (npc.m_ID == LWOOBJID_EMPTY) continue; + if (npc.m_LOT == 176){ + Game::zoneManager->RemoveSpawner(npc.m_ID); + } else{ + auto* entity = Game::entityManager->GetEntity(npc.m_ID); + if (!entity) continue; + entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); + } + } + + m_Objects.clear(); + m_LoadedFiles.clear(); + + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/root.xml").string()); + + // Loop through all objects + for (auto& object : m_Objects) { + if (object.m_Locations.find(Game::server->GetZoneID()) == object.m_Locations.end()) continue; + + const std::vector& locations = object.m_Locations.at(Game::server->GetZoneID()); + + // Pick a random location + const auto& location = locations[GeneralUtils::GenerateRandomNumber( + static_cast(0), static_cast(locations.size() - 1))]; + + float rate = GeneralUtils::GenerateRandomNumber(0, 1); + if (location.m_Chance < rate) continue; + + if (object.m_Config.empty()) { + object.m_Config = { + new LDFData>(u"syncLDF", { u"custom_script_client" }), + new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") + }; + } + if (object.m_LOT == 176){ + object.m_ID = SpawnSpawner(object, location); + } else { + // Spawn the NPC + auto* objectEntity = SpawnObject(object, location); + if (!objectEntity) continue; + object.m_ID = objectEntity->GetObjectID(); + if (!object.m_Phrases.empty()){ + objectEntity->SetVar>(u"chats", object.m_Phrases); + + auto* scriptComponent = objectEntity->GetComponent(); + + if (scriptComponent && !object.m_Script.empty()) { + scriptComponent->SetScript(object.m_Script); + scriptComponent->SetSerialized(false); + } + SetupNPCTalk(objectEntity); + } + } + } } -LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& ldf){ +LWOOBJID VanityUtilities::SpawnSpawner(const VanityObject& object, const VanityObjectLocation& location) { SceneObject obj; - obj.lot = lot; + obj.lot = object.m_LOT; // guratantee we have no collisions do { obj.id = ObjectIDManager::GenerateObjectID(); } while(Game::zoneManager->GetSpawner(obj.id)); - obj.position = position; - obj.rotation = rotation; - obj.settings = ldf; + obj.position = location.m_Position; + obj.rotation = location.m_Rotation; + obj.settings = object.m_Config; Level::MakeSpawner(obj); return obj.id; } -Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf) { +Entity* VanityUtilities::SpawnObject(const VanityObject& object, const VanityObjectLocation& location) { EntityInfo info; - info.lot = lot; - info.pos = position; - info.rot = rotation; + info.lot = object.m_LOT; + info.pos = location.m_Position; + info.rot = location.m_Rotation; + info.scale = location.m_Scale; info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - info.settings = ldf; + info.settings = object.m_Config; auto* entity = Game::entityManager->CreateEntity(info); - entity->SetVar(u"npcName", name); + entity->SetVar(u"npcName", object.m_Name); if (entity->GetVar(u"noGhosting")) entity->SetIsGhostingCandidate(false); auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent && !inventory.empty()) { - inventoryComponent->SetNPCItems(inventory); + if (inventoryComponent && !object.m_Equipment.empty()) { + inventoryComponent->SetNPCItems(object.m_Equipment); } auto* destroyableComponent = entity->GetComponent(); @@ -223,6 +154,11 @@ Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoin } void VanityUtilities::ParseXML(const std::string& file) { + if (m_LoadedFiles.contains(file)){ + LOG("Trying to load vanity file %s twice!!!", file.c_str()); + return; + } + m_LoadedFiles.insert(file); // Read the entire file std::ifstream xmlFile(file); std::string xml((std::istreambuf_iterator(xmlFile)), std::istreambuf_iterator()); @@ -231,210 +167,112 @@ void VanityUtilities::ParseXML(const std::string& file) { tinyxml2::XMLDocument doc; doc.Parse(xml.c_str(), xml.size()); - // Read the NPCs - auto* npcs = doc.FirstChildElement("npcs"); - - if (npcs == nullptr) { - LOG("Failed to parse NPCs"); - return; - } - - for (auto* party = npcs->FirstChildElement("party"); party != nullptr; party = party->NextSiblingElement("party")) { - // Get 'zone' as uint32_t and 'chance' as float - uint32_t zone = 0; - float chance = 0.0f; - - if (party->Attribute("zone") != nullptr) { - zone = std::stoul(party->Attribute("zone")); - } - - if (party->Attribute("chance") != nullptr) { - chance = std::stof(party->Attribute("chance")); - } - - VanityParty partyInfo; - partyInfo.m_Zone = zone; - partyInfo.m_Chance = chance; - - auto* locations = party->FirstChildElement("locations"); - - if (locations == nullptr) { - LOG("Failed to parse party locations"); - continue; - } - - for (auto* location = locations->FirstChildElement("location"); location != nullptr; - location = location->NextSiblingElement("location")) { - // Get the location data - auto* x = location->Attribute("x"); - auto* y = location->Attribute("y"); - auto* z = location->Attribute("z"); - auto* rw = location->Attribute("rw"); - auto* rx = location->Attribute("rx"); - auto* ry = location->Attribute("ry"); - auto* rz = location->Attribute("rz"); - - if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr - || rz == nullptr) { - LOG("Failed to parse party location data"); + // Read the objects + auto* files = doc.FirstChildElement("files"); + if (files) { + for (auto* file = files->FirstChildElement("file"); file != nullptr; file = file->NextSiblingElement("file")) { + std::string enabled = file->Attribute("enabled"); + std::string filename = file->Attribute("name"); + if (enabled != "1") { continue; } - - VanityNPCLocation locationData; - locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) }; - locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) }; - locationData.m_Chance = 1.0f; - - partyInfo.m_Locations.push_back(locationData); - } - - m_Parties.push_back(partyInfo); - } - - auto* partyPhrases = npcs->FirstChildElement("partyphrases"); - - if (partyPhrases == nullptr) { - LOG("No party phrases found"); - } else { - for (auto* phrase = partyPhrases->FirstChildElement("phrase"); phrase != nullptr; - phrase = phrase->NextSiblingElement("phrase")) { - // Get the phrase - auto* text = phrase->GetText(); - - if (text == nullptr) { - LOG("Failed to parse party phrase"); - continue; - } - - m_PartyPhrases.push_back(text); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity" / filename).string()); } } - for (auto* npc = npcs->FirstChildElement("npc"); npc != nullptr; npc = npc->NextSiblingElement("npc")) { - // Get the NPC name - auto* name = npc->Attribute("name"); + // Read the objects + auto* objects = doc.FirstChildElement("objects"); - if (!name) name = ""; + if (objects) { + for (auto* object = objects->FirstChildElement("object"); object != nullptr; object = object->NextSiblingElement("object")) { + // Get the NPC name + auto* name = object->Attribute("name"); - // Get the NPC lot - auto* lot = npc->Attribute("lot"); + if (!name) name = ""; - if (lot == nullptr) { - LOG("Failed to parse NPC lot"); - continue; - } + // Get the NPC lot + auto* lot = object->Attribute("lot"); - // Get the equipment - auto* equipment = npc->FirstChildElement("equipment"); - std::vector inventory; - - if (equipment) { - auto* text = equipment->GetText(); - - if (text != nullptr) { - std::string equipmentString(text); - - std::vector splitEquipment = GeneralUtils::SplitString(equipmentString, ','); - - for (auto& item : splitEquipment) { - inventory.push_back(std::stoi(item)); - } - } - } - - - // Get the phrases - auto* phrases = npc->FirstChildElement("phrases"); - - std::vector phraseList = {}; - - if (phrases) { - for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr; - phrase = phrase->NextSiblingElement("phrase")) { - // Get the phrase - auto* text = phrase->GetText(); - if (text == nullptr) { - LOG("Failed to parse NPC phrase"); - continue; - } - phraseList.push_back(text); - } - } - - // Get the script - auto* scriptElement = npc->FirstChildElement("script"); - - std::string scriptName = ""; - - if (scriptElement != nullptr) { - auto* scriptNameAttribute = scriptElement->Attribute("name"); - if (scriptNameAttribute) scriptName = scriptNameAttribute; - } - - auto* ldfElement = npc->FirstChildElement("ldf"); - std::vector keys = {}; - - std::vector ldf = {}; - if(ldfElement) { - for (auto* entry = ldfElement->FirstChildElement("entry"); entry != nullptr; - entry = entry->NextSiblingElement("entry")) { - // Get the ldf data - auto* data = entry->Attribute("data"); - if (!data) continue; - - LDFBaseData* ldfData = LDFBaseData::DataFromString(data); - keys.push_back(ldfData->GetKey()); - ldf.push_back(ldfData); - } - } - if (!keys.empty()) ldf.push_back(new LDFData>(u"syncLDF", keys)); - - VanityNPC npcData; - npcData.m_Name = name; - npcData.m_LOT = std::stoi(lot); - npcData.m_Equipment = inventory; - npcData.m_Phrases = phraseList; - npcData.m_Script = scriptName; - npcData.ldf = ldf; - - // Get flags - auto* flags = npc->FirstChildElement("flags"); - - if (flags != nullptr) { - for (auto* flag = flags->FirstChildElement("flag"); flag != nullptr; - flag = flag->NextSiblingElement("flag")) { - // Get the flag name - auto* name = flag->Attribute("name"); - - if (name == nullptr) { - LOG("Failed to parse NPC flag name"); - continue; - } - - // Get the flag value - auto* value = flag->Attribute("value"); - - if (value == nullptr) { - LOG("Failed to parse NPC flag value"); - continue; - } - - npcData.m_Flags[name] = std::stoi(value); - } - } - - // Get the zones - for (auto* zone = npc->FirstChildElement("zone"); zone != nullptr; zone = zone->NextSiblingElement("zone")) { - // Get the zone ID - auto* zoneID = zone->Attribute("id"); - - if (zoneID == nullptr) { - LOG("Failed to parse NPC zone ID"); + if (lot == nullptr) { + LOG("Failed to parse object lot"); continue; } + // Get the equipment + auto* equipment = object->FirstChildElement("equipment"); + std::vector inventory; + + if (equipment) { + auto* text = equipment->GetText(); + + if (text != nullptr) { + std::string equipmentString(text); + + std::vector splitEquipment = GeneralUtils::SplitString(equipmentString, ','); + + for (auto& item : splitEquipment) { + inventory.push_back(std::stoi(item)); + } + } + } + + + // Get the phrases + auto* phrases = object->FirstChildElement("phrases"); + + std::vector phraseList = {}; + + if (phrases) { + for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr; + phrase = phrase->NextSiblingElement("phrase")) { + // Get the phrase + auto* text = phrase->GetText(); + if (text == nullptr) { + LOG("Failed to parse NPC phrase"); + continue; + } + phraseList.push_back(text); + } + } + + // Get the script + auto* scriptElement = object->FirstChildElement("script"); + + std::string scriptName = ""; + + if (scriptElement != nullptr) { + auto* scriptNameAttribute = scriptElement->Attribute("name"); + if (scriptNameAttribute) scriptName = scriptNameAttribute; + } + + auto* configElement = object->FirstChildElement("config"); + std::vector keys = {}; + + std::vector config = {}; + if(configElement) { + for (auto* key = configElement->FirstChildElement("key"); key != nullptr; + key = key->NextSiblingElement("key")) { + // Get the config data + auto* data = key->Attribute("data"); + if (!data) continue; + + LDFBaseData* configData = LDFBaseData::DataFromString(data); + keys.push_back(configData->GetKey()); + config.push_back(configData); + } + } + if (!keys.empty()) config.push_back(new LDFData>(u"syncLDF", keys)); + + VanityObject objectData; + objectData.m_Name = name; + objectData.m_LOT = std::stoi(lot); + objectData.m_Equipment = inventory; + objectData.m_Phrases = phraseList; + objectData.m_Script = scriptName; + objectData.m_Config = config; + // Get the locations - auto* locations = zone->FirstChildElement("locations"); + auto* locations = object->FirstChildElement("locations"); if (locations == nullptr) { LOG("Failed to parse NPC locations"); @@ -443,7 +281,9 @@ void VanityUtilities::ParseXML(const std::string& file) { for (auto* location = locations->FirstChildElement("location"); location != nullptr; location = location->NextSiblingElement("location")) { + // Get the location data + auto* zoneID = location->Attribute("zone"); auto* x = location->Attribute("x"); auto* y = location->Attribute("y"); auto* z = location->Attribute("z"); @@ -452,41 +292,52 @@ void VanityUtilities::ParseXML(const std::string& file) { auto* ry = location->Attribute("ry"); auto* rz = location->Attribute("rz"); - if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr + if (zoneID == nullptr || x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr || rz == nullptr) { LOG("Failed to parse NPC location data"); continue; } - VanityNPCLocation locationData; + VanityObjectLocation locationData; locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) }; locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) }; locationData.m_Chance = 1.0f; - if (location->Attribute("chance") != nullptr) { + if (location->Attribute("chance")) { locationData.m_Chance = std::stof(location->Attribute("chance")); } - const auto& it = npcData.m_Locations.find(std::stoi(zoneID)); + if (location->Attribute("scale")) { + locationData.m_Scale = std::stof(location->Attribute("scale")); + } - if (it != npcData.m_Locations.end()) { + + const auto& it = objectData.m_Locations.find(std::stoi(zoneID)); + + if (it != objectData.m_Locations.end()) { it->second.push_back(locationData); } else { - std::vector locations; + std::vector locations; locations.push_back(locationData); - npcData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations)); + objectData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations)); + } + + if (!(std::find(keys.begin(), keys.end(), u"teleport") != keys.end())) { + m_Objects.push_back(objectData); + objectData.m_Locations.clear(); } } + if (std::find(keys.begin(), keys.end(), u"teleport") != keys.end()) { + m_Objects.push_back(objectData); + } } - - m_NPCs.push_back(npcData); } } -VanityNPC* VanityUtilities::GetNPC(const std::string& name) { - for (size_t i = 0; i < m_NPCs.size(); i++) { - if (m_NPCs[i].m_Name == name) { - return &m_NPCs[i]; +VanityObject* VanityUtilities::GetObject(const std::string& name) { + for (size_t i = 0; i < m_Objects.size(); i++) { + if (m_Objects[i].m_Name == name) { + return &m_Objects[i]; } } @@ -498,10 +349,13 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) { // Read the file into a string std::ifstream t(file); - + std::stringstream output; // If the file does not exist, return an empty string. if (!t.good()) { - return ""; + output << "File "; + output << file.substr(file.rfind("/") + 1); + output << " not found!\nContact your DarkflameServer admin\nor find the server source at https://github.com/DarkflameUniverse/DarkflameServer"; + return output.str(); } std::stringstream buffer; @@ -511,7 +365,6 @@ std::string VanityUtilities::ParseMarkdown(const std::string& file) { // Loop through all lines in the file. // Replace all instances of the markdown syntax with the corresponding HTML. // Only care about headers - std::stringstream output; std::string line; std::stringstream ss; ss << fileContents; diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index cff73bce..49bd23ab 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -3,15 +3,17 @@ #include "dCommonVars.h" #include "Entity.h" #include +#include -struct VanityNPCLocation +struct VanityObjectLocation { float m_Chance = 1.0f; NiPoint3 m_Position; NiQuaternion m_Rotation; + float m_Scale = 1.0f; }; -struct VanityNPC +struct VanityObject { LWOOBJID m_ID = LWOOBJID_EMPTY; std::string m_Name; @@ -19,37 +21,24 @@ struct VanityNPC std::vector m_Equipment; std::vector m_Phrases; std::string m_Script; - std::map m_Flags; - std::map> m_Locations; - std::vector ldf; + std::map> m_Locations; + std::vector m_Config; }; -struct VanityParty -{ - uint32_t m_Zone; - float m_Chance = 1.0f; - std::vector m_Locations; -}; class VanityUtilities { public: static void SpawnVanity(); - static Entity* SpawnNPC( - LOT lot, - const std::string& name, - const NiPoint3& position, - const NiQuaternion& rotation, - const std::vector& inventory, - const std::vector& ldf + static Entity* SpawnObject( + const VanityObject& object, + const VanityObjectLocation& location ); static LWOOBJID SpawnSpawner( - LOT lot, - const NiPoint3& position, - const NiQuaternion& rotation, - const std::vector& ldf + const VanityObject& object, + const VanityObjectLocation& location ); static std::string ParseMarkdown( @@ -60,16 +49,14 @@ public: const std::string& file ); - static VanityNPC* GetNPC(const std::string& name); + static VanityObject* GetObject(const std::string& name); private: static void SetupNPCTalk(Entity* npc); static void NPCTalk(Entity* npc); - static std::vector m_NPCs; - - static std::vector m_Parties; - - static std::vector m_PartyPhrases; + static std::vector m_Objects; + + static std::set m_LoadedFiles; }; diff --git a/dScripts/02_server/DLU/CMakeLists.txt b/dScripts/02_server/DLU/CMakeLists.txt index 64d4cbbd..fb257d3e 100644 --- a/dScripts/02_server/DLU/CMakeLists.txt +++ b/dScripts/02_server/DLU/CMakeLists.txt @@ -1,3 +1,3 @@ set(DSCRIPTS_SOURCES_02_SERVER_DLU - "DLUVanityNPC.cpp" + "DLUVanityTeleportingObject.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/DLU/DLUVanityNPC.cpp b/dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp similarity index 51% rename from dScripts/02_server/DLU/DLUVanityNPC.cpp rename to dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp index ba2c6604..8aff1995 100644 --- a/dScripts/02_server/DLU/DLUVanityNPC.cpp +++ b/dScripts/02_server/DLU/DLUVanityTeleportingObject.cpp @@ -1,22 +1,22 @@ -#include "DLUVanityNPC.h" +#include "DLUVanityTeleportingObject.h" #include "GameMessages.h" #include "dServer.h" #include "VanityUtilities.h" #include "RenderComponent.h" -void DLUVanityNPC::OnStartup(Entity* self) { - m_NPC = VanityUtilities::GetNPC("averysumner - Destroyer of Worlds"); +void DLUVanityTeleportingObject::OnStartup(Entity* self) { + if (!self->HasVar(u"npcName") || !self->HasVar(u"teleport")) return; + m_Object = VanityUtilities::GetObject(self->GetVarAsString(u"npcName")); - if (m_NPC == nullptr) { - return; - } + if (!m_Object) return; + if (self->HasVar(u"teleportInterval")) m_TeleportInterval = self->GetVar(u"teleportInterval"); if (self->GetVar(u"teleport")) { - self->AddTimer("setupTeleport", 15.0f); + self->AddTimer("setupTeleport", m_TeleportInterval); } } -void DLUVanityNPC::OnTimerDone(Entity* self, std::string timerName) { +void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) { if (timerName == "setupTeleport") { RenderComponent::PlayAnimation(self, u"interact"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam"); @@ -28,20 +28,22 @@ void DLUVanityNPC::OnTimerDone(Entity* self, std::string timerName) { GameMessages::SendStopFXEffect(self, true, "teleportBeam"); GameMessages::SendStopFXEffect(self, true, "teleportRings"); } else if (timerName == "teleport") { - std::vector& locations = m_NPC->m_Locations[Game::server->GetZoneID()]; + std::vector& locations = m_Object->m_Locations[Game::server->GetZoneID()]; selectLocation: - VanityNPCLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; + VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; + // try to get not the same position, but if we get the same one twice, it's fine if (self->GetPosition() == newLocation.m_Position) { - goto selectLocation; // cry about it + VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber(0, locations.size() - 1)]; } self->SetPosition(newLocation.m_Position); self->SetRotation(newLocation.m_Rotation); + self->SetScale(newLocation.m_Scale); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings"); self->AddTimer("stopFX", 2.0f); - self->AddTimer("setupTeleport", 15.0f); + self->AddTimer("setupTeleport", m_TeleportInterval); } } diff --git a/dScripts/02_server/DLU/DLUVanityNPC.h b/dScripts/02_server/DLU/DLUVanityTeleportingObject.h similarity index 54% rename from dScripts/02_server/DLU/DLUVanityNPC.h rename to dScripts/02_server/DLU/DLUVanityTeleportingObject.h index aeb8e051..a13ba901 100644 --- a/dScripts/02_server/DLU/DLUVanityNPC.h +++ b/dScripts/02_server/DLU/DLUVanityTeleportingObject.h @@ -1,13 +1,14 @@ #pragma once #include "CppScripts.h" -class VanityNPC; -class DLUVanityNPC : public CppScripts::Script +class VanityObject; +class DLUVanityTeleportingObject : public CppScripts::Script { public: void OnStartup(Entity* self) override; void OnTimerDone(Entity* self, std::string timerName) override; private: - VanityNPC* m_NPC; + VanityObject* m_Object; + float m_TeleportInterval = 15.0f; }; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 071bd7a3..7cb853e6 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -216,7 +216,7 @@ #include "NtNaomiBreadcrumbServer.h" // DLU Scripts -#include "DLUVanityNPC.h" +#include "DLUVanityTeleportingObject.h" // AM Scripts #include "AmConsoleTeleportServer.h" @@ -834,8 +834,8 @@ CppScripts::Script* CppScripts::GetScript(Entity* parent, const std::string& scr script = new NjNyaMissionitems(); //DLU: - else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityNPC.lua") - script = new DLUVanityNPC(); + else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua") + script = new DLUVanityTeleportingObject(); // Survival minigame else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua") diff --git a/vanity/atm.xml b/vanity/atm.xml new file mode 100644 index 00000000..96ed1a2b --- /dev/null +++ b/vanity/atm.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vanity/NPC.xml b/vanity/dev-tribute.xml similarity index 65% rename from vanity/NPC.xml rename to vanity/dev-tribute.xml index 2311ab46..d20e31a6 100644 --- a/vanity/NPC.xml +++ b/vanity/dev-tribute.xml @@ -1,5 +1,5 @@ - - + + 6802, 2519, 2623, 14806 Sorry for the mess. @@ -11,39 +11,33 @@ Everything is awesome! I hope my behaviors are behaving themselves. - - - - - - - + + + + + 12947, 12949, 12962, 12963 I hope quickbulds are still working! Be careful crossing the gap! Have The Maelstrom stopped going invisible? - - - - - - - + + + + + 9950, 9944, 14102, 14092 Hello Explorer! It's great to see you made it! Have you heard about Darkflame? I've traveled across this entire system, but nothing beats the view here. - - - - - - - + + + + + cmerw[acowipaejio;fawjioefasdl;kfjm; @@ -51,20 +45,18 @@ zxnpoasdfiopwemsadf'kawpfo[ekasdf;'s *teleports behind you* -