getpos improvements: automatic description
Pressing '@' will move the cursor on top of the hero. Pressing '#' will toggle automatic description mode, where the feature under the cursor is automatically described when the cursor is moved.
This commit is contained in:
@@ -1080,6 +1080,9 @@ Add database entry for shuriken and make it match throwing star
|
||||
Add database entry for fedora
|
||||
Add database entry for land mine
|
||||
change command X to twoweapon toggle
|
||||
pressing @ when cursor positioning moves cursor on top of hero
|
||||
pressing # when cursor positioning toggles automatic description of features
|
||||
under the cursor
|
||||
|
||||
|
||||
Platform- and/or Interface-Specific New Features
|
||||
|
||||
@@ -40,6 +40,8 @@ const char *goal;
|
||||
putstr(tmpwin, 0, sbuf);
|
||||
putstr(tmpwin, 0, "Use [HJKL] to move the cursor 8 units at a time.");
|
||||
putstr(tmpwin, 0, "Or enter a background symbol (ex. <).");
|
||||
putstr(tmpwin, 0, "Use @ to move the cursor on yourself.");
|
||||
putstr(tmpwin, 0, "Use # to toggle automatic description.");
|
||||
/* disgusting hack; the alternate selection characters work for any
|
||||
getpos call, but they only matter for dowhatis (and doquickwhatis) */
|
||||
doing_what_is = (goal == what_is_an_unknown_object);
|
||||
@@ -63,6 +65,8 @@ const char *goal;
|
||||
int cx, cy, i, c;
|
||||
int sidx, tx, ty;
|
||||
boolean msg_given = TRUE; /* clear message window by default */
|
||||
boolean auto_msg = FALSE;
|
||||
boolean show_goal_msg = FALSE;
|
||||
static const char pick_chars[] = ".,;:";
|
||||
const char *cp;
|
||||
|
||||
@@ -82,7 +86,32 @@ const char *goal;
|
||||
lock_mouse_cursor(TRUE);
|
||||
#endif
|
||||
for (;;) {
|
||||
|
||||
if (show_goal_msg) {
|
||||
pline("Move cursor to %s:", goal);
|
||||
curs(WIN_MAP, cx, cy);
|
||||
flush_screen(0);
|
||||
show_goal_msg = FALSE;
|
||||
} else if (auto_msg && !msg_given) {
|
||||
coord cc;
|
||||
int sym = 0;
|
||||
char tmpbuf[BUFSZ];
|
||||
char *firstmatch = NULL;
|
||||
cc.x = cx;
|
||||
cc.y = cy;
|
||||
if (do_screen_description(cc, TRUE, sym, tmpbuf, &firstmatch)) {
|
||||
/* there may be an encoded glyph */
|
||||
putmixed(WIN_MESSAGE, 0, tmpbuf);
|
||||
curs(WIN_MAP, cx, cy);
|
||||
flush_screen(0);
|
||||
}
|
||||
}
|
||||
|
||||
c = nh_poskey(&tx, &ty, &sidx);
|
||||
|
||||
if (auto_msg)
|
||||
msg_given = FALSE;
|
||||
|
||||
if (c == '\033') {
|
||||
cx = cy = -10;
|
||||
msg_given = TRUE; /* force clear */
|
||||
@@ -142,8 +171,20 @@ const char *goal;
|
||||
else /* ^R */
|
||||
docrt(); /* redraw */
|
||||
/* update message window to reflect that we're still targetting */
|
||||
pline("Move cursor to %s:", goal);
|
||||
show_goal_msg = TRUE;
|
||||
msg_given = TRUE;
|
||||
} else if (c == '#') {
|
||||
auto_msg = !auto_msg;
|
||||
pline("Automatic description %sis %s.",
|
||||
flags.verbose ? "of features under cursor " : "",
|
||||
auto_msg ? "on" : "off");
|
||||
if (!auto_msg) show_goal_msg = TRUE;
|
||||
msg_given = TRUE;
|
||||
goto nxtc;
|
||||
} else if (c == '@') {
|
||||
cx = u.ux;
|
||||
cy = u.uy;
|
||||
goto nxtc;
|
||||
} else {
|
||||
if (!index(quitchars, c)) {
|
||||
char matching[MAXPCHARS];
|
||||
|
||||
427
src/pager.c
427
src/pager.c
@@ -437,6 +437,229 @@ bad_data_file: impossible("'data' file in wrong format");
|
||||
(void) dlb_fclose(fp);
|
||||
}
|
||||
|
||||
int
|
||||
do_screen_description(cc, looked, sym, out_str, firstmatch)
|
||||
coord cc;
|
||||
boolean looked;
|
||||
int sym;
|
||||
char *out_str;
|
||||
const char **firstmatch;
|
||||
{
|
||||
boolean need_to_look = FALSE;
|
||||
int glyph;
|
||||
char look_buf[BUFSZ], prefix[BUFSZ];
|
||||
int found = 0; /* count of matching syms found */
|
||||
int i;
|
||||
int skipped_venom = 0;
|
||||
boolean hit_trap;
|
||||
const char *x_str;
|
||||
static const char *mon_interior = "the interior of a monster";
|
||||
struct permonst *pm = NULL;
|
||||
|
||||
if (looked) {
|
||||
int oc, so;
|
||||
unsigned os;
|
||||
|
||||
glyph = glyph_at(cc.x,cc.y);
|
||||
|
||||
/* Convert the glyph at the selected position to a symbol. */
|
||||
so = mapglyph(glyph, &sym, &oc, &os, cc.x, cc.y);
|
||||
}
|
||||
|
||||
if (looked)
|
||||
Sprintf(prefix, "%s ", encglyph(glyph));
|
||||
else
|
||||
Sprintf(prefix, "%c ", sym);
|
||||
|
||||
/*
|
||||
* Check all the possibilities, saving all explanations in a buffer.
|
||||
* When all have been checked then the string is printed.
|
||||
*/
|
||||
|
||||
/* Check for monsters */
|
||||
for (i = 0; i < MAXMCLASSES; i++) {
|
||||
if (sym == ((looked) ?
|
||||
showsyms[i + SYM_OFF_M] : def_monsyms[i].sym) &&
|
||||
def_monsyms[i].explain) {
|
||||
need_to_look = TRUE;
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(def_monsyms[i].explain));
|
||||
*firstmatch = def_monsyms[i].explain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(def_monsyms[i].explain));
|
||||
}
|
||||
}
|
||||
}
|
||||
/* handle '@' as a special case if it refers to you and you're
|
||||
playing a character which isn't normally displayed by that
|
||||
symbol; firstmatch is assumed to already be set for '@' */
|
||||
if (((looked) ?
|
||||
(sym == showsyms[S_HUMAN + SYM_OFF_M] && cc.x == u.ux && cc.y == u.uy) :
|
||||
(sym == def_monsyms[S_HUMAN].sym && !flags.showrace)) &&
|
||||
!(Race_if(PM_HUMAN) || Race_if(PM_ELF)) && !Upolyd)
|
||||
found += append_str(out_str, "you"); /* tack on "or you" */
|
||||
|
||||
/*
|
||||
* Special case: if identifying from the screen, and we're swallowed,
|
||||
* and looking at something other than our own symbol, then just say
|
||||
* "the interior of a monster".
|
||||
*/
|
||||
if (u.uswallow && (looked) && is_swallow_sym(sym)) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, mon_interior);
|
||||
*firstmatch = mon_interior;
|
||||
} else {
|
||||
found += append_str(out_str, mon_interior);
|
||||
}
|
||||
need_to_look = TRUE;
|
||||
}
|
||||
|
||||
/* Now check for objects */
|
||||
for (i = 1; i < MAXOCLASSES; i++) {
|
||||
if (sym == ((looked) ?
|
||||
showsyms[i + SYM_OFF_O] : def_oc_syms[i].sym)) {
|
||||
need_to_look = TRUE;
|
||||
if ((looked) && i == VENOM_CLASS) {
|
||||
skipped_venom++;
|
||||
continue;
|
||||
}
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(def_oc_syms[i].explain));
|
||||
*firstmatch = def_oc_syms[i].explain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(def_oc_syms[i].explain));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sym == DEF_INVISIBLE) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix,
|
||||
an(invisexplain));
|
||||
*firstmatch = invisexplain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(invisexplain));
|
||||
}
|
||||
}
|
||||
|
||||
#define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap)
|
||||
#define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge)
|
||||
|
||||
/* Now check for graphics symbols */
|
||||
for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) {
|
||||
x_str = defsyms[i].explanation;
|
||||
if (sym == ((looked) ?
|
||||
showsyms[i] : defsyms[i].sym) && *x_str) {
|
||||
/* avoid "an air", "a water", or "a floor of a room" */
|
||||
int article = (i == S_room) ? 2 : /* 2=>"the" */
|
||||
!(strcmp(x_str, "air") == 0 || /* 1=>"an" */
|
||||
strcmp(x_str, "water") == 0); /* 0=>(none)*/
|
||||
|
||||
if (!found) {
|
||||
if (is_cmap_trap(i)) {
|
||||
Sprintf(out_str, "%sa trap", prefix);
|
||||
hit_trap = TRUE;
|
||||
} else {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix,
|
||||
article == 2 ? the(x_str) :
|
||||
article == 1 ? an(x_str) : x_str);
|
||||
}
|
||||
*firstmatch = x_str;
|
||||
found++;
|
||||
} else if (!u.uswallow && !(hit_trap && is_cmap_trap(i)) &&
|
||||
!(found >= 3 && is_cmap_drawbridge(i))) {
|
||||
found += append_str(out_str,
|
||||
article == 2 ? the(x_str) :
|
||||
article == 1 ? an(x_str) : x_str);
|
||||
if (is_cmap_trap(i)) hit_trap = TRUE;
|
||||
}
|
||||
|
||||
if (i == S_altar || is_cmap_trap(i))
|
||||
need_to_look = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now check for warning symbols */
|
||||
for (i = 1; i < WARNCOUNT; i++) {
|
||||
x_str = def_warnsyms[i].explanation;
|
||||
if (sym == ((looked) ?
|
||||
warnsyms[i] : def_warnsyms[i].sym)) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, def_warnsyms[i].explanation);
|
||||
*firstmatch = def_warnsyms[i].explanation;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, def_warnsyms[i].explanation);
|
||||
}
|
||||
/* Kludge: warning trumps boulders on the display.
|
||||
Reveal the boulder too or player can get confused */
|
||||
if ((looked) && sobj_at(BOULDER, cc.x, cc.y))
|
||||
Strcat(out_str, " co-located with a boulder");
|
||||
break; /* out of for loop*/
|
||||
}
|
||||
}
|
||||
|
||||
/* if we ignored venom and list turned out to be short, put it back */
|
||||
if (skipped_venom && found < 2) {
|
||||
x_str = def_oc_syms[VENOM_CLASS].explain;
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(x_str));
|
||||
*firstmatch = x_str;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(x_str));
|
||||
}
|
||||
}
|
||||
|
||||
/* handle optional boulder symbol as a special case */
|
||||
if (iflags.bouldersym && sym == iflags.bouldersym) {
|
||||
if (!found) {
|
||||
*firstmatch = "boulder";
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(*firstmatch));
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, "boulder");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are looking at the screen, follow multiple possibilities or
|
||||
* an ambiguous explanation by something more detailed.
|
||||
*/
|
||||
if (looked) {
|
||||
if (found > 1 || need_to_look) {
|
||||
char monbuf[BUFSZ];
|
||||
char temp_buf[BUFSZ];
|
||||
|
||||
pm = lookat(cc.x, cc.y, look_buf, monbuf);
|
||||
*firstmatch = look_buf;
|
||||
if (*(*firstmatch)) {
|
||||
Sprintf(temp_buf, " (%s)", *firstmatch);
|
||||
(void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
|
||||
found = 1; /* we have something to look up */
|
||||
}
|
||||
if (monbuf[0]) {
|
||||
Sprintf(temp_buf, " [seen: %s]", monbuf);
|
||||
(void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
/* getpos() return values */
|
||||
#define LOOK_TRADITIONAL 0 /* '.' -- ask about "more info?" */
|
||||
#define LOOK_QUICK 1 /* ',' -- skip "more info?" */
|
||||
@@ -453,19 +676,15 @@ do_look(mode, click_cc)
|
||||
{
|
||||
boolean quick = (mode == 1); /* use cursor && don't search for "more info" */
|
||||
boolean clicklook = (mode == 2); /* right mouse-click method */
|
||||
char out_str[BUFSZ], look_buf[BUFSZ], prefix[BUFSZ];
|
||||
const char *x_str, *firstmatch = 0;
|
||||
char out_str[BUFSZ];
|
||||
const char *firstmatch = 0;
|
||||
struct permonst *pm = 0;
|
||||
int glyph; /* glyph at selected position */
|
||||
int i, ans = 0;
|
||||
int sym; /* typed symbol or converted glyph */
|
||||
int found; /* count of matching syms found */
|
||||
coord cc; /* screen pos of unknown glyph */
|
||||
boolean save_verbose; /* saved value of flags.verbose */
|
||||
boolean from_screen; /* question from the screen */
|
||||
boolean need_to_look; /* need to get explan. from glyph */
|
||||
boolean hit_trap; /* true if found trap explanation */
|
||||
int skipped_venom; /* non-zero if we ignored "splash of venom" */
|
||||
static const char *mon_interior = "the interior of a monster";
|
||||
|
||||
if (!clicklook) {
|
||||
@@ -506,9 +725,7 @@ do_look(mode, click_cc)
|
||||
*/
|
||||
do {
|
||||
/* Reset some variables. */
|
||||
need_to_look = FALSE;
|
||||
pm = (struct permonst *)0;
|
||||
skipped_venom = 0;
|
||||
found = 0;
|
||||
out_str[0] = '\0';
|
||||
|
||||
@@ -529,201 +746,9 @@ do_look(mode, click_cc)
|
||||
}
|
||||
flags.verbose = FALSE; /* only print long question once */
|
||||
}
|
||||
glyph = glyph_at(cc.x,cc.y);
|
||||
|
||||
/* Convert the glyph at the selected position to a symbol. */
|
||||
so = mapglyph(glyph, &sym, &oc, &os, cc.x, cc.y);
|
||||
}
|
||||
|
||||
if (from_screen || clicklook)
|
||||
Sprintf(prefix, "%s ", encglyph(glyph));
|
||||
else
|
||||
Sprintf(prefix, "%c ", sym);
|
||||
|
||||
/*
|
||||
* Check all the possibilities, saving all explanations in a buffer.
|
||||
* When all have been checked then the string is printed.
|
||||
*/
|
||||
|
||||
/* Check for monsters */
|
||||
for (i = 0; i < MAXMCLASSES; i++) {
|
||||
if (sym == ((from_screen || clicklook) ?
|
||||
showsyms[i + SYM_OFF_M] : def_monsyms[i].sym) &&
|
||||
def_monsyms[i].explain) {
|
||||
need_to_look = TRUE;
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(def_monsyms[i].explain));
|
||||
firstmatch = def_monsyms[i].explain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(def_monsyms[i].explain));
|
||||
}
|
||||
}
|
||||
}
|
||||
/* handle '@' as a special case if it refers to you and you're
|
||||
playing a character which isn't normally displayed by that
|
||||
symbol; firstmatch is assumed to already be set for '@' */
|
||||
if (((from_screen || clicklook) ?
|
||||
(sym == showsyms[S_HUMAN + SYM_OFF_M] && cc.x == u.ux && cc.y == u.uy) :
|
||||
(sym == def_monsyms[S_HUMAN].sym && !flags.showrace)) &&
|
||||
!(Race_if(PM_HUMAN) || Race_if(PM_ELF)) && !Upolyd)
|
||||
found += append_str(out_str, "you"); /* tack on "or you" */
|
||||
|
||||
/*
|
||||
* Special case: if identifying from the screen, and we're swallowed,
|
||||
* and looking at something other than our own symbol, then just say
|
||||
* "the interior of a monster".
|
||||
*/
|
||||
if (u.uswallow && (from_screen || clicklook) && is_swallow_sym(sym)) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, mon_interior);
|
||||
firstmatch = mon_interior;
|
||||
} else {
|
||||
found += append_str(out_str, mon_interior);
|
||||
}
|
||||
need_to_look = TRUE;
|
||||
}
|
||||
|
||||
/* Now check for objects */
|
||||
for (i = 1; i < MAXOCLASSES; i++) {
|
||||
if (sym == ((from_screen || clicklook) ?
|
||||
showsyms[i + SYM_OFF_O] : def_oc_syms[i].sym)) {
|
||||
need_to_look = TRUE;
|
||||
if ((from_screen || clicklook) && i == VENOM_CLASS) {
|
||||
skipped_venom++;
|
||||
continue;
|
||||
}
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(def_oc_syms[i].explain));
|
||||
firstmatch = def_oc_syms[i].explain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(def_oc_syms[i].explain));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sym == DEF_INVISIBLE) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix,
|
||||
an(invisexplain));
|
||||
firstmatch = invisexplain;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(invisexplain));
|
||||
}
|
||||
}
|
||||
|
||||
#define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap)
|
||||
#define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge)
|
||||
|
||||
/* Now check for graphics symbols */
|
||||
for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) {
|
||||
x_str = defsyms[i].explanation;
|
||||
if (sym == ((from_screen || clicklook) ?
|
||||
showsyms[i] : defsyms[i].sym) && *x_str) {
|
||||
/* avoid "an air", "a water", or "a floor of a room" */
|
||||
int article = (i == S_room) ? 2 : /* 2=>"the" */
|
||||
!(strcmp(x_str, "air") == 0 || /* 1=>"an" */
|
||||
strcmp(x_str, "water") == 0); /* 0=>(none)*/
|
||||
|
||||
if (!found) {
|
||||
if (is_cmap_trap(i)) {
|
||||
Sprintf(out_str, "%sa trap", prefix);
|
||||
hit_trap = TRUE;
|
||||
} else {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix,
|
||||
article == 2 ? the(x_str) :
|
||||
article == 1 ? an(x_str) : x_str);
|
||||
}
|
||||
firstmatch = x_str;
|
||||
found++;
|
||||
} else if (!u.uswallow && !(hit_trap && is_cmap_trap(i)) &&
|
||||
!(found >= 3 && is_cmap_drawbridge(i))) {
|
||||
found += append_str(out_str,
|
||||
article == 2 ? the(x_str) :
|
||||
article == 1 ? an(x_str) : x_str);
|
||||
if (is_cmap_trap(i)) hit_trap = TRUE;
|
||||
}
|
||||
|
||||
if (i == S_altar || is_cmap_trap(i))
|
||||
need_to_look = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now check for warning symbols */
|
||||
for (i = 1; i < WARNCOUNT; i++) {
|
||||
x_str = def_warnsyms[i].explanation;
|
||||
if (sym == ((from_screen || clicklook) ?
|
||||
warnsyms[i] : def_warnsyms[i].sym)) {
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, def_warnsyms[i].explanation);
|
||||
firstmatch = def_warnsyms[i].explanation;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, def_warnsyms[i].explanation);
|
||||
}
|
||||
/* Kludge: warning trumps boulders on the display.
|
||||
Reveal the boulder too or player can get confused */
|
||||
if ((from_screen || clicklook) && sobj_at(BOULDER, cc.x, cc.y))
|
||||
Strcat(out_str, " co-located with a boulder");
|
||||
break; /* out of for loop*/
|
||||
}
|
||||
}
|
||||
|
||||
/* if we ignored venom and list turned out to be short, put it back */
|
||||
if (skipped_venom && found < 2) {
|
||||
x_str = def_oc_syms[VENOM_CLASS].explain;
|
||||
if (!found) {
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(x_str));
|
||||
firstmatch = x_str;
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, an(x_str));
|
||||
}
|
||||
}
|
||||
|
||||
/* handle optional boulder symbol as a special case */
|
||||
if (iflags.bouldersym && sym == iflags.bouldersym) {
|
||||
if (!found) {
|
||||
firstmatch = "boulder";
|
||||
Sprintf(out_str, "%s%s",
|
||||
prefix, an(firstmatch));
|
||||
found++;
|
||||
} else {
|
||||
found += append_str(out_str, "boulder");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are looking at the screen, follow multiple possibilities or
|
||||
* an ambiguous explanation by something more detailed.
|
||||
*/
|
||||
if (from_screen || clicklook) {
|
||||
if (found > 1 || need_to_look) {
|
||||
char monbuf[BUFSZ];
|
||||
char temp_buf[BUFSZ];
|
||||
|
||||
pm = lookat(cc.x, cc.y, look_buf, monbuf);
|
||||
firstmatch = look_buf;
|
||||
if (*firstmatch) {
|
||||
Sprintf(temp_buf, " (%s)", firstmatch);
|
||||
(void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
|
||||
found = 1; /* we have something to look up */
|
||||
}
|
||||
if (monbuf[0]) {
|
||||
Sprintf(temp_buf, " [seen: %s]", monbuf);
|
||||
(void)strncat(out_str, temp_buf, BUFSZ-strlen(out_str)-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
found = do_screen_description(cc, (from_screen||clicklook), sym, out_str, &firstmatch);
|
||||
|
||||
/* Finally, print out our explanation. */
|
||||
if (found) {
|
||||
|
||||
Reference in New Issue
Block a user