mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-07-13 08:03:43 +01:00
add: minesweeper PDA game (#26860)
* minesweeper with no style * less string operations * review and aylongo tgui changes * bundle * minesweeper with no style * less string operations * review and aylongo tgui changes * bundle * bundle * bundle * silent. pda is booming * codestyle * review * revert * Apply suggestions from code review Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> Signed-off-by: Burzah <116982774+Burzah@users.noreply.github.com> * adjusts the name of a proc --------- Signed-off-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
This commit is contained in:
@@ -63,7 +63,10 @@ GLOBAL_LIST_EMPTY(PDAs)
|
||||
new/datum/data/pda/app/manifest,
|
||||
new/datum/data/pda/app/nanobank,
|
||||
new/datum/data/pda/app/atmos_scanner,
|
||||
new/datum/data/pda/utility/flashlight)
|
||||
new/datum/data/pda/utility/flashlight,
|
||||
new/datum/data/pda/app/games,
|
||||
// Here our games go
|
||||
new/datum/data/pda/app/game/minesweeper)
|
||||
var/list/shortcut_cache = list()
|
||||
var/list/shortcut_cat_order = list()
|
||||
var/list/notifying_programs = list()
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Games Hub
|
||||
|
||||
/datum/data/pda/app/games
|
||||
name = "Games"
|
||||
icon = "gamepad"
|
||||
template = "pda_games"
|
||||
|
||||
/datum/data/pda/app/games/update_ui(mob/user, list/data)
|
||||
var/list/games = list()
|
||||
for(var/datum/data/pda/app/game/game in pda.programs)
|
||||
games += list(game.get_game_info())
|
||||
data["games"] = games
|
||||
|
||||
/datum/data/pda/app/games/ui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("play")
|
||||
var/datum/data/pda/app/game/game = locate(params["id"]) in pda.programs
|
||||
pda.start_program(game)
|
||||
|
||||
// Game-type App
|
||||
|
||||
/datum/data/pda/app/game
|
||||
name = "base game"
|
||||
icon = "gamepad"
|
||||
hidden = TRUE
|
||||
|
||||
/datum/data/pda/app/game/proc/get_game_info()
|
||||
return list(name = name, icon = icon, id = UID())
|
||||
@@ -0,0 +1,240 @@
|
||||
// Minesweeper
|
||||
|
||||
#define MINESWEEPER_ROWS 16
|
||||
#define MINESWEEPER_COLUMNS 16
|
||||
#define MINESWEEPER_BOMBS 40
|
||||
|
||||
/datum/data/pda/app/game/minesweeper
|
||||
name = "Minesweeper"
|
||||
template = "pda_minesweeper"
|
||||
update = PDA_APP_NOUPDATE
|
||||
|
||||
/// Thing, to make first touch safety
|
||||
var/first_touch = TRUE
|
||||
// Win condition things
|
||||
var/setted_flags = 0
|
||||
var/flagged_bombs = 0
|
||||
var/opened_cells = 0
|
||||
/// Decision to make interface untouchable in the momemnt of regenerating
|
||||
var/ignore_touches = FALSE
|
||||
/// Here we have all the minesweeper info
|
||||
var/list/minesweeper_matrix = list()
|
||||
// generations vars
|
||||
var/generation_rows = MINESWEEPER_ROWS
|
||||
var/generation_columns = MINESWEEPER_COLUMNS
|
||||
var/generation_bombs = MINESWEEPER_BOMBS
|
||||
|
||||
/// The moment then game was started for point count
|
||||
var/start_time = 0
|
||||
|
||||
/// The leaderboard list
|
||||
var/static/list/leaderboard = list()
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/start()
|
||||
. = ..()
|
||||
make_empty_matrix()
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/update_ui(mob/user, list/data)
|
||||
data["matrix"] = minesweeper_matrix
|
||||
data["flags"] = setted_flags
|
||||
data["bombs"] = generation_bombs
|
||||
data["leaderboard"] = leaderboard
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
if(..())
|
||||
return
|
||||
|
||||
if(ignore_touches)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("Square")
|
||||
switch(params["mode"])
|
||||
if("bomb")
|
||||
if(first_touch)
|
||||
generate_matrix(params["X"], params["Y"])
|
||||
open_cell(params["X"], params["Y"])
|
||||
|
||||
if(minesweeper_matrix[params["X"]][params["Y"]]["bomb"])
|
||||
on_loose(ui.user)
|
||||
SStgui.update_uis(pda)
|
||||
return
|
||||
|
||||
if("flag")
|
||||
if(first_touch || minesweeper_matrix[params["X"]][params["Y"]]["open"])
|
||||
return
|
||||
|
||||
if(minesweeper_matrix[params["X"]][params["Y"]]["flag"])
|
||||
minesweeper_matrix[params["X"]][params["Y"]]["flag"] = FALSE
|
||||
setted_flags -= 1
|
||||
if(minesweeper_matrix[params["X"]][params["Y"]]["bomb"])
|
||||
flagged_bombs -= 1
|
||||
else
|
||||
minesweeper_matrix[params["X"]][params["Y"]]["flag"] = TRUE
|
||||
setted_flags += 1
|
||||
if(minesweeper_matrix[params["X"]][params["Y"]]["bomb"])
|
||||
flagged_bombs += 1
|
||||
|
||||
check_win(ui.user)
|
||||
SStgui.update_uis(pda)
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/check_win(mob/user)
|
||||
if(flagged_bombs == generation_bombs && \
|
||||
setted_flags == generation_bombs && \
|
||||
opened_cells == (generation_rows * generation_columns - generation_bombs))
|
||||
on_win(user)
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/on_win(mob/user)
|
||||
ignore_touches = TRUE
|
||||
if(!pda.silent)
|
||||
playsound(get_turf(pda), 'sound/machines/ping.ogg', 20, TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(make_empty_matrix)), 5 SECONDS)
|
||||
add_into_leaders(user, world.time - start_time)
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/add_into_leaders(mob/user, game_time)
|
||||
var/nickname = tgui_input_text(user, "You finished the game in [game_time / 10] seconds.\n Write a nickname to save your result on the leaderboard.\n", "Minesweeper", "", 10)
|
||||
if(!nickname)
|
||||
return
|
||||
|
||||
leaderboard += list(list("name" = nickname, "time" = "[game_time/10]"))
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/on_loose(mob/user)
|
||||
ignore_touches = TRUE
|
||||
if(!pda.silent)
|
||||
playsound(get_turf(pda), 'sound/effects/explosionfar.ogg', 50, TRUE)
|
||||
addtimer(CALLBACK(src, PROC_REF(make_empty_matrix)), 3 SECONDS)
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/make_empty_matrix(pay = TRUE)
|
||||
minesweeper_matrix = list()
|
||||
for(var/i in 1 to generation_rows)
|
||||
var/list/new_row = list()
|
||||
for(var/j in 1 to generation_columns)
|
||||
new_row["[j]"] = list("open" = FALSE, "bomb" = FALSE, "flag" = FALSE, "around" = 0)
|
||||
minesweeper_matrix["[i]"] = new_row
|
||||
first_touch = TRUE
|
||||
ignore_touches = FALSE
|
||||
SStgui.update_uis(pda)
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/generate_matrix(x, y)
|
||||
flagged_bombs = 0
|
||||
setted_flags = 0
|
||||
opened_cells = 0
|
||||
var/list/possible_list = list()
|
||||
var/num_x = text2num(x)
|
||||
var/num_y = text2num(y)
|
||||
var/count = 0
|
||||
|
||||
for(var/i in 1 to generation_rows)
|
||||
for(var/j in 1 to generation_columns)
|
||||
if((i in list(num_x - 1, num_x, num_x + 1)) && (j in list(num_y - 1, num_y, num_y + 1)))
|
||||
continue
|
||||
possible_list["[count]"] = list(i, j)
|
||||
count++
|
||||
|
||||
for(var/bomb in 1 to generation_bombs)
|
||||
var/cell = pick(possible_list)
|
||||
var/coordinates = possible_list[cell]
|
||||
possible_list -= cell
|
||||
var/new_x = "[coordinates[1]]"
|
||||
var/new_y = "[coordinates[2]]"
|
||||
minesweeper_matrix[new_x][new_y]["bomb"] = TRUE
|
||||
|
||||
if(new_x != "1")
|
||||
minesweeper_matrix["[text2num(new_x)-1]"][new_y]["around"] += 1
|
||||
|
||||
if(new_y != "1")
|
||||
minesweeper_matrix[new_x]["[text2num(new_y)-1]"]["around"] += 1
|
||||
|
||||
if(new_x != "1" && new_y != "1")
|
||||
minesweeper_matrix["[text2num(new_x)-1]"]["[text2num(new_y)-1]"]["around"] += 1
|
||||
|
||||
if(new_x != "[generation_rows]")
|
||||
minesweeper_matrix["[text2num(new_x)+1]"][new_y]["around"] += 1
|
||||
|
||||
if(new_y != "[generation_columns]")
|
||||
minesweeper_matrix[new_x]["[text2num(new_y)+1]"]["around"] += 1
|
||||
|
||||
if(new_x != "[generation_rows]" && new_y != "[generation_columns]")
|
||||
minesweeper_matrix["[text2num(new_x)+1]"]["[text2num(new_y)+1]"]["around"] += 1
|
||||
|
||||
if(new_x != "1" && new_y != "[generation_columns]")
|
||||
minesweeper_matrix["[text2num(new_x)-1]"]["[text2num(new_y)+1]"]["around"] += 1
|
||||
|
||||
if(new_x != "[generation_rows]" && new_y != "1")
|
||||
minesweeper_matrix["[text2num(new_x)+1]"]["[text2num(new_y)-1]"]["around"] += 1
|
||||
|
||||
first_touch = FALSE
|
||||
start_time = world.time
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/open_cell(x, y, start_cycle = TRUE)
|
||||
. = list()
|
||||
if(!minesweeper_matrix[x][y]["open"])
|
||||
minesweeper_matrix[x][y]["open"] = TRUE
|
||||
opened_cells += 1
|
||||
|
||||
if(minesweeper_matrix[x][y]["flag"])
|
||||
minesweeper_matrix[x][y]["flag"] = FALSE
|
||||
setted_flags -= 1
|
||||
if(minesweeper_matrix[x][y]["bomb"])
|
||||
flagged_bombs -= 1
|
||||
|
||||
if(minesweeper_matrix[x][y]["around"] == 0)
|
||||
if(start_cycle)
|
||||
update_zeros(x, y)
|
||||
else
|
||||
. = list(list(x, y))
|
||||
|
||||
/datum/data/pda/app/game/minesweeper/proc/update_zeros(x, y)
|
||||
var/list/list_for_update = list(list(x, y))
|
||||
for(var/list/coordinates in list_for_update)
|
||||
var/this_x = coordinates[1]
|
||||
var/this_y = coordinates[2]
|
||||
var/num_x = text2num(coordinates[1])
|
||||
var/num_y = text2num(coordinates[2])
|
||||
var/new_x
|
||||
var/new_y
|
||||
|
||||
if(num_x != 1)
|
||||
new_x = "[num_x-1]"
|
||||
list_for_update += open_cell(new_x, this_y)
|
||||
|
||||
if(num_y != 1)
|
||||
new_y = "[num_y-1]"
|
||||
list_for_update += open_cell(this_x, new_y)
|
||||
|
||||
if(num_x != generation_rows)
|
||||
new_x = "[num_x+1]"
|
||||
list_for_update += open_cell(new_x, this_y)
|
||||
|
||||
if(num_y != generation_columns)
|
||||
new_y = "[num_y+1]"
|
||||
list_for_update += open_cell(this_x, new_y)
|
||||
|
||||
if(num_x != 1 && num_y != 1)
|
||||
new_x = "[num_x-1]"
|
||||
new_y = "[num_y-1]"
|
||||
if(minesweeper_matrix[new_x][this_y]["open"] && minesweeper_matrix[this_x][new_y]["open"])
|
||||
list_for_update += open_cell(new_x, new_y)
|
||||
|
||||
if(num_x != generation_rows && num_y != generation_columns)
|
||||
new_x = "[num_x+1]"
|
||||
new_y = "[num_y+1]"
|
||||
if(minesweeper_matrix[new_x][this_y]["open"] && minesweeper_matrix[this_x][new_y]["open"])
|
||||
list_for_update += open_cell(new_x, new_y)
|
||||
|
||||
if(num_x != 1 && num_y != generation_columns)
|
||||
new_x = "[num_x-1]"
|
||||
new_y = "[num_y+1]"
|
||||
if(minesweeper_matrix[new_x][this_y]["open"] && minesweeper_matrix[this_x][new_y]["open"])
|
||||
list_for_update += open_cell(new_x, new_y)
|
||||
|
||||
if(num_x != generation_rows && num_y != 1)
|
||||
new_x = "[num_x+1]"
|
||||
new_y = "[num_y-1]"
|
||||
if(minesweeper_matrix[new_x][this_y]["open"] && minesweeper_matrix[this_x][new_y]["open"])
|
||||
list_for_update += open_cell(new_x, new_y)
|
||||
|
||||
|
||||
#undef MINESWEEPER_ROWS
|
||||
#undef MINESWEEPER_COLUMNS
|
||||
#undef MINESWEEPER_BOMBS
|
||||
@@ -2574,6 +2574,8 @@
|
||||
#include "code\modules\pda\cart.dm"
|
||||
#include "code\modules\pda\cart_apps.dm"
|
||||
#include "code\modules\pda\core_apps.dm"
|
||||
#include "code\modules\pda\games.dm"
|
||||
#include "code\modules\pda\games\minesweeper.dm"
|
||||
#include "code\modules\pda\messenger.dm"
|
||||
#include "code\modules\pda\messenger_plugins.dm"
|
||||
#include "code\modules\pda\nanobank.dm"
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useBackend } from '../../backend';
|
||||
import { Box, Button, ImageButton, Section } from '../../components';
|
||||
import { Icon, IconStack } from '../../components/Icon';
|
||||
|
||||
export const pda_games = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { games } = data;
|
||||
|
||||
const GetAppImage = (AppName) => {
|
||||
switch (AppName) {
|
||||
case 'Minesweeper':
|
||||
return (
|
||||
<IconStack>
|
||||
<Icon ml="0" mt="10px" name="flag" size="6" color="gray" rotation={30} />
|
||||
<Icon ml="9px" mt="23px" name="bomb" size="3" color="black" />
|
||||
</IconStack>
|
||||
);
|
||||
default:
|
||||
return <Icon ml="16px" mt="10px" name="gamepad" size="6" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{games.map((game) => (
|
||||
<Button key={game.name} width="33%" textAlign="center" translucent onClick={() => act('play', { id: game.id })}>
|
||||
{GetAppImage(game.name)}
|
||||
<Box>{game.name}</Box>
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
import { useBackend, useLocalState } from '../../backend';
|
||||
import { Box, Button, Stack, Section, Table, Icon } from '../../components';
|
||||
|
||||
export const pda_minesweeper = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const [currentWindow, setWindow] = useLocalState(context, 'window', 'Game');
|
||||
|
||||
const AltWindow = {
|
||||
'Game': 'Leaderboard',
|
||||
'Leaderboard': 'Game',
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack fill vertical textAlign="center">
|
||||
<Stack.Item grow>{currentWindow === 'Game' ? <MineSweeperGame /> : <MineSweeperLeaderboard />}</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
translucent
|
||||
fontSize={2}
|
||||
lineHeight={1.75}
|
||||
icon={currentWindow === 'Game' ? 'book' : 'gamepad'}
|
||||
onClick={() => setWindow(AltWindow[currentWindow])}
|
||||
>
|
||||
{AltWindow[currentWindow]}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const MineSweeperGame = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { matrix, flags, bombs } = data;
|
||||
|
||||
const NumColor = {
|
||||
1: 'blue',
|
||||
2: 'green',
|
||||
3: 'red',
|
||||
4: 'darkblue',
|
||||
5: 'brown',
|
||||
6: 'lightblue',
|
||||
7: 'black',
|
||||
8: 'white',
|
||||
};
|
||||
|
||||
const handleClick = (row, cell, mode) => {
|
||||
act('Square', {
|
||||
'X': row,
|
||||
'Y': cell,
|
||||
'mode': mode,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
{Object.keys(matrix).map((row) => (
|
||||
<Box key={row}>
|
||||
{Object.keys(matrix[row]).map((cell) => (
|
||||
<Button
|
||||
key={cell}
|
||||
m={0.25}
|
||||
height={2}
|
||||
width={2}
|
||||
className={matrix[row][cell]['open'] ? 'Minesweeper__open' : 'Minesweeper__closed'}
|
||||
bold
|
||||
color="transparent"
|
||||
icon={
|
||||
matrix[row][cell]['open']
|
||||
? matrix[row][cell]['bomb']
|
||||
? 'bomb'
|
||||
: ''
|
||||
: matrix[row][cell]['flag']
|
||||
? 'flag'
|
||||
: ''
|
||||
}
|
||||
textColor={
|
||||
matrix[row][cell]['open']
|
||||
? matrix[row][cell]['bomb']
|
||||
? 'black'
|
||||
: NumColor[matrix[row][cell]['around']]
|
||||
: matrix[row][cell]['flag']
|
||||
? 'red'
|
||||
: 'gray'
|
||||
}
|
||||
onClick={(e) => handleClick(row, cell, 'bomb')}
|
||||
onContextMenu={(e) => {
|
||||
event.preventDefault();
|
||||
handleClick(row, cell, 'flag');
|
||||
}}
|
||||
>
|
||||
{!!matrix[row][cell]['open'] && !matrix[row][cell]['bomb'] && matrix[row][cell]['around']
|
||||
? matrix[row][cell]['around']
|
||||
: ' '}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Stack.Item>
|
||||
<Stack.Item grow className="Minesweeper__infobox">
|
||||
<Stack vertical textAlign="left" pt={1}>
|
||||
<Stack.Item pl={2} fontSize={2}>
|
||||
<Icon name="bomb" color="gray" /> : {bombs}
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item pl={2} fontSize={2}>
|
||||
<Icon name="flag" color="red" /> : {flags}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const MineSweeperLeaderboard = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { leaderboard } = data;
|
||||
|
||||
return (
|
||||
<Table className="Minesweeper__list">
|
||||
<Table.Row bold>
|
||||
<SortButton id="name">Nick</SortButton>
|
||||
<SortButton id="time">Time</SortButton>
|
||||
</Table.Row>
|
||||
{leaderboard &&
|
||||
leaderboard
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((player, i) => (
|
||||
<Table.Row key={i}>
|
||||
<Table.Cell>{player.name}</Table.Cell>
|
||||
<Table.Cell>{player.time}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (properties, context) => {
|
||||
const [sortId, setSortId] = useLocalState(context, 'sortId', 'time');
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, 'sortOrder', false);
|
||||
const { id, children } = properties;
|
||||
return (
|
||||
<Table.Cell>
|
||||
<Button
|
||||
fluid
|
||||
color="transparent"
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{sortId === id && <Icon name={sortOrder ? 'sort-up' : 'sort-down'} ml="0.25rem;" />}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
@use '../base.scss';
|
||||
@use '../functions.scss' as *;
|
||||
|
||||
.Minesweeper__closed {
|
||||
vertical-align: middle;
|
||||
background-color: lighten(base.$color-bg, 20%);
|
||||
border: base.em(2px) outset lighten(base.$color-bg, 80%);
|
||||
}
|
||||
|
||||
.Minesweeper__open {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
font-size: medium;
|
||||
background-color: lighten(base.$color-bg, 50%) !important;
|
||||
}
|
||||
|
||||
.Minesweeper__list {
|
||||
tr > td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
tr:not(:first-child) {
|
||||
height: 2em;
|
||||
line-height: 1.75em;
|
||||
transition: background-color 50ms;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: rgba(base.$color-bg, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Minesweeper__infobox {
|
||||
max-height: 8em;
|
||||
border: base.em(3px) outset lighten(base.$color-bg, 25%);
|
||||
user-select: none;
|
||||
-ms-user-select: none; // Remove with Byond 516 release
|
||||
}
|
||||
@@ -63,6 +63,7 @@
|
||||
@include meta.load-css('./interfaces/OreRedemption.scss');
|
||||
@include meta.load-css('./interfaces/PanDEMIC.scss');
|
||||
@include meta.load-css('./interfaces/PDA.scss');
|
||||
@include meta.load-css('./interfaces/pda_minesweeper.scss');
|
||||
@include meta.load-css('./interfaces/PdaPainter.scss');
|
||||
@include meta.load-css('./interfaces/PoolController.scss');
|
||||
@include meta.load-css('./interfaces/ReagentsEditor.scss');
|
||||
|
||||
File diff suppressed because one or more lines are too long
+57
-57
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user