diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 7315631b0..3a7df513a 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -2638,6 +2638,50 @@ The second example results in the exclusion of any corpse from autopickup. The last example results in the exclusion of items known to be cursed from autopickup. .hn 2 +Configuring Message Types +.pg +You can change the way the messages are shown in the message area, when +the message matches a user-defined pattern. +.pg +In general, the config file entries to configure the message types +look like this: +.si +MSGTYPE=type "pattern" +.ei +.PS "message type" +.PL type +how the message should be shown; +.PL pattern +the pattern to match. +.PE +.lp "" +The pattern should be a regular expression. +.lp "" +Allowed types are: +.sd +.si +show - show message normally. +hide - never show the message. +stop - wait for user with more-prompt. +norep - show the message once, but not again if no other message is shown in between. +.ei +.ed +.lp "" +Here's an example of message types using NetHack's internal +pattern matching facility: +.sd +.si +MSGTYPE=stop "You feel hungry." +MSGTYPE=hide "You displaced *." +.ei +.ed +specifies that whenever a message "You feel hungry" is shown, +the user is prompted with more-prompt, and a message matching +"You displaced ." is not shown at all. +.lp "" +The order of the defined MSGTYPE-lines is important; the last matching +rule is used. Put the general case first, exceptions below them. +.hn 2 Configuring Menu Colors .pg Some platforms allow you to define colors used in menu lines when the diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index f9fb923bc..566402cd9 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -3184,6 +3184,64 @@ The second example results in the exclusion of any corpse from autopickup. The last example results in the exclusion of items known to be cursed from autopickup. +%.lp +%.hn 2 +\subsection*{Configuring Message Types} + +%.pg +You can change the way the messages are shown in the message area, when +the message matches a user-defined pattern. + +%.pg +In general, the config file entries to configure the message types +look like this: +\begin{verbatim} + MSGTYPE=type "pattern" +\end{verbatim} + +\blist{} +%.lp +\item[\ib{type}] +how the message should be shown; +%.lp +\item[\ib{pattern}] +the pattern to match. +\elist + +%.lp "" +The pattern should be a regular expression. + +%.lp "" +Allowed types are: + +%.sd +%.si +{\tt show} --- show message normally.\\ +{\tt hide} --- never show the message.\\ +{\tt stop} --- wait for user with more-prompt.\\ +{\tt norep} --- show the message once, but not again if no other message is shown in between. +%.ei +%.ed + +%.lp "" +Here's an example of menu colors using NetHack's internal +pattern matching facility: + +\begin{verbatim} + MSGTYPE=stop "You feel hungry." + MSGTYPE=hide "You displaced *." +\end{verbatim} + +specifies that whenever a message ``You feel hungry'' is shown, +the user is prompted with more-prompt, and a message matching +``You displaced '' is not shown at all. + +%.lp +The order of the defined MSGTYPE-lines is important; the last matching +rule is used. Put the general case first, exceptions below them. + +%.pg + %.lp %.hn 2 \subsection*{Configuring Menu Colors} diff --git a/include/decl.h b/include/decl.h index 61abc88b2..fae4fcf4b 100644 --- a/include/decl.h +++ b/include/decl.h @@ -395,6 +395,20 @@ struct autopickup_exception { struct autopickup_exception *next; }; +struct plinemsg_type { + xchar msgtype; /* one of MSGTYP_foo */ + struct nhregex *regex; + char *pattern; + struct plinemsg_type *next; +}; + +#define MSGTYP_NORMAL 0 +#define MSGTYP_NOREP 1 +#define MSGTYP_NOSHOW 2 +#define MSGTYP_STOP 3 + +E struct plinemsg_type *plinemsg_types; + #ifdef PANICTRACE E char *ARGV0; #endif diff --git a/include/extern.h b/include/extern.h index 75272c205..1d26a6e55 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1638,6 +1638,9 @@ E int FDECL(sym_val, (char *)); E boolean FDECL(add_menu_coloring, (char *)); E boolean FDECL(get_menu_coloring, (char *, int *, int *)); E void NDECL(free_menu_coloring); +E boolean FDECL(msgtype_parse_add, (char *)); +E int FDECL(msgtype_type, (const char *)); +E void NDECL(msgtype_free); /* ### pager.c ### */ diff --git a/src/decl.c b/src/decl.c index c99eae4c8..7e4f95f6d 100644 --- a/src/decl.c +++ b/src/decl.c @@ -328,6 +328,8 @@ NEARDATA struct savefile_info sfrestinfo, sfsaveinfo = { #endif }; +struct plinemsg_type *plinemsg_types = NULL; + #ifdef PANICTRACE char *ARGV0; #endif diff --git a/src/files.c b/src/files.c index a43869cfa..697a1fdd1 100644 --- a/src/files.c +++ b/src/files.c @@ -2134,6 +2134,8 @@ int src; plnamesuffix(); /* set the character class */ } else if (match_varname(buf, "AUTOPICKUP_EXCEPTION", 5)) { add_autopickup_exception(bufp); + } else if (match_varname(buf, "MSGTYPE", 7)) { + (void) msgtype_parse_add(bufp); #ifdef NOCWD_ASSUMPTIONS } else if (match_varname(buf, "HACKDIR", 4)) { adjust_prefix(bufp, HACKPREFIX); diff --git a/src/options.c b/src/options.c index 78bd23398..9b767a810 100644 --- a/src/options.c +++ b/src/options.c @@ -505,6 +505,7 @@ STATIC_OVL int FDECL(count_ape_maps, (int *, int *)); STATIC_DCL const char *FDECL(clr2colorname, (int)); STATIC_DCL const char *FDECL(attr2attrname, (int)); STATIC_DCL int NDECL(query_color); +STATIC_DCL int NDECL(query_msgtype); STATIC_DCL int FDECL(query_attr, (const char *)); STATIC_DCL boolean FDECL(add_menu_coloring_parsed, (char *, int, int)); STATIC_DCL void FDECL(free_one_menu_coloring, (int)); @@ -1279,6 +1280,168 @@ const char *prompt; return -1; } +static const struct { + const char *name; + const xchar msgtyp; + const char *descr; +} msgtype_names[] = { + { "show", MSGTYP_NORMAL, "Show message normally" }, + { "hide", MSGTYP_NOSHOW, "Hide message" }, + { "noshow", MSGTYP_NOSHOW, NULL }, + { "stop", MSGTYP_STOP, "Prompt for more after the message" }, + { "more", MSGTYP_STOP, NULL }, + { "norep", MSGTYP_NOREP, "Do not repeat the message" } +}; + +const char * +msgtype2name(typ) +int typ; +{ + int i; + for (i = 0; i < SIZE(msgtype_names); i++) + if (msgtype_names[i].descr && msgtype_names[i].msgtyp == typ) + return msgtype_names[i].name; + return NULL; +} + +int +query_msgtype() +{ + winid tmpwin; + anything any; + int i, pick_cnt; + xchar prev_typ = -1; + menu_item *picks = (menu_item *) 0; + + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin); + any = zeroany; + for (i = 0; i < SIZE(msgtype_names); i++) + if (msgtype_names[i].descr) { + any.a_int = msgtype_names[i].msgtyp + 1; + add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, + msgtype_names[i].descr, MENU_UNSELECTED); + } + end_menu(tmpwin, "How to show the message"); + pick_cnt = select_menu(tmpwin, PICK_ONE, &picks); + destroy_nhwindow(tmpwin); + if (pick_cnt > 0) { + i = picks->item.a_int - 1; + free((genericptr_t) picks); + return i; + } + return -1; +} + +boolean +msgtype_add(typ, pattern) +int typ; +char *pattern; +{ + struct plinemsg_type *tmp = (struct plinemsg_type *) alloc(sizeof(struct plinemsg_type)); + if (!tmp) return FALSE; + tmp->msgtype = typ; + tmp->regex = regex_init(); + if (!regex_compile(pattern, tmp->regex)) { + static const char *re_error = "MSGTYPE regex error"; + if (!iflags.window_inited) + raw_printf("\n%s: %s\n", re_error, regex_error_desc(tmp->regex)); + else + pline("%s: %s", re_error, regex_error_desc(tmp->regex)); + wait_synch(); + free(tmp); + return FALSE; + } + tmp->pattern = dupstr(pattern); + tmp->next = plinemsg_types; + plinemsg_types = tmp; + return TRUE; +} + +void +msgtype_free() +{ + struct plinemsg_type *tmp = plinemsg_types; + struct plinemsg_type *tmp2; + while (tmp) { + free(tmp->pattern); + regex_free(tmp->regex); + tmp2 = tmp; + tmp = tmp->next; + free(tmp2); + } + plinemsg_types = NULL; +} + +void +free_one_msgtype(idx) +int idx; /* 0 .. */ +{ + struct plinemsg_type *tmp = plinemsg_types; + struct plinemsg_type *prev = NULL; + + while (tmp) { + if (idx == 0) { + struct plinemsg_type *next = tmp->next; + regex_free(tmp->regex); + free(tmp->pattern); + free(tmp); + if (prev) + prev->next = next; + else + plinemsg_types = next; + return; + } + idx--; + prev = tmp; + tmp = tmp->next; + } +} + +int +msgtype_type(msg) +const char *msg; +{ + struct plinemsg_type *tmp = plinemsg_types; + while (tmp) { + if (regex_match(msg, tmp->regex)) return tmp->msgtype; + tmp = tmp->next; + } + return MSGTYP_NORMAL; +} + +int +msgtype_count() +{ + int c = 0; + struct plinemsg_type *tmp = plinemsg_types; + while (tmp) { + c++; + tmp = tmp->next; + } + return c; +} + +boolean +msgtype_parse_add(str) +char *str; +{ + char pattern[256]; + char msgtype[11]; + if (sscanf(str, "%10s \"%255[^\"]\"", msgtype, pattern) == 2) { + int typ = -1; + int i; + for (i = 0; i < SIZE(msgtype_names); i++) + if (!strcasecmp(msgtype_names[i].name, msgtype)) { + typ = msgtype_names[i].msgtyp; + break; + } + if (typ != -1) + return msgtype_add(typ, pattern); + } + return FALSE; +} + boolean add_menu_coloring_parsed(str, c, a) char *str; @@ -3379,6 +3542,11 @@ doset() doset_add_menu(tmpwin, compopt[i].name, (pass == DISP_IN_GAME) ? 0 : indexoffset); } + any.a_int = -4; + Sprintf(buf2, "(%d currently set)", msgtype_count()); + Sprintf(buf, fmtstr_doset_add_menu, any.a_int ? "" : " ", "message types", + buf2); + add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, buf, MENU_UNSELECTED); any.a_int = -3; Sprintf(buf2, "(%d currently set)", count_menucolors()); Sprintf(buf, fmtstr_doset_add_menu, any.a_int ? "" : " ", "menucolors", @@ -3438,6 +3606,8 @@ doset() #endif if (opt_indx == -4) { (void) special_handling("menucolors", setinitial, fromfile); + } else if (opt_indx == -5) { + (void) special_handling("msgtype", setinitial, fromfile); } else if (opt_indx < boolcount) { /* boolean option */ Sprintf(buf, "%s%s", *boolopt[opt_indx].addr ? "!" : "", @@ -3835,6 +4005,60 @@ boolean setinitial, setfromfile; int mhattr = query_attr("How to highlight menu headings:"); if (mhattr != -1) iflags.menu_headings = mhattr; + } else if (!strcmp("msgtype", optname)) { + int opt_idx, nmt, mttyp; + char mtbuf[BUFSZ]; + msgtypes_again: + nmt = msgtype_count(); + opt_idx = handle_add_list_remove("message type", nmt); + if (opt_idx == 3) { + ; /* done--fall through to function exit */ + } else if (opt_idx == 0) { /* add new */ + getlin("What new message pattern?", mtbuf); + if (*mtbuf == '\033' || !*mtbuf) + goto msgtypes_again; + mttyp = query_msgtype(); + if (mttyp == -1) + goto msgtypes_again; + if (!msgtype_add(mttyp, mtbuf)) { + pline("Error adding the message type."); + wait_synch(); + goto msgtypes_again; + } + } else { /* list or remove */ + int pick_idx, pick_cnt; + int mt_idx; + char mtbuf[BUFSZ]; + menu_item *pick_list = (menu_item *) 0; + struct plinemsg_type *tmp = plinemsg_types; + tmpwin = create_nhwindow(NHW_MENU); + start_menu(tmpwin); + any = zeroany; + mt_idx = 0; + while (tmp) { + const char *mtype = msgtype2name(tmp->msgtype); + any.a_int = (++mt_idx); + Sprintf(mtbuf, "%-5s \"%s\"", mtype, tmp->pattern); + add_menu(tmpwin, NO_GLYPH, &any, 0, 0, ATR_NONE, mtbuf, + MENU_UNSELECTED); + tmp = tmp->next; + } + Sprintf(mtbuf, "%s message types", + (opt_idx == 1) ? "List of" : "Remove which"); + end_menu(tmpwin, mtbuf); + pick_cnt = select_menu( + tmpwin, (opt_idx == 1) ? PICK_NONE : PICK_ANY, &pick_list); + if (pick_cnt > 0) { + for (pick_idx = 0; pick_idx < pick_cnt; ++pick_idx) + free_one_msgtype(pick_list[pick_idx].item.a_int - 1 + - pick_idx); + } + free((genericptr_t) pick_list); + pick_list = (menu_item *) 0; + destroy_nhwindow(tmpwin); + if (pick_cnt >= 0) + goto msgtypes_again; + } } else if (!strcmp("menucolors", optname)) { int opt_idx, nmc, mcclr, mcattr; char mcbuf[BUFSZ]; diff --git a/src/pline.c b/src/pline.c index ab4fc8a17..ce4b60702 100644 --- a/src/pline.c +++ b/src/pline.c @@ -7,6 +7,7 @@ #include "hack.h" static boolean no_repeat = FALSE; +static char prevmsg[BUFSZ]; static char *FDECL(You_buf, (int)); @@ -47,6 +48,7 @@ VA_DECL(const char *, line) { /* start of vpline() or of nested block in USE_OLDARG's pline() */ char pbuf[3 * BUFSZ]; int ln; + xchar msgtyp; /* Do NOT use VA_START and VA_END in here... see above */ if (!line || !*line) @@ -89,9 +91,15 @@ VA_DECL(const char *, line) vision_recalc(0); if (u.ux) flush_screen(1); /* %% */ + msgtyp = msgtype_type(line); + if (msgtyp == MSGTYP_NOSHOW) return; + if (msgtyp == MSGTYP_NOREP && !strcmp(line, prevmsg)) return; putstr(WIN_MESSAGE, 0, line); /* this gets cleared after every pline message */ iflags.last_msg = PLNMSG_UNKNOWN; + strncpy(prevmsg, line, BUFSZ); + if (msgtyp == MSGTYP_STOP) display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */ + #if !(defined(USE_STDARG) || defined(USE_VARARGS)) /* provide closing brace for the nested block which immediately follows USE_OLDARGS's VA_DECL() */ diff --git a/src/save.c b/src/save.c index 4f65ececf..8ab8225de 100644 --- a/src/save.c +++ b/src/save.c @@ -1320,6 +1320,7 @@ freedynamicdata() free_menu_coloring(); free_invbuf(); /* let_to_name (invent.c) */ free_youbuf(); /* You_buf,&c (pline.c) */ + msgtype_free(); tmp_at(DISP_FREEMEM, 0); /* temporary display effects */ #ifdef FREE_ALL_MEMORY #define freeobjchn(X) (saveobjchn(0, X, FREE_SAVE), X = 0)