mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2024-11-24 06:27:24 +00:00
Add more modular saving of config data for items (#1591)
* stubbing for saving item extra data * add declaration to header * modularize loading for all possible extra data * move logic to Item * remove extra map
This commit is contained in:
parent
dc430d9758
commit
f0960d48b2
@ -31,22 +31,22 @@ public:
|
|||||||
|
|
||||||
virtual ~LDFBaseData() {}
|
virtual ~LDFBaseData() {}
|
||||||
|
|
||||||
virtual void WriteToPacket(RakNet::BitStream& packet) = 0;
|
virtual void WriteToPacket(RakNet::BitStream& packet) const = 0;
|
||||||
|
|
||||||
virtual const std::u16string& GetKey() = 0;
|
virtual const std::u16string& GetKey() const = 0;
|
||||||
|
|
||||||
virtual eLDFType GetValueType() = 0;
|
virtual eLDFType GetValueType() const = 0;
|
||||||
|
|
||||||
/** Gets a string from the key/value pair
|
/** Gets a string from the key/value pair
|
||||||
* @param includeKey Whether or not to include the key in the data
|
* @param includeKey Whether or not to include the key in the data
|
||||||
* @param includeTypeId Whether or not to include the type id in the data
|
* @param includeTypeId Whether or not to include the type id in the data
|
||||||
* @return The string representation of the data
|
* @return The string representation of the data
|
||||||
*/
|
*/
|
||||||
virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) = 0;
|
virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) const = 0;
|
||||||
|
|
||||||
virtual std::string GetValueAsString() = 0;
|
virtual std::string GetValueAsString() const = 0;
|
||||||
|
|
||||||
virtual LDFBaseData* Copy() = 0;
|
virtual LDFBaseData* Copy() const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an input string, return the data as a LDF key.
|
* Given an input string, return the data as a LDF key.
|
||||||
@ -62,7 +62,7 @@ private:
|
|||||||
T value;
|
T value;
|
||||||
|
|
||||||
//! Writes the key to the packet
|
//! Writes the key to the packet
|
||||||
void WriteKey(RakNet::BitStream& packet) {
|
void WriteKey(RakNet::BitStream& packet) const {
|
||||||
packet.Write<uint8_t>(this->key.length() * sizeof(uint16_t));
|
packet.Write<uint8_t>(this->key.length() * sizeof(uint16_t));
|
||||||
for (uint32_t i = 0; i < this->key.length(); ++i) {
|
for (uint32_t i = 0; i < this->key.length(); ++i) {
|
||||||
packet.Write<uint16_t>(this->key[i]);
|
packet.Write<uint16_t>(this->key[i]);
|
||||||
@ -70,7 +70,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
//! Writes the value to the packet
|
//! Writes the value to the packet
|
||||||
void WriteValue(RakNet::BitStream& packet) {
|
void WriteValue(RakNet::BitStream& packet) const {
|
||||||
packet.Write<uint8_t>(this->GetValueType());
|
packet.Write<uint8_t>(this->GetValueType());
|
||||||
packet.Write(this->value);
|
packet.Write(this->value);
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
\return The value
|
\return The value
|
||||||
*/
|
*/
|
||||||
const T& GetValue(void) { return this->value; }
|
const T& GetValue(void) const { return this->value; }
|
||||||
|
|
||||||
//! Sets the value
|
//! Sets the value
|
||||||
/*!
|
/*!
|
||||||
@ -102,13 +102,13 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
\return The value string
|
\return The value string
|
||||||
*/
|
*/
|
||||||
std::string GetValueString(void) { return ""; }
|
std::string GetValueString(void) const { return ""; }
|
||||||
|
|
||||||
//! Writes the data to a packet
|
//! Writes the data to a packet
|
||||||
/*!
|
/*!
|
||||||
\param packet The packet
|
\param packet The packet
|
||||||
*/
|
*/
|
||||||
void WriteToPacket(RakNet::BitStream& packet) override {
|
void WriteToPacket(RakNet::BitStream& packet) const override {
|
||||||
this->WriteKey(packet);
|
this->WriteKey(packet);
|
||||||
this->WriteValue(packet);
|
this->WriteValue(packet);
|
||||||
}
|
}
|
||||||
@ -117,13 +117,13 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
\return The key
|
\return The key
|
||||||
*/
|
*/
|
||||||
const std::u16string& GetKey(void) override { return this->key; }
|
const std::u16string& GetKey(void) const override { return this->key; }
|
||||||
|
|
||||||
//! Gets the LDF Type
|
//! Gets the LDF Type
|
||||||
/*!
|
/*!
|
||||||
\return The LDF value type
|
\return The LDF value type
|
||||||
*/
|
*/
|
||||||
eLDFType GetValueType(void) override { return LDF_TYPE_UNKNOWN; }
|
eLDFType GetValueType(void) const override { return LDF_TYPE_UNKNOWN; }
|
||||||
|
|
||||||
//! Gets the string data
|
//! Gets the string data
|
||||||
/*!
|
/*!
|
||||||
@ -131,7 +131,7 @@ public:
|
|||||||
\param includeTypeId Whether or not to include the type id in the data
|
\param includeTypeId Whether or not to include the type id in the data
|
||||||
\return The string representation of the data
|
\return The string representation of the data
|
||||||
*/
|
*/
|
||||||
std::string GetString(const bool includeKey = true, const bool includeTypeId = true) override {
|
std::string GetString(const bool includeKey = true, const bool includeTypeId = true) const override {
|
||||||
if (GetValueType() == -1) {
|
if (GetValueType() == -1) {
|
||||||
return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:<server variable>";
|
return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:<server variable>";
|
||||||
}
|
}
|
||||||
@ -154,11 +154,11 @@ public:
|
|||||||
return stream.str();
|
return stream.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetValueAsString() override {
|
std::string GetValueAsString() const override {
|
||||||
return this->GetValueString();
|
return this->GetValueString();
|
||||||
}
|
}
|
||||||
|
|
||||||
LDFBaseData* Copy() override {
|
LDFBaseData* Copy() const override {
|
||||||
return new LDFData<T>(key, value);
|
return new LDFData<T>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,19 +166,19 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// LDF Types
|
// LDF Types
|
||||||
template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) { return LDF_TYPE_UTF_16; };
|
template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) const { return LDF_TYPE_UTF_16; };
|
||||||
template<> inline eLDFType LDFData<int32_t>::GetValueType(void) { return LDF_TYPE_S32; };
|
template<> inline eLDFType LDFData<int32_t>::GetValueType(void) const { return LDF_TYPE_S32; };
|
||||||
template<> inline eLDFType LDFData<float>::GetValueType(void) { return LDF_TYPE_FLOAT; };
|
template<> inline eLDFType LDFData<float>::GetValueType(void) const { return LDF_TYPE_FLOAT; };
|
||||||
template<> inline eLDFType LDFData<double>::GetValueType(void) { return LDF_TYPE_DOUBLE; };
|
template<> inline eLDFType LDFData<double>::GetValueType(void) const { return LDF_TYPE_DOUBLE; };
|
||||||
template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) { return LDF_TYPE_U32; };
|
template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) const { return LDF_TYPE_U32; };
|
||||||
template<> inline eLDFType LDFData<bool>::GetValueType(void) { return LDF_TYPE_BOOLEAN; };
|
template<> inline eLDFType LDFData<bool>::GetValueType(void) const { return LDF_TYPE_BOOLEAN; };
|
||||||
template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) { return LDF_TYPE_U64; };
|
template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) const { return LDF_TYPE_U64; };
|
||||||
template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) { return LDF_TYPE_OBJID; };
|
template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) const { return LDF_TYPE_OBJID; };
|
||||||
template<> inline eLDFType LDFData<std::string>::GetValueType(void) { return LDF_TYPE_UTF_8; };
|
template<> inline eLDFType LDFData<std::string>::GetValueType(void) const { return LDF_TYPE_UTF_8; };
|
||||||
|
|
||||||
// The specialized version for std::u16string (UTF-16)
|
// The specialized version for std::u16string (UTF-16)
|
||||||
template<>
|
template<>
|
||||||
inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) {
|
inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) const {
|
||||||
packet.Write<uint8_t>(this->GetValueType());
|
packet.Write<uint8_t>(this->GetValueType());
|
||||||
|
|
||||||
packet.Write<uint32_t>(this->value.length());
|
packet.Write<uint32_t>(this->value.length());
|
||||||
@ -189,7 +189,7 @@ inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) {
|
|||||||
|
|
||||||
// The specialized version for bool
|
// The specialized version for bool
|
||||||
template<>
|
template<>
|
||||||
inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) {
|
inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) const {
|
||||||
packet.Write<uint8_t>(this->GetValueType());
|
packet.Write<uint8_t>(this->GetValueType());
|
||||||
|
|
||||||
packet.Write<uint8_t>(this->value);
|
packet.Write<uint8_t>(this->value);
|
||||||
@ -197,7 +197,7 @@ inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) {
|
|||||||
|
|
||||||
// The specialized version for std::string (UTF-8)
|
// The specialized version for std::string (UTF-8)
|
||||||
template<>
|
template<>
|
||||||
inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) {
|
inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) const {
|
||||||
packet.Write<uint8_t>(this->GetValueType());
|
packet.Write<uint8_t>(this->GetValueType());
|
||||||
|
|
||||||
packet.Write<uint32_t>(this->value.length());
|
packet.Write<uint32_t>(this->value.length());
|
||||||
@ -206,18 +206,18 @@ inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> inline std::string LDFData<std::u16string>::GetValueString() {
|
template<> inline std::string LDFData<std::u16string>::GetValueString() const {
|
||||||
return GeneralUtils::UTF16ToWTF8(this->value, this->value.size());
|
return GeneralUtils::UTF16ToWTF8(this->value, this->value.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> inline std::string LDFData<int32_t>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<int32_t>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<float>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<float>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<double>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<double>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<uint32_t>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<uint32_t>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<bool>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<bool>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<uint64_t>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<uint64_t>::GetValueString() const { return std::to_string(this->value); }
|
||||||
template<> inline std::string LDFData<LWOOBJID>::GetValueString() { return std::to_string(this->value); }
|
template<> inline std::string LDFData<LWOOBJID>::GetValueString() const { return std::to_string(this->value); }
|
||||||
|
|
||||||
template<> inline std::string LDFData<std::string>::GetValueString() { return this->value; }
|
template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; }
|
||||||
|
|
||||||
#endif //!__LDFFORMAT__H__
|
#endif //!__LDFFORMAT__H__
|
||||||
|
@ -558,19 +558,9 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) {
|
|||||||
itemElement->QueryAttribute("parent", &parent);
|
itemElement->QueryAttribute("parent", &parent);
|
||||||
// End custom xml
|
// End custom xml
|
||||||
|
|
||||||
std::vector<LDFBaseData*> config;
|
auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey);
|
||||||
|
|
||||||
auto* extraInfo = itemElement->FirstChildElement("x");
|
item->LoadConfigXml(*itemElement);
|
||||||
|
|
||||||
if (extraInfo) {
|
|
||||||
std::string modInfo = extraInfo->Attribute("ma");
|
|
||||||
|
|
||||||
LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1)));
|
|
||||||
|
|
||||||
config.push_back(moduleAssembly);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey);
|
|
||||||
|
|
||||||
if (equipped) {
|
if (equipped) {
|
||||||
const auto info = Inventory::FindItemComponent(lot);
|
const auto info = Inventory::FindItemComponent(lot);
|
||||||
@ -676,17 +666,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
|
|||||||
itemElement->SetAttribute("parent", item->GetParent());
|
itemElement->SetAttribute("parent", item->GetParent());
|
||||||
// End custom xml
|
// End custom xml
|
||||||
|
|
||||||
for (auto* data : item->GetConfig()) {
|
item->SaveConfigXml(*itemElement);
|
||||||
if (data->GetKey() != u"assemblyPartLOTs") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* extraInfo = document.NewElement("x");
|
|
||||||
|
|
||||||
extraInfo->SetAttribute("ma", data->GetString(false).c_str());
|
|
||||||
|
|
||||||
itemElement->LinkEndChild(extraInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
bagElement->LinkEndChild(itemElement);
|
bagElement->LinkEndChild(itemElement);
|
||||||
}
|
}
|
||||||
@ -1600,18 +1580,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){
|
bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) {
|
||||||
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
|
BehaviorSlot behaviorSlot = BehaviorSlot::Invalid;
|
||||||
if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary;
|
if (slot == 1) behaviorSlot = BehaviorSlot::Primary;
|
||||||
else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand;
|
else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand;
|
||||||
else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck;
|
else if (slot == 3) behaviorSlot = BehaviorSlot::Neck;
|
||||||
else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head;
|
else if (slot == 4) behaviorSlot = BehaviorSlot::Head;
|
||||||
else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable;
|
else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable;
|
||||||
else return false;
|
else return false;
|
||||||
return SetSkill(behaviorSlot, skillId);
|
return SetSkill(behaviorSlot, skillId);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
|
bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
|
||||||
if (skillId == 0) return false;
|
if (skillId == 0) return false;
|
||||||
const auto index = m_Skills.find(slot);
|
const auto index = m_Skills.find(slot);
|
||||||
if (index != m_Skills.end()) {
|
if (index != m_Skills.end()) {
|
||||||
@ -1623,4 +1603,3 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){
|
|||||||
m_Skills.insert_or_assign(slot, skillId);
|
m_Skills.insert_or_assign(slot, skillId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,23 @@
|
|||||||
#include "CDComponentsRegistryTable.h"
|
#include "CDComponentsRegistryTable.h"
|
||||||
#include "CDPackageComponentTable.h"
|
#include "CDPackageComponentTable.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const std::map<std::string, std::string> ExtraSettingAbbreviations = {
|
||||||
|
{ "assemblyPartLOTs", "ma" },
|
||||||
|
{ "blueprintID", "b" },
|
||||||
|
{ "userModelID", "ui" },
|
||||||
|
{ "userModelName", "un" },
|
||||||
|
{ "userModelDesc", "ud" },
|
||||||
|
{ "userModelHasBhvr", "ub" },
|
||||||
|
{ "userModelBehaviors", "ubh" },
|
||||||
|
{ "userModelBehaviorSourceID", "ubs" },
|
||||||
|
{ "userModelPhysicsType", "up" },
|
||||||
|
{ "userModelMod", "um" },
|
||||||
|
{ "userModelOpt", "uo" },
|
||||||
|
{ "reforgedLOT", "rl" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector<LDFBaseData*>& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) {
|
Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector<LDFBaseData*>& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) {
|
||||||
if (!Inventory::IsValidItem(lot)) {
|
if (!Inventory::IsValidItem(lot)) {
|
||||||
return;
|
return;
|
||||||
@ -122,6 +139,10 @@ uint32_t Item::GetSlot() const {
|
|||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<LDFBaseData*> Item::GetConfig() const {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<LDFBaseData*>& Item::GetConfig() {
|
std::vector<LDFBaseData*>& Item::GetConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
@ -515,3 +536,35 @@ Item::~Item() {
|
|||||||
|
|
||||||
config.clear();
|
config.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Item::SaveConfigXml(tinyxml2::XMLElement& i) const {
|
||||||
|
tinyxml2::XMLElement* x = nullptr;
|
||||||
|
|
||||||
|
for (const auto* config : this->config) {
|
||||||
|
const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey());
|
||||||
|
const auto saveKey = ExtraSettingAbbreviations.find(key);
|
||||||
|
if (saveKey == ExtraSettingAbbreviations.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!x) {
|
||||||
|
x = i.InsertNewChildElement("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dataToSave = config->GetString(false);
|
||||||
|
x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Item::LoadConfigXml(const tinyxml2::XMLElement& i) {
|
||||||
|
const auto* x = i.FirstChildElement("x");
|
||||||
|
if (!x) return;
|
||||||
|
|
||||||
|
for (const auto& pair : ExtraSettingAbbreviations) {
|
||||||
|
const auto* data = x->Attribute(pair.second.c_str());
|
||||||
|
if (!data) continue;
|
||||||
|
|
||||||
|
const auto value = pair.first + "=" + data;
|
||||||
|
config.push_back(LDFBaseData::DataFromString(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,6 +9,10 @@
|
|||||||
#include "eInventoryType.h"
|
#include "eInventoryType.h"
|
||||||
#include "eLootSourceType.h"
|
#include "eLootSourceType.h"
|
||||||
|
|
||||||
|
namespace tinyxml2 {
|
||||||
|
class XMLElement;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An item that can be stored in an inventory and optionally consumed or equipped
|
* An item that can be stored in an inventory and optionally consumed or equipped
|
||||||
* TODO: ideally this should be a component
|
* TODO: ideally this should be a component
|
||||||
@ -116,6 +120,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::vector<LDFBaseData*>& GetConfig();
|
std::vector<LDFBaseData*>& GetConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current config info for this item, e.g. for rockets
|
||||||
|
* @return current config info for this item
|
||||||
|
*/
|
||||||
|
std::vector<LDFBaseData*> GetConfig() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the database info for this item
|
* Returns the database info for this item
|
||||||
* @return the database info for this item
|
* @return the database info for this item
|
||||||
@ -214,6 +224,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
void RemoveFromInventory();
|
void RemoveFromInventory();
|
||||||
|
|
||||||
|
void SaveConfigXml(tinyxml2::XMLElement& i) const;
|
||||||
|
|
||||||
|
void LoadConfigXml(const tinyxml2::XMLElement& i);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* The object ID of this item
|
* The object ID of this item
|
||||||
|
Loading…
Reference in New Issue
Block a user