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>
This commit is contained in:
HailStorm32
2025-10-05 20:09:43 -07:00
committed by GitHub
parent 5791c55a9e
commit 5d5bce53d0
7 changed files with 112 additions and 22 deletions

View File

@@ -19,6 +19,7 @@
#include "dPlatforms.h"
#include "Game.h"
#include "Logger.h"
#include "DluAssert.h"
#include <glm/ext/vector_float3.hpp>
@@ -305,7 +306,7 @@ namespace GeneralUtils {
template<typename Container>
inline Container::value_type GetRandomElement(const Container& container) {
DluAssert(!container.empty());
return container[GenerateRandomNumber<typename Container::value_type>(0, container.size() - 1)];
return container[GenerateRandomNumber<typename Container::size_type>(0, container.size() - 1)];
}
/**

View File

@@ -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);

View File

@@ -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<SystemAddress, User*> m_Users;
@@ -50,6 +55,11 @@ private:
std::vector<std::string> m_MiddleNames;
std::vector<std::string> m_LastNames;
std::vector<std::string> 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

View File

@@ -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,10 +556,20 @@ 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();
// 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<int32_t>(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 = "";
@@ -566,6 +579,7 @@ void PetComponent::RequestSetPetName(std::u16string name) {
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;
}
// 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";
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast<int32_t>(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);

View File

@@ -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,

View File

@@ -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);

View File

@@ -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