From 5d5bce53d0173ea625ce62757b26ec825a91ab36 Mon Sep 17 00:00:00 2001 From: HailStorm32 Date: Sun, 5 Oct 2025 20:09:43 -0700 Subject: [PATCH] feat: Add configurable restrictions for muted accounts (#1887) * Add configurable restrictions for muted accounts * switched to and updated GetRandomElement * Update config option check * implement cached config values for mute settings and update handlers * Address review * Update dGame/dComponents/PetComponent.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dComponents/PetComponent.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * reduce if argument chain --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dCommon/GeneralUtils.h | 5 +-- dGame/UserManager.cpp | 49 ++++++++++++++++++++++++++-- dGame/UserManager.h | 10 ++++++ dGame/dComponents/PetComponent.cpp | 44 +++++++++++++++++++------ dGame/dGameMessages/GameMessages.cpp | 7 ++-- dGame/dUtilities/Mail.cpp | 13 +++++--- resources/sharedconfig.ini | 6 ++++ 7 files changed, 112 insertions(+), 22 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index b5915739..828fcee3 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -3,7 +3,7 @@ // C++ #include #include -#include +#include #include #include #include @@ -19,6 +19,7 @@ #include "dPlatforms.h" #include "Game.h" #include "Logger.h" +#include "DluAssert.h" #include @@ -305,7 +306,7 @@ namespace GeneralUtils { template inline Container::value_type GetRandomElement(const Container& container) { DluAssert(!container.empty()); - return container[GenerateRandomNumber(0, container.size() - 1)]; + return container[GenerateRandomNumber(0, container.size() - 1)]; } /** diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 343ce28d..15fbc2d6 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -30,6 +30,7 @@ #include "BitStreamUtils.h" #include "CheatDetection.h" #include "CharacterComponent.h" +#include "dConfig.h" #include "eCharacterVersion.h" UserManager* UserManager::m_Address = nullptr; @@ -92,6 +93,23 @@ void UserManager::Initialize() { StripCR(line); m_PreapprovedNames.push_back(line); } + + // Initialize cached config values and register a handler to update them on config reload + // This avoids repeated lookups into dConfig at runtime. + if (Game::config) { + m_MuteAutoRejectNames = (Game::config->GetValue("mute_auto_reject_names") == "1"); + m_MuteRestrictTrade = (Game::config->GetValue("mute_restrict_trade") == "1"); + m_MuteRestrictMail = (Game::config->GetValue("mute_restrict_mail") == "1"); + + Game::config->AddConfigHandler([this]() { + this->m_MuteAutoRejectNames = (Game::config->GetValue("mute_auto_reject_names") == "1"); + this->m_MuteRestrictTrade = (Game::config->GetValue("mute_restrict_trade") == "1"); + this->m_MuteRestrictMail = (Game::config->GetValue("mute_restrict_mail") == "1"); + }); + } + else { + LOG("Warning: dConfig not initialized before UserManager. Cached config values will not be available."); + } } UserManager::~UserManager() { @@ -301,7 +319,9 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) inStream.Read(eyes); inStream.Read(mouth); - const auto name = LUWStringName.GetAsString(); + const bool autoRejectNames = this->GetMuteAutoRejectNames() && u->GetIsMuted(); + + const auto name = autoRejectNames ? "" : LUWStringName.GetAsString(); std::string predefinedName = GetPredefinedName(firstNameIndex, middleNameIndex, lastNameIndex); LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle); @@ -319,6 +339,10 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) return; } + if (autoRejectNames) { + LOG("AccountID: %i is muted, forcing use of predefined name", u->GetAccountID()); + } + if (name.empty()) { LOG("AccountID: %i is creating a character with predefined name: %s", u->GetAccountID(), predefinedName.c_str()); } else { @@ -369,6 +393,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) //Check to see if our name was pre-approved: bool nameOk = IsNamePreapproved(name); + if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; // If predefined name is invalid, change it to be their object id @@ -448,9 +473,10 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) LUWString LUWStringName; inStream.Read(LUWStringName); - const auto newName = LUWStringName.GetAsString(); + auto newName = LUWStringName.GetAsString(); Character* character = nullptr; + const bool autoRejectNames = this->GetMuteAutoRejectNames() && u->GetIsMuted(); //Check if this user has this character: bool ownsCharacter = CheatDetection::VerifyLwoobjidIsSender( @@ -471,13 +497,30 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) if (!ownsCharacter || !character) { WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::UNKNOWN_ERROR); } else if (ownsCharacter && character) { + if (autoRejectNames) { + // Create a random preapproved name (fallback to default if none available) + if (!m_FirstNames.empty() && !m_MiddleNames.empty() && !m_LastNames.empty()) { + std::string firstName = GeneralUtils::GetRandomElement(m_FirstNames); + std::string middleName = GeneralUtils::GetRandomElement(m_MiddleNames); + std::string lastName = GeneralUtils::GetRandomElement(m_LastNames); + newName = firstName + middleName + lastName; + } else { + newName = "character" + std::to_string(objectID); + } + } + if (newName == character->GetName()) { WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::NAME_UNAVAILABLE); return; } if (!Database::Get()->GetCharacterInfo(newName)) { - if (IsNamePreapproved(newName)) { + if (autoRejectNames) { + Database::Get()->SetCharacterName(objectID, newName); + LOG("Character %s auto-renamed to preapproved name %s due to mute", character->GetName().c_str(), newName.c_str()); + WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); + UserManager::RequestCharacterList(sysAddr); + } else if (IsNamePreapproved(newName)) { Database::Get()->SetCharacterName(objectID, newName); LOG("Character %s now known as %s", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); diff --git a/dGame/UserManager.h b/dGame/UserManager.h index 9b4af26e..0244807e 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -41,6 +41,11 @@ public: size_t GetUserCount() const { return m_Users.size(); } + // Access cached config values + bool GetMuteAutoRejectNames() const { return m_MuteAutoRejectNames; } + bool GetMuteRestrictTrade() const { return m_MuteRestrictTrade; } + bool GetMuteRestrictMail() const { return m_MuteRestrictMail; } + private: static UserManager* m_Address; //Singleton std::map m_Users; @@ -50,6 +55,11 @@ private: std::vector m_MiddleNames; std::vector m_LastNames; std::vector m_PreapprovedNames; + + // Cached config values that can change on config reload + bool m_MuteAutoRejectNames = false; + bool m_MuteRestrictTrade = false; + bool m_MuteRestrictMail = false; }; #endif // USERMANAGER_H diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 06b7f21c..6fb5eedf 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -10,9 +10,11 @@ #include "InventoryComponent.h" #include "Item.h" #include "MissionComponent.h" +#include "User.h" #include "SwitchComponent.h" #include "DestroyableComponent.h" #include "dpWorld.h" +#include "UserManager.h" #include "PetDigServer.h" #include "ObjectIDManager.h" #include "eUnequippableActiveType.h" @@ -21,6 +23,7 @@ #include "eUseItemResponse.h" #include "ePlayerFlag.h" +#include "GeneralUtils.h" #include "Game.h" #include "dConfig.h" #include "dChatFilter.h" @@ -553,18 +556,29 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { } void PetComponent::RequestSetPetName(std::u16string name) { + const bool autoRejectNames = UserManager::Instance()->GetMuteAutoRejectNames(); + if (m_Tamer == LWOOBJID_EMPTY) { if (m_Owner != LWOOBJID_EMPTY) { auto* owner = GetOwner(); - m_ModerationStatus = 1; // Pending - m_Name = ""; + // If auto reject names is on, and the user is muted, force use of predefined name + if (autoRejectNames && owner && owner->GetCharacter() && owner->GetCharacter()->GetParentUser()->GetIsMuted()) { + m_ModerationStatus = 2; // Approved + std::string forcedName = "Pet"; + Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast(m_ModerationStatus) }); + GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); + GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + } else { + m_ModerationStatus = 1; // Pending + m_Name = ""; - //Save our pet's new name to the db: - SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + //Save our pet's new name to the db: + SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); + GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + } } return; @@ -586,11 +600,21 @@ void PetComponent::RequestSetPetName(std::u16string name) { return; } - m_ModerationStatus = 1; // Pending - m_Name = ""; + // If auto reject names is on, and the user is muted, force use of predefined name ELSE proceed with normal name check + if (autoRejectNames && tamer->GetCharacter() && tamer->GetCharacter()->GetParentUser()->GetIsMuted()) { + m_ModerationStatus = 2; // Approved + m_Name = ""; + std::string forcedName = "Pet"; - //Save our pet's new name to the db: - SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast(m_ModerationStatus) }); + LOG("AccountID: %i is muted, forcing use of predefined pet name", tamer->GetCharacter()->GetParentUser()->GetAccountID()); + } else { + m_ModerationStatus = 1; // Pending + m_Name = ""; + + //Save our pet's new name to the db: + SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + } Game::entityManager->SerializeEntity(m_Parent); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c7108e50..47262629 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3243,12 +3243,13 @@ void GameMessages::SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, cons void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { // Check if the player has restricted trade access auto* character = entity->GetCharacter(); + const bool restrictTradeOnMute = UserManager::Instance()->GetMuteRestrictTrade(); - if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) { + if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) { // Send a message to the player ChatPackets::SendSystemMessage( sysAddr, - u"This character has restricted trade access." + u"Your character has restricted trade access." ); return; @@ -3264,7 +3265,7 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* if (invitee != nullptr && invitee->IsPlayer()) { character = invitee->GetCharacter(); - if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) { + if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) { // Send a message to the player ChatPackets::SendSystemMessage( sysAddr, diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 5ab82f61..5f1377e7 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -9,6 +9,7 @@ #include "GeneralUtils.h" #include "Database.h" #include "Game.h" +#include "dConfig.h" #include "dServer.h" #include "Entity.h" #include "Character.h" @@ -28,6 +29,7 @@ #include "ServiceType.h" #include "User.h" #include "StringifiedEnum.h" +#include "UserManager.h" namespace { const std::string DefaultSender = "%[MAIL_SYSTEM_NOTIFICATION]"; @@ -72,7 +74,10 @@ namespace Mail { void SendRequest::Handle() { SendResponse response; auto* character = player->GetCharacter(); - if (character && !(character->HasPermission(ePermissionMap::RestrictedMailAccess) || character->GetParentUser()->GetIsMuted())) { + const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted(); + const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess); + + if (character && !(restrictedMailAccess || restrictMailOnMute)) { mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), ""); auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient); @@ -83,7 +88,7 @@ namespace Mail { } else { uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee; uint32_t stackSize = 0; - + auto inventoryComponent = player->GetComponent(); Item* item = nullptr; @@ -123,7 +128,7 @@ namespace Mail { Database::Get()->InsertNewMail(mailInfo); response.status = eSendResponse::Success; - character->SaveXMLToDatabase(); + character->SaveXMLToDatabase(); } else { response.status = eSendResponse::AttachmentNotFound; } @@ -165,7 +170,7 @@ namespace Mail { void DataResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(this->throttled); - + bitStream.Write(this->playerMail.size()); bitStream.Write(0); // packing for (const auto& mail : this->playerMail) { diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 2e004eae..ace0ef55 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -74,3 +74,9 @@ database_type=sqlite # Skips the account creation check in master. Used for non-interactive setups. skip_account_creation=0 +# 0 or 1, restrict mail while account is muted +mute_restrict_mail=1 +# 0 or 1, restrict trade while account is muted +mute_restrict_trade=1 +# 0 or 1, automatically reject character and pet names submitted while muted +mute_auto_reject_names=1