Added new AI rules
+ Added new AI rules handling cacti and large heights * Fixed cIniFile not recognising comments in cIniFile::ReadFile() * Fixed users.ini not being properly generated * Changed all instances of (int)floor(GetPosXXX()) to POSXXX_TOINT
This commit is contained in:
parent
afb20132fe
commit
bdea8706d8
@ -154,7 +154,7 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect)
|
||||
case ';':
|
||||
case '#':
|
||||
{
|
||||
if (names.size() == 0)
|
||||
if (names.empty())
|
||||
{
|
||||
AddHeaderComment(line.substr(pLeft + 1));
|
||||
}
|
||||
@ -168,8 +168,9 @@ bool cIniFile::ReadFile(const AString & a_FileName, bool a_AllowExampleRedirect)
|
||||
} // while (getline())
|
||||
|
||||
f.close();
|
||||
if (names.size() == 0)
|
||||
if (keys.empty() && names.empty() && comments.empty())
|
||||
{
|
||||
// File be empty or unreadable, equivalent to nonexistant
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -806,7 +806,7 @@ void cEntity::TickBurning(cChunk & a_Chunk)
|
||||
int MaxRelX = (int)floor(GetPosX() + m_Width / 2) - a_Chunk.GetPosX() * cChunkDef::Width;
|
||||
int MinRelZ = (int)floor(GetPosZ() - m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||
int MaxRelZ = (int)floor(GetPosZ() + m_Width / 2) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||
int MinY = std::max(0, std::min(cChunkDef::Height - 1, (int)floor(GetPosY())));
|
||||
int MinY = std::max(0, std::min(cChunkDef::Height - 1, POSY_TOINT));
|
||||
int MaxY = std::max(0, std::min(cChunkDef::Height - 1, (int)ceil (GetPosY() + m_Height)));
|
||||
bool HasWater = false;
|
||||
bool HasLava = false;
|
||||
|
@ -118,7 +118,8 @@ public:
|
||||
BURN_TICKS = 200, ///< How long to keep an entity burning after it has stood in lava / fire
|
||||
MAX_AIR_LEVEL = 300, ///< Maximum air an entity can have
|
||||
DROWNING_TICKS = 20, ///< Number of ticks per heart of damage
|
||||
VOID_BOUNDARY = -46 ///< At what position Y to begin applying void damage
|
||||
VOID_BOUNDARY = -46, ///< At what position Y to begin applying void damage
|
||||
FALL_DAMAGE_HEIGHT = 4 ///< At what position Y fall damage is applied
|
||||
} ;
|
||||
|
||||
cEntity(eEntityType a_EntityType, double a_X, double a_Y, double a_Z, double a_Width, double a_Height);
|
||||
|
@ -132,7 +132,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk)
|
||||
return;
|
||||
}
|
||||
|
||||
int PosY = (int)floor(GetPosY());
|
||||
int PosY = POSY_TOINT;
|
||||
if ((PosY <= 0) || (PosY >= cChunkDef::Height))
|
||||
{
|
||||
// Outside the world, just process normal falling physics
|
||||
@ -141,8 +141,8 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk)
|
||||
return;
|
||||
}
|
||||
|
||||
int RelPosX = (int)floor(GetPosX()) - a_Chunk.GetPosX() * cChunkDef::Width;
|
||||
int RelPosZ = (int)floor(GetPosZ()) - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||
int RelPosX = POSX_TOINT - a_Chunk.GetPosX() * cChunkDef::Width;
|
||||
int RelPosZ = POSZ_TOINT - a_Chunk.GetPosZ() * cChunkDef::Width;
|
||||
cChunk * Chunk = a_Chunk.GetRelNeighborChunkAdjustCoords(RelPosX, RelPosZ);
|
||||
if (Chunk == NULL)
|
||||
{
|
||||
@ -195,7 +195,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk)
|
||||
super::HandlePhysics(a_Dt, *Chunk);
|
||||
}
|
||||
|
||||
if (m_bIsOnDetectorRail && !Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())).Equals(m_DetectorRailPosition))
|
||||
if (m_bIsOnDetectorRail && !Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT).Equals(m_DetectorRailPosition))
|
||||
{
|
||||
m_World->SetBlock(m_DetectorRailPosition.x, m_DetectorRailPosition.y, m_DetectorRailPosition.z, E_BLOCK_DETECTOR_RAIL, m_World->GetBlockMeta(m_DetectorRailPosition) & 0x07);
|
||||
m_bIsOnDetectorRail = false;
|
||||
@ -203,7 +203,7 @@ void cMinecart::HandlePhysics(float a_Dt, cChunk & a_Chunk)
|
||||
else if (WasDetectorRail)
|
||||
{
|
||||
m_bIsOnDetectorRail = true;
|
||||
m_DetectorRailPosition = Vector3i((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
m_DetectorRailPosition = Vector3i(POSX_TOINT, POSY_TOINT, POSZ_TOINT);
|
||||
}
|
||||
|
||||
// Broadcast positioning changes to client
|
||||
@ -719,11 +719,11 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
{
|
||||
if (GetSpeedZ() > 0)
|
||||
{
|
||||
BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)ceil(GetPosZ()));
|
||||
BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT, POSY_TOINT, (int)ceil(GetPosZ()));
|
||||
if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block))
|
||||
{
|
||||
// We could try to detect a block in front based purely on coordinates, but xoft made a bounding box system - why not use? :P
|
||||
cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()), (int)floor(GetPosY()), (int)ceil(GetPosZ())), 0.5, 1);
|
||||
cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, (int)ceil(GetPosZ())), 0.5, 1);
|
||||
cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight());
|
||||
|
||||
if (bbBlock.DoesIntersect(bbMinecart))
|
||||
@ -736,10 +736,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
}
|
||||
else if (GetSpeedZ() < 0)
|
||||
{
|
||||
BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1);
|
||||
BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1);
|
||||
if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block))
|
||||
{
|
||||
cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) - 1), 0.5, 1);
|
||||
cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1), 0.5, 1);
|
||||
cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ() - 1), GetWidth() / 2, GetHeight());
|
||||
|
||||
if (bbBlock.DoesIntersect(bbMinecart))
|
||||
@ -756,10 +756,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
{
|
||||
if (GetSpeedX() > 0)
|
||||
{
|
||||
BLOCKTYPE Block = m_World->GetBlock((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
BLOCKTYPE Block = m_World->GetBlock((int)ceil(GetPosX()), POSY_TOINT, POSZ_TOINT);
|
||||
if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block))
|
||||
{
|
||||
cBoundingBox bbBlock(Vector3d((int)ceil(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1);
|
||||
cBoundingBox bbBlock(Vector3d((int)ceil(GetPosX()), POSY_TOINT, POSZ_TOINT), 0.5, 1);
|
||||
cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight());
|
||||
|
||||
if (bbBlock.DoesIntersect(bbMinecart))
|
||||
@ -772,10 +772,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
}
|
||||
else if (GetSpeedX() < 0)
|
||||
{
|
||||
BLOCKTYPE Block = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT);
|
||||
if (!IsBlockRail(Block) && cBlockInfo::IsSolid(Block))
|
||||
{
|
||||
cBoundingBox bbBlock(Vector3d((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ())), 0.5, 1);
|
||||
cBoundingBox bbBlock(Vector3d(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT), 0.5, 1);
|
||||
cBoundingBox bbMinecart(Vector3d(GetPosX() - 1, floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight());
|
||||
|
||||
if (bbBlock.DoesIntersect(bbMinecart))
|
||||
@ -793,10 +793,10 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
case E_META_RAIL_CURVED_ZP_XM:
|
||||
case E_META_RAIL_CURVED_ZP_XP:
|
||||
{
|
||||
BLOCKTYPE BlockXM = m_World->GetBlock((int)floor(GetPosX()) - 1, (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
BLOCKTYPE BlockXP = m_World->GetBlock((int)floor(GetPosX()) + 1, (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
BLOCKTYPE BlockZM = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) + 1);
|
||||
BLOCKTYPE BlockZP = m_World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()) + 1);
|
||||
BLOCKTYPE BlockXM = m_World->GetBlock(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT);
|
||||
BLOCKTYPE BlockXP = m_World->GetBlock(POSX_TOINT + 1, POSY_TOINT, POSZ_TOINT);
|
||||
BLOCKTYPE BlockZM = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT + 1);
|
||||
BLOCKTYPE BlockZP = m_World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT + 1);
|
||||
if (
|
||||
(!IsBlockRail(BlockXM) && cBlockInfo::IsSolid(BlockXM)) ||
|
||||
(!IsBlockRail(BlockXP) && cBlockInfo::IsSolid(BlockXP)) ||
|
||||
@ -805,7 +805,7 @@ bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta)
|
||||
)
|
||||
{
|
||||
SetSpeed(0, 0, 0);
|
||||
SetPosition((int)floor(GetPosX()) + 0.5, GetPosY(), (int)floor(GetPosZ()) + 0.5);
|
||||
SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@ -822,7 +822,7 @@ bool cMinecart::TestEntityCollision(NIBBLETYPE a_RailMeta)
|
||||
{
|
||||
cMinecartCollisionCallback MinecartCollisionCallback(GetPosition(), GetHeight(), GetWidth(), GetUniqueID(), ((m_Attachee == NULL) ? -1 : m_Attachee->GetUniqueID()));
|
||||
int ChunkX, ChunkZ;
|
||||
cChunkDef::BlockToChunk((int)floor(GetPosX()), (int)floor(GetPosZ()), ChunkX, ChunkZ);
|
||||
cChunkDef::BlockToChunk(POSX_TOINT, POSZ_TOINT, ChunkX, ChunkZ);
|
||||
m_World->ForEachEntityInChunk(ChunkX, ChunkZ, MinecartCollisionCallback);
|
||||
|
||||
if (!MinecartCollisionCallback.FoundIntersection())
|
||||
|
@ -437,7 +437,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
|
||||
cWorld * World = GetWorld();
|
||||
if ((GetPosY() >= 0) && (GetPosY() < cChunkDef::Height))
|
||||
{
|
||||
BLOCKTYPE BlockType = World->GetBlock((int)floor(GetPosX()), (int)floor(GetPosY()), (int)floor(GetPosZ()));
|
||||
BLOCKTYPE BlockType = World->GetBlock(POSX_TOINT, POSY_TOINT, POSZ_TOINT);
|
||||
if (BlockType != E_BLOCK_AIR)
|
||||
{
|
||||
m_bTouchGround = true;
|
||||
@ -466,7 +466,7 @@ void cPlayer::SetTouchGround(bool a_bTouchGround)
|
||||
TakeDamage(dtFalling, NULL, Damage, Damage, 0);
|
||||
|
||||
// Fall particles
|
||||
GetWorld()->BroadcastSoundParticleEffect(2006, (int)floor(GetPosX()), (int)GetPosY() - 1, (int)floor(GetPosZ()), Damage /* Used as particle effect speed modifier */);
|
||||
GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, (int)GetPosY() - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */);
|
||||
}
|
||||
|
||||
m_LastGroundHeight = (float)GetPosY();
|
||||
@ -1519,22 +1519,16 @@ void cPlayer::LoadPermissionsFromDisk()
|
||||
cIniFile IniFile;
|
||||
if (IniFile.ReadFile("users.ini"))
|
||||
{
|
||||
std::string Groups = IniFile.GetValue(m_PlayerName, "Groups", "");
|
||||
if (!Groups.empty())
|
||||
AString Groups = IniFile.GetValueSet(m_PlayerName, "Groups", "Default");
|
||||
AStringVector Split = StringSplitAndTrim(Groups, ",");
|
||||
|
||||
for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr)
|
||||
{
|
||||
AStringVector Split = StringSplitAndTrim(Groups, ",");
|
||||
for (AStringVector::const_iterator itr = Split.begin(), end = Split.end(); itr != end; ++itr)
|
||||
if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr))
|
||||
{
|
||||
if (!cRoot::Get()->GetGroupManager()->ExistsGroup(*itr))
|
||||
{
|
||||
LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str());
|
||||
}
|
||||
AddToGroup(*itr);
|
||||
LOGWARNING("The group %s for player %s was not found!", itr->c_str(), m_PlayerName.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToGroup("Default");
|
||||
AddToGroup(*itr);
|
||||
}
|
||||
|
||||
AString Color = IniFile.GetValue(m_PlayerName, "Color", "-");
|
||||
@ -1546,8 +1540,10 @@ void cPlayer::LoadPermissionsFromDisk()
|
||||
else
|
||||
{
|
||||
cGroupManager::GenerateDefaultUsersIni(IniFile);
|
||||
IniFile.AddValue("Groups", m_PlayerName, "Default");
|
||||
AddToGroup("Default");
|
||||
}
|
||||
IniFile.WriteFile("users.ini");
|
||||
ResolvePermissions();
|
||||
}
|
||||
|
||||
@ -1899,9 +1895,9 @@ void cPlayer::ApplyFoodExhaustionFromMovement()
|
||||
void cPlayer::Detach()
|
||||
{
|
||||
super::Detach();
|
||||
int PosX = (int)floor(GetPosX());
|
||||
int PosY = (int)floor(GetPosY());
|
||||
int PosZ = (int)floor(GetPosZ());
|
||||
int PosX = POSX_TOINT;
|
||||
int PosY = POSY_TOINT;
|
||||
int PosZ = POSZ_TOINT;
|
||||
|
||||
// Search for a position within an area to teleport player after detachment
|
||||
// Position must be solid land, and occupied by a nonsolid block
|
||||
|
@ -111,9 +111,9 @@ void cMonster::SpawnOn(cClientHandle & a_Client)
|
||||
|
||||
void cMonster::TickPathFinding()
|
||||
{
|
||||
const int PosX = (int)floor(GetPosX());
|
||||
const int PosY = (int)floor(GetPosY());
|
||||
const int PosZ = (int)floor(GetPosZ());
|
||||
const int PosX = POSX_TOINT;
|
||||
const int PosY = POSY_TOINT;
|
||||
const int PosZ = POSZ_TOINT;
|
||||
|
||||
m_FinalDestination.y = (double)FindFirstNonAirBlockPosition(m_FinalDestination.x, m_FinalDestination.z);
|
||||
|
||||
@ -148,13 +148,27 @@ void cMonster::TickPathFinding()
|
||||
BLOCKTYPE BlockAtY = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY, gCrossCoords[i].z + PosZ);
|
||||
BLOCKTYPE BlockAtYP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 1, gCrossCoords[i].z + PosZ);
|
||||
BLOCKTYPE BlockAtYPP = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY + 2, gCrossCoords[i].z + PosZ);
|
||||
BLOCKTYPE BlockAtYM = m_World->GetBlock(gCrossCoords[i].x + PosX, PosY - 1, gCrossCoords[i].z + PosZ);
|
||||
int LowestY = FindFirstNonAirBlockPosition(gCrossCoords[i].x + PosX, gCrossCoords[i].z + PosZ);
|
||||
BLOCKTYPE BlockAtLowestY = m_World->GetBlock(gCrossCoords[i].x + PosX, LowestY, gCrossCoords[i].z + PosZ);
|
||||
|
||||
if ((!cBlockInfo::IsSolid(BlockAtY)) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE))
|
||||
if (
|
||||
(!cBlockInfo::IsSolid(BlockAtY)) &&
|
||||
(!cBlockInfo::IsSolid(BlockAtYP)) &&
|
||||
(!IsBlockLava(BlockAtLowestY)) &&
|
||||
(BlockAtLowestY != E_BLOCK_CACTUS) &&
|
||||
(PosY - LowestY < FALL_DAMAGE_HEIGHT)
|
||||
)
|
||||
{
|
||||
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY, gCrossCoords[i].z + PosZ));
|
||||
}
|
||||
else if ((cBlockInfo::IsSolid(BlockAtY)) && (!cBlockInfo::IsSolid(BlockAtYP)) && (!cBlockInfo::IsSolid(BlockAtYPP)) && (!IsBlockLava(BlockAtYM)) && (BlockAtY != E_BLOCK_FENCE) && (BlockAtY != E_BLOCK_FENCE_GATE))
|
||||
else if (
|
||||
(cBlockInfo::IsSolid(BlockAtY)) &&
|
||||
(BlockAtY != E_BLOCK_CACTUS) &&
|
||||
(!cBlockInfo::IsSolid(BlockAtYP)) &&
|
||||
(!cBlockInfo::IsSolid(BlockAtYPP)) &&
|
||||
(BlockAtY != E_BLOCK_FENCE) &&
|
||||
(BlockAtY != E_BLOCK_FENCE_GATE)
|
||||
)
|
||||
{
|
||||
m_PotentialCoordinates.push_back(Vector3d((gCrossCoords[i].x + PosX), PosY + 1, gCrossCoords[i].z + PosZ));
|
||||
}
|
||||
@ -402,7 +416,7 @@ void cMonster::HandleFalling()
|
||||
GetWorld()->BroadcastSoundParticleEffect(2006, POSX_TOINT, POSY_TOINT - 1, POSZ_TOINT, Damage /* Used as particle effect speed modifier */);
|
||||
}
|
||||
|
||||
m_LastGroundHeight = (int)floor(GetPosY());
|
||||
m_LastGroundHeight = POSY_TOINT;
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,7 +425,7 @@ void cMonster::HandleFalling()
|
||||
|
||||
int cMonster::FindFirstNonAirBlockPosition(double a_PosX, double a_PosZ)
|
||||
{
|
||||
int PosY = (int)floor(GetPosY());
|
||||
int PosY = POSY_TOINT;
|
||||
|
||||
if (PosY < 0)
|
||||
PosY = 0;
|
||||
@ -969,15 +983,15 @@ void cMonster::HandleDaylightBurning(cChunk & a_Chunk)
|
||||
return;
|
||||
}
|
||||
|
||||
int RelY = (int)floor(GetPosY());
|
||||
int RelY = POSY_TOINT;
|
||||
if ((RelY < 0) || (RelY >= cChunkDef::Height))
|
||||
{
|
||||
// Outside the world
|
||||
return;
|
||||
}
|
||||
|
||||
int RelX = (int)floor(GetPosX()) - GetChunkX() * cChunkDef::Width;
|
||||
int RelZ = (int)floor(GetPosZ()) - GetChunkZ() * cChunkDef::Width;
|
||||
int RelX = POSX_TOINT - GetChunkX() * cChunkDef::Width;
|
||||
int RelZ = POSZ_TOINT - GetChunkZ() * cChunkDef::Width;
|
||||
|
||||
if (!a_Chunk.IsLightValid())
|
||||
{
|
||||
|
@ -185,14 +185,14 @@ protected:
|
||||
inline bool IsNextYPosReachable(int a_PosY)
|
||||
{
|
||||
return (
|
||||
(a_PosY <= (int)floor(GetPosY())) ||
|
||||
(a_PosY <= POSY_TOINT) ||
|
||||
DoesPosYRequireJump(a_PosY)
|
||||
);
|
||||
}
|
||||
/** Returns if a monster can reach a given height by jumping */
|
||||
inline bool DoesPosYRequireJump(int a_PosY)
|
||||
{
|
||||
return ((a_PosY > (int)floor(GetPosY())) && (a_PosY == (int)floor(GetPosY()) + 1));
|
||||
return ((a_PosY > POSY_TOINT) && (a_PosY == POSY_TOINT + 1));
|
||||
}
|
||||
|
||||
/** A semi-temporary list to store the traversed coordinates during active pathfinding so we don't visit them again */
|
||||
|
Loading…
Reference in New Issue
Block a user