mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Added textures for Blocks
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 533 B |
Binary file not shown.
|
After Width: | Height: | Size: 565 B |
Binary file not shown.
|
After Width: | Height: | Size: 482 B |
Binary file not shown.
|
After Width: | Height: | Size: 332 B |
@@ -27,69 +27,116 @@ namespace Sandbox.VoxelFun
|
|||||||
{
|
{
|
||||||
Color _color;
|
Color _color;
|
||||||
|
|
||||||
|
BlockTexture _textureTop;
|
||||||
|
BlockTexture _textureSide;
|
||||||
|
BlockTexture _textureBottom;
|
||||||
|
|
||||||
|
|
||||||
public this(Color color)
|
public this(Color color)
|
||||||
{
|
{
|
||||||
_color = color;
|
_color = color;
|
||||||
|
|
||||||
|
bottomCoords = sideCoords = topCoords = (.Zero, .UnitX, .UnitY, .One);
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(Color color, BlockTexture texture) : this(color, texture, texture, texture)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public this(Color color, BlockTexture topTexture, BlockTexture sideTexture, BlockTexture bottomTexture)
|
||||||
|
{
|
||||||
|
_color = color;
|
||||||
|
_textureTop = topTexture;
|
||||||
|
_textureSide = sideTexture;
|
||||||
|
_textureBottom = bottomTexture;
|
||||||
|
|
||||||
|
CalculateTexCoords();
|
||||||
|
}
|
||||||
|
|
||||||
|
typealias TexCoords = (Vector2 Zero, Vector2 UnitX, Vector2 UnitY, Vector2 One);
|
||||||
|
|
||||||
|
TexCoords topCoords;
|
||||||
|
TexCoords sideCoords;
|
||||||
|
TexCoords bottomCoords;
|
||||||
|
|
||||||
|
private void CalculateTexCoords()
|
||||||
|
{
|
||||||
|
topCoords = GetQuadCoords(_textureTop);
|
||||||
|
sideCoords = GetQuadCoords(_textureSide);
|
||||||
|
bottomCoords = GetQuadCoords(_textureBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TexCoords GetQuadCoords(BlockTexture texture)
|
||||||
|
{
|
||||||
|
TexCoords coords;
|
||||||
|
|
||||||
|
coords.UnitX = texture.TransformTexCoords(.UnitX);
|
||||||
|
coords.UnitY = texture.TransformTexCoords(.UnitY);
|
||||||
|
coords.Zero = texture.TransformTexCoords(.Zero);
|
||||||
|
coords.One = texture.TransformTexCoords(.One);
|
||||||
|
|
||||||
|
return coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void GenerateGeometry(Block block, Vector3 blockPosition, BlockFace visibleFaces, List<VertexColorTexture> vertices, List<uint32> indices, ref uint32 lastIndex)
|
public override void GenerateGeometry(Block block, Vector3 blockPosition, BlockFace visibleFaces, List<VertexColorTexture> vertices, List<uint32> indices, ref uint32 lastIndex)
|
||||||
{
|
{
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Back))
|
if(visibleFaces.HasFlag(.Back))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, sideCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, sideCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, sideCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, sideCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Top))
|
if(visibleFaces.HasFlag(.Top))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, topCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, topCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, topCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, topCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Bottom))
|
if(visibleFaces.HasFlag(.Bottom))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, bottomCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, bottomCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, bottomCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, bottomCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Front))
|
if(visibleFaces.HasFlag(.Front))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, sideCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, sideCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, sideCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, sideCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Right))
|
if(visibleFaces.HasFlag(.Right))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 0), _color, sideCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 0, 1), _color, sideCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 1), _color, sideCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(1, 1, 0), _color, sideCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(visibleFaces.HasFlag(.Left))
|
if(visibleFaces.HasFlag(.Left))
|
||||||
{
|
{
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, .UnitY));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 1), _color, sideCoords.UnitY));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, .One));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 0, 0), _color, sideCoords.One));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, .UnitX));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 0), _color, sideCoords.UnitX));
|
||||||
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, .Zero));
|
vertices.Add(VertexColorTexture(blockPosition + .(0, 1, 1), _color, sideCoords.Zero));
|
||||||
|
|
||||||
AddQuadIndices(ref lastIndex, indices);
|
AddQuadIndices(ref lastIndex, indices);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
|
||||||
|
namespace Sandbox.VoxelFun
|
||||||
|
{
|
||||||
|
class BlockTexture
|
||||||
|
{
|
||||||
|
private String _fileName;
|
||||||
|
|
||||||
|
public String FileName => _fileName;
|
||||||
|
|
||||||
|
private Vector2 _atlasStart;
|
||||||
|
private Vector2 _atlasSize;
|
||||||
|
|
||||||
|
[AllowAppend]
|
||||||
|
public this(String fileName)
|
||||||
|
{
|
||||||
|
String str = append String(fileName);
|
||||||
|
|
||||||
|
_fileName = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector2 TransformTexCoords(Vector2 texCoords)
|
||||||
|
{
|
||||||
|
return _atlasStart + texCoords * _atlasSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using System.Collections;
|
||||||
|
using GlitchyEngine.Renderer;
|
||||||
|
using GlitchyEngine.Math;
|
||||||
|
using GlitchyEngine;
|
||||||
|
using DirectX.D3D11;
|
||||||
|
using System;
|
||||||
|
namespace Sandbox.VoxelFun
|
||||||
|
{
|
||||||
|
class BlockTextures
|
||||||
|
{
|
||||||
|
static List<BlockTexture> _textures = new List<BlockTexture>() ~ DeleteContainerAndItems!(_);
|
||||||
|
|
||||||
|
static TextureAtlas _atlas ~ _?.ReleaseRef();
|
||||||
|
|
||||||
|
public static BlockTexture Stone;
|
||||||
|
public static BlockTexture Dirt;
|
||||||
|
public static BlockTexture GrassTop;
|
||||||
|
public static BlockTexture GrassSide;
|
||||||
|
|
||||||
|
public static TextureAtlas Atlas => _atlas;
|
||||||
|
|
||||||
|
public static void Init(GraphicsContext context)
|
||||||
|
{
|
||||||
|
Stone = RegisterTexture(.. new BlockTexture("Content\\Textures\\Stone.png"));
|
||||||
|
Dirt = RegisterTexture(.. new BlockTexture("Content\\Textures\\Dirt.png"));
|
||||||
|
GrassTop = RegisterTexture(.. new BlockTexture("Content\\Textures\\GrassTop.png"));
|
||||||
|
GrassSide = RegisterTexture(.. new BlockTexture("Content\\Textures\\GrassSide.png"));
|
||||||
|
|
||||||
|
GenerateAtlas(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterTexture(BlockTexture texture)
|
||||||
|
{
|
||||||
|
_textures.Add(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void GenerateAtlas(GraphicsContext context)
|
||||||
|
{
|
||||||
|
_atlas = new TextureAtlas(context, _textures);
|
||||||
|
|
||||||
|
GlitchyEngine.Renderer.SamplerStateDescription desc = .();
|
||||||
|
desc.MagFilter = .Point;
|
||||||
|
desc.MinFilter = .Point;
|
||||||
|
desc.MipFilter = .Point;
|
||||||
|
desc.MipMaxLOD = 0;
|
||||||
|
desc.MipMinLOD = 0;
|
||||||
|
desc.MaxAnisotropy = 0;
|
||||||
|
|
||||||
|
_atlas.SamplerState = new SamplerState(context, desc)..ReleaseRefNoDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextureAtlas : Texture
|
||||||
|
{
|
||||||
|
// TODO: native code
|
||||||
|
private ID3D11Texture2D* nativeTexture ~ _?.Release();
|
||||||
|
|
||||||
|
public override extern uint32 Width {get;}
|
||||||
|
public override extern uint32 Height {get;}
|
||||||
|
public override uint32 Depth => 1;
|
||||||
|
public override extern uint32 ArraySize {get;}
|
||||||
|
public override extern uint32 MipLevels {get;}
|
||||||
|
|
||||||
|
public this(GraphicsContext context, List<BlockTexture> textures) : base(context)
|
||||||
|
{
|
||||||
|
// Todo: rewrite this garbage algorithm
|
||||||
|
|
||||||
|
List<(Color* data, uint32 width, uint32 height, BlockTexture blockTex)> textureDatas = new .(textures.Count);
|
||||||
|
defer
|
||||||
|
{
|
||||||
|
for(let item in textureDatas)
|
||||||
|
{
|
||||||
|
LodePng.LodePng.Free(item.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete textureDatas;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 maxWidth = 0;
|
||||||
|
uint32 maxHeight = 0;
|
||||||
|
|
||||||
|
for(let texture in textures)
|
||||||
|
{
|
||||||
|
(Color* data, uint32 width, uint32 height, BlockTexture blockTex) tex;
|
||||||
|
tex.blockTex = texture;
|
||||||
|
|
||||||
|
let error = LodePng.LodePng.Decode32File(out tex.data, out tex.width, out tex.height, texture.FileName);
|
||||||
|
|
||||||
|
if(error != 0)
|
||||||
|
Log.ClientLogger.Error($"Failed to load texture \"{texture.FileName}\". ({error}) \"{StringView(LodePng.LodePng.ErrorText(error))}\"");
|
||||||
|
|
||||||
|
if(tex.width > maxWidth)
|
||||||
|
maxWidth = tex.width;
|
||||||
|
|
||||||
|
if(tex.height > maxHeight)
|
||||||
|
maxHeight = tex.height;
|
||||||
|
|
||||||
|
textureDatas.Add(tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 texturesX = (.)System.Math.Ceiling(System.Math.Sqrt(textureDatas.Count));
|
||||||
|
uint32 texturesY = (.)System.Math.Ceiling((float)textureDatas.Count / (float)texturesX);
|
||||||
|
|
||||||
|
uint32 textureWidth = texturesX * maxWidth;
|
||||||
|
uint32 textureHeight = texturesY * maxHeight;
|
||||||
|
|
||||||
|
Color[] atlasColors = new DirectX.Color[textureWidth * textureHeight];
|
||||||
|
defer delete atlasColors;
|
||||||
|
|
||||||
|
uint32 currentX = 0;
|
||||||
|
uint32 currentY = 0;
|
||||||
|
|
||||||
|
for(let texture in textureDatas)
|
||||||
|
{
|
||||||
|
texture.blockTex.[Friend]_atlasStart = .((float)currentX / (float)textureWidth, (float)currentY / (float)textureHeight);
|
||||||
|
texture.blockTex.[Friend]_atlasSize = .((float)texture.width / (float)textureWidth, (float)texture.height / (float)textureHeight);
|
||||||
|
|
||||||
|
for(uint32 penY = currentY, uint32 y = 0; y < texture.height; penY++, y++)
|
||||||
|
for(uint32 penX = currentX, uint32 x = 0; x < texture.width; penX++, x++)
|
||||||
|
{
|
||||||
|
atlasColors[penX + penY * textureWidth] = texture.data[x + y * texture.width];
|
||||||
|
}
|
||||||
|
|
||||||
|
currentX += maxWidth;
|
||||||
|
if(currentX >= textureWidth)
|
||||||
|
{
|
||||||
|
currentX = 0;
|
||||||
|
currentY += maxHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture2DDescription desc;
|
||||||
|
desc.Width = textureWidth;
|
||||||
|
desc.Height = textureHeight;
|
||||||
|
desc.ArraySize = 1;
|
||||||
|
desc.MipLevels = 1;
|
||||||
|
desc.SampleDesc = .(1, 0);
|
||||||
|
desc.Usage = .Immutable;
|
||||||
|
desc.MiscFlags = .None;
|
||||||
|
desc.BindFlags = .ShaderResource;
|
||||||
|
desc.CpuAccessFlags = .None;
|
||||||
|
desc.Format = .R8G8B8A8_UNorm;
|
||||||
|
|
||||||
|
//SubresourceData data = .(atlasColors.CArray(), textureWidth, textureWidth * textureHeight);
|
||||||
|
SubresourceData data = .(atlasColors.CArray(), textureWidth * sizeof(Color), 0);
|
||||||
|
|
||||||
|
// TODO: this is bad platform dependant code...
|
||||||
|
var result = _context.[Friend]nativeDevice.CreateTexture2D(ref desc, &data, &nativeTexture);
|
||||||
|
|
||||||
|
//LodePng.LodePng.Encode32File("testout.png", textureD)
|
||||||
|
|
||||||
|
if(result.Failed)
|
||||||
|
Log.ClientLogger.Error($"Failed to create atlas texture. Error ({result.Underlying}): {result}");
|
||||||
|
|
||||||
|
result = _context.[Friend]nativeDevice.CreateShaderResourceView(nativeTexture, null, &nativeView);
|
||||||
|
|
||||||
|
if(result.Failed)
|
||||||
|
Log.ClientLogger.Error($"Failed to create atlas texture. Error ({result.Underlying}): {result}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -84,7 +84,6 @@ namespace Sandbox.VoxelFun
|
|||||||
private HashSet<Int32_3> _generatingChunks = new .() ~ delete _;
|
private HashSet<Int32_3> _generatingChunks = new .() ~ delete _;
|
||||||
private Monitor _generatingChunksLock = new .() ~ delete _;
|
private Monitor _generatingChunksLock = new .() ~ delete _;
|
||||||
|
|
||||||
public Texture2D Texture ~ _?.ReleaseRef();
|
|
||||||
public Effect TextureEffect ~ _?.ReleaseRef();
|
public Effect TextureEffect ~ _?.ReleaseRef();
|
||||||
|
|
||||||
VoxelGeometryGenerator voxelGeoGen = new VoxelGeometryGenerator() ~ delete _;
|
VoxelGeometryGenerator voxelGeoGen = new VoxelGeometryGenerator() ~ delete _;
|
||||||
@@ -618,7 +617,7 @@ namespace Sandbox.VoxelFun
|
|||||||
|
|
||||||
chunkGeo.AddRef();
|
chunkGeo.AddRef();
|
||||||
|
|
||||||
Texture.Bind();
|
BlockTextures.Atlas.Bind();
|
||||||
|
|
||||||
Renderer.Submit(chunk.Geometry, TextureEffect, chunk.Transform);
|
Renderer.Submit(chunk.Geometry, TextureEffect, chunk.Transform);
|
||||||
chunkGeo.ReleaseRef();
|
chunkGeo.ReleaseRef();
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ namespace Sandbox.VoxelFun
|
|||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
Stone = RegisterModel(Blocks.Stone, .. new BlockModel(.Gray));
|
Stone = RegisterModel(Blocks.Stone, .. new BlockModel(.White, BlockTextures.Stone));
|
||||||
Grass = RegisterModel(Blocks.Grass, .. new BlockModel(.Green));
|
Grass = RegisterModel(Blocks.Grass, .. new BlockModel(.White, BlockTextures.GrassTop, BlockTextures.GrassSide, BlockTextures.Dirt));
|
||||||
Dirt = RegisterModel(Blocks.Dirt, .. new BlockModel(.Brown));
|
Dirt = RegisterModel(Blocks.Dirt, .. new BlockModel(.White, BlockTextures.Dirt));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RegisterModel(Block block, Model model)
|
public static void RegisterModel(Block block, Model model)
|
||||||
|
|||||||
@@ -88,10 +88,12 @@ namespace Sandbox.VoxelFun
|
|||||||
public this() : base("VoxelTest")
|
public this() : base("VoxelTest")
|
||||||
{
|
{
|
||||||
Blocks.Init();
|
Blocks.Init();
|
||||||
Models.Init();
|
|
||||||
|
|
||||||
_context = Application.Get().Window.Context..AddRef();
|
_context = Application.Get().Window.Context..AddRef();
|
||||||
|
|
||||||
|
BlockTextures.Init(_context);
|
||||||
|
Models.Init();
|
||||||
|
|
||||||
_effectLibrary = new EffectLibrary(_context);
|
_effectLibrary = new EffectLibrary(_context);
|
||||||
|
|
||||||
_depthStencilTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height);
|
_depthStencilTarget = new DepthStencilTarget(_context, _context.SwapChain.Width, _context.SwapChain.Height);
|
||||||
@@ -255,7 +257,6 @@ namespace Sandbox.VoxelFun
|
|||||||
}
|
}
|
||||||
|
|
||||||
_world.ChunkManager = new ChunkManager(_context, _vertexLayout, _world);
|
_world.ChunkManager = new ChunkManager(_context, _vertexLayout, _world);
|
||||||
_world.ChunkManager.Texture = _texture..AddRef();
|
|
||||||
_world.ChunkManager.TextureEffect = _textureEffect..AddRef();
|
_world.ChunkManager.TextureEffect = _textureEffect..AddRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user