mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-10-30 20:22:04 +00:00 
			
		
		
		
	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:
		| @@ -3,7 +3,7 @@ | ||||
| // C++ | ||||
| #include <charconv> | ||||
| #include <cstdint> | ||||
| #include <cmath>  | ||||
| #include <cmath> | ||||
| #include <ctime> | ||||
| #include <functional> | ||||
| #include <optional> | ||||
| @@ -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)]; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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<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 = ""; | ||||
|  | ||||
| 			//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<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); | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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<InventoryComponent>(); | ||||
| 				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<uint16_t>(this->playerMail.size()); | ||||
| 		bitStream.Write<uint16_t>(0); // packing | ||||
| 		for (const auto& mail : this->playerMail) { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 HailStorm32
					HailStorm32