See grand prix high scores in the GUI high score manager (#4573)
This commit is contained in:
parent
639eadaa2d
commit
35ddf4c4d9
@ -22,30 +22,9 @@
|
||||
I18N="In the high score selection screen" text="Time Trial"/>
|
||||
<icon-button id="tab_egg_hunt" width="128" height="128" icon="gui/icons/mode_easter.png"
|
||||
I18N="In the high score selection screen" text="Egg Hunt"/>
|
||||
<icon-button id="tab_grand_prix" width="128" height="128" icon="gui/icons/gp_new.png"
|
||||
I18N="In the high score selection screen" text="Grand Prix"/>
|
||||
</tabs>
|
||||
|
||||
<spacer width="100%" height="2%" />
|
||||
<!--
|
||||
<div width="98%" align="center" layout="horizontal-row" height="fit">
|
||||
<div width="60%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="best_times_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" text_align="left" I18N="In the high score selection screen" text="Only show the best times"/>
|
||||
</div>
|
||||
<div width="40%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="compare_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" id="compare-toggle-text" text_align="left" I18N="In the high score selection screen" text="Compare high scores"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div width="98%" align="center" layout="horizontal-row" height="fit">
|
||||
<div width="60%" height="fit" layout="horizontal-row" >
|
||||
<checkbox width="fit" id="high_scores_difficulty_toggle" text_align="left"/>
|
||||
<spacer width="2%" height="fit"/>
|
||||
<label height="100%" text_align="left" I18N="In the high score selection screen" text="Only show high scores matching the current difficulty"/>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</stkgui>
|
||||
|
@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<stkgui>
|
||||
<icon-button id="back" x="1%" y="0" height="9%" icon="gui/icons/back.png"/>
|
||||
|
||||
<div x="2%" y="1%" width="96%" height="98%" layout="vertical-row">
|
||||
<header id="name" height="8%" width="80%" align="center" text_align="center"/>
|
||||
<spacer width="1" height="1%"/>
|
||||
|
||||
<div width="100%" height="72%" padding="10" layout="horizontal-row">
|
||||
<box width="55%" height="100%" layout="vertical-row">
|
||||
<!-- Track screenshot -->
|
||||
<div width="100%" height="54%" layout="vertical-row">
|
||||
<icon-button proportion="1" width="100%" height="100%" id="screenshot" custom_ratio="1.33333"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<!-- Options list -->
|
||||
<div width="100%" height="45%" layout="vertical-row">
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="ai-spinner" width="100%" min_value="1" max_value="20" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="ai-text" width="52%" height="fit" I18N="In the grand prix info screen" text="AI karts" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="reverse-spinner" width="100%" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="reverse-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Reverse" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row">
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="track-spinner" width="100%" min_value="1" max_value="20" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="track-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Tracks" text_align="left"/>
|
||||
</div>
|
||||
<spacer width="1" height="1%"/>
|
||||
<div width="100%" height="fit" layout="horizontal-row" >
|
||||
<div width="45%" height="fit" layout="horizontal-row">
|
||||
<spinner id="group-spinner" width="100%" align="center" wrap_around="true" />
|
||||
</div>
|
||||
<spacer width="3%"/>
|
||||
<label id="group-text" width="52%" height="fit" I18N="In the grand prix info screen" text="Track group" text_align="left"/>
|
||||
</div>
|
||||
</div>
|
||||
</box>
|
||||
<spacer width="2%" height="1"/>
|
||||
<!-- Track list -->
|
||||
<box width="43%" height="100%" layout="vertical-row">
|
||||
<list id="tracks" width="100%" height="100%"/>
|
||||
</box>
|
||||
</div>
|
||||
|
||||
<spacer width="1" height="1%"/>
|
||||
<buttonbar id="buttons" height="17%" width="100%" align="center">
|
||||
<icon-button id="start" width="64" height="64" icon="gui/icons/green_check.png"
|
||||
I18N="In the grand prix info screen" text="Start Race"/>
|
||||
|
||||
<icon-button id="continue" width="64" height="64" icon="gui/icons/green_check.png"
|
||||
I18N="In the grand prix info screen" text="Continue saved GP"/>
|
||||
</buttonbar>
|
||||
|
||||
</div>
|
||||
</stkgui>
|
@ -26,6 +26,8 @@
|
||||
#include "input/input_manager.hpp"
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "karts/kart_properties_manager.hpp"
|
||||
#include "race/grand_prix_data.hpp"
|
||||
#include "race/grand_prix_manager.hpp"
|
||||
#include "race/highscores.hpp"
|
||||
#include "race/highscore_manager.hpp"
|
||||
#include "race/race_manager.hpp"
|
||||
@ -40,15 +42,14 @@ using namespace GUIEngine;
|
||||
using namespace irr::core;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear)
|
||||
HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear, RaceManager::MajorRaceModeType major_mode)
|
||||
: ModalDialog(0.75f,0.75f)
|
||||
{
|
||||
m_hs = highscore;
|
||||
m_major_mode = major_mode;
|
||||
|
||||
loadFromFile("high_score_info_dialog.stkgui");
|
||||
|
||||
Track* track = track_manager->getTrack(m_hs->m_track);
|
||||
|
||||
m_track_screenshot_widget = getWidget<IconButtonWidget>("track_screenshot");
|
||||
m_track_screenshot_widget->setFocusable(false);
|
||||
m_track_screenshot_widget->m_tab_stop = false;
|
||||
@ -56,6 +57,24 @@ HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear)
|
||||
// temporary icon, will replace it just after (but it will be shown if the given icon is not found)
|
||||
m_track_screenshot_widget->m_properties[PROP_ICON] = "gui/icons/main_help.png";
|
||||
|
||||
Track* track;
|
||||
core::stringw track_name;
|
||||
core::stringw track_type_name;
|
||||
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
m_gp = grand_prix_manager->getGrandPrix(m_hs->m_track);
|
||||
track = track_manager->getTrack(m_gp->getTrackId(0));
|
||||
track_name = m_gp->getName();
|
||||
track_type_name = _("Grand Prix");
|
||||
}
|
||||
else
|
||||
{
|
||||
track = track_manager->getTrack(m_hs->m_track);
|
||||
track_name = track->getName();
|
||||
track_type_name = _("Track");
|
||||
}
|
||||
|
||||
irr::video::ITexture* image = STKTexManager::getInstance()
|
||||
->getTexture(track->getScreenshotFile(),
|
||||
"While loading screenshot for track '%s':", track->getFilename());
|
||||
@ -82,12 +101,12 @@ HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear)
|
||||
|
||||
updateHighscoreEntries();
|
||||
|
||||
//Setup static text labels
|
||||
// Setup static text labels
|
||||
m_high_score_label = getWidget<LabelWidget>("name");
|
||||
m_high_score_label->setText(_("Top %d High Scores", m_hs->HIGHSCORE_LEN), true);
|
||||
m_track_name_label = getWidget<LabelWidget>("track-name");
|
||||
m_track_name_label->setText(_("Track: %s",
|
||||
track_manager->getTrack(m_hs->m_track)->getName()), true);
|
||||
m_track_name_label->setText(_("%s: %s",
|
||||
track_type_name.c_str(), track_name), true);
|
||||
m_difficulty_label = getWidget<LabelWidget>("difficulty");
|
||||
m_difficulty_label->setText(_("Difficulty: %s", RaceManager::get()->
|
||||
getDifficultyName((RaceManager::Difficulty)
|
||||
@ -101,12 +120,14 @@ HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear)
|
||||
m_num_karts_label->setVisible(true);
|
||||
m_num_karts_label->setText(_("Number of karts: %d", m_hs->m_number_of_karts), true);
|
||||
|
||||
m_num_laps_label->setVisible(true);
|
||||
m_num_laps_label->setText(_("Laps: %d", m_hs->m_number_of_laps), true);
|
||||
|
||||
stringw is_reverse = m_hs->m_reverse ? _("Yes") : _("No");
|
||||
m_reverse_label->setVisible(true);
|
||||
m_reverse_label->setText(_("Reverse: %s", is_reverse), true);
|
||||
if (m_major_mode != RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
m_num_laps_label->setVisible(true);
|
||||
m_num_laps_label->setText(_("Laps: %d", m_hs->m_number_of_laps), true);
|
||||
}
|
||||
stringw is_reverse = m_hs->m_reverse ? _("Yes") : _("No");
|
||||
m_reverse_label->setVisible(true);
|
||||
m_reverse_label->setText(_("Reverse: %s", is_reverse), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -115,6 +136,11 @@ HighScoreInfoDialog::HighScoreInfoDialog(Highscores* highscore, bool is_linear)
|
||||
m_reverse_label->setVisible(false);
|
||||
}
|
||||
|
||||
m_start_widget = getWidget<IconButtonWidget>("start");
|
||||
|
||||
// Disable starting a grand prix, as there is currently no way to tell the minor mode used
|
||||
getWidget<IconButtonWidget>("start")->setActive(major_mode == RaceManager::MAJOR_MODE_SINGLE);
|
||||
|
||||
m_action_widget = getWidget<RibbonWidget>("actions");
|
||||
|
||||
m_action_widget->setFocusForPlayer(PLAYER_ID_GAME_MASTER);
|
||||
@ -247,3 +273,31 @@ GUIEngine::EventPropagation
|
||||
}
|
||||
return GUIEngine::EVENT_LET;
|
||||
} // processEvent
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Called every update. Used to cycle the screenshots for grand prix entries.
|
||||
* \param dt Time step size.
|
||||
*/
|
||||
void HighScoreInfoDialog::onUpdate(float dt)
|
||||
{
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
if (dt == 0)
|
||||
return; // if nothing changed, return right now
|
||||
|
||||
m_curr_time += dt;
|
||||
int frame_after = (int)(m_curr_time / 1.5f);
|
||||
|
||||
const std::vector<std::string> tracks = m_gp->getTrackNames();
|
||||
if (frame_after >= (int)tracks.size())
|
||||
{
|
||||
frame_after = 0;
|
||||
m_curr_time = 0;
|
||||
}
|
||||
|
||||
Track* track = track_manager->getTrack(tracks[frame_after]);
|
||||
std::string file = track->getScreenshotFile();
|
||||
m_track_screenshot_widget->setImage(file, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE);
|
||||
m_track_screenshot_widget->m_properties[PROP_ICON] = file;
|
||||
}
|
||||
} // onUpdate
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include "guiengine/modaldialog.hpp"
|
||||
#include "guiengine/widgets.hpp"
|
||||
#include "race/grand_prix_data.hpp"
|
||||
#include "race/highscores.hpp"
|
||||
|
||||
/** \brief Dialog that allows a user to manage a high score
|
||||
@ -33,6 +34,7 @@ private:
|
||||
Highscores* m_hs;
|
||||
|
||||
GUIEngine::RibbonWidget* m_action_widget;
|
||||
GUIEngine::IconButtonWidget* m_start_widget;
|
||||
|
||||
GUIEngine::LabelWidget* m_high_score_label;
|
||||
GUIEngine::LabelWidget* m_track_name_label;
|
||||
@ -46,11 +48,19 @@ private:
|
||||
|
||||
void updateHighscoreEntries();
|
||||
|
||||
RaceManager::MajorRaceModeType m_major_mode;
|
||||
|
||||
float m_curr_time;
|
||||
|
||||
const GrandPrixData* m_gp;
|
||||
|
||||
public:
|
||||
HighScoreInfoDialog(Highscores* highscore, bool is_linear);
|
||||
HighScoreInfoDialog(Highscores* highscore, bool is_linear, RaceManager::MajorRaceModeType major_mode);
|
||||
~HighScoreInfoDialog();
|
||||
|
||||
GUIEngine::EventPropagation processEvent(const std::string& eventSource);
|
||||
|
||||
virtual void onUpdate(float dt);
|
||||
}; // class HighScoreInfoDialog
|
||||
|
||||
#endif
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "guiengine/CGUISpriteBank.hpp"
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "karts/kart_properties_manager.hpp"
|
||||
#include "race/grand_prix_data.hpp"
|
||||
#include "race/grand_prix_manager.hpp"
|
||||
#include "race/highscores.hpp"
|
||||
#include "race/highscore_manager.hpp"
|
||||
#include "states_screens/dialogs/high_score_info_dialog.hpp"
|
||||
@ -86,7 +88,7 @@ void HighScoreSelection::refresh(bool forced_update, bool update_columns)
|
||||
if (update_columns)
|
||||
{
|
||||
m_high_scores_list_widget->clearColumns();
|
||||
beforeAddingWidget();//Reload the columns used
|
||||
beforeAddingWidget(); //Reload the columns used
|
||||
}
|
||||
} // refresh
|
||||
|
||||
@ -101,6 +103,7 @@ void HighScoreSelection::loadedFromFile()
|
||||
|
||||
m_mode_tabs = getWidget<GUIEngine::RibbonWidget>("race_mode");
|
||||
m_active_mode = RaceManager::MINOR_MODE_NORMAL_RACE;
|
||||
m_major_mode = RaceManager::MAJOR_MODE_SINGLE;
|
||||
m_active_mode_is_linear = true;
|
||||
|
||||
m_icon_bank = new irr::gui::STKModifiedSpriteBank( GUIEngine::getGUIEnv());
|
||||
@ -127,13 +130,24 @@ void HighScoreSelection::loadedFromFile()
|
||||
*/
|
||||
void HighScoreSelection::beforeAddingWidget()
|
||||
{
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Track"), 7);
|
||||
core::stringw track_type_name;
|
||||
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
track_type_name = _("Grand Prix");
|
||||
else
|
||||
track_type_name = _("Track");
|
||||
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", track_type_name.c_str()), 7);
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Difficulty"), 4);
|
||||
if (m_active_mode_is_linear)
|
||||
{
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Number of karts"), 4);
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Laps"), 3);
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Reverse"), 3);
|
||||
|
||||
if (m_major_mode != RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Laps"), 3);
|
||||
}
|
||||
m_high_scores_list_widget->addColumn(_C("column_name", "Reverse"), 3);
|
||||
}
|
||||
|
||||
m_high_scores_list_widget->createHeader();
|
||||
@ -170,24 +184,44 @@ void HighScoreSelection::loadList()
|
||||
{
|
||||
Highscores* hs = highscore_manager->getHighscoresAt(i);
|
||||
|
||||
if (m_active_mode == RaceManager::MINOR_MODE_NORMAL_RACE &&
|
||||
hs->m_highscore_type != "HST_STANDARD")
|
||||
continue;
|
||||
else if (m_active_mode == RaceManager::MINOR_MODE_TIME_TRIAL &&
|
||||
hs->m_highscore_type != "HST_STD_TIMETRIAL")
|
||||
continue;
|
||||
else if (m_active_mode == RaceManager::MINOR_MODE_EASTER_EGG &&
|
||||
hs->m_highscore_type != "HST_EASTER_EGG_HUNT")
|
||||
continue;
|
||||
|
||||
Track* track = track_manager->getTrack(hs->m_track);
|
||||
|
||||
if (track == NULL || hs->getNumberEntries() < 1)
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_SINGLE)
|
||||
{
|
||||
if (m_active_mode == RaceManager::MINOR_MODE_NORMAL_RACE &&
|
||||
hs->m_highscore_type != "HST_STANDARD")
|
||||
continue;
|
||||
else if (m_active_mode == RaceManager::MINOR_MODE_TIME_TRIAL &&
|
||||
hs->m_highscore_type != "HST_STD_TIMETRIAL")
|
||||
continue;
|
||||
else if (m_active_mode == RaceManager::MINOR_MODE_EASTER_EGG &&
|
||||
hs->m_highscore_type != "HST_EASTER_EGG_HUNT")
|
||||
continue;
|
||||
}
|
||||
else if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX &&
|
||||
hs->m_highscore_type != "HST_GRANDPRIX")
|
||||
continue;
|
||||
|
||||
std::vector<GUIEngine::ListWidget::ListCell> row;
|
||||
//The third argument should match the numbers used in beforeAddingWidget
|
||||
row.push_back(GUIEngine::ListWidget::ListCell(track->getName() , -1, 7));
|
||||
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
const GrandPrixData* gp = grand_prix_manager->getGrandPrix(hs->m_track);
|
||||
|
||||
if (gp == NULL || hs->getNumberEntries() < 1)
|
||||
continue;
|
||||
|
||||
//The third argument should match the numbers used in beforeAddingWidget
|
||||
row.push_back(GUIEngine::ListWidget::ListCell(gp->getName() , -1, 7));
|
||||
}
|
||||
else
|
||||
{
|
||||
Track* track = track_manager->getTrack(hs->m_track);
|
||||
|
||||
if (track == NULL || hs->getNumberEntries() < 1)
|
||||
continue;
|
||||
|
||||
//The third argument should match the numbers used in beforeAddingWidget
|
||||
row.push_back(GUIEngine::ListWidget::ListCell(track->getName() , -1, 7));
|
||||
}
|
||||
|
||||
bool display_lock = false;
|
||||
if ((RaceManager::Difficulty)hs->m_difficulty == RaceManager::DIFFICULTY_BEST &&
|
||||
@ -202,10 +236,14 @@ void HighScoreSelection::loadList()
|
||||
{
|
||||
row.push_back(GUIEngine::ListWidget::ListCell
|
||||
(StringUtils::toWString(hs->m_number_of_karts), -1, 4, true));
|
||||
row.push_back(GUIEngine::ListWidget::ListCell
|
||||
(StringUtils::toWString(hs->m_number_of_laps), -1, 3, true));
|
||||
row.push_back(GUIEngine::ListWidget::ListCell
|
||||
(hs->m_reverse ? _("Yes") : _("No"), -1, 3, true));
|
||||
|
||||
if (m_major_mode != RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
{
|
||||
row.push_back(GUIEngine::ListWidget::ListCell
|
||||
(StringUtils::toWString(hs->m_number_of_laps), -1, 3, true));
|
||||
}
|
||||
row.push_back(GUIEngine::ListWidget::ListCell
|
||||
(hs->m_reverse ? _("Yes") : _("No"), -1, 3, true));
|
||||
}
|
||||
m_high_scores_list_widget->addItem(StringUtils::toString(i), row);
|
||||
}
|
||||
@ -243,20 +281,36 @@ void HighScoreSelection::eventCallback(GUIEngine::Widget* widget,
|
||||
return;
|
||||
}
|
||||
|
||||
new HighScoreInfoDialog(highscore_manager->getHighscoresAt(m_selected_index), m_active_mode_is_linear);
|
||||
new HighScoreInfoDialog(highscore_manager->getHighscoresAt(m_selected_index), m_active_mode_is_linear, m_major_mode);
|
||||
} // click on high score entry
|
||||
else if (name == "race_mode")
|
||||
{
|
||||
std::string selection = ((RibbonWidget*)widget)->getSelectionIDString(PLAYER_ID_GAME_MASTER);
|
||||
|
||||
if (selection == "tab_normal_race")
|
||||
{
|
||||
m_active_mode = RaceManager::MINOR_MODE_NORMAL_RACE;
|
||||
m_major_mode = RaceManager::MAJOR_MODE_SINGLE;
|
||||
}
|
||||
else if (selection == "tab_time_trial")
|
||||
{
|
||||
m_active_mode = RaceManager::MINOR_MODE_TIME_TRIAL;
|
||||
m_major_mode = RaceManager::MAJOR_MODE_SINGLE;
|
||||
}
|
||||
else if (selection == "tab_egg_hunt")
|
||||
{
|
||||
m_active_mode = RaceManager::MINOR_MODE_EASTER_EGG;
|
||||
m_major_mode = RaceManager::MAJOR_MODE_SINGLE;
|
||||
}
|
||||
else if (selection == "tab_grand_prix")
|
||||
{
|
||||
m_major_mode = RaceManager::MAJOR_MODE_GRAND_PRIX;
|
||||
}
|
||||
|
||||
m_active_mode_is_linear = RaceManager::get()->isLinearRaceMode(m_active_mode);
|
||||
if (m_major_mode == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
||||
m_active_mode_is_linear = true;
|
||||
else
|
||||
m_active_mode_is_linear = RaceManager::get()->isLinearRaceMode(m_active_mode);
|
||||
refresh(/*keep high score list*/ false, /* update columns */ true);
|
||||
}
|
||||
} // eventCallback
|
||||
|
@ -46,6 +46,7 @@ private:
|
||||
GUIEngine::RibbonWidget* m_mode_tabs;
|
||||
bool m_active_mode_is_linear;
|
||||
bool m_reverse_sort;
|
||||
RaceManager::MajorRaceModeType m_major_mode;
|
||||
RaceManager::MinorRaceModeType m_active_mode;
|
||||
int m_selected_index;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user