Radiant:
misc... * oranje names for group entities, having childrens selected (also no distance cull in cam for those)
This commit is contained in:
parent
6a7758012d
commit
62ccd942e5
|
|
@ -25,6 +25,70 @@
|
||||||
#include <pango/pangoft2.h>
|
#include <pango/pangoft2.h>
|
||||||
#include <pango/pango-utils.h>
|
#include <pango/pango-utils.h>
|
||||||
|
|
||||||
|
|
||||||
|
void gray_to_texture( const int x_max, const int y_max, const unsigned char *in, unsigned char *out, const unsigned int fontColorR, const unsigned int fontColorG, const unsigned int fontColorB ){ /* normal with shadow */
|
||||||
|
int x, y, bitmapIter = 0;
|
||||||
|
|
||||||
|
const unsigned int backgroundColorR = 0;
|
||||||
|
const unsigned int backgroundColorG = 0;
|
||||||
|
const unsigned int backgroundColorB = 0;
|
||||||
|
|
||||||
|
for( y = 0; y < y_max; y++ ) {
|
||||||
|
for( x = 0; x < x_max; x++ ) {
|
||||||
|
int iter = ( y * x_max + x ) * 4;
|
||||||
|
if( x == 0 || y == 0 || x == 1 || y == 1 ) {
|
||||||
|
out[iter] = fontColorB;
|
||||||
|
out[iter + 1] = fontColorG;
|
||||||
|
out[iter + 2] = fontColorR;
|
||||||
|
out[iter + 3] = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( in[bitmapIter] == 0 ){
|
||||||
|
out[iter] = fontColorB;
|
||||||
|
out[iter + 1] = fontColorG;
|
||||||
|
out[iter + 2] = fontColorR;
|
||||||
|
out[iter + 3] = 0;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
out[iter] = backgroundColorB;
|
||||||
|
out[iter + 1] = backgroundColorG;
|
||||||
|
out[iter + 2] = backgroundColorR;
|
||||||
|
out[iter + 3] = in[bitmapIter];
|
||||||
|
}
|
||||||
|
++bitmapIter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitmapIter = 0;
|
||||||
|
for( y = 0; y < y_max; y++ ) {
|
||||||
|
for( x = 0; x < x_max; x++ ) {
|
||||||
|
int iter = ( y * x_max + x ) * 4;
|
||||||
|
if( x == 0 || y == 0 || x == ( x_max - 1 ) || y == ( y_max - 1 ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( in[bitmapIter] != 0 ) {
|
||||||
|
if( out[iter + 3] == 0 ){
|
||||||
|
out[iter] = fontColorB;
|
||||||
|
out[iter + 1] = fontColorG;
|
||||||
|
out[iter + 2] = fontColorR;
|
||||||
|
out[iter + 3] = in[bitmapIter];
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
/* Calculate alpha (opacity). */
|
||||||
|
float opacityFont = in[bitmapIter] / 255.f;
|
||||||
|
float opacityBack = out[iter + 3] / 255.f;
|
||||||
|
out[iter] = fontColorB * opacityFont + ( 1 - opacityFont ) * backgroundColorB;
|
||||||
|
out[iter + 1] = fontColorG * opacityFont + ( 1 - opacityFont ) * backgroundColorG;
|
||||||
|
out[iter + 2] = fontColorR * opacityFont + ( 1 - opacityFont ) * backgroundColorR;
|
||||||
|
out[iter + 3] = ( opacityFont + ( 1 - opacityFont ) * opacityBack ) * 255.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++bitmapIter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// generic string printing with call lists
|
// generic string printing with call lists
|
||||||
class GLFontCallList : public GLFont
|
class GLFontCallList : public GLFont
|
||||||
{
|
{
|
||||||
|
|
@ -153,7 +217,7 @@ void renderString( const char *s, const GLuint& tex, const unsigned int colour[3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#elif 0
|
||||||
if( 1 ){ /* normal with shadow */
|
if( 1 ){ /* normal with shadow */
|
||||||
int x_max = wid;
|
int x_max = wid;
|
||||||
int y_max = hei;
|
int y_max = hei;
|
||||||
|
|
@ -224,7 +288,6 @@ void renderString( const char *s, const GLuint& tex, const unsigned int colour[3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // 0
|
|
||||||
else{ /* normal */
|
else{ /* normal */
|
||||||
int x_max = wid;
|
int x_max = wid;
|
||||||
int y_max = hei;
|
int y_max = hei;
|
||||||
|
|
@ -255,6 +318,7 @@ void renderString( const char *s, const GLuint& tex, const unsigned int colour[3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -273,8 +337,23 @@ void renderString( const char *s, const GLuint& tex, const unsigned int colour[3
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
// glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
|
// glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
|
||||||
//Here we actually create the texture itself
|
//Here we actually create the texture itself
|
||||||
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, wid, hei,
|
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, wid * 3, hei,
|
||||||
0, GL_BGRA, GL_UNSIGNED_BYTE, buf );
|
0, GL_BGRA, GL_UNSIGNED_BYTE, 0 );
|
||||||
|
|
||||||
|
/* normal with shadow */
|
||||||
|
gray_to_texture( wid, hei, bitmap.buffer, buf, colour[0], colour[1], colour[2] );
|
||||||
|
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, wid, hei, GL_BGRA, GL_UNSIGNED_BYTE, buf );
|
||||||
|
|
||||||
|
memset( buf, 0x00, 4 * hei * wid );
|
||||||
|
/* yellow selected with shadow */
|
||||||
|
gray_to_texture( wid, hei, bitmap.buffer, buf, 255, 255, 0 );
|
||||||
|
glTexSubImage2D( GL_TEXTURE_2D, 0, wid, 0, wid, hei, GL_BGRA, GL_UNSIGNED_BYTE, buf );
|
||||||
|
|
||||||
|
memset( buf, 0x00, 4 * hei * wid );
|
||||||
|
/* orange childSselected with shadow */
|
||||||
|
gray_to_texture( wid, hei, bitmap.buffer, buf, 255, 128, 0 );
|
||||||
|
glTexSubImage2D( GL_TEXTURE_2D, 0, wid * 2, 0, wid, hei, GL_BGRA, GL_UNSIGNED_BYTE, buf );
|
||||||
|
|
||||||
|
|
||||||
glBindTexture( GL_TEXTURE_2D, 0 );
|
glBindTexture( GL_TEXTURE_2D, 0 );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,7 @@ const AABB& localAABB() const {
|
||||||
return m_curveBounds;
|
return m_curveBounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, const AABB& childBounds ) const {
|
void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, bool childSelected, const AABB& childBounds ) const {
|
||||||
if ( isModel() && selected ) {
|
if ( isModel() && selected ) {
|
||||||
m_renderOrigin.render( renderer, volume, localToWorld );
|
m_renderOrigin.render( renderer, volume, localToWorld );
|
||||||
}
|
}
|
||||||
|
|
@ -375,12 +375,12 @@ void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& l
|
||||||
m_name_origin = childBounds.origin;
|
m_name_origin = childBounds.origin;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_renderName.render( renderer, volume, localToWorld, selected );
|
m_renderName.render( renderer, volume, localToWorld, selected, childSelected );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, const AABB& childBounds ) const {
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, bool childSelected, const AABB& childBounds ) const {
|
||||||
renderSolid( renderer, volume, localToWorld, selected, childBounds );
|
renderSolid( renderer, volume, localToWorld, selected, childSelected, childBounds );
|
||||||
}
|
}
|
||||||
|
|
||||||
void testSelect( Selector& selector, SelectionTest& test, SelectionIntersection& best ){
|
void testSelect( Selector& selector, SelectionTest& test, SelectionIntersection& best ){
|
||||||
|
|
@ -499,13 +499,13 @@ Doom3GroupInstance( const scene::Path& path, scene::Instance* parent, Doom3Group
|
||||||
m_contained.instanceDetach( Instance::path() );
|
m_contained.instanceDetach( Instance::path() );
|
||||||
}
|
}
|
||||||
void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
|
void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
|
||||||
m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childBounds() );
|
m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childSelected(), Instance::childBounds() );
|
||||||
|
|
||||||
m_curveNURBS.renderComponentsSelected( renderer, volume, localToWorld() );
|
m_curveNURBS.renderComponentsSelected( renderer, volume, localToWorld() );
|
||||||
m_curveCatmullRom.renderComponentsSelected( renderer, volume, localToWorld() );
|
m_curveCatmullRom.renderComponentsSelected( renderer, volume, localToWorld() );
|
||||||
}
|
}
|
||||||
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
|
||||||
m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childBounds() );
|
m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childSelected(), Instance::childBounds() );
|
||||||
|
|
||||||
m_curveNURBS.renderComponentsSelected( renderer, volume, localToWorld() );
|
m_curveNURBS.renderComponentsSelected( renderer, volume, localToWorld() );
|
||||||
m_curveCatmullRom.renderComponentsSelected( renderer, volume, localToWorld() );
|
m_curveCatmullRom.renderComponentsSelected( renderer, volume, localToWorld() );
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ void detach( scene::Traversable::Observer* observer ){
|
||||||
m_traverse.detach( observer );
|
m_traverse.detach( observer );
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, const AABB& childBounds ) const {
|
void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, bool childSelected, const AABB& childBounds ) const {
|
||||||
renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly );
|
renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly );
|
||||||
if ( g_showNames ) {
|
if ( g_showNames ) {
|
||||||
// don't draw the name for worldspawn
|
// don't draw the name for worldspawn
|
||||||
|
|
@ -159,12 +159,12 @@ void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& l
|
||||||
// place name in the middle of the "children cloud"
|
// place name in the middle of the "children cloud"
|
||||||
m_name_origin = childBounds.origin;
|
m_name_origin = childBounds.origin;
|
||||||
|
|
||||||
m_renderName.render( renderer, volume, localToWorld, selected );
|
m_renderName.render( renderer, volume, localToWorld, selected, childSelected );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, const AABB& childBounds ) const {
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, bool childSelected, const AABB& childBounds ) const {
|
||||||
renderSolid( renderer, volume, localToWorld, selected, childBounds );
|
renderSolid( renderer, volume, localToWorld, selected, childSelected, childBounds );
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateTransform(){
|
void updateTransform(){
|
||||||
|
|
@ -298,10 +298,10 @@ GroupInstance( const scene::Path& path, scene::Instance* parent, Group& group )
|
||||||
m_contained.instanceDetach( Instance::path() );
|
m_contained.instanceDetach( Instance::path() );
|
||||||
}
|
}
|
||||||
void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
|
void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
|
||||||
m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childBounds() );
|
m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childSelected(), Instance::childBounds() );
|
||||||
}
|
}
|
||||||
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
|
void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
|
||||||
m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childBounds() );
|
m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected(), Instance::childSelected(), Instance::childBounds() );
|
||||||
}
|
}
|
||||||
|
|
||||||
STRING_CONSTANT( Name, "GroupInstance" );
|
STRING_CONSTANT( Name, "GroupInstance" );
|
||||||
|
|
|
||||||
|
|
@ -95,14 +95,18 @@ typedef MemberCaller1<NamedEntity, const char*, &NamedEntity::identifierChanged>
|
||||||
#include "math/frustum.h"
|
#include "math/frustum.h"
|
||||||
|
|
||||||
class RenderableNamedEntity : public OpenGLRenderable {
|
class RenderableNamedEntity : public OpenGLRenderable {
|
||||||
|
enum ENameMode{
|
||||||
|
eNameNormal = 0,
|
||||||
|
eNameSelected = 1,
|
||||||
|
eNameChildSelected = 2,
|
||||||
|
};
|
||||||
|
mutable ENameMode m_nameMode;
|
||||||
|
|
||||||
NamedEntity& m_named;
|
NamedEntity& m_named;
|
||||||
const Vector3& m_position;
|
const Vector3& m_position;
|
||||||
mutable GLuint m_tex_normal;
|
GLuint m_tex;
|
||||||
mutable GLuint m_tex_selected;
|
|
||||||
mutable GLuint* m_tex;
|
|
||||||
int m_width;
|
int m_width;
|
||||||
int m_height;
|
int m_height;
|
||||||
unsigned int m_colour[3];
|
|
||||||
mutable float m_screenPos[2];
|
mutable float m_screenPos[2];
|
||||||
public:
|
public:
|
||||||
typedef Static<Shader*, RenderableNamedEntity> StaticShader;
|
typedef Static<Shader*, RenderableNamedEntity> StaticShader;
|
||||||
|
|
@ -110,50 +114,40 @@ public:
|
||||||
return StaticShader::instance();
|
return StaticShader::instance();
|
||||||
}
|
}
|
||||||
RenderableNamedEntity( NamedEntity& named, const Vector3& position )
|
RenderableNamedEntity( NamedEntity& named, const Vector3& position )
|
||||||
: m_named( named ), m_position( position ), m_tex_normal( 0 ), m_tex_selected( 0 ) {
|
: m_named( named ), m_position( position ), m_tex( 0 ) {
|
||||||
construct_textures( g_showTargetNames ? m_named.name() : m_named.classname() );
|
construct_textures( g_showTargetNames ? m_named.name() : m_named.classname() );
|
||||||
m_named.attach( IdentifierChangedCaller( *this ) );
|
m_named.attach( IdentifierChangedCaller( *this ) );
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void setSelected( bool selected ) const {
|
|
||||||
m_tex = selected ? &m_tex_selected : &m_tex_normal;
|
|
||||||
}
|
|
||||||
void setSelectedColour( bool selected ){
|
|
||||||
if( selected ){
|
|
||||||
m_colour[0] = 255;
|
|
||||||
m_colour[1] = 255;
|
|
||||||
m_colour[2] = 0;
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
m_colour[0] = static_cast<unsigned int>( m_named.color()[0] * 255.f );
|
|
||||||
m_colour[1] = static_cast<unsigned int>( m_named.color()[1] * 255.f );
|
|
||||||
m_colour[2] = static_cast<unsigned int>( m_named.color()[2] * 255.f );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void construct_texture( const char* name ){
|
|
||||||
glGenTextures( 1, m_tex );
|
|
||||||
if( *m_tex > 0 ) {
|
|
||||||
GlobalOpenGL().m_font->renderString( name, *m_tex, m_colour, m_width, m_height );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void construct_textures( const char* name ){
|
void construct_textures( const char* name ){
|
||||||
setSelected( false );
|
glGenTextures( 1, &m_tex );
|
||||||
setSelectedColour( false );
|
if( m_tex > 0 ) {
|
||||||
construct_texture( name );
|
unsigned int colour[3];
|
||||||
setSelected( true );
|
colour[0] = static_cast<unsigned int>( m_named.color()[0] * 255.f );
|
||||||
setSelectedColour( true );
|
colour[1] = static_cast<unsigned int>( m_named.color()[1] * 255.f );
|
||||||
construct_texture( name );
|
colour[2] = static_cast<unsigned int>( m_named.color()[2] * 255.f );
|
||||||
|
GlobalOpenGL().m_font->renderString( name, m_tex, colour, m_width, m_height );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void delete_textures(){
|
void delete_textures(){
|
||||||
glDeleteTextures( 1, &m_tex_normal );
|
glDeleteTextures( 1, &m_tex );
|
||||||
glDeleteTextures( 1, &m_tex_selected );
|
m_tex = 0;
|
||||||
m_tex_normal = 0;
|
}
|
||||||
m_tex_selected = 0;
|
void setMode( bool selected, bool childSelected ) const{
|
||||||
|
if( selected ){
|
||||||
|
m_nameMode = eNameSelected;
|
||||||
|
}
|
||||||
|
else if( childSelected ){
|
||||||
|
m_nameMode = eNameChildSelected;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
m_nameMode = eNameNormal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
void render( RenderStateFlags state ) const {
|
void render( RenderStateFlags state ) const {
|
||||||
if( *m_tex > 0 ){
|
if( m_tex > 0 ){
|
||||||
glBindTexture( GL_TEXTURE_2D, *m_tex );
|
glBindTexture( GL_TEXTURE_2D, m_tex );
|
||||||
|
|
||||||
//Here we draw the texturemaped quads.
|
//Here we draw the texturemaped quads.
|
||||||
//The bitmap that we got from FreeType was not
|
//The bitmap that we got from FreeType was not
|
||||||
|
|
@ -161,21 +155,25 @@ public:
|
||||||
//so we need to link the texture to the quad
|
//so we need to link the texture to the quad
|
||||||
//so that the result will be properly aligned.
|
//so that the result will be properly aligned.
|
||||||
glBegin( GL_QUADS );
|
glBegin( GL_QUADS );
|
||||||
glTexCoord2i( 0, 1 );
|
float xoffset0 = m_nameMode / 3.f;
|
||||||
|
float xoffset1 = ( m_nameMode + 1 ) / 3.f;
|
||||||
|
glTexCoord2f( xoffset0, 1 );
|
||||||
glVertex2f( m_screenPos[0], m_screenPos[1] );
|
glVertex2f( m_screenPos[0], m_screenPos[1] );
|
||||||
glTexCoord2i( 0, 0 );
|
glTexCoord2f( xoffset0, 0 );
|
||||||
glVertex2f( m_screenPos[0], m_screenPos[1] + m_height + .01f );
|
glVertex2f( m_screenPos[0], m_screenPos[1] + m_height + .01f );
|
||||||
glTexCoord2i( 1, 0 );
|
glTexCoord2f( xoffset1, 0 );
|
||||||
glVertex2f( m_screenPos[0] + m_width + .01f, m_screenPos[1] + m_height + .01f );
|
glVertex2f( m_screenPos[0] + m_width + .01f, m_screenPos[1] + m_height + .01f );
|
||||||
glTexCoord2i( 1, 1 );
|
glTexCoord2f( xoffset1, 1 );
|
||||||
glVertex2f( m_screenPos[0] + m_width + .01f, m_screenPos[1] );
|
glVertex2f( m_screenPos[0] + m_width + .01f, m_screenPos[1] );
|
||||||
glEnd();
|
glEnd();
|
||||||
|
|
||||||
glBindTexture( GL_TEXTURE_2D, 0 );
|
glBindTexture( GL_TEXTURE_2D, 0 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const{
|
void render( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected, bool childSelected = false ) const{
|
||||||
if( !selected && volume.fill() ){
|
setMode( selected, childSelected );
|
||||||
|
|
||||||
|
if( m_nameMode == eNameNormal && volume.fill() ){
|
||||||
// globalOutputStream() << localToWorld << " localToWorld\n";
|
// globalOutputStream() << localToWorld << " localToWorld\n";
|
||||||
// globalOutputStream() << volume.GetModelview() << " modelview\n";
|
// globalOutputStream() << volume.GetModelview() << " modelview\n";
|
||||||
// globalOutputStream() << volume.GetProjection() << " Projection\n";
|
// globalOutputStream() << volume.GetProjection() << " Projection\n";
|
||||||
|
|
@ -191,7 +189,6 @@ public:
|
||||||
//globalOutputStream() << m_position[0] << " " << m_position[1] << " " << m_position[2] << " position\n";
|
//globalOutputStream() << m_position[0] << " " << m_position[1] << " " << m_position[2] << " position\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelected( selected );
|
|
||||||
|
|
||||||
Vector4 position;
|
Vector4 position;
|
||||||
position[0] = m_position[0];
|
position[0] = m_position[0];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user