mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-22 14:24:33 +01:00
Update for rebase, add window theme, fix bugs
- Along with rebasing the code it became nessecary to update various calls to `scenery_small_entry_has_flag` and `EntityBase`. - Added a new window theme with default colours. - Fixed bug with see-through supports not working. - Removed incomplete "ride exclusions" tab - Removed unused text string (Ride Exclusions)
This commit is contained in:
@@ -175,6 +175,7 @@ static constexpr const WindowThemeDesc WindowThemeDescriptors[] =
|
||||
{ THEME_WC(WC_TILE_INSPECTOR), STR_TILE_INSPECTOR_TITLE, COLOURS_2(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
|
||||
{ THEME_WC(WC_VIEW_CLIPPING), STR_VIEW_CLIPPING_TITLE, COLOURS_1(COLOUR_DARK_GREEN ) },
|
||||
{ THEME_WC(WC_PATROL_AREA), STR_SET_PATROL_AREA, COLOURS_3(COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE, COLOUR_LIGHT_PURPLE ) },
|
||||
{ THEME_WC(WC_TRANSPARENCY), STR_TRANSPARENCY_OPTIONS_TITLE, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
|
||||
{ THEME_WC(WC_ABOUT), STR_ABOUT, COLOURS_2(COLOUR_GREY, COLOUR_LIGHT_BLUE ) },
|
||||
{ THEME_WC(WC_CHANGELOG), STR_CHANGELOG_TITLE, COLOURS_2(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
|
||||
{ THEME_WC(WC_MULTIPLAYER), STR_MULTIPLAYER, COLOURS_3(COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE, COLOUR_LIGHT_BLUE ) },
|
||||
|
||||
@@ -222,6 +222,7 @@ static rct_windowclass window_themes_tab_6_classes[] = {
|
||||
WC_CHEATS,
|
||||
WC_TILE_INSPECTOR,
|
||||
WC_VIEW_CLIPPING,
|
||||
WC_TRANSPARENCY,
|
||||
WC_THEMES,
|
||||
WC_TITLE_EDITOR,
|
||||
WC_OPTIONS,
|
||||
|
||||
@@ -30,24 +30,13 @@
|
||||
#include <openrct2/world/Surface.h>
|
||||
|
||||
// clang-format off
|
||||
enum
|
||||
{
|
||||
WINDOW_TRANSPARENCY_PAGE_MAIN,
|
||||
WINDOW_TRANSPARENCY_PAGE_RIDES,
|
||||
WINDOW_TRANSPARENCY_PAGE_COUNT,
|
||||
};
|
||||
|
||||
enum WINDOW_TRANSPARENCY_WIDGET_IDX
|
||||
{
|
||||
WIDX_BACKGROUND,
|
||||
WIDX_TITLE,
|
||||
WIDX_CLOSE,
|
||||
WIDX_PAGE_BACKGROUND,
|
||||
WIDX_TAB_1,
|
||||
WIDX_TAB_2,
|
||||
WIDX_TAB_CONTENT,
|
||||
|
||||
WIDX_SEE_THROUGH_RIDES = WIDX_TAB_CONTENT,
|
||||
WIDX_SEE_THROUGH_RIDES,
|
||||
WIDX_SEE_THROUGH_VEHICLES,
|
||||
WIDX_SEE_THROUGH_SCENERY,
|
||||
WIDX_SEE_THROUGH_TREES,
|
||||
@@ -61,31 +50,22 @@ enum WINDOW_TRANSPARENCY_WIDGET_IDX
|
||||
WIDX_INVISIBLE_TREES,
|
||||
WIDX_INVISIBLE_PATHS,
|
||||
WIDX_INVISIBLE_SUPPORTS,
|
||||
|
||||
WIDX_LIST = WIDX_TAB_CONTENT,
|
||||
};
|
||||
|
||||
#pragma region MEASUREMENTS
|
||||
|
||||
static constexpr const rct_string_id WINDOW_TITLE = STR_TRANSPARENCY_OPTIONS_TITLE;
|
||||
static constexpr const int32_t WW = 203;
|
||||
static constexpr const int32_t WH = 70;
|
||||
static constexpr const int32_t WW = 204;
|
||||
static constexpr const int32_t WH = 57;
|
||||
|
||||
static constexpr ScreenSize ICON_BUTTON = {24, 24};
|
||||
static constexpr ScreenSize FLAT_BUTTON = {24, 12};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#define MAIN_TRANSPARENCY_WIDGETS \
|
||||
WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
|
||||
MakeWidget({ 0, 0}, { 0, 0}, WindowWidgetType::ImgBtn, WindowColour::Secondary), /* tab content panel */ \
|
||||
MakeWidget({189, 42}, { 12, 6}, WindowWidgetType::Button, WindowColour::Tertiary, STR_NONE, STR_TRANSPARENCY_OPTIONS_TITLE), \
|
||||
MakeWidget({189, 48}, { 12, 6}, WindowWidgetType::Button, WindowColour::Tertiary, STR_NONE, STR_TRANSPARENCY_OPTIONS_TITLE_RIDE)
|
||||
|
||||
static rct_widget window_transparency_main_widgets[] =
|
||||
{
|
||||
MAIN_TRANSPARENCY_WIDGETS,
|
||||
|
||||
WINDOW_SHIM(WINDOW_TITLE, WW, WH),
|
||||
MakeWidget({ 77, 17}, ICON_BUTTON, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_RIDE, STR_SEE_THROUGH_RIDES),
|
||||
MakeWidget({102, 17}, ICON_BUTTON, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_BUTTON_COASTER_TRAIN, STR_SEE_THROUGH_VEHICLES),
|
||||
MakeWidget({ 27, 17}, ICON_BUTTON, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_BUTTON_LARGE_SCENERY, STR_SEE_THROUGH_SCENERY),
|
||||
@@ -104,70 +84,26 @@ static rct_widget window_transparency_main_widgets[] =
|
||||
|
||||
{ WIDGETS_END },
|
||||
};
|
||||
|
||||
static rct_widget window_transparency_rides_widgets[] =
|
||||
{
|
||||
MAIN_TRANSPARENCY_WIDGETS,
|
||||
MakeWidget({ 2, 17}, {160, 200}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL),
|
||||
{ WIDGETS_END },
|
||||
};
|
||||
|
||||
static rct_widget *window_transparency_page_widgets[] =
|
||||
{
|
||||
window_transparency_main_widgets,
|
||||
window_transparency_rides_widgets,
|
||||
};
|
||||
|
||||
#define MAIN_TRANSPARENCY_ENABLED_WIDGETS (1ULL << WIDX_CLOSE) | (1ULL << WIDX_TAB_1) | (1ULL << WIDX_TAB_2)
|
||||
|
||||
static uint64_t window_transparency_page_enabled_widgets[] = {
|
||||
MAIN_TRANSPARENCY_ENABLED_WIDGETS |
|
||||
(1ULL << WIDX_SEE_THROUGH_RIDES) |
|
||||
(1ULL << WIDX_SEE_THROUGH_VEHICLES) |
|
||||
(1ULL << WIDX_SEE_THROUGH_SCENERY) |
|
||||
(1ULL << WIDX_SEE_THROUGH_TREES) |
|
||||
(1ULL << WIDX_SEE_THROUGH_PATHS) |
|
||||
(1ULL << WIDX_INVISIBLE_RIDES) |
|
||||
(1ULL << WIDX_INVISIBLE_VEHICLES) |
|
||||
(1ULL << WIDX_INVISIBLE_SCENERY) |
|
||||
(1ULL << WIDX_INVISIBLE_TREES) |
|
||||
(1ULL << WIDX_INVISIBLE_PATHS) |
|
||||
(1ULL << WIDX_INVISIBLE_SUPPORTS) |
|
||||
(1ULL << WIDX_INVISIBLE_GUESTS) |
|
||||
(1ULL << WIDX_INVISIBLE_STAFF) |
|
||||
(1ULL << WIDX_SEE_THROUGH_SUPPORTS),
|
||||
|
||||
MAIN_TRANSPARENCY_ENABLED_WIDGETS |
|
||||
(1ULL << WIDX_LIST)
|
||||
};
|
||||
|
||||
static rct_string_id window_transparency_page_titles[] = {
|
||||
STR_TRANSPARENCY_OPTIONS_TITLE,
|
||||
STR_TRANSPARENCY_OPTIONS_TITLE_RIDE
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
class TransparencyWindow final : public Window
|
||||
{
|
||||
private:
|
||||
int32_t _selected_list_item = -1;
|
||||
std::vector<ride_id_t> _rideList;
|
||||
|
||||
public:
|
||||
void OnOpen() override
|
||||
{
|
||||
SetPage(WINDOW_TRANSPARENCY_PAGE_MAIN);
|
||||
widgets = window_transparency_main_widgets;
|
||||
enabled_widgets = (1ULL << WIDX_CLOSE) | (1ULL << WIDX_SEE_THROUGH_RIDES) | (1ULL << WIDX_SEE_THROUGH_VEHICLES)
|
||||
| (1ULL << WIDX_SEE_THROUGH_SCENERY) | (1ULL << WIDX_SEE_THROUGH_TREES) | (1ULL << WIDX_SEE_THROUGH_PATHS)
|
||||
| (1ULL << WIDX_INVISIBLE_RIDES) | (1ULL << WIDX_INVISIBLE_VEHICLES) | (1ULL << WIDX_INVISIBLE_SCENERY)
|
||||
| (1ULL << WIDX_INVISIBLE_TREES) | (1ULL << WIDX_INVISIBLE_PATHS) | (1ULL << WIDX_INVISIBLE_SUPPORTS)
|
||||
| (1ULL << WIDX_INVISIBLE_GUESTS) | (1ULL << WIDX_INVISIBLE_STAFF) | (1ULL << WIDX_SEE_THROUGH_SUPPORTS);
|
||||
|
||||
window_push_others_below(this);
|
||||
|
||||
auto* w = window_get_main();
|
||||
if (w != nullptr)
|
||||
windowPos.x = ((w->width / 2) - (width / 2));
|
||||
|
||||
RefreshRideList();
|
||||
}
|
||||
|
||||
void OnUpdate() override
|
||||
{
|
||||
InvalidateWidget(WIDX_TAB_1 + page);
|
||||
}
|
||||
|
||||
void OnMouseUp(rct_widgetindex widgetIndex) override
|
||||
@@ -177,101 +113,45 @@ public:
|
||||
case WIDX_CLOSE:
|
||||
Close();
|
||||
break;
|
||||
case WIDX_TAB_1:
|
||||
case WIDX_TAB_2:
|
||||
SetPage(widgetIndex - WIDX_TAB_1);
|
||||
break;
|
||||
default:
|
||||
switch (page)
|
||||
{
|
||||
case WINDOW_TRANSPARENCY_PAGE_MAIN:
|
||||
OnMouseUpMain(widgetIndex);
|
||||
break;
|
||||
}
|
||||
OnMouseUpMain(widgetIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override
|
||||
{
|
||||
if (page == WINDOW_TRANSPARENCY_PAGE_RIDES)
|
||||
{
|
||||
auto i = screenCoords.y / SCROLLABLE_ROW_HEIGHT;
|
||||
// i += static_cast<int32_t>(_selectedPage * GUESTS_PER_PAGE);
|
||||
for (const auto& rideItem : _rideList)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
auto ridec = get_ride(rideItem);
|
||||
if (ridec != nullptr)
|
||||
{
|
||||
ridec->ignore_invisible_flag = (ridec->ignore_invisible_flag == true ? false : true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnPrepareDraw() override
|
||||
{
|
||||
auto* targetWidgets = window_transparency_page_widgets[page];
|
||||
if (widgets != targetWidgets)
|
||||
{
|
||||
widgets = targetWidgets;
|
||||
WindowInitScrollWidgets(this);
|
||||
}
|
||||
|
||||
pressed_widgets = 0;
|
||||
disabled_widgets = 0;
|
||||
|
||||
// Set correct active tab
|
||||
for (auto i = 0; i < WINDOW_TRANSPARENCY_PAGE_COUNT; i++)
|
||||
SetWidgetPressed(WIDX_TAB_1 + i, false);
|
||||
SetWidgetPressed(WIDX_TAB_1 + page, true);
|
||||
|
||||
// Set title
|
||||
widgets[WIDX_TITLE].text = window_transparency_page_titles[page];
|
||||
|
||||
rct_window* w = window_get_main();
|
||||
if (w == nullptr)
|
||||
return;
|
||||
|
||||
switch (page)
|
||||
{
|
||||
case WINDOW_TRANSPARENCY_PAGE_MAIN:
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_RIDES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_RIDES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_VEHICLES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_VEHICLES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_SCENERY, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_SCENERY));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_TREES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_TREES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_PATHS, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_PATHS));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_SUPPORTS, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_SUPPORTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_RIDES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_RIDES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_VEHICLES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_VEHICLES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_SCENERY, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_SCENERY));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_TREES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_TREES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_PATHS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_PATHS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_SUPPORTS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_SUPPORTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_GUESTS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_GUESTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_STAFF, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_STAFF));
|
||||
break;
|
||||
}
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_RIDES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_RIDES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_VEHICLES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_VEHICLES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_SCENERY, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_SCENERY));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_TREES, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_TREES));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_PATHS, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_PATHS));
|
||||
SetWidgetPressed(WIDX_SEE_THROUGH_SUPPORTS, (w->viewport->flags & VIEWPORT_FLAG_SEETHROUGH_SUPPORTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_RIDES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_RIDES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_VEHICLES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_VEHICLES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_SCENERY, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_SCENERY));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_TREES, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_TREES));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_PATHS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_PATHS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_SUPPORTS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_SUPPORTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_GUESTS, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_GUESTS));
|
||||
SetWidgetPressed(WIDX_INVISIBLE_STAFF, (w->viewport->flags & VIEWPORT_FLAG_INVISIBLE_STAFF));
|
||||
}
|
||||
|
||||
void OnDraw(rct_drawpixelinfo& dpi) override
|
||||
{
|
||||
DrawWidgets(dpi);
|
||||
|
||||
if (page == WINDOW_TRANSPARENCY_PAGE_MAIN)
|
||||
{
|
||||
// Locate mechanic button image
|
||||
rct_widget* widget = &widgets[WIDX_INVISIBLE_STAFF];
|
||||
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
|
||||
gfx_draw_sprite(
|
||||
&dpi, (gStaffMechanicColour << 24) | IMAGE_TYPE_REMAP | IMAGE_TYPE_REMAP_2_PLUS | SPR_MECHANIC, screenCoords,
|
||||
0);
|
||||
}
|
||||
// Locate mechanic button image
|
||||
rct_widget* widget = &widgets[WIDX_INVISIBLE_STAFF];
|
||||
auto screenCoords = windowPos + ScreenCoordsXY{ widget->left, widget->top };
|
||||
gfx_draw_sprite(
|
||||
&dpi, (gStaffMechanicColour << 24) | IMAGE_TYPE_REMAP | IMAGE_TYPE_REMAP_2_PLUS | SPR_MECHANIC, screenCoords, 0);
|
||||
}
|
||||
|
||||
OpenRCT2String OnTooltip(rct_widgetindex widgetIndex, rct_string_id fallback) override
|
||||
@@ -280,29 +160,6 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void SetPage(int32_t p)
|
||||
{
|
||||
page = p;
|
||||
enabled_widgets = window_transparency_page_enabled_widgets[p];
|
||||
pressed_widgets = 0;
|
||||
widgets = window_transparency_page_widgets[p];
|
||||
|
||||
auto maxY = 0;
|
||||
auto* widget = &widgets[WIDX_TAB_CONTENT];
|
||||
while (widget->type != WindowWidgetType::Last)
|
||||
{
|
||||
maxY = std::max<int32_t>(maxY, widget->bottom);
|
||||
widget++;
|
||||
}
|
||||
maxY += 4;
|
||||
|
||||
Invalidate();
|
||||
height = maxY;
|
||||
widgets[WIDX_BACKGROUND].bottom = maxY - 1;
|
||||
widgets[WIDX_PAGE_BACKGROUND].bottom = maxY - 1;
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
void OnMouseUpMain(rct_widgetindex widgetIndex)
|
||||
{
|
||||
rct_window* w = window_get_main();
|
||||
@@ -356,62 +213,6 @@ private:
|
||||
}
|
||||
w->Invalidate();
|
||||
}
|
||||
|
||||
void OnScrollDraw(int32_t scrollIndex, rct_drawpixelinfo& dpi) override
|
||||
{
|
||||
ScreenCoordsXY screenCoords;
|
||||
auto bgColour = ThemeGetColour(WC_CUSTOM, 0);
|
||||
|
||||
screenCoords.y = 2;
|
||||
for (size_t i = 0; i < _rideList.size(); i++)
|
||||
{
|
||||
auto ridec = get_ride(_rideList[i]);
|
||||
if (ridec == nullptr)
|
||||
continue;
|
||||
|
||||
if (screenCoords.y + SCROLLABLE_ROW_HEIGHT >= dpi.y && screenCoords.y <= dpi.y + dpi.height)
|
||||
{
|
||||
gfx_fill_rect_inset(&dpi, { { 2, screenCoords.y }, { 11, screenCoords.y + 10 } }, bgColour, INSET_RECT_F_E0);
|
||||
|
||||
// Draw checkmark
|
||||
if (ridec->ignore_invisible_flag == true)
|
||||
{
|
||||
screenCoords.x = 2;
|
||||
FontSpriteBase fontSpriteBase = FontSpriteBase::MEDIUM_DARK;
|
||||
colour_t colour2 = NOT_TRANSLUCENT(bgColour);
|
||||
gfx_draw_string(
|
||||
&dpi, screenCoords, static_cast<const char*>(CheckBoxMarkString),
|
||||
{ static_cast<colour_t>(colour2), fontSpriteBase });
|
||||
}
|
||||
|
||||
screenCoords.x = 13;
|
||||
|
||||
int32_t width_limit = widgets[WIDX_LIST].width() - screenCoords.x;
|
||||
|
||||
// Ride name
|
||||
auto ft = Formatter();
|
||||
ridec->FormatNameTo(ft);
|
||||
DrawTextEllipsised(&dpi, { screenCoords.x, screenCoords.y }, width_limit, STR_WINDOW_COLOUR_2_STRINGID, ft);
|
||||
}
|
||||
screenCoords.y += SCROLLABLE_ROW_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRideList()
|
||||
{
|
||||
_rideList.clear();
|
||||
|
||||
size_t list_index = 0;
|
||||
for (auto& ridec : GetRideManager())
|
||||
{
|
||||
auto rided = &ridec;
|
||||
_rideList.push_back(rided->id);
|
||||
list_index++;
|
||||
}
|
||||
|
||||
_selected_list_item = -1;
|
||||
Invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
rct_window* WindowTransparencyOpen()
|
||||
|
||||
@@ -3941,7 +3941,6 @@ enum : uint16_t
|
||||
STR_INVISIBLE_VEHICLES = 6481,
|
||||
STR_SEE_THROUGH_SUPPORTS = 6482,
|
||||
STR_SHORTCUT_OPEN_TRANSPARENCY_OPTIONS = 6483,
|
||||
STR_TRANSPARENCY_OPTIONS_TITLE_RIDE = 6484,
|
||||
|
||||
// Have to include resource strings (from scenarios and objects) for the time being now that language is partially working
|
||||
/* MAX_STR_COUNT = 32768 */ // MAX_STR_COUNT - upper limit for number of strings, not the current count strings
|
||||
|
||||
Reference in New Issue
Block a user