mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-07-22 12:49:18 +02:00
fixed animation rate of scene elements
This commit is contained in:
@@ -205,8 +205,9 @@ void TAnimContainer::AnimSetVMD(double fNewSpeed)
|
|||||||
// "+AnsiString(pMovementData->f3Vector.y)+" "+AnsiString(pMovementData->f3Vector.z));
|
// "+AnsiString(pMovementData->f3Vector.y)+" "+AnsiString(pMovementData->f3Vector.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TAnimContainer::UpdateModel()
|
// przeliczanie animacji wykonać tylko raz na model
|
||||||
{ // przeliczanie animacji wykonać tylko raz na model
|
void TAnimContainer::UpdateModel() {
|
||||||
|
|
||||||
if (pSubModel) // pozbyć się tego - sprawdzać wcześniej
|
if (pSubModel) // pozbyć się tego - sprawdzać wcześniej
|
||||||
{
|
{
|
||||||
if (fTranslateSpeed != 0.0)
|
if (fTranslateSpeed != 0.0)
|
||||||
@@ -236,7 +237,7 @@ void TAnimContainer::UpdateModel()
|
|||||||
// zakończeniu
|
// zakończeniu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fRotateSpeed != 0)
|
if (fRotateSpeed != 0.0)
|
||||||
{
|
{
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -303,8 +304,9 @@ void TAnimContainer::UpdateModel()
|
|||||||
// zakończeniu
|
// zakończeniu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fAngleSpeed != 0.0)
|
if( fAngleSpeed != 0.f ) {
|
||||||
{ // obrót kwaternionu (interpolacja)
|
// NOTE: this is angle- not quaternion-based rotation TBD, TODO: switch to quaternion rotations?
|
||||||
|
fAngleCurrent += fAngleSpeed * Timer::GetDeltaTime(); // aktualny parametr interpolacji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -322,15 +324,14 @@ void TAnimContainer::PrepareModel()
|
|||||||
{
|
{
|
||||||
if (fAngleSpeed > 0.0f)
|
if (fAngleSpeed > 0.0f)
|
||||||
{
|
{
|
||||||
fAngleCurrent +=
|
|
||||||
fAngleSpeed * Timer::GetDeltaTime(); // aktualny parametr interpolacji
|
|
||||||
if (fAngleCurrent >= 1.0f)
|
if (fAngleCurrent >= 1.0f)
|
||||||
{ // interpolacja zakończona, ustawienie na pozycję końcową
|
{ // interpolacja zakończona, ustawienie na pozycję końcową
|
||||||
qCurrent = qDesired;
|
qCurrent = qDesired;
|
||||||
fAngleSpeed = 0.0; // wyłączenie przeliczania wektora
|
fAngleSpeed = 0.0; // wyłączenie przeliczania wektora
|
||||||
if (evDone)
|
if( evDone ) {
|
||||||
Global::AddToQuery(evDone,
|
// wykonanie eventu informującego o zakończeniu
|
||||||
NULL); // wykonanie eventu informującego o zakończeniu
|
Global::AddToQuery( evDone, NULL );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{ // obliczanie pozycji pośredniej
|
{ // obliczanie pozycji pośredniej
|
||||||
@@ -412,9 +413,9 @@ TAnimModel::TAnimModel()
|
|||||||
iNumLights = 0;
|
iNumLights = 0;
|
||||||
fBlinkTimer = 0;
|
fBlinkTimer = 0;
|
||||||
|
|
||||||
for (int i = 0; i < iMaxNumLights; i++)
|
for (int i = 0; i < iMaxNumLights; ++i)
|
||||||
{
|
{
|
||||||
LightsOn[i] = LightsOff[i] = NULL; // normalnie nie ma
|
LightsOn[i] = LightsOff[i] = nullptr; // normalnie nie ma
|
||||||
lsLights[i] = ls_Off; // a jeśli są, to wyłączone
|
lsLights[i] = ls_Off; // a jeśli są, to wyłączone
|
||||||
}
|
}
|
||||||
vAngle.x = vAngle.y = vAngle.z = 0.0; // zerowanie obrotów egzemplarza
|
vAngle.x = vAngle.y = vAngle.z = 0.0; // zerowanie obrotów egzemplarza
|
||||||
@@ -550,8 +551,15 @@ TAnimContainer * TAnimModel::GetContainer(char *pName)
|
|||||||
return AddContainer(pName);
|
return AddContainer(pName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TAnimModel::RaAnimate()
|
// przeliczenie animacji - jednorazowo na klatkę
|
||||||
{ // przeliczenie animacji - jednorazowo na klatkę
|
void TAnimModel::RaAnimate( unsigned int const Framestamp ) {
|
||||||
|
|
||||||
|
if( Framestamp == m_framestamp ) { return; }
|
||||||
|
|
||||||
|
fBlinkTimer -= Timer::GetDeltaTime();
|
||||||
|
if( fBlinkTimer <= 0 )
|
||||||
|
fBlinkTimer += fOffTime;
|
||||||
|
|
||||||
// Ra 2F1I: to by można pomijać dla modeli bez animacji, których jest większość
|
// Ra 2F1I: to by można pomijać dla modeli bez animacji, których jest większość
|
||||||
TAnimContainer *pCurrent;
|
TAnimContainer *pCurrent;
|
||||||
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
|
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext)
|
||||||
@@ -560,15 +568,14 @@ void TAnimModel::RaAnimate()
|
|||||||
// if () //tylko dla modeli z IK !!!!
|
// if () //tylko dla modeli z IK !!!!
|
||||||
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext) // albo osobny łańcuch
|
for (pCurrent = pRoot; pCurrent != NULL; pCurrent = pCurrent->pNext) // albo osobny łańcuch
|
||||||
pCurrent->UpdateModelIK(); // przeliczenie odwrotnej kinematyki
|
pCurrent->UpdateModelIK(); // przeliczenie odwrotnej kinematyki
|
||||||
|
|
||||||
|
m_framestamp = Framestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
void TAnimModel::RaPrepare()
|
void TAnimModel::RaPrepare()
|
||||||
{ // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza
|
{ // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza
|
||||||
fBlinkTimer -= Timer::GetDeltaTime();
|
|
||||||
if (fBlinkTimer <= 0)
|
|
||||||
fBlinkTimer = fOffTime;
|
|
||||||
bool state; // stan światła
|
bool state; // stan światła
|
||||||
for (int i = 0; i < iNumLights; i++)
|
for (int i = 0; i < iNumLights; ++i)
|
||||||
{
|
{
|
||||||
switch (lsLights[i])
|
switch (lsLights[i])
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -149,8 +149,9 @@ class TAnimModel {
|
|||||||
TLightState lsLights[iMaxNumLights];
|
TLightState lsLights[iMaxNumLights];
|
||||||
float fDark; // poziom zapalanie światła (powinno być chyba powiązane z danym światłem?)
|
float fDark; // poziom zapalanie światła (powinno być chyba powiązane z danym światłem?)
|
||||||
float fOnTime, fOffTime; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza
|
float fOnTime, fOffTime; // były stałymi, teraz mogą być zmienne dla każdego egzemplarza
|
||||||
private:
|
unsigned int m_framestamp { 0 }; // id of last rendered gfx frame
|
||||||
void RaAnimate(); // przeliczenie animacji egzemplarza
|
private:
|
||||||
|
void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji egzemplarza
|
||||||
void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu
|
void RaPrepare(); // ustawienie animacji egzemplarza na wzorcu
|
||||||
public:
|
public:
|
||||||
static TAnimContainer *acAnimList; // lista animacji z eventem, które muszą być przeliczane również bez wyświetlania
|
static TAnimContainer *acAnimList; // lista animacji z eventem, które muszą być przeliczane również bez wyświetlania
|
||||||
|
|||||||
14
Driver.cpp
14
Driver.cpp
@@ -1253,7 +1253,7 @@ TCommandType TController::TableUpdate(double &fVelDes, double &fDist, double &fN
|
|||||||
if( fBrakeDist > 0.0 ) {
|
if( fBrakeDist > 0.0 ) {
|
||||||
// maintain desired acc while we have enough room to brake safely, when close enough start paying attention
|
// maintain desired acc while we have enough room to brake safely, when close enough start paying attention
|
||||||
// try to make a smooth transition instead of sharp change
|
// try to make a smooth transition instead of sharp change
|
||||||
a = interpolate( a, fAcc, clamp( ( d - fBrakeDist ) / fBrakeDist, 0.0, 1.0 ) );
|
a = interpolate( a, AccPreferred, clamp( ( d - fBrakeDist ) / fBrakeDist, 0.0, 1.0 ) );
|
||||||
}
|
}
|
||||||
if( ( d < fMinProximityDist )
|
if( ( d < fMinProximityDist )
|
||||||
&& ( v < fVelDes ) ) {
|
&& ( v < fVelDes ) ) {
|
||||||
@@ -4059,17 +4059,13 @@ bool TController::UpdateSituation(double dt)
|
|||||||
switch (comm)
|
switch (comm)
|
||||||
{ // ustawienie VelSignal - trochę proteza = do przemyślenia
|
{ // ustawienie VelSignal - trochę proteza = do przemyślenia
|
||||||
case cm_Ready: // W4 zezwolił na jazdę
|
case cm_Ready: // W4 zezwolił na jazdę
|
||||||
TableCheck(
|
// ewentualne doskanowanie trasy za W4, który zezwolił na jazdę
|
||||||
scanmax); // ewentualne doskanowanie trasy za W4, który zezwolił na jazdę
|
TableCheck( scanmax);
|
||||||
TableUpdate(VelDesired, ActualProximityDist, VelNext,
|
TableUpdate(VelDesired, ActualProximityDist, VelNext, AccDesired); // aktualizacja po skanowaniu
|
||||||
AccDesired); // aktualizacja po skanowaniu
|
|
||||||
// if (comm!=cm_SetVelocity) //jeśli dalej jest kolejny W4, to ma zwrócić
|
|
||||||
// cm_SetVelocity
|
|
||||||
if (VelNext == 0.0)
|
if (VelNext == 0.0)
|
||||||
break; // ale jak coś z przodu zamyka, to ma stać
|
break; // ale jak coś z przodu zamyka, to ma stać
|
||||||
if (iDrivigFlags & moveStopCloser)
|
if (iDrivigFlags & moveStopCloser)
|
||||||
VelSignal = -1.0; // niech jedzie, jak W4 puściło - nie, ma czekać na
|
VelSignal = -1.0; // ma czekać na sygnał z sygnalizatora!
|
||||||
// sygnał z sygnalizatora!
|
|
||||||
case cm_SetVelocity: // od wersji 357 semafor nie budzi wyłączonej lokomotywy
|
case cm_SetVelocity: // od wersji 357 semafor nie budzi wyłączonej lokomotywy
|
||||||
if (!(OrderList[OrderPos] &
|
if (!(OrderList[OrderPos] &
|
||||||
~(Obey_train | Shunt))) // jedzie w dowolnym trybie albo Wait_for_orders
|
~(Obey_train | Shunt))) // jedzie w dowolnym trybie albo Wait_for_orders
|
||||||
|
|||||||
12
Ground.cpp
12
Ground.cpp
@@ -356,13 +356,17 @@ bool TSubRect::RaTrackAnimAdd(TTrack *t)
|
|||||||
return false; // będzie animowane...
|
return false; // będzie animowane...
|
||||||
}
|
}
|
||||||
|
|
||||||
void TSubRect::RaAnimate()
|
// wykonanie animacji
|
||||||
{ // wykonanie animacji
|
void TSubRect::RaAnimate( unsigned int const Framestamp ) {
|
||||||
if( tTrackAnim == nullptr ) {
|
|
||||||
|
if( ( tTrackAnim == nullptr )
|
||||||
|
|| ( Framestamp == m_framestamp ) ) {
|
||||||
// nie ma nic do animowania
|
// nie ma nic do animowania
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego
|
tTrackAnim = tTrackAnim->RaAnimate(); // przeliczenie animacji kolejnego
|
||||||
|
|
||||||
|
m_framestamp = Framestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
TTraction * TSubRect::FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude)
|
TTraction * TSubRect::FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude)
|
||||||
@@ -431,8 +435,6 @@ void TSubRect::RenderSounds()
|
|||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
//------------------ Kwadrat kilometrowy ------------------------------------
|
//------------------ Kwadrat kilometrowy ------------------------------------
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
int TGroundRect::iFrameNumber = 0; // licznik wyświetlanych klatek
|
|
||||||
|
|
||||||
TGroundRect::~TGroundRect()
|
TGroundRect::~TGroundRect()
|
||||||
{
|
{
|
||||||
SafeDeleteArray(pSubRects);
|
SafeDeleteArray(pSubRects);
|
||||||
|
|||||||
8
Ground.h
8
Ground.h
@@ -171,6 +171,7 @@ class TSubRect : /*public Resource,*/ public CMesh
|
|||||||
{ // sektor składowy kwadratu kilometrowego
|
{ // sektor składowy kwadratu kilometrowego
|
||||||
public:
|
public:
|
||||||
bounding_area m_area;
|
bounding_area m_area;
|
||||||
|
unsigned int m_framestamp { 0 }; // id of last rendered gfx frame
|
||||||
int iTracks = 0; // ilość torów w (tTracks)
|
int iTracks = 0; // ilość torów w (tTracks)
|
||||||
TTrack **tTracks = nullptr; // tory do renderowania pojazdów
|
TTrack **tTracks = nullptr; // tory do renderowania pojazdów
|
||||||
protected:
|
protected:
|
||||||
@@ -192,15 +193,12 @@ class TSubRect : /*public Resource,*/ public CMesh
|
|||||||
void LoadNodes(); // utworzenie VBO sektora
|
void LoadNodes(); // utworzenie VBO sektora
|
||||||
public:
|
public:
|
||||||
virtual ~TSubRect();
|
virtual ~TSubRect();
|
||||||
/*
|
|
||||||
virtual void Release(); // zwalnianie VBO sektora
|
|
||||||
*/
|
|
||||||
virtual void NodeAdd(TGroundNode *Node); // dodanie obiektu do sektora na etapie rozdzielania na sektory
|
virtual void NodeAdd(TGroundNode *Node); // dodanie obiektu do sektora na etapie rozdzielania na sektory
|
||||||
void Sort(); // optymalizacja obiektów w sektorze (sortowanie wg tekstur)
|
void Sort(); // optymalizacja obiektów w sektorze (sortowanie wg tekstur)
|
||||||
TTrack * FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude);
|
TTrack * FindTrack(vector3 *Point, int &iConnection, TTrack *Exclude);
|
||||||
TTraction * FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude);
|
TTraction * FindTraction(glm::dvec3 const &Point, int &iConnection, TTraction *Exclude);
|
||||||
bool RaTrackAnimAdd(TTrack *t); // zgłoszenie toru do animacji
|
bool RaTrackAnimAdd(TTrack *t); // zgłoszenie toru do animacji
|
||||||
void RaAnimate(); // przeliczenie animacji torów
|
void RaAnimate( unsigned int const Framestamp ); // przeliczenie animacji torów
|
||||||
void RenderSounds(); // dźwięki pojazdów z niewidocznych sektorów
|
void RenderSounds(); // dźwięki pojazdów z niewidocznych sektorów
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,7 +219,6 @@ class TGroundRect : public TSubRect
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
TSubRect *pSubRects { nullptr };
|
TSubRect *pSubRects { nullptr };
|
||||||
int iLastDisplay; // numer klatki w której był ostatnio wyświetlany
|
|
||||||
|
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
@@ -251,7 +248,6 @@ public:
|
|||||||
// optymalizacja obiektów w sektorach
|
// optymalizacja obiektów w sektorach
|
||||||
pSubRects[ i ].Sort(); } } };
|
pSubRects[ i ].Sort(); } } };
|
||||||
|
|
||||||
static int iFrameNumber; // numer kolejny wyświetlanej klatki
|
|
||||||
TGroundNode *nTerrain { nullptr }; // model terenu z E3D - użyć nRootMesh?
|
TGroundNode *nTerrain { nullptr }; // model terenu z E3D - użyć nRootMesh?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
15
renderer.cpp
15
renderer.cpp
@@ -320,6 +320,7 @@ opengl_renderer::Render() {
|
|||||||
|
|
||||||
m_renderpass.draw_mode = rendermode::none; // force setup anew
|
m_renderpass.draw_mode = rendermode::none; // force setup anew
|
||||||
m_debuginfo.clear();
|
m_debuginfo.clear();
|
||||||
|
++m_framestamp;
|
||||||
Render_pass( rendermode::color );
|
Render_pass( rendermode::color );
|
||||||
|
|
||||||
m_drawcount = m_drawqueue.size();
|
m_drawcount = m_drawqueue.size();
|
||||||
@@ -1330,8 +1331,6 @@ opengl_renderer::Texture( texture_handle const Texture ) const {
|
|||||||
bool
|
bool
|
||||||
opengl_renderer::Render( TGround *Ground ) {
|
opengl_renderer::Render( TGround *Ground ) {
|
||||||
|
|
||||||
++TGroundRect::iFrameNumber; // zwięszenie licznika ramek (do usuwniania nadanimacji)
|
|
||||||
|
|
||||||
m_drawqueue.clear();
|
m_drawqueue.clear();
|
||||||
|
|
||||||
switch( m_renderpass.draw_mode ) {
|
switch( m_renderpass.draw_mode ) {
|
||||||
@@ -1440,8 +1439,6 @@ opengl_renderer::Render( TGroundRect *Groundcell ) {
|
|||||||
|
|
||||||
bool result { false }; // will be true if we do any rendering
|
bool result { false }; // will be true if we do any rendering
|
||||||
|
|
||||||
if( Groundcell->iLastDisplay != Groundcell->iFrameNumber ) {
|
|
||||||
// tylko jezeli dany kwadrat nie był jeszcze renderowany
|
|
||||||
Groundcell->LoadNodes(); // ewentualne tworzenie siatek
|
Groundcell->LoadNodes(); // ewentualne tworzenie siatek
|
||||||
|
|
||||||
switch( m_renderpass.draw_mode ) {
|
switch( m_renderpass.draw_mode ) {
|
||||||
@@ -1456,6 +1453,7 @@ opengl_renderer::Render( TGroundRect *Groundcell ) {
|
|||||||
for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) {
|
for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) {
|
||||||
Render( node );
|
Render( node );
|
||||||
}
|
}
|
||||||
|
result = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1467,6 +1465,7 @@ opengl_renderer::Render( TGroundRect *Groundcell ) {
|
|||||||
for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) {
|
for( TGroundNode *node = Groundcell->nRenderRect; node != nullptr; node = node->nNext3 ) {
|
||||||
Render( node );
|
Render( node );
|
||||||
}
|
}
|
||||||
|
result = true;
|
||||||
::glEnable( GL_CULL_FACE );
|
::glEnable( GL_CULL_FACE );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1481,8 +1480,6 @@ opengl_renderer::Render( TGroundRect *Groundcell ) {
|
|||||||
Render( Groundcell->nTerrain );
|
Render( Groundcell->nTerrain );
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Groundcell->iLastDisplay = Groundcell->iFrameNumber; // drugi raz nie potrzeba
|
|
||||||
result = true;
|
|
||||||
|
|
||||||
// add the subcells of the cell to the draw queue
|
// add the subcells of the cell to the draw queue
|
||||||
switch( m_renderpass.draw_mode ) {
|
switch( m_renderpass.draw_mode ) {
|
||||||
@@ -1508,7 +1505,6 @@ opengl_renderer::Render( TGroundRect *Groundcell ) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1518,7 +1514,8 @@ opengl_renderer::Render( TSubRect *Groundsubcell ) {
|
|||||||
// oznaczanie aktywnych sektorów
|
// oznaczanie aktywnych sektorów
|
||||||
Groundsubcell->LoadNodes();
|
Groundsubcell->LoadNodes();
|
||||||
|
|
||||||
Groundsubcell->RaAnimate(); // przeliczenia animacji torów w sektorze
|
// przeliczenia animacji torów w sektorze
|
||||||
|
Groundsubcell->RaAnimate( m_framestamp );
|
||||||
|
|
||||||
TGroundNode *node;
|
TGroundNode *node;
|
||||||
|
|
||||||
@@ -1667,7 +1664,7 @@ opengl_renderer::Render( TGroundNode *Node ) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Node->Model->RaAnimate(); // jednorazowe przeliczenie animacji
|
Node->Model->RaAnimate( m_framestamp ); // jednorazowe przeliczenie animacji
|
||||||
Node->Model->RaPrepare();
|
Node->Model->RaPrepare();
|
||||||
if( Node->Model->pModel ) {
|
if( Node->Model->pModel ) {
|
||||||
// renderowanie rekurencyjne submodeli
|
// renderowanie rekurencyjne submodeli
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ private:
|
|||||||
int m_diffusetextureunit{ GL_TEXTURE3 };
|
int m_diffusetextureunit{ GL_TEXTURE3 };
|
||||||
units_state m_unitstate;
|
units_state m_unitstate;
|
||||||
|
|
||||||
|
unsigned int m_framestamp; // id of currently rendered gfx frame
|
||||||
float m_drawtime { 1000.f / 30.f * 20.f }; // start with presumed 'neutral' average of 30 fps
|
float m_drawtime { 1000.f / 30.f * 20.f }; // start with presumed 'neutral' average of 30 fps
|
||||||
std::chrono::steady_clock::time_point m_drawstart; // cached start time of previous frame
|
std::chrono::steady_clock::time_point m_drawstart; // cached start time of previous frame
|
||||||
float m_framerate;
|
float m_framerate;
|
||||||
|
|||||||
Reference in New Issue
Block a user