From 4a5dd68e87241ae531575cddd8701b43da6dc141 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:36:32 -0700 Subject: [PATCH] fix: hardcore mode fixes (#1882) fixes hardcore modes uscore drops adds config option for excluded item drops --- dCommon/dConfig.cpp | 5 ++++ dCommon/dConfig.h | 5 ++++ dGame/EntityManager.cpp | 30 +++++++++++++++------- dGame/EntityManager.h | 3 +++ dGame/dComponents/DestroyableComponent.cpp | 25 ++++++++---------- resources/worldconfig.ini | 3 +++ 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 72a07d5b..7ebfad25 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -47,6 +47,7 @@ void dConfig::LoadConfig() { void dConfig::ReloadConfig() { this->m_ConfigValues.clear(); LoadConfig(); + for (const auto& handler : m_ConfigHandlers) handler(); LogSettings(); } @@ -59,6 +60,10 @@ const std::string& dConfig::GetValue(std::string key) { return this->m_ConfigValues[key]; } +void dConfig::AddConfigHandler(std::function handler) { + m_ConfigHandlers.push_back(handler); +} + void dConfig::LogSettings() const { LOG("Configuration settings:"); for (const auto& [key, value] : m_ConfigValues) { diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 59923b78..36c94148 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,5 +1,7 @@ #pragma once + #include +#include #include #include @@ -30,11 +32,14 @@ public: */ void ReloadConfig(); + // Adds a function to be called when the config is (re)loaded + void AddConfigHandler(std::function handler); void LogSettings() const; private: void ProcessLine(const std::string& line); std::map m_ConfigValues; + std::vector> m_ConfigHandlers; std::string m_ConfigFilePath; }; diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 762529f3..f7faa5f1 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -52,6 +52,25 @@ std::vector EntityManager::m_GhostingExcludedLOTs = { 4967 }; +void EntityManager::ReloadConfig() { + auto hcmode = Game::config->GetValue("hardcore_mode"); + m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1"); + auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent"); + m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent); + auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); + m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult); + auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death"); + m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1"); + auto hcExcludedItemDrops = Game::config->GetValue("hardcore_excluded_item_drops"); + m_HardcoreExcludedItemDrops.clear(); + for (const auto& strLot : GeneralUtils::SplitString(hcExcludedItemDrops, ',')) { + const auto lot = GeneralUtils::TryParse(strLot); + if (lot) { + m_HardcoreExcludedItemDrops.insert(lot.value()); + } + } +} + void EntityManager::Initialize() { // Check if this zone has ghosting enabled m_GhostingEnabled = std::find( @@ -61,15 +80,8 @@ void EntityManager::Initialize() { ) == m_GhostingExcludedZones.end(); // grab hardcore mode settings and load them with sane defaults - auto hcmode = Game::config->GetValue("hardcore_mode"); - m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1"); - auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent"); - m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent); - auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); - m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult); - auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death"); - m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1"); - + Game::config->AddConfigHandler([]() {Game::entityManager->ReloadConfig();}); + Game::entityManager->ReloadConfig(); // If cloneID is not zero, then hardcore mode is disabled // aka minigames and props if (Game::zoneManager->GetZoneID().GetCloneID() != 0) m_HardcoreMode = false; diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 3ea97676..066ebb66 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -75,11 +75,13 @@ public: const uint32_t GetHardcoreLoseUscoreOnDeathPercent() { return m_HardcoreLoseUscoreOnDeathPercent; }; const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; }; const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; }; + const std::set& GetHardcoreExcludedItemDrops() { return m_HardcoreExcludedItemDrops; }; // Messaging bool SendMessage(GameMessages::GameMsg& msg) const; private: + void ReloadConfig(); void SerializeEntities(); void KillEntities(); void DeleteEntities(); @@ -112,6 +114,7 @@ private: uint32_t m_HardcoreLoseUscoreOnDeathPercent; bool m_HardcoreDropinventoryOnDeath; uint32_t m_HardcoreUscoreEnemiesMultiplier; + std::set m_HardcoreExcludedItemDrops; }; #endif // ENTITYMANAGER_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 39d91045..18319a3e 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -37,6 +37,7 @@ #include "eMissionTaskType.h" #include "eStateChangeType.h" #include "eGameActivity.h" +#include #include "CDComponentsRegistryTable.h" @@ -981,7 +982,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto* character = m_Parent->GetComponent(); auto uscore = character->GetUScore(); - auto uscoreToLose = uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100); + auto uscoreToLose = static_cast(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f)); + LOG("Player %llu has lost %llu uscore!", m_Parent->GetObjectID(), uscoreToLose); character->SetUScore(uscore - uscoreToLose); GameMessages::SendModifyLEGOScore(m_Parent, m_Parent->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION); @@ -995,13 +997,11 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { if (items) { auto itemMap = items->GetItems(); if (!itemMap.empty()) { - for (const auto& item : itemMap) { - //drop the item: - if (!item.second) continue; - // don't drop the thinkng cap - if (item.second->GetLot() == 6086) continue; - GameMessages::SendDropClientLoot(m_Parent, source, item.second->GetLot(), 0, m_Parent->GetPosition(), item.second->GetCount()); - item.second->SetCount(0, false, false); + for (const auto item : itemMap | std::views::values) { + // Don't drop excluded items or null ones + if (!item || Game::entityManager->GetHardcoreExcludedItemDrops().contains(item->GetLot())) continue; + GameMessages::SendDropClientLoot(m_Parent, source, item->GetLot(), 0, m_Parent->GetPosition(), item->GetCount()); + item->SetCount(0, false, false); } Game::entityManager->SerializeEntity(m_Parent); } @@ -1020,11 +1020,6 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //drop all coins: GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coins, m_Parent->GetPosition()); } - - // Reload the player since we can't normally reduce uscore from the server and we want the UI to update - // do this last so we don't get killed.... again - Game::entityManager->DestructEntity(m_Parent); - Game::entityManager->ConstructEntity(m_Parent); return; } @@ -1032,12 +1027,12 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto* player = Game::entityManager->GetEntity(source); if (player && player->IsPlayer()) { auto* playerStats = player->GetComponent(); - if (playerStats) { + if (playerStats && GetMaxHealth() > 0) { //get the maximum health from this enemy: auto maxHealth = GetMaxHealth(); int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); - + LOG("Rewarding player %llu with %i uscore for killing enemy %i", player->GetObjectID(), uscore, m_Parent->GetLOT()); playerStats->SetUScore(playerStats->GetUScore() + uscore); GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION); diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 1e73ceea..3579be89 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -80,3 +80,6 @@ cdclient_mismatch_message=We detected that your client is out of date. Please up # Auto reject properties which contain no models | must be 1 in order to auto reject. auto_reject_empty_properties=0 + +# comma delimited list of items to not drop in hardcore mode +hardcore_excluded_item_drops=6086,7044