Map item handler; Fixed several bugs
This commit is contained in:
parent
cf96e69716
commit
3b24bc870b
@ -1199,8 +1199,6 @@ void cClientHandle::HandleSlotSelected(short a_SlotNum)
|
|||||||
|
|
||||||
if (Map != NULL)
|
if (Map != NULL)
|
||||||
{
|
{
|
||||||
Map->UpdateRadius(*m_Player, 128); // Temporary
|
|
||||||
|
|
||||||
// TODO 2014-02-14 xdot: Optimization - Do not send the whole map.
|
// TODO 2014-02-14 xdot: Optimization - Do not send the whole map.
|
||||||
Map->SendTo(*this);
|
Map->SendTo(*this);
|
||||||
}
|
}
|
||||||
|
@ -254,6 +254,9 @@ void cPlayer::Tick(float a_Dt, cChunk & a_Chunk)
|
|||||||
HandleFloater();
|
HandleFloater();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update items (e.g. Maps)
|
||||||
|
m_Inventory.UpdateItems();
|
||||||
|
|
||||||
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
|
// Send Player List (Once per m_LastPlayerListTime/1000 ms)
|
||||||
cTimer t1;
|
cTimer t1;
|
||||||
if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime())
|
if (m_LastPlayerListTime + cPlayer::PLAYER_LIST_TIME_MS <= t1.GetNowTime())
|
||||||
|
@ -515,6 +515,31 @@ bool cInventory::AddToBar( cItem & a_Item, const int a_Offset, const int a_Size,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cInventory::UpdateItems(void)
|
||||||
|
{
|
||||||
|
const cItem & Slot = GetEquippedItem();
|
||||||
|
|
||||||
|
if (Slot.IsEmpty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Slot.m_ItemType)
|
||||||
|
{
|
||||||
|
case E_ITEM_MAP:
|
||||||
|
{
|
||||||
|
ItemHandler(Slot.m_ItemType)->OnUpdate(m_Owner.GetWorld(), &m_Owner, Slot);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cInventory::SaveToJson(Json::Value & a_Value)
|
void cInventory::SaveToJson(Json::Value & a_Value)
|
||||||
{
|
{
|
||||||
// The JSON originally included the 4 crafting slots and the result, so we have to put empty items there, too:
|
// The JSON originally included the 4 crafting slots and the result, so we have to put empty items there, too:
|
||||||
|
@ -150,6 +150,9 @@ public:
|
|||||||
/// Sends the slot contents to the owner
|
/// Sends the slot contents to the owner
|
||||||
void SendSlot(int a_SlotNum);
|
void SendSlot(int a_SlotNum);
|
||||||
|
|
||||||
|
/// Update items (e.g. Maps)
|
||||||
|
void UpdateItems(void);
|
||||||
|
|
||||||
/// Converts an armor slot number into the ID for the EntityEquipment packet
|
/// Converts an armor slot number into the ID for the EntityEquipment packet
|
||||||
static int ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum);
|
static int ArmorSlotNumToEntityEquipmentID(short a_ArmorSlotNum);
|
||||||
|
|
||||||
|
@ -41,7 +41,21 @@ public:
|
|||||||
int CenterX = round(a_Player->GetPosX() / (float) RegionWidth) * RegionWidth;
|
int CenterX = round(a_Player->GetPosX() / (float) RegionWidth) * RegionWidth;
|
||||||
int CenterZ = round(a_Player->GetPosZ() / (float) RegionWidth) * RegionWidth;
|
int CenterZ = round(a_Player->GetPosZ() / (float) RegionWidth) * RegionWidth;
|
||||||
|
|
||||||
a_World->CreateMap(CenterX, CenterZ, DEFAULT_SCALE);
|
cMap * NewMap = a_World->CreateMap(CenterX, CenterZ, DEFAULT_SCALE);
|
||||||
|
|
||||||
|
// Remove empty map from inventory
|
||||||
|
if (!a_Player->GetInventory().RemoveOneEquippedItem())
|
||||||
|
{
|
||||||
|
ASSERT(!"Inventory mismatch");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NewMap == NULL)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
a_Player->GetInventory().AddItem(cItem(E_ITEM_MAP, 1, NewMap->GetID()), true, true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "ItemHoe.h"
|
#include "ItemHoe.h"
|
||||||
#include "ItemLeaves.h"
|
#include "ItemLeaves.h"
|
||||||
#include "ItemLighter.h"
|
#include "ItemLighter.h"
|
||||||
|
#include "ItemMap.h"
|
||||||
#include "ItemMinecart.h"
|
#include "ItemMinecart.h"
|
||||||
#include "ItemNetherWart.h"
|
#include "ItemNetherWart.h"
|
||||||
#include "ItemPickaxe.h"
|
#include "ItemPickaxe.h"
|
||||||
@ -107,6 +108,7 @@ cItemHandler *cItemHandler::CreateItemHandler(int a_ItemType)
|
|||||||
case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType);
|
case E_ITEM_FISHING_ROD: return new cItemFishingRodHandler(a_ItemType);
|
||||||
case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType);
|
case E_ITEM_FLINT_AND_STEEL: return new cItemLighterHandler(a_ItemType);
|
||||||
case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType);
|
case E_ITEM_FLOWER_POT: return new cItemFlowerPotHandler(a_ItemType);
|
||||||
|
case E_ITEM_MAP: return new cItemMapHandler();
|
||||||
case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType);
|
case E_ITEM_NETHER_WART: return new cItemNetherWartHandler(a_ItemType);
|
||||||
case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType);
|
case E_ITEM_REDSTONE_DUST: return new cItemRedstoneDustHandler(a_ItemType);
|
||||||
case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType);
|
case E_ITEM_REDSTONE_REPEATER: return new cItemRedstoneRepeaterHandler(a_ItemType);
|
||||||
|
@ -32,6 +32,14 @@ public:
|
|||||||
UNUSED(a_BlockZ);
|
UNUSED(a_BlockZ);
|
||||||
UNUSED(a_BlockFace);
|
UNUSED(a_BlockFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called every tick while the item is on the player's inventory (Used by maps) - For now, called only for equipped items
|
||||||
|
virtual void OnUpdate(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item)
|
||||||
|
{
|
||||||
|
UNUSED(a_World);
|
||||||
|
UNUSED(a_Player);
|
||||||
|
UNUSED(a_Item);
|
||||||
|
}
|
||||||
|
|
||||||
/// Called while the player diggs a block using this item
|
/// Called while the player diggs a block using this item
|
||||||
virtual bool OnDiggingBlock(cWorld * a_World, cPlayer * a_Player, const cItem & a_HeldItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace);
|
virtual bool OnDiggingBlock(cWorld * a_World, cPlayer * a_Player, const cItem & a_HeldItem, int a_BlockX, int a_BlockY, int a_BlockZ, eBlockFace a_BlockFace);
|
||||||
|
43
src/Items/ItemMap.h
Normal file
43
src/Items/ItemMap.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
// ItemMap.h
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../Entities/Entity.h"
|
||||||
|
#include "../Item.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class cItemMapHandler :
|
||||||
|
public cItemHandler
|
||||||
|
{
|
||||||
|
typedef cItemHandler super;
|
||||||
|
|
||||||
|
static const unsigned int DEFAULT_RADIUS = 128;
|
||||||
|
|
||||||
|
public:
|
||||||
|
cItemMapHandler() :
|
||||||
|
super(E_ITEM_MAP)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void OnUpdate(cWorld * a_World, cPlayer * a_Player, const cItem & a_Item)
|
||||||
|
{
|
||||||
|
cMap * Map = a_World->GetMapData(a_Item.m_ItemDamage);
|
||||||
|
|
||||||
|
if (Map == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map->AddTrackedPlayer(a_Player);
|
||||||
|
|
||||||
|
Map->UpdateRadius(*a_Player, DEFAULT_RADIUS);
|
||||||
|
}
|
||||||
|
} ;
|
63
src/Map.cpp
63
src/Map.cpp
@ -50,15 +50,25 @@ cMap::cMap(unsigned int a_ID, int a_CenterX, int a_CenterZ, cWorld * a_World, un
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T Clamp(T a_X, T a_Min, T a_Max)
|
||||||
|
{
|
||||||
|
return std::min(std::max(a_X, a_Min), a_Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cMap::UpdateRadius(int a_PixelX, int a_PixelZ, unsigned int a_Radius)
|
void cMap::UpdateRadius(int a_PixelX, int a_PixelZ, unsigned int a_Radius)
|
||||||
{
|
{
|
||||||
int PixelRadius = a_Radius / GetPixelWidth();
|
int PixelRadius = a_Radius / GetPixelWidth();
|
||||||
|
|
||||||
unsigned int StartX = std::max(a_PixelX - PixelRadius, 0);
|
unsigned int StartX = Clamp(a_PixelX - PixelRadius, 0, (int)m_Width);
|
||||||
unsigned int StartZ = std::max(a_PixelZ - PixelRadius, 0);
|
unsigned int StartZ = Clamp(a_PixelZ - PixelRadius, 0, (int)m_Height);
|
||||||
|
|
||||||
unsigned int EndX = std::min(a_PixelX + PixelRadius, (int)m_Width);
|
unsigned int EndX = Clamp(a_PixelX + PixelRadius, 0, (int)m_Width);
|
||||||
unsigned int EndZ = std::min(a_PixelZ + PixelRadius, (int)m_Height);
|
unsigned int EndZ = Clamp(a_PixelZ + PixelRadius, 0, (int)m_Height);
|
||||||
|
|
||||||
for (unsigned int X = StartX; X < EndX; ++X)
|
for (unsigned int X = StartX; X < EndX; ++X)
|
||||||
{
|
{
|
||||||
@ -93,11 +103,9 @@ void cMap::UpdateRadius(cPlayer & a_Player, unsigned int a_Radius)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Y)
|
bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Z)
|
||||||
{
|
{
|
||||||
ASSERT(m_World != NULL);
|
/*unsigned int PixelWidth = GetPixelWidth();
|
||||||
|
|
||||||
unsigned int PixelWidth = GetPixelWidth();
|
|
||||||
|
|
||||||
int BlockX = m_CenterX + ((a_X - m_Width) * PixelWidth);
|
int BlockX = m_CenterX + ((a_X - m_Width) * PixelWidth);
|
||||||
int BlockZ = m_CenterZ + ((a_Y - m_Height) * PixelWidth);
|
int BlockZ = m_CenterZ + ((a_Y - m_Height) * PixelWidth);
|
||||||
@ -119,7 +127,7 @@ bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Y)
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
cCalculatePixelCb(cMap * a_Map, int a_RelX, int a_RelZ)
|
cCalculatePixelCb(cMap * a_Map, int a_RelX, int a_RelZ)
|
||||||
: m_Map(a_Map), m_RelX(a_RelX), m_RelZ(a_RelZ), m_PixelData(0) {}
|
: m_Map(a_Map), m_RelX(a_RelX), m_RelZ(a_RelZ), m_PixelData(4) {}
|
||||||
|
|
||||||
virtual bool Item(cChunk * a_Chunk) override
|
virtual bool Item(cChunk * a_Chunk) override
|
||||||
{
|
{
|
||||||
@ -155,9 +163,9 @@ bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Y)
|
|||||||
} CalculatePixelCb(this, RelX, RelZ);
|
} CalculatePixelCb(this, RelX, RelZ);
|
||||||
|
|
||||||
ASSERT(m_World != NULL);
|
ASSERT(m_World != NULL);
|
||||||
m_World->DoWithChunk(ChunkX, ChunkZ, CalculatePixelCb);
|
m_World->DoWithChunk(ChunkX, ChunkZ, CalculatePixelCb);*/
|
||||||
|
|
||||||
m_Data[a_Y + (a_X * m_Height)] = CalculatePixelCb.GetPixelData();
|
m_Data[a_Z + (a_X * m_Height)] = 4;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -166,6 +174,39 @@ bool cMap::UpdatePixel(unsigned int a_X, unsigned int a_Y)
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMap::UpdateTrackedPlayers(void)
|
||||||
|
{
|
||||||
|
cTrackedPlayerList NewList;
|
||||||
|
|
||||||
|
for (cTrackedPlayerList::iterator it = m_TrackedPlayers.begin(); it != m_TrackedPlayers.end(); ++it)
|
||||||
|
{
|
||||||
|
cPlayer * Player = *it;
|
||||||
|
|
||||||
|
UpdateRadius(*Player, DEFAULT_RADIUS);
|
||||||
|
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
NewList.insert(Player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::swap(m_TrackedPlayers, NewList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cMap::AddTrackedPlayer(cPlayer * a_Player)
|
||||||
|
{
|
||||||
|
ASSERT(a_Player != NULL);
|
||||||
|
m_TrackedPlayers.insert(a_Player);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void cMap::EraseData(void)
|
void cMap::EraseData(void)
|
||||||
{
|
{
|
||||||
m_Data.assign(m_Width * m_Height, 0);
|
m_Data.assign(m_Width * m_Height, 0);
|
||||||
|
13
src/Map.h
13
src/Map.h
@ -38,6 +38,8 @@ public:
|
|||||||
|
|
||||||
typedef std::vector<ColorID> cColorList;
|
typedef std::vector<ColorID> cColorList;
|
||||||
|
|
||||||
|
static const unsigned int DEFAULT_RADIUS = 128;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -54,6 +56,10 @@ public:
|
|||||||
|
|
||||||
void UpdateRadius(cPlayer & a_Player, unsigned int a_Radius);
|
void UpdateRadius(cPlayer & a_Player, unsigned int a_Radius);
|
||||||
|
|
||||||
|
void UpdateTrackedPlayers(void);
|
||||||
|
|
||||||
|
void AddTrackedPlayer(cPlayer * a_Player);
|
||||||
|
|
||||||
// tolua_begin
|
// tolua_begin
|
||||||
|
|
||||||
/** Erase pixel data */
|
/** Erase pixel data */
|
||||||
@ -93,7 +99,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
|
|
||||||
/** Update the specified pixel. */
|
/** Update the specified pixel. */
|
||||||
bool UpdatePixel(unsigned int a_X, unsigned int a_Y);
|
bool UpdatePixel(unsigned int a_X, unsigned int a_Z);
|
||||||
|
|
||||||
unsigned int m_ID;
|
unsigned int m_ID;
|
||||||
|
|
||||||
@ -111,8 +117,9 @@ private:
|
|||||||
|
|
||||||
cWorld * m_World;
|
cWorld * m_World;
|
||||||
|
|
||||||
//typedef std::vector<cPlayer*> cPlayerList;
|
typedef std::set<cPlayer*> cTrackedPlayerList;
|
||||||
//cPlayerList m_TrackedPlayers;
|
|
||||||
|
cTrackedPlayerList m_TrackedPlayers;
|
||||||
|
|
||||||
AString m_Name;
|
AString m_Name;
|
||||||
|
|
||||||
|
@ -1574,8 +1574,7 @@ cMap * cWorld::CreateMap(int a_CenterX, int a_CenterY, int a_Scale)
|
|||||||
{
|
{
|
||||||
if (m_MapData.size() >= 65536)
|
if (m_MapData.size() >= 65536)
|
||||||
{
|
{
|
||||||
LOGD("cWorld::CreateMap - Too many maps in use");
|
LOGWARN("Could not craft map - Too many maps in use");
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2992,9 +2991,12 @@ void cWorld::LoadMapData(void)
|
|||||||
{
|
{
|
||||||
cIDCountSerializer IDSerializer(GetName());
|
cIDCountSerializer IDSerializer(GetName());
|
||||||
|
|
||||||
IDSerializer.Load();
|
if (!IDSerializer.Load())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int MapCount = IDSerializer.GetMapCount() + 1;
|
unsigned int MapCount = IDSerializer.GetMapCount();
|
||||||
|
|
||||||
m_MapData.clear();
|
m_MapData.clear();
|
||||||
|
|
||||||
@ -3004,7 +3006,10 @@ void cWorld::LoadMapData(void)
|
|||||||
|
|
||||||
cMapSerializer Serializer(GetName(), &Map);
|
cMapSerializer Serializer(GetName(), &Map);
|
||||||
|
|
||||||
Serializer.Load();
|
if (!Serializer.Load())
|
||||||
|
{
|
||||||
|
LOGWARN("Could not load map #%i", Map.GetID());
|
||||||
|
}
|
||||||
|
|
||||||
m_MapData.push_back(Map);
|
m_MapData.push_back(Map);
|
||||||
}
|
}
|
||||||
@ -3023,9 +3028,13 @@ void cWorld::SaveMapData(void)
|
|||||||
|
|
||||||
cIDCountSerializer IDSerializer(GetName());
|
cIDCountSerializer IDSerializer(GetName());
|
||||||
|
|
||||||
IDSerializer.SetMapCount(m_MapData.size() - 1);
|
IDSerializer.SetMapCount(m_MapData.size());
|
||||||
|
|
||||||
IDSerializer.Save();
|
if (!IDSerializer.Save())
|
||||||
|
{
|
||||||
|
LOGERROR("Could not save idcounts.dat");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (cMapList::iterator it = m_MapData.begin(); it != m_MapData.end(); ++it)
|
for (cMapList::iterator it = m_MapData.begin(); it != m_MapData.end(); ++it)
|
||||||
{
|
{
|
||||||
@ -3033,7 +3042,10 @@ void cWorld::SaveMapData(void)
|
|||||||
|
|
||||||
cMapSerializer Serializer(GetName(), &Map);
|
cMapSerializer Serializer(GetName(), &Map);
|
||||||
|
|
||||||
Serializer.Save();
|
if (!Serializer.Save())
|
||||||
|
{
|
||||||
|
LOGWARN("Could not save map #%i", Map.GetID());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +223,11 @@ bool cIDCountSerializer::Load(void)
|
|||||||
int CurrLine = NBT.FindChildByName(0, "map");
|
int CurrLine = NBT.FindChildByName(0, "map");
|
||||||
if (CurrLine >= 0)
|
if (CurrLine >= 0)
|
||||||
{
|
{
|
||||||
m_MapCount = (int)NBT.GetShort(CurrLine);
|
m_MapCount = (int)NBT.GetShort(CurrLine) + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_MapCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -237,7 +241,10 @@ bool cIDCountSerializer::Save(void)
|
|||||||
{
|
{
|
||||||
cFastNBTWriter Writer;
|
cFastNBTWriter Writer;
|
||||||
|
|
||||||
Writer.AddShort("map", m_MapCount);
|
if (m_MapCount > 0)
|
||||||
|
{
|
||||||
|
Writer.AddShort("map", m_MapCount - 1);
|
||||||
|
}
|
||||||
|
|
||||||
Writer.Finish();
|
Writer.Finish();
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ public:
|
|||||||
|
|
||||||
cMapSerializer(const AString& a_WorldName, cMap * a_Map);
|
cMapSerializer(const AString& a_WorldName, cMap * a_Map);
|
||||||
|
|
||||||
/// Try to load the scoreboard
|
/** Try to load the scoreboard */
|
||||||
bool Load(void);
|
bool Load(void);
|
||||||
|
|
||||||
/// Try to save the scoreboard
|
/** Try to save the scoreboard */
|
||||||
bool Save(void);
|
bool Save(void);
|
||||||
|
|
||||||
|
|
||||||
@ -56,8 +56,10 @@ public:
|
|||||||
|
|
||||||
cIDCountSerializer(const AString & a_WorldName);
|
cIDCountSerializer(const AString & a_WorldName);
|
||||||
|
|
||||||
|
/** Try to load the ID counts */
|
||||||
bool Load(void);
|
bool Load(void);
|
||||||
|
|
||||||
|
/** Try to save the ID counts */
|
||||||
bool Save(void);
|
bool Save(void);
|
||||||
|
|
||||||
inline unsigned int GetMapCount(void) const { return m_MapCount; }
|
inline unsigned int GetMapCount(void) const { return m_MapCount; }
|
||||||
@ -70,6 +72,7 @@ private:
|
|||||||
AString m_Path;
|
AString m_Path;
|
||||||
|
|
||||||
unsigned int m_MapCount;
|
unsigned int m_MapCount;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user