From 3dc7b6ef7f61fa3fc34bc8762f5dee69503f4a44 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 9 Nov 2023 18:33:39 -0600 Subject: [PATCH 01/10] add spawner handeling and reload to deleted exsiting entities spawned don't get deleted yet --- dGame/dUtilities/VanityUtilities.cpp | 119 +++++++++++++++++++++++++-- dGame/dUtilities/VanityUtilities.h | 9 ++ 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 95436cd3..ad3f22b0 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -15,6 +15,9 @@ #include "Logger.h" #include "BinaryPathFinder.h" #include "EntityInfo.h" +#include "Spawner.h" +#include "dZoneManager.h" +#include "../dWorldServer/ObjectIDManager.h" #include @@ -28,6 +31,27 @@ void VanityUtilities::SpawnVanity() { } const uint32_t zoneID = Game::server->GetZoneID(); + if (!m_NPCs.empty()){ + for (auto& npc : m_NPCs) { + if (npc.m_ID == LWOOBJID_EMPTY) continue; + if (npc.m_LOT == 176){ + auto* spawner = Game::zoneManager->GetSpawner(npc.m_ID); + if (!spawner) continue; + Game::zoneManager->RemoveSpawner(spawner->m_Info.spawnerID); + } + auto* entity = Game::entityManager->GetEntity(npc.m_ID); + if (!entity) continue; + entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); + } + m_NPCs.clear(); + } + + if (!m_Parties.empty()){ + m_Parties.clear(); + } + if (!m_PartyPhrases.empty()){ + m_PartyPhrases.clear(); + } ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); @@ -53,7 +77,7 @@ void VanityUtilities::SpawnVanity() { // Loop through all locations for (const auto& location : party.m_Locations) { - rate = GeneralUtils::GenerateRandomNumber(0, 1); + rate = GeneralUtils::GenerateRandomNumber(0, 1); if (0.75f < rate) { continue; } @@ -66,7 +90,9 @@ void VanityUtilities::SpawnVanity() { } auto& npc = npcList[npcIndex]; - + // Skip spawners + if (npc.m_LOT == 176) continue; + taken.push_back(npcIndex); // Spawn the NPC @@ -85,7 +111,6 @@ void VanityUtilities::SpawnVanity() { SetupNPCTalk(npcEntity); } } - return; } @@ -111,9 +136,15 @@ void VanityUtilities::SpawnVanity() { new LDFData(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") }; } - + if (npc.m_LOT == 176){ + auto* spawner = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); + if (!spawner) continue; + npc.m_ID = spawner->m_Info.spawnerID; + } // Spawn the NPC - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf); + auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf, npc.m_ID); + if (!npcEntity) continue; + npc.m_ID = npcEntity->GetObjectID(); if (!npc.m_Phrases.empty()){ npcEntity->SetVar>(u"chats", npc.m_Phrases); @@ -149,13 +180,85 @@ void VanityUtilities::SpawnVanity() { } } -Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, - const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf) { +Spawner* VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& ldf){ + SpawnerInfo spawnInfo = SpawnerInfo(); + SpawnerNode* node = new SpawnerNode(); + spawnInfo.templateID = lot; + spawnInfo.spawnerID = ObjectIDManager::Instance()->GenerateObjectID(); + spawnInfo.templateScale = 1.0; + node->position = position; + node->rotation = rotation; + node->config = ldf; + spawnInfo.nodes.push_back(node); + for (LDFBaseData* data : node->config) { + if (data) { + if (data->GetKey() == u"spawntemplate") { + spawnInfo.templateID = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_node_id") { + node->nodeID = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_name") { + spawnInfo.name = data->GetValueAsString(); + } + + if (data->GetKey() == u"max_to_spawn") { + spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_active_on_load") { + spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"active_on_load") { + spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"respawn") { + if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds + { + spawnInfo.respawnTime = std::stof(data->GetValueAsString()); + } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? + { + spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; + } + } + if (data->GetKey() == u"spawnsGroupOnSmash") { + spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString()); + } + if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") { + spawnInfo.spawnOnSmashGroupName = data->GetValueAsString(); + } + if (data->GetKey() == u"groupID") { // Load object groups + std::string groupStr = data->GetValueAsString(); + spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';'); + spawnInfo.groups.erase(spawnInfo.groups.end() - 1); + } + if (data->GetKey() == u"no_auto_spawn") { + spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); + } + if (data->GetKey() == u"no_timed_spawn") { + spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); + } + if (data->GetKey() == u"spawnActivator") { + spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); + } + } + } + auto* spawner = new Spawner(spawnInfo); + Game::zoneManager->AddSpawner(spawnInfo.spawnerID, spawner); + return spawner; +} + +Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf, const LWOOBJID ID) { EntityInfo info; info.lot = lot; info.pos = position; info.rot = rotation; - info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); + if (ID == LWOOBJID_EMPTY) info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); + else info.spawnerID = ID; info.settings = ldf; auto* entity = Game::entityManager->CreateEntity(info); diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index 0cca0aba..5f639a93 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -13,6 +13,7 @@ struct VanityNPCLocation struct VanityNPC { + LWOOBJID m_ID = LWOOBJID_EMPTY; std::string m_Name; LOT m_LOT; std::vector m_Equipment; @@ -41,6 +42,14 @@ public: const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, + const std::vector& ldf, + const LWOOBJID ID = LWOOBJID_EMPTY + ); + + static Spawner* SpawnSpawner( + LOT lot, + const NiPoint3& position, + const NiQuaternion& rotation, const std::vector& ldf ); From 6e0779802388d7d8457eb988ae99029dc386a252 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 9 Nov 2023 18:44:51 -0600 Subject: [PATCH 02/10] try to kill spawners, they don't exist? --- dGame/dUtilities/VanityUtilities.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index ad3f22b0..b5f376c7 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -35,9 +35,8 @@ void VanityUtilities::SpawnVanity() { for (auto& npc : m_NPCs) { if (npc.m_ID == LWOOBJID_EMPTY) continue; if (npc.m_LOT == 176){ - auto* spawner = Game::zoneManager->GetSpawner(npc.m_ID); - if (!spawner) continue; - Game::zoneManager->RemoveSpawner(spawner->m_Info.spawnerID); + LOG("Removing spawner %llu", npc.m_ID); + Game::zoneManager->RemoveSpawner(npc.m_ID); } auto* entity = Game::entityManager->GetEntity(npc.m_ID); if (!entity) continue; From 486713613309d1faae440e0bcc39a324edafb005 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 9 Nov 2023 18:59:43 -0600 Subject: [PATCH 03/10] fix spawners and cleanup --- dGame/dUtilities/VanityUtilities.cpp | 43 ++++++++++++++-------------- dGame/dUtilities/VanityUtilities.h | 3 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index b5f376c7..e047eb83 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -37,10 +37,11 @@ void VanityUtilities::SpawnVanity() { if (npc.m_LOT == 176){ LOG("Removing spawner %llu", npc.m_ID); Game::zoneManager->RemoveSpawner(npc.m_ID); - } - auto* entity = Game::entityManager->GetEntity(npc.m_ID); - if (!entity) continue; - entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); + } else{ + auto* entity = Game::entityManager->GetEntity(npc.m_ID); + if (!entity) continue; + entity->Smash(LWOOBJID_EMPTY, eKillType::VIOLENT); + } } m_NPCs.clear(); } @@ -139,25 +140,26 @@ void VanityUtilities::SpawnVanity() { auto* spawner = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); if (!spawner) continue; npc.m_ID = spawner->m_Info.spawnerID; - } - // Spawn the NPC - auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, npc.ldf, npc.m_ID); - if (!npcEntity) continue; - npc.m_ID = npcEntity->GetObjectID(); - if (!npc.m_Phrases.empty()){ - npcEntity->SetVar>(u"chats", npc.m_Phrases); + } 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(); + auto* scriptComponent = npcEntity->GetComponent(); - if (scriptComponent && !npc.m_Script.empty()) { - scriptComponent->SetScript(npc.m_Script); - scriptComponent->SetSerialized(false); + 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); + for (const auto& npc : npc.m_Flags) { + npcEntity->SetVar(GeneralUtils::ASCIIToUTF16(npc.first), npc.second); + } } + SetupNPCTalk(npcEntity); } - SetupNPCTalk(npcEntity); } } @@ -251,13 +253,12 @@ Spawner* VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const return spawner; } -Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf, const LWOOBJID ID) { +Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, const std::vector& ldf) { EntityInfo info; info.lot = lot; info.pos = position; info.rot = rotation; - if (ID == LWOOBJID_EMPTY) info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - else info.spawnerID = ID; + info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); info.settings = ldf; auto* entity = Game::entityManager->CreateEntity(info); diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index 5f639a93..97738c8d 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -42,8 +42,7 @@ public: const NiPoint3& position, const NiQuaternion& rotation, const std::vector& inventory, - const std::vector& ldf, - const LWOOBJID ID = LWOOBJID_EMPTY + const std::vector& ldf ); static Spawner* SpawnSpawner( From f727e3951c63bcd178d30a8d7696be1704269054 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 9 Nov 2023 21:28:52 -0600 Subject: [PATCH 04/10] consolidate code --- dGame/dUtilities/VanityUtilities.cpp | 101 +++++-------------- dGame/dUtilities/VanityUtilities.h | 2 +- dZoneManager/Level.cpp | 139 ++++++++++++++------------- dZoneManager/Level.h | 2 + 4 files changed, 97 insertions(+), 147 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index e047eb83..d2941620 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -18,6 +18,7 @@ #include "Spawner.h" #include "dZoneManager.h" #include "../dWorldServer/ObjectIDManager.h" +#include "Level.h" #include @@ -95,7 +96,6 @@ void VanityUtilities::SpawnVanity() { taken.push_back(npcIndex); - // Spawn the NPC LOG("ldf size is %i", npc.ldf.size()); if (npc.ldf.empty()) { npc.ldf = { @@ -105,10 +105,14 @@ void VanityUtilities::SpawnVanity() { } // Spawn the NPC - 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); + 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; @@ -137,9 +141,7 @@ void VanityUtilities::SpawnVanity() { }; } if (npc.m_LOT == 176){ - auto* spawner = SpawnSpawner(npc.m_LOT, location.m_Position, location.m_Rotation, npc.ldf); - if (!spawner) continue; - npc.m_ID = spawner->m_Info.spawnerID; + 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); @@ -181,76 +183,19 @@ void VanityUtilities::SpawnVanity() { } } -Spawner* VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& ldf){ - SpawnerInfo spawnInfo = SpawnerInfo(); - SpawnerNode* node = new SpawnerNode(); - spawnInfo.templateID = lot; - spawnInfo.spawnerID = ObjectIDManager::Instance()->GenerateObjectID(); - spawnInfo.templateScale = 1.0; - node->position = position; - node->rotation = rotation; - node->config = ldf; - spawnInfo.nodes.push_back(node); - for (LDFBaseData* data : node->config) { - if (data) { - if (data->GetKey() == u"spawntemplate") { - spawnInfo.templateID = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_node_id") { - node->nodeID = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_name") { - spawnInfo.name = data->GetValueAsString(); - } - - if (data->GetKey() == u"max_to_spawn") { - spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"respawn") { - if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds - { - spawnInfo.respawnTime = std::stof(data->GetValueAsString()); - } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? - { - spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; - } - } - if (data->GetKey() == u"spawnsGroupOnSmash") { - spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString()); - } - if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") { - spawnInfo.spawnOnSmashGroupName = data->GetValueAsString(); - } - if (data->GetKey() == u"groupID") { // Load object groups - std::string groupStr = data->GetValueAsString(); - spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';'); - spawnInfo.groups.erase(spawnInfo.groups.end() - 1); - } - if (data->GetKey() == u"no_auto_spawn") { - spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); - } - if (data->GetKey() == u"no_timed_spawn") { - spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); - } - if (data->GetKey() == u"spawnActivator") { - spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); - } - } - } - auto* spawner = new Spawner(spawnInfo); - Game::zoneManager->AddSpawner(spawnInfo.spawnerID, spawner); - return spawner; +LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const NiQuaternion& rotation, const std::vector& ldf){ + SceneObject obj; + obj.lot = lot; + // guratantee we have no collisions + do { + obj.id = ObjectIDManager::Instance()->GenerateObjectID(); + } while(Game::zoneManager->GetSpawner(obj.id)); + obj.position = position; + obj.rotation = rotation; + obj.settings = ldf; + Level::MakeSpawner(obj); + LOG("created spawner %llu", obj.id); + 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) { diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index 97738c8d..cff73bce 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -45,7 +45,7 @@ public: const std::vector& ldf ); - static Spawner* SpawnSpawner( + static LWOOBJID SpawnSpawner( LOT lot, const NiPoint3& position, const NiQuaternion& rotation, diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 953b11e2..4c83d494 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -38,6 +38,76 @@ Level::~Level() { } } +void Level::MakeSpawner(SceneObject obj){ + SpawnerInfo spawnInfo = SpawnerInfo(); + SpawnerNode* node = new SpawnerNode(); + spawnInfo.templateID = obj.lot; + spawnInfo.spawnerID = obj.id; + spawnInfo.templateScale = obj.scale; + node->position = obj.position; + node->rotation = obj.rotation; + node->config = obj.settings; + spawnInfo.nodes.push_back(node); + for (LDFBaseData* data : obj.settings) { + if (data) { + if (data->GetKey() == u"spawntemplate") { + spawnInfo.templateID = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_node_id") { + node->nodeID = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_name") { + spawnInfo.name = data->GetValueAsString(); + } + + if (data->GetKey() == u"max_to_spawn") { + spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"spawner_active_on_load") { + spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"active_on_load") { + spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + } + + if (data->GetKey() == u"respawn") { + if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds + { + spawnInfo.respawnTime = std::stof(data->GetValueAsString()); + } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? + { + spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; + } + } + if (data->GetKey() == u"spawnsGroupOnSmash") { + spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString()); + } + if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") { + spawnInfo.spawnOnSmashGroupName = data->GetValueAsString(); + } + if (data->GetKey() == u"groupID") { // Load object groups + std::string groupStr = data->GetValueAsString(); + spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';'); + spawnInfo.groups.erase(spawnInfo.groups.end() - 1); + } + if (data->GetKey() == u"no_auto_spawn") { + spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); + } + if (data->GetKey() == u"no_timed_spawn") { + spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); + } + if (data->GetKey() == u"spawnActivator") { + spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); + } + } + } + Game::zoneManager->MakeSpawner(spawnInfo); +} + const void Level::PrintAllObjects() { for (std::map::iterator it = m_ChunkHeaders.begin(); it != m_ChunkHeaders.end(); ++it) { if (it->second.id == Level::ChunkTypeID::SceneObjectData) { @@ -230,74 +300,7 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { } if (obj.lot == 176) { //Spawner - SpawnerInfo spawnInfo = SpawnerInfo(); - SpawnerNode* node = new SpawnerNode(); - spawnInfo.templateID = obj.lot; - spawnInfo.spawnerID = obj.id; - spawnInfo.templateScale = obj.scale; - node->position = obj.position; - node->rotation = obj.rotation; - node->config = obj.settings; - spawnInfo.nodes.push_back(node); - for (LDFBaseData* data : obj.settings) { - if (data) { - if (data->GetKey() == u"spawntemplate") { - spawnInfo.templateID = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_node_id") { - node->nodeID = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_name") { - spawnInfo.name = data->GetValueAsString(); - } - - if (data->GetKey() == u"max_to_spawn") { - spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"spawner_active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); - } - - if (data->GetKey() == u"respawn") { - if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds - { - spawnInfo.respawnTime = std::stof(data->GetValueAsString()); - } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? - { - spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; - } - } - if (data->GetKey() == u"spawnsGroupOnSmash") { - spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString()); - } - if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") { - spawnInfo.spawnOnSmashGroupName = data->GetValueAsString(); - } - if (data->GetKey() == u"groupID") { // Load object groups - std::string groupStr = data->GetValueAsString(); - spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';'); - spawnInfo.groups.erase(spawnInfo.groups.end() - 1); - } - if (data->GetKey() == u"no_auto_spawn") { - spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); - } - if (data->GetKey() == u"no_timed_spawn") { - spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); - } - if (data->GetKey() == u"spawnActivator") { - spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); - } - } - } - Spawner* spawner = new Spawner(spawnInfo); - Game::zoneManager->AddSpawner(obj.id, spawner); + MakeSpawner(obj); } else { //Regular object EntityInfo info; info.spawnerID = 0; diff --git a/dZoneManager/Level.h b/dZoneManager/Level.h index 0f8fa72d..a27326a2 100644 --- a/dZoneManager/Level.h +++ b/dZoneManager/Level.h @@ -53,6 +53,8 @@ public: public: Level(Zone* parentZone, const std::string& filepath); ~Level(); + + static void MakeSpawner(SceneObject obj); const void PrintAllObjects(); From 4c3949e5d87614cf5132a77e7d92211087c8b5fb Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Thu, 9 Nov 2023 21:29:29 -0600 Subject: [PATCH 05/10] remove logs --- dGame/dUtilities/VanityUtilities.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index d2941620..10e8c489 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -36,7 +36,6 @@ void VanityUtilities::SpawnVanity() { for (auto& npc : m_NPCs) { if (npc.m_ID == LWOOBJID_EMPTY) continue; if (npc.m_LOT == 176){ - LOG("Removing spawner %llu", npc.m_ID); Game::zoneManager->RemoveSpawner(npc.m_ID); } else{ auto* entity = Game::entityManager->GetEntity(npc.m_ID); @@ -194,7 +193,6 @@ LWOOBJID VanityUtilities::SpawnSpawner(LOT lot, const NiPoint3& position, const obj.rotation = rotation; obj.settings = ldf; Level::MakeSpawner(obj); - LOG("created spawner %llu", obj.id); return obj.id; } From 175b354e68ce723ac877e20e65a47ca54c105f83 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre Date: Fri, 10 Nov 2023 22:01:25 -0600 Subject: [PATCH 06/10] address feedback --- dGame/dUtilities/VanityUtilities.cpp | 31 ++++++++++++---------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 10e8c489..6c008524 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -32,27 +32,22 @@ void VanityUtilities::SpawnVanity() { } const uint32_t zoneID = Game::server->GetZoneID(); - if (!m_NPCs.empty()){ - for (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); - } + + 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(); - } - - if (!m_Parties.empty()){ - m_Parties.clear(); - } - if (!m_PartyPhrases.empty()){ - m_PartyPhrases.clear(); } + m_NPCs.clear(); + m_Parties.clear(); + m_PartyPhrases.clear(); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); // Loop through all parties From 411dce7457fcede6ad2439a44e7f924cfc34fb0f Mon Sep 17 00:00:00 2001 From: jadebenn Date: Sun, 12 Nov 2023 05:53:03 -0600 Subject: [PATCH 07/10] Adding damage cooldown/"invincibility frames" as in Live (#1276) * Added cooldown handling * Made most of the logs hidden outside of debug mode * removed weird submodule * kill this phantom submodule * updated to reflect reviewed feedback * Added IsCooldownImmune() method to DestroyableComponent * friggin typo * Implemented non-pending changes and added cooldown immunity functions to DestroyableComponentTests * add trailing linebreak * another typo :( * flipped cooldown test order (not leaving immune) * Clean up comment and add DestroyableComponent test --- dGame/dBehaviors/BasicAttackBehavior.cpp | 26 +++++++++- dGame/dBehaviors/BasicAttackBehavior.h | 14 +++--- dGame/dComponents/DestroyableComponent.cpp | 47 ++++++++++++------- dGame/dComponents/DestroyableComponent.h | 36 ++++++++++---- dGame/dUtilities/SlashCommandHandler.cpp | 32 ++++++++++--- docs/Commands.md | 1 + .../DestroyableComponentTests.cpp | 19 ++++++++ 7 files changed, 132 insertions(+), 43 deletions(-) diff --git a/dGame/dBehaviors/BasicAttackBehavior.cpp b/dGame/dBehaviors/BasicAttackBehavior.cpp index 2bd6ce41..36615a9f 100644 --- a/dGame/dBehaviors/BasicAttackBehavior.cpp +++ b/dGame/dBehaviors/BasicAttackBehavior.cpp @@ -3,6 +3,8 @@ #include "Game.h" #include "Logger.h" #include "EntityManager.h" +#include "dZoneManager.h" +#include "WorldConfig.h" #include "DestroyableComponent.h" #include "BehaviorContext.h" #include "eBasicAttackSuccessTypes.h" @@ -13,8 +15,15 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr) { - PlayFx(u"onhit", entity->GetObjectID()); + PlayFx(u"onhit", entity->GetObjectID()); //This damage animation doesn't seem to play consistently destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID); + + //Handle player damage cooldown + if (entity->IsPlayer() && !this->m_DontApplyImmune) { + const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime; + destroyableComponent->SetDamageCooldownTimer(immunityTime); + LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime); + } } this->m_OnSuccess->Handle(context, bitStream, branch); @@ -72,6 +81,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit } if (isImmune) { + LOG_DEBUG("Target targetEntity %llu is immune!", branch.target); this->m_OnFailImmune->Handle(context, bitStream, branch); return; } @@ -178,11 +188,15 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet return; } - const bool isImmune = destroyableComponent->IsImmune(); + const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime; + LOG_DEBUG("Damage cooldown timer currently %f s", destroyableComponent->GetDamageCooldownTimer()); + + const bool isImmune = (destroyableComponent->IsImmune()) || (destroyableComponent->IsCooldownImmune()); bitStream->Write(isImmune); if (isImmune) { + LOG_DEBUG("Target targetEntity %llu is immune!", branch.target); this->m_OnFailImmune->Calculate(context, bitStream, branch); return; } @@ -203,6 +217,12 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet bitStream->Write(isSuccess); + //Handle player damage cooldown + if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) { + destroyableComponent->SetDamageCooldownTimer(immunityTime); + LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime); + } + eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE; if (isSuccess) { if (healthDamageDealt >= 1) { @@ -236,6 +256,8 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet } void BasicAttackBehavior::Load() { + this->m_DontApplyImmune = GetBoolean("dont_apply_immune"); + this->m_MinDamage = GetInt("min damage"); if (this->m_MinDamage == 0) this->m_MinDamage = 1; diff --git a/dGame/dBehaviors/BasicAttackBehavior.h b/dGame/dBehaviors/BasicAttackBehavior.h index f6e3fa28..6525c343 100644 --- a/dGame/dBehaviors/BasicAttackBehavior.h +++ b/dGame/dBehaviors/BasicAttackBehavior.h @@ -10,14 +10,14 @@ public: /** * @brief Reads a 16bit short from the bitStream and when the actual behavior handling finishes with all of its branches, the bitStream * is then offset to after the allocated bits for this stream. - * + * */ void DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch); /** * @brief Handles a client initialized Basic Attack Behavior cast to be deserialized and verified on the server. - * - * @param context The Skill's Behavior context. All behaviors in the same tree share the same context + * + * @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param bitStream The bitStream to deserialize. BitStreams will always check their bounds before reading in a behavior * and will fail gracefully if an overread is detected. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. @@ -27,13 +27,13 @@ public: /** * @brief Writes a 16bit short to the bitStream and when the actual behavior calculation finishes with all of its branches, the number * of bits used is then written to where the 16bit short initially was. - * + * */ void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; /** * @brief Calculates a server initialized Basic Attack Behavior cast to be serialized to the client - * + * * @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param bitStream The bitStream to serialize to. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. @@ -44,10 +44,12 @@ public: * @brief Loads this Behaviors parameters from the database. For this behavior specifically: * max and min damage will always be the same. If min is less than max, they are both set to max. * If an action is not in the database, then no action is taken for that result. - * + * */ void Load() override; private: + bool m_DontApplyImmune; + uint32_t m_MinDamage; uint32_t m_MaxDamage; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 1cf1da40..e98bc33b 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -73,6 +73,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { m_ImmuneToQuickbuildInterruptCount = 0; m_ImmuneToPullToPointCount = 0; m_DeathBehavior = -1; + + m_DamageCooldownTimer = 0.0f; } DestroyableComponent::~DestroyableComponent() { @@ -179,6 +181,10 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn } } +void DestroyableComponent::Update(float deltaTime) { + m_DamageCooldownTimer -= deltaTime; +} + void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { @@ -409,7 +415,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore } bool DestroyableComponent::IsEnemy(const Entity* other) const { - if (m_Parent->IsPlayer() && other->IsPlayer()){ + if (m_Parent->IsPlayer() && other->IsPlayer()) { auto* thisCharacterComponent = m_Parent->GetComponent(); if (!thisCharacterComponent) return false; auto* otherCharacterComponent = other->GetComponent(); @@ -464,6 +470,10 @@ bool DestroyableComponent::IsImmune() const { return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0; } +bool DestroyableComponent::IsCooldownImmune() const { + return m_DamageCooldownTimer > 0.0f; +} + bool DestroyableComponent::IsKnockbackImmune() const { auto* characterComponent = m_Parent->GetComponent(); auto* inventoryComponent = m_Parent->GetComponent(); @@ -546,7 +556,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 return; } - if (IsImmune()) { + if (IsImmune() || IsCooldownImmune()) { + LOG_DEBUG("Target targetEntity %llu is immune!", m_Parent->GetObjectID()); //Immune is succesfully proc'd return; } @@ -634,9 +645,9 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 } //check if hardcore mode is enabled - if (Game::entityManager->GetHardcoreMode()) { + if (Game::entityManager->GetHardcoreMode()) { DoHardcoreModeDrops(source); - } + } Smash(source, eKillType::VIOLENT, u"", skillID); } @@ -796,16 +807,16 @@ void DestroyableComponent::SetFaction(int32_t factionID, bool ignoreChecks) { } void DestroyableComponent::SetStatusImmunity( - const eStateChangeType state, - const bool bImmuneToBasicAttack, - const bool bImmuneToDamageOverTime, - const bool bImmuneToKnockback, - const bool bImmuneToInterrupt, - const bool bImmuneToSpeed, - const bool bImmuneToImaginationGain, - const bool bImmuneToImaginationLoss, - const bool bImmuneToQuickbuildInterrupt, - const bool bImmuneToPullToPoint) { + const eStateChangeType state, + const bool bImmuneToBasicAttack, + const bool bImmuneToDamageOverTime, + const bool bImmuneToKnockback, + const bool bImmuneToInterrupt, + const bool bImmuneToSpeed, + const bool bImmuneToImaginationGain, + const bool bImmuneToImaginationLoss, + const bool bImmuneToQuickbuildInterrupt, + const bool bImmuneToPullToPoint) { if (state == eStateChangeType::POP) { if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1; @@ -818,7 +829,7 @@ void DestroyableComponent::SetStatusImmunity( if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1; if (bImmuneToPullToPoint && m_ImmuneToPullToPointCount > 0) m_ImmuneToPullToPointCount -= 1; - } else if (state == eStateChangeType::PUSH){ + } else if (state == eStateChangeType::PUSH) { if (bImmuneToBasicAttack) m_ImmuneToBasicAttackCount += 1; if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1; if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1; @@ -945,7 +956,7 @@ void DestroyableComponent::AddOnHitCallback(const std::function& m_OnHitCallbacks.push_back(callback); } -void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){ +void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //check if this is a player: if (m_Parent->IsPlayer()) { //remove hardcore_lose_uscore_on_death_percent from the player's uscore: @@ -963,9 +974,9 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){ if (inventory) { //get the items inventory: auto items = inventory->GetInventory(eInventoryType::ITEMS); - if (items){ + if (items) { auto itemMap = items->GetItems(); - if (!itemMap.empty()){ + if (!itemMap.empty()) { for (const auto& item : itemMap) { //drop the item: if (!item.second) continue; diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 52d4be5a..b81ab9f3 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -24,6 +24,7 @@ public: DestroyableComponent(Entity* parentEntity); ~DestroyableComponent() override; + void Update(float deltaTime) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; void LoadFromXml(tinyxml2::XMLDocument* doc) override; void UpdateXml(tinyxml2::XMLDocument* doc) override; @@ -166,6 +167,11 @@ public: */ bool IsImmune() const; + /** + * @return whether this entity is currently immune to attacks due to a damage cooldown period + */ + bool IsCooldownImmune() const; + /** * Sets if this entity has GM immunity, making it not killable * @param value the GM immunity of this entity @@ -406,18 +412,23 @@ public: ); // Getters for status immunities - const bool GetImmuneToBasicAttack() {return m_ImmuneToBasicAttackCount > 0;}; - const bool GetImmuneToDamageOverTime() {return m_ImmuneToDamageOverTimeCount > 0;}; - const bool GetImmuneToKnockback() {return m_ImmuneToKnockbackCount > 0;}; - const bool GetImmuneToInterrupt() {return m_ImmuneToInterruptCount > 0;}; - const bool GetImmuneToSpeed() {return m_ImmuneToSpeedCount > 0;}; - const bool GetImmuneToImaginationGain() {return m_ImmuneToImaginationGainCount > 0;}; - const bool GetImmuneToImaginationLoss() {return m_ImmuneToImaginationLossCount > 0;}; - const bool GetImmuneToQuickbuildInterrupt() {return m_ImmuneToQuickbuildInterruptCount > 0;}; - const bool GetImmuneToPullToPoint() {return m_ImmuneToPullToPointCount > 0;}; + const bool GetImmuneToBasicAttack() { return m_ImmuneToBasicAttackCount > 0; }; + const bool GetImmuneToDamageOverTime() { return m_ImmuneToDamageOverTimeCount > 0; }; + const bool GetImmuneToKnockback() { return m_ImmuneToKnockbackCount > 0; }; + const bool GetImmuneToInterrupt() { return m_ImmuneToInterruptCount > 0; }; + const bool GetImmuneToSpeed() { return m_ImmuneToSpeedCount > 0; }; + const bool GetImmuneToImaginationGain() { return m_ImmuneToImaginationGainCount > 0; }; + const bool GetImmuneToImaginationLoss() { return m_ImmuneToImaginationLossCount > 0; }; + const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 0; }; + const bool GetImmuneToPullToPoint() { return m_ImmuneToPullToPointCount > 0; }; - int32_t GetDeathBehavior() const { return m_DeathBehavior; } + // Damage cooldown setters/getters + void SetDamageCooldownTimer(float value) { m_DamageCooldownTimer = value; } + float GetDamageCooldownTimer() { return m_DamageCooldownTimer; } + + // Death behavior setters/getters void SetDeathBehavior(int32_t value) { m_DeathBehavior = value; } + int32_t GetDeathBehavior() const { return m_DeathBehavior; } /** * Utility to reset all stats to the default stats based on items and completed missions @@ -605,6 +616,11 @@ private: * Death behavior type. If 0, the client plays a death animation as opposed to a smash animation. */ int32_t m_DeathBehavior; + + /** + * Damage immunity cooldown timer. Set to a value that then counts down to create a damage cooldown for players + */ + float m_DamageCooldownTimer; }; #endif // DESTROYABLECOMPONENT_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index af44d543..47037071 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -247,7 +247,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); { AMFArrayValue args; @@ -1490,6 +1490,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + //Testing basic attack immunity + if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + auto* destroyableComponent = entity->GetComponent(); + + int32_t state = false; + + if (!GeneralUtils::TryParse(args[0], state)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent != nullptr) { + destroyableComponent->SetIsImmune(state); + } + + return; + } + if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { auto* buffComponent = entity->GetComponent(); @@ -1843,7 +1861,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { auto* skillComponent = entity->GetComponent(); - if (skillComponent){ + if (skillComponent) { uint32_t skillId; if (!GeneralUtils::TryParse(args[0], skillId)) { @@ -1860,7 +1878,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit uint32_t skillId; int slot; auto* inventoryComponent = entity->GetComponent(); - if (inventoryComponent){ + if (inventoryComponent) { if (!GeneralUtils::TryParse(args[0], slot)) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); return; @@ -1869,7 +1887,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); return; } else { - if(inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); + if (inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); } } @@ -1878,7 +1896,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { auto* destroyableComponent = entity->GetComponent(); - if (destroyableComponent){ + if (destroyableComponent) { int32_t faction; if (!GeneralUtils::TryParse(args[0], faction)) { @@ -1893,7 +1911,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { auto* destroyableComponent = entity->GetComponent(); - if (destroyableComponent){ + if (destroyableComponent) { int32_t faction; if (!GeneralUtils::TryParse(args[0], faction)) { @@ -1908,7 +1926,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { auto* destroyableComponent = entity->GetComponent(); - if (destroyableComponent){ + if (destroyableComponent) { ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); for (const auto entry : destroyableComponent->GetFactionIDs()) { ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); diff --git a/docs/Commands.md b/docs/Commands.md index ea81ff56..71a3da43 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -25,6 +25,7 @@ |ban|`/ban `|Bans a user from the server.|4| |approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5| |mute|`/mute (days) (hours)`|Mute player for the given amount of time. If no time is given, the mute is indefinite.|6| +|attackimmune|`/attackimmune `|Sets the character's immunity to basic attacks state, where value can be one of "1", to make yourself immune to basic attack damage, or "0" to undo.|8| |gmimmune|`/gmimmunve `|Sets the character's GMImmune state, where value can be one of "1", to make yourself immune to damage, or "0" to undo.|8| |gminvis|`/gminvis`|Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8.|8| |setname|`/setname `|Sets a temporary name for your player. The name resets when you log out.|8| diff --git a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp index acb90352..ff37f154 100644 --- a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp @@ -536,3 +536,22 @@ TEST_F(DestroyableTest, DestroyableComponentImmunityTest) { } +/** + * Test the Damage cooldown timer of DestroyableComponent + */ +TEST_F(DestroyableTest, DestroyableComponentDamageCooldownTest) { + // Test the damage immune timer state (anything above 0.0f) + destroyableComponent->SetDamageCooldownTimer(1.0f); + EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 1.0f); + ASSERT_TRUE(destroyableComponent->IsCooldownImmune()); + + // Test that the Update() function correctly decrements the damage cooldown timer + destroyableComponent->Update(0.5f); + EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.5f); + ASSERT_TRUE(destroyableComponent->IsCooldownImmune()); + + // Test the non damage immune timer state (anything below or equal to 0.0f) + destroyableComponent->SetDamageCooldownTimer(0.0f); + EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.0f); + ASSERT_FALSE(destroyableComponent->IsCooldownImmune()); +} From 68e5737b744bb5f781655d0ad0bbc5b91f1707e2 Mon Sep 17 00:00:00 2001 From: jadebenn Date: Mon, 13 Nov 2023 04:41:27 -0600 Subject: [PATCH 08/10] Fix: Pets can no longer dig treasure without completing Bella Pepper's "Lost Tags" mission (#1287) * Added mission check to pet digs * Little formatting change * More formatting clean-up --- dGame/dComponents/PetComponent.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index b82427c1..df473055 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -29,6 +29,7 @@ #include "RenderComponent.h" #include "eObjectBits.h" #include "eGameMasterLevel.h" +#include "eMissionState.h" std::unordered_map PetComponent::buildCache{}; std::unordered_map PetComponent::currentActivities{}; @@ -439,9 +440,15 @@ void PetComponent::Update(float deltaTime) { } } + auto* missionComponent = owner->GetComponent(); + if (!missionComponent) return; + + // Determine if the "Lost Tags" mission has been completed and digging has been unlocked + const bool digUnlocked = missionComponent->GetMissionState(842) == eMissionState::COMPLETE; + Entity* closestTresure = PetDigServer::GetClosestTresure(position); - if (closestTresure != nullptr) { + if (closestTresure != nullptr && digUnlocked) { // Skeleton Dragon Pat special case for bone digging if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) { goto skipTresure; From 23c5d1315173dab0e86ebc2672c24c0f49b1de8d Mon Sep 17 00:00:00 2001 From: David Markowitz Date: Mon, 13 Nov 2023 22:51:45 -0800 Subject: [PATCH 09/10] Models: Fix leak --- dGame/dInventory/Item.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 3b12f8b3..a585e60e 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -433,7 +433,7 @@ void Item::DisassembleModel() { return; } - auto* doc = new tinyxml2::XMLDocument(); + std::unique_ptr doc(new tinyxml2::XMLDocument()); if (!doc) { return; From 8a9883c224da968d94c8dcea793a9d5f7743a805 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 14 Nov 2023 07:02:17 -0600 Subject: [PATCH 10/10] feat: use more zoneTable options (#1273) * feat: use more zoneTable options Allow setting framrate for the zone Allow setting if pets are allowed in the zone Allow setting if mounts are allowed in a zone Allow disabling saving location to a zone * address feedback --- dDatabase/Tables/CDZoneTableTable.cpp | 6 ++--- dDatabase/Tables/CDZoneTableTable.h | 6 ++--- dGame/Character.cpp | 2 +- .../ControllablePhysicsComponent.cpp | 2 +- dGame/dInventory/Item.cpp | 21 ++++++++++----- dWorldServer/PerformanceManager.cpp | 26 ++++++++++++++++--- dZoneManager/dZoneManager.cpp | 3 +++ dZoneManager/dZoneManager.h | 8 +++++- 8 files changed, 56 insertions(+), 18 deletions(-) diff --git a/dDatabase/Tables/CDZoneTableTable.cpp b/dDatabase/Tables/CDZoneTableTable.cpp index 1030593e..2793ccb9 100644 --- a/dDatabase/Tables/CDZoneTableTable.cpp +++ b/dDatabase/Tables/CDZoneTableTable.cpp @@ -31,7 +31,7 @@ void CDZoneTableTable::LoadValuesFromDatabase() { entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f); UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", "")); UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", "")); - UNUSED(entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "")); + entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", ""); entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1); entry.widthInChunks = tableData.getIntField("widthInChunks", -1); entry.heightInChunks = tableData.getIntField("heightInChunks", -1); @@ -40,10 +40,10 @@ void CDZoneTableTable::LoadValuesFromDatabase() { entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f); UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", "")); entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false; - UNUSED(entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false); + entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false; entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f); UNUSED(entry.gate_version = tableData.getStringField("gate_version", "")); - UNUSED(entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false); + entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false; this->m_Entries.insert(std::make_pair(entry.zoneID, entry)); tableData.nextRow(); diff --git a/dDatabase/Tables/CDZoneTableTable.h b/dDatabase/Tables/CDZoneTableTable.h index fef8096f..c3f51aa6 100644 --- a/dDatabase/Tables/CDZoneTableTable.h +++ b/dDatabase/Tables/CDZoneTableTable.h @@ -18,7 +18,7 @@ struct CDZoneTable { float smashableMaxDistance; //!< The maximum smashable distance? UNUSED(std::string mixerProgram); //!< ??? UNUSED(std::string clientPhysicsFramerate); //!< The client physics framerate - UNUSED(std::string serverPhysicsFramerate); //!< The server physics framerate + std::string serverPhysicsFramerate; //!< The server physics framerate unsigned int zoneControlTemplate; //!< The Zone Control template unsigned int widthInChunks; //!< The width of the world in chunks unsigned int heightInChunks; //!< The height of the world in chunks @@ -27,10 +27,10 @@ struct CDZoneTable { float fZoneWeight; //!< ??? UNUSED(std::string thumbnail); //!< The thumbnail of the world bool PlayerLoseCoinsOnDeath; //!< Whether or not the user loses coins on death - UNUSED(bool disableSaveLoc); //!< Disables the saving location? + bool disableSaveLoc; //!< Disables the saving location? float teamRadius; //!< ??? UNUSED(std::string gate_version); //!< The gate version - UNUSED(bool mountsAllowed); //!< Whether or not mounts are allowed + bool mountsAllowed; //!< Whether or not mounts are allowed }; class CDZoneTableTable : public CDTable { diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 401af2d4..7259602f 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -314,7 +314,7 @@ void Character::SaveXMLToDatabase() { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); // lzid garbage, binary concat of zoneID, zoneInstance and zoneClone - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0) { + if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { uint64_t lzidConcat = zoneInfo.GetCloneID(); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID()); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 08281ed5..dd981f66 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -187,7 +187,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0) { + if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzz", m_Position.z); diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index a585e60e..6a0fd82b 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -19,6 +19,8 @@ #include "eObjectBits.h" #include "eReplicaComponentType.h" #include "eUseItemResponse.h" +#include "dZoneManager.h" +#include "ChatPackets.h" #include "CDBrickIDTableTable.h" #include "CDObjectSkillsTable.h" @@ -292,12 +294,19 @@ void Item::UseNonEquip(Item* item) { const auto type = static_cast(info->itemType); if (type == eItemType::MOUNT) { - playerInventoryComponent->HandlePossession(this); - // TODO Check if mounts are allowed to be spawned - } else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { - const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); - if (databasePet.lot != LOT_NULL) { - playerInventoryComponent->SpawnPet(this); + if (Game::zoneManager->GetMountsAllowed()){ + playerInventoryComponent->HandlePossession(this); + } else { + ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Mounts are not allowed in this zone"); + } + } else if (type == eItemType::PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY ) { + if (Game::zoneManager->GetPetsAllowed()){ + const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); + if (databasePet.lot != LOT_NULL) { + playerInventoryComponent->SpawnPet(this); + } + } else { + ChatPackets::SendSystemMessage(playerEntity->GetSystemAddress(), u"Pets are not allowed in this zone"); } // This precondition response is taken care of in SpawnPet(). } else { diff --git a/dWorldServer/PerformanceManager.cpp b/dWorldServer/PerformanceManager.cpp index 19f38d00..248be54a 100644 --- a/dWorldServer/PerformanceManager.cpp +++ b/dWorldServer/PerformanceManager.cpp @@ -1,5 +1,6 @@ #include "PerformanceManager.h" - +#include "CDZoneTableTable.h" +#include "CDClientManager.h" #include "UserManager.h" #define SOCIAL { lowFrameDelta } @@ -68,11 +69,30 @@ std::map PerformanceManager::m_Profiles = { }; void PerformanceManager::SelectProfile(LWOMAPID mapID) { - const auto pair = m_Profiles.find(mapID); + // Try to get it from zoneTable + CDZoneTableTable* zoneTable = CDClientManager::Instance().GetTable(); + if (zoneTable) { + const CDZoneTable* zone = zoneTable->Query(mapID); + if (zone) { + if (zone->serverPhysicsFramerate == "high"){ + m_CurrentProfile = { highFrameDelta }; + return; + } + if (zone->serverPhysicsFramerate == "medium"){ + m_CurrentProfile = { mediumFrameDelta }; + return; + } + if (zone->serverPhysicsFramerate == "low"){ + m_CurrentProfile = { lowFrameDelta }; + return; + } + } + } + // Fall back to hardcoded list and defaults + const auto pair = m_Profiles.find(mapID); if (pair == m_Profiles.end()) { m_CurrentProfile = m_DefaultProfile; - return; } diff --git a/dZoneManager/dZoneManager.cpp b/dZoneManager/dZoneManager.cpp index c87619cd..137a9cab 100644 --- a/dZoneManager/dZoneManager.cpp +++ b/dZoneManager/dZoneManager.cpp @@ -40,6 +40,9 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) { Game::entityManager->SetGhostDistanceMax(max + min); Game::entityManager->SetGhostDistanceMin(max); m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath; + m_DisableSaveLocation = zone->disableSaveLoc; + m_MountsAllowed = zone->mountsAllowed; + m_PetsAllowed = zone->petsAllowed; } } diff --git a/dZoneManager/dZoneManager.h b/dZoneManager/dZoneManager.h index 1e08b008..b5db3f6e 100644 --- a/dZoneManager/dZoneManager.h +++ b/dZoneManager/dZoneManager.h @@ -41,6 +41,9 @@ public: void Update(float deltaTime); Entity* GetZoneControlObject() { return m_ZoneControlObject; } bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; } + bool GetDisableSaveLocation() { return m_DisableSaveLocation; } + bool GetMountsAllowed() { return m_MountsAllowed; } + bool GetPetsAllowed() { return m_PetsAllowed; } uint32_t GetUniqueMissionIdStartingValue(); bool CheckIfAccessibleZone(LWOMAPID zoneID); @@ -58,7 +61,10 @@ private: Zone* m_pZone = nullptr; LWOZONEID m_ZoneID; - bool m_PlayerLoseCoinsOnDeath; //Do players drop coins in this zone when smashed + bool m_PlayerLoseCoinsOnDeath = false; + bool m_DisableSaveLocation = false; + bool m_MountsAllowed = true; + bool m_PetsAllowed = true; std::map m_Spawners; WorldConfig* m_WorldConfig = nullptr;