1 /* NetHack 3.7	questpgr.c	$NHDT-Date: 1596498201 2020/08/03 23:43:21 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.72 $ */
2 /*      Copyright 1991, M. Stephenson                             */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 #include "hack.h"
6 #include "dlb.h"
7 
8 /*  quest-specific pager routines. */
9 
10 #define QTEXT_FILE "quest.lua"
11 
12 #ifdef TTY_GRAPHICS
13 #include "wintty.h"
14 #endif
15 
16 static const char *intermed(void);
17 static struct obj *find_qarti(struct obj *);
18 static const char *neminame(void);
19 static const char *guardname(void);
20 static const char *homebase(void);
21 static void qtext_pronoun(char, char);
22 static void convert_arg(char);
23 static void deliver_by_pline(const char *);
24 static void deliver_by_window(const char *, int);
25 static boolean skip_pager(boolean);
26 static boolean com_pager_core(const char *, const char *, boolean);
27 
28 short
quest_info(int typ)29 quest_info(int typ)
30 {
31     switch (typ) {
32     case 0:
33         return g.urole.questarti;
34     case MS_LEADER:
35         return g.urole.ldrnum;
36     case MS_NEMESIS:
37         return g.urole.neminum;
38     case MS_GUARDIAN:
39         return g.urole.guardnum;
40     default:
41         impossible("quest_info(%d)", typ);
42     }
43     return 0;
44 }
45 
46 /* return your role leader's name */
47 const char *
ldrname(void)48 ldrname(void)
49 {
50     int i = g.urole.ldrnum;
51 
52     Sprintf(g.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
53             mons[i].pmnames[NEUTRAL]);
54     return g.nambuf;
55 }
56 
57 /* return your intermediate target string */
58 static const char *
intermed(void)59 intermed(void)
60 {
61     return g.urole.intermed;
62 }
63 
64 boolean
is_quest_artifact(struct obj * otmp)65 is_quest_artifact(struct obj *otmp)
66 {
67     return (boolean) (otmp->oartifact == g.urole.questarti);
68 }
69 
70 static struct obj *
find_qarti(struct obj * ochain)71 find_qarti(struct obj *ochain)
72 {
73     struct obj *otmp, *qarti;
74 
75     for (otmp = ochain; otmp; otmp = otmp->nobj) {
76         if (is_quest_artifact(otmp))
77             return otmp;
78         if (Has_contents(otmp) && (qarti = find_qarti(otmp->cobj)) != 0)
79             return qarti;
80     }
81     return (struct obj *) 0;
82 }
83 
84 /* check several object chains for the quest artifact to determine
85    whether it is present on the current level */
86 struct obj *
find_quest_artifact(unsigned whichchains)87 find_quest_artifact(unsigned whichchains)
88 {
89     struct monst *mtmp;
90     struct obj *qarti = 0;
91 
92     if ((whichchains & (1 << OBJ_INVENT)) != 0)
93         qarti = find_qarti(g.invent);
94     if (!qarti && (whichchains & (1 << OBJ_FLOOR)) != 0)
95         qarti = find_qarti(fobj);
96     if (!qarti && (whichchains & (1 << OBJ_MINVENT)) != 0)
97         for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
98             if (DEADMONSTER(mtmp))
99                 continue;
100             if ((qarti = find_qarti(mtmp->minvent)) != 0)
101                 break;
102         }
103     if (!qarti && (whichchains & (1 << OBJ_MIGRATING)) != 0) {
104         /* check migrating objects and minvent of migrating monsters */
105         for (mtmp = g.migrating_mons; mtmp; mtmp = mtmp->nmon) {
106             if (DEADMONSTER(mtmp))
107                 continue;
108             if ((qarti = find_qarti(mtmp->minvent)) != 0)
109                 break;
110         }
111         if (!qarti)
112             qarti = find_qarti(g.migrating_objs);
113     }
114     if (!qarti && (whichchains & (1 << OBJ_BURIED)) != 0)
115         qarti = find_qarti(g.level.buriedobjlist);
116 
117     return qarti;
118 }
119 
120 /* return your role nemesis' name */
121 static const char *
neminame(void)122 neminame(void)
123 {
124     int i = g.urole.neminum;
125 
126     Sprintf(g.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
127             mons[i].pmnames[NEUTRAL]);
128     return g.nambuf;
129 }
130 
131 static const char *
guardname(void)132 guardname(void) /* return your role leader's guard monster name */
133 {
134     int i = g.urole.guardnum;
135 
136     return mons[i].pmnames[NEUTRAL];
137 }
138 
139 static const char *
homebase(void)140 homebase(void) /* return your role leader's location */
141 {
142     return g.urole.homebase;
143 }
144 
145 /* replace deity, leader, nemesis, or artifact name with pronoun;
146    overwrites cvt_buf[] */
147 static void
qtext_pronoun(char who,char which)148 qtext_pronoun(char who,   /* 'd' => deity, 'l' => leader, 'n' => nemesis,
149                              'o' => artifact */
150               char which) /* 'h'|'H'|'i'|'I'|'j'|'J' */
151 {
152     const char *pnoun;
153     int godgend;
154     char lwhich = lowc(which); /* H,I,J -> h,i,j */
155 
156     /*
157      * Invalid subject (not d,l,n,o) yields neuter, singular result.
158      *
159      * For %o, treat all artifacts as neuter; some have plural names,
160      * which genders[] doesn't handle; cvt_buf[] already contains name.
161      */
162     if (who == 'o'
163         && (strstri(g.cvt_buf, "Eyes ")
164             || strcmpi(g.cvt_buf, makesingular(g.cvt_buf)))) {
165         pnoun = (lwhich == 'h') ? "they"
166                 : (lwhich == 'i') ? "them"
167                 : (lwhich == 'j') ? "their" : "?";
168     } else {
169         godgend = (who == 'd') ? g.quest_status.godgend
170             : (who == 'l') ? g.quest_status.ldrgend
171             : (who == 'n') ? g.quest_status.nemgend
172             : 2; /* default to neuter */
173         pnoun = (lwhich == 'h') ? genders[godgend].he
174                 : (lwhich == 'i') ? genders[godgend].him
175                 : (lwhich == 'j') ? genders[godgend].his : "?";
176     }
177     Strcpy(g.cvt_buf, pnoun);
178     /* capitalize for H,I,J */
179     if (lwhich != which)
180         g.cvt_buf[0] = highc(g.cvt_buf[0]);
181     return;
182 }
183 
184 static void
convert_arg(char c)185 convert_arg(char c)
186 {
187     register const char *str;
188 
189     switch (c) {
190     case 'p':
191         str = g.plname;
192         break;
193     case 'c':
194         str = (flags.female && g.urole.name.f) ? g.urole.name.f : g.urole.name.m;
195         break;
196     case 'r':
197         str = rank_of(u.ulevel, Role_switch, flags.female);
198         break;
199     case 'R':
200         str = rank_of(MIN_QUEST_LEVEL, Role_switch, flags.female);
201         break;
202     case 's':
203         str = (flags.female) ? "sister" : "brother";
204         break;
205     case 'S':
206         str = (flags.female) ? "daughter" : "son";
207         break;
208     case 'l':
209         str = ldrname();
210         break;
211     case 'i':
212         str = intermed();
213         break;
214     case 'O':
215     case 'o':
216         str = the(artiname(g.urole.questarti));
217         if (c == 'O') {
218             /* shorten "the Foo of Bar" to "the Foo"
219                (buffer returned by the() is modifiable) */
220             char *p = strstri(str, " of ");
221 
222             if (p)
223                 *p = '\0';
224         }
225         break;
226     case 'n':
227         str = neminame();
228         break;
229     case 'g':
230         str = guardname();
231         break;
232     case 'G':
233         str = align_gtitle(u.ualignbase[A_ORIGINAL]);
234         break;
235     case 'H':
236         str = homebase();
237         break;
238     case 'a':
239         str = align_str(u.ualignbase[A_ORIGINAL]);
240         break;
241     case 'A':
242         str = align_str(u.ualign.type);
243         break;
244     case 'd':
245         str = align_gname(u.ualignbase[A_ORIGINAL]);
246         break;
247     case 'D':
248         str = align_gname(A_LAWFUL);
249         break;
250     case 'C':
251         str = "chaotic";
252         break;
253     case 'N':
254         str = "neutral";
255         break;
256     case 'L':
257         str = "lawful";
258         break;
259     case 'x':
260         str = Blind ? "sense" : "see";
261         break;
262     case 'Z':
263         str = g.dungeons[0].dname;
264         break;
265     case '%':
266         str = "%";
267         break;
268     default:
269         str = "";
270         break;
271     }
272     Strcpy(g.cvt_buf, str);
273 }
274 
275 void
convert_line(const char * in_line,char * out_line)276 convert_line(const char *in_line, char *out_line)
277 {
278     const char *c;
279     char *cc;
280 
281     cc = out_line;
282     for (c = in_line; *c; c++) {
283         *cc = 0;
284         switch (*c) {
285         case '\r':
286         case '\n':
287             *(++cc) = 0;
288             return;
289 
290         case '%':
291             if (*(c + 1)) {
292                 convert_arg(*(++c));
293                 switch (*(++c)) {
294                 /* insert "a"/"an" prefix */
295                 case 'A':
296                     Strcat(cc, An(g.cvt_buf));
297                     cc += strlen(cc);
298                     continue; /* for */
299                 case 'a':
300                     Strcat(cc, an(g.cvt_buf));
301                     cc += strlen(cc);
302                     continue; /* for */
303 
304                 /* capitalize */
305                 case 'C':
306                     g.cvt_buf[0] = highc(g.cvt_buf[0]);
307                     break;
308 
309                 /* replace name with pronoun;
310                    valid for %d, %l, %n, and %o */
311                 case 'h': /* he/she */
312                 case 'H': /* He/She */
313                 case 'i': /* him/her */
314                 case 'I':
315                 case 'j': /* his/her */
316                 case 'J':
317                     if (index("dlno", lowc(*(c - 1))))
318                         qtext_pronoun(*(c - 1), *c);
319                     else
320                         --c; /* default action */
321                     break;
322 
323                 /* pluralize */
324                 case 'P':
325                     g.cvt_buf[0] = highc(g.cvt_buf[0]);
326                     /*FALLTHRU*/
327                 case 'p':
328                     Strcpy(g.cvt_buf, makeplural(g.cvt_buf));
329                     break;
330 
331                 /* append possessive suffix */
332                 case 'S':
333                     g.cvt_buf[0] = highc(g.cvt_buf[0]);
334                     /*FALLTHRU*/
335                 case 's':
336                     Strcpy(g.cvt_buf, s_suffix(g.cvt_buf));
337                     break;
338 
339                 /* strip any "the" prefix */
340                 case 't':
341                     if (!strncmpi(g.cvt_buf, "the ", 4)) {
342                         Strcat(cc, &g.cvt_buf[4]);
343                         cc += strlen(cc);
344                         continue; /* for */
345                     }
346                     break;
347 
348                 default:
349                     --c; /* undo switch increment */
350                     break;
351                 }
352                 Strcat(cc, g.cvt_buf);
353                 cc += strlen(g.cvt_buf);
354                 break;
355             } /* else fall through */
356 
357         default:
358             *cc++ = *c;
359             break;
360         }
361         if (cc > &out_line[BUFSZ - 1])
362             panic("convert_line: overflow");
363     }
364     *cc = 0;
365     return;
366 }
367 
368 static void
deliver_by_pline(const char * str)369 deliver_by_pline(const char *str)
370 {
371     const char *msgp = str;
372     const char *msgend = eos((char *)str);
373     char in_line[BUFSZ], out_line[BUFSZ];
374 
375     while (msgp && msgp != msgend) {
376         int i = 0;
377         while (*msgp != '\0' && *msgp != '\n' && (i < BUFSZ-2)) {
378             in_line[i] = *msgp;
379             i++;
380             msgp++;
381         }
382         if (*msgp == '\n')
383             msgp++;
384         in_line[i] = '\0';
385         convert_line(in_line, out_line);
386         pline("%s", out_line);
387     }
388 }
389 
390 static void
deliver_by_window(const char * msg,int how)391 deliver_by_window(const char *msg, int how)
392 {
393     const char *msgp = msg;
394     const char *msgend = eos((char *)msg);
395     char in_line[BUFSZ], out_line[BUFSZ];
396     winid datawin = create_nhwindow(how);
397 
398     while (msgp && msgp != msgend) {
399         int i = 0;
400         while (*msgp != '\0' && *msgp != '\n' && (i < BUFSZ-2)) {
401             in_line[i] = *msgp;
402             i++;
403             msgp++;
404         }
405         if (*msgp == '\n')
406             msgp++;
407         in_line[i] = '\0';
408         convert_line(in_line, out_line);
409         putstr(datawin, 0, out_line);
410     }
411 
412     display_nhwindow(datawin, TRUE);
413     destroy_nhwindow(datawin);
414 }
415 
416 static boolean
skip_pager(boolean common UNUSED)417 skip_pager(boolean common UNUSED)
418 {
419     /* WIZKIT: suppress plot feedback if starting with quest artifact */
420     if (g.program_state.wizkit_wishing)
421         return TRUE;
422     return FALSE;
423 }
424 
425 static boolean
com_pager_core(const char * section,const char * msgid,boolean showerror)426 com_pager_core(const char *section, const char *msgid, boolean showerror)
427 {
428     static const char *const howtoput[] = {
429         "pline", "window", "text", "menu", "default", NULL
430     };
431     static const int howtoput2i[] = { 1, 2, 2, 3, 0, 0 };
432     int output;
433     lua_State *L;
434     char *text = NULL, *synopsis = NULL, *fallback_msgid = NULL;
435     boolean res = FALSE;
436 
437     if (skip_pager(TRUE))
438         return FALSE;
439 
440     L = nhl_init();
441 
442     if (!nhl_loadlua(L, QTEXT_FILE)) {
443         if (showerror)
444             impossible("com_pager: %s not found.", QTEXT_FILE);
445         goto compagerdone;
446     }
447 
448     lua_settop(L, 0);
449     lua_getglobal(L, "questtext");
450     if (!lua_istable(L, -1)) {
451         if (showerror)
452             impossible("com_pager: questtext in %s is not a lua table",
453                        QTEXT_FILE);
454         goto compagerdone;
455     }
456 
457     lua_getfield(L, -1, section);
458     if (!lua_istable(L, -1)) {
459         if (showerror)
460             impossible("com_pager: questtext[%s] in %s is not a lua table",
461                        section, QTEXT_FILE);
462         goto compagerdone;
463     }
464 
465  tryagain:
466     lua_getfield(L, -1, fallback_msgid ? fallback_msgid : msgid);
467     if (!lua_istable(L, -1)) {
468         if (!fallback_msgid) {
469             /* Do we have questtxt[msg_fallbacks][<msgid>]? */
470             lua_getfield(L, -3, "msg_fallbacks");
471             if (lua_istable(L, -1)) {
472                 fallback_msgid = get_table_str_opt(L, msgid, NULL);
473                 lua_pop(L, 2);
474                 if (fallback_msgid)
475                     goto tryagain;
476             }
477         }
478         if (showerror) {
479             if (!fallback_msgid)
480                 impossible(
481                       "com_pager: questtext[%s][%s] in %s is not a lua table",
482                            section, msgid, QTEXT_FILE);
483             else
484                 impossible(
485            "com_pager: questtext[%s][%s] and [][%s] in %s are not lua tables",
486                            section, msgid, fallback_msgid, QTEXT_FILE);
487         }
488         goto compagerdone;
489     }
490 
491     synopsis = get_table_str_opt(L, "synopsis", NULL);
492     text = get_table_str_opt(L, "text", NULL);
493     output = howtoput2i[get_table_option(L, "output", "default", howtoput)];
494 
495     if (!text) {
496         int nelems;
497 
498         lua_len(L, -1);
499         nelems = (int) lua_tointeger(L, -1);
500         lua_pop(L, 1);
501         if (nelems < 2) {
502             if (showerror)
503                 impossible(
504               "com_pager: questtext[%s][%s] in %s in not an array of strings",
505                            section, fallback_msgid ? fallback_msgid : msgid,
506                            QTEXT_FILE);
507             goto compagerdone;
508         }
509         nelems = rn2(nelems) + 1;
510         lua_pushinteger(L, nelems);
511         lua_gettable(L, -2);
512         text = dupstr(luaL_checkstring(L, -1));
513     }
514 
515     if (output == 0 && (index(text, '\n') || (strlen(text) >= (BUFSZ - 1))))
516         output = 2;
517 
518     if (output == 0 || output == 1)
519         deliver_by_pline(text);
520     else
521         deliver_by_window(text, (output == 3) ? NHW_MENU : NHW_TEXT);
522 
523     if (synopsis) {
524         char in_line[BUFSZ], out_line[BUFSZ];
525 
526 #if 0   /* not yet -- brackets need to be removed from quest.lua */
527         Sprintf(in_line, "[%.*s]",
528                 (int) (sizeof in_line - sizeof "[]"), synopsis);
529 #else
530         Strcpy(in_line, synopsis);
531 #endif
532         convert_line(in_line, out_line);
533         /* bypass message delivery but be available for ^P recall */
534         putmsghistory(out_line, FALSE);
535     }
536     res = TRUE;
537 
538  compagerdone:
539     if (text)
540         free((genericptr_t) text);
541     if (synopsis)
542         free((genericptr_t) synopsis);
543     if (fallback_msgid)
544         free((genericptr_t) fallback_msgid);
545     nhl_done(L);
546     return res;
547 }
548 
549 void
com_pager(const char * msgid)550 com_pager(const char *msgid)
551 {
552     com_pager_core("common", msgid, TRUE);
553 }
554 
555 void
qt_pager(const char * msgid)556 qt_pager(const char *msgid)
557 {
558     if (!com_pager_core(g.urole.filecode, msgid, FALSE))
559         com_pager_core("common", msgid, TRUE);
560 }
561 
562 struct permonst *
qt_montype(void)563 qt_montype(void)
564 {
565     int qpm;
566 
567     if (rn2(5)) {
568         qpm = g.urole.enemy1num;
569         if (qpm != NON_PM && rn2(5) && !(g.mvitals[qpm].mvflags & G_GENOD))
570             return &mons[qpm];
571         return mkclass(g.urole.enemy1sym, 0);
572     }
573     qpm = g.urole.enemy2num;
574     if (qpm != NON_PM && rn2(5) && !(g.mvitals[qpm].mvflags & G_GENOD))
575         return &mons[qpm];
576     return mkclass(g.urole.enemy2sym, 0);
577 }
578 
579 /* special levels can include a custom arrival message; display it */
580 void
deliver_splev_message(void)581 deliver_splev_message(void)
582 {
583     char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
584 
585     /* there's no provision for delivering via window instead of pline */
586     if (g.lev_message) {
587         /* lev_message can span multiple lines using embedded newline chars;
588            any segments too long to fit within in_line[] will be truncated */
589         for (str = g.lev_message; *str; str = nl + 1) {
590             /* copying will stop at newline if one is present */
591             copynchars(in_line, str, (int) (sizeof in_line) - 1);
592 
593             convert_line(in_line, out_line);
594             pline("%s", out_line);
595 
596             if ((nl = index(str, '\n')) == 0)
597                 break; /* done if no newline */
598         }
599 
600         free((genericptr_t) g.lev_message);
601         g.lev_message = NULL;
602     }
603 }
604 
605 /*questpgr.c*/
606