1 /* NetHack 3.6	questpgr.c	$NHDT-Date: 1505172128 2017/09/11 23:22:08 $  $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.38 $ */
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 #include "qtext.h"
11 
12 #define QTEXT_FILE "quest.dat"
13 
14 #ifdef TTY_GRAPHICS
15 #include "wintty.h"
16 #endif
17 
18 /* from sp_lev.c, for deliver_splev_message() */
19 extern char *lev_message;
20 
21 static void NDECL(dump_qtlist);
22 static void FDECL(Fread, (genericptr_t, int, int, dlb *));
23 STATIC_DCL struct qtmsg *FDECL(construct_qtlist, (long));
24 STATIC_DCL const char *NDECL(intermed);
25 STATIC_DCL struct obj *FDECL(find_qarti, (struct obj *));
26 STATIC_DCL const char *NDECL(neminame);
27 STATIC_DCL const char *NDECL(guardname);
28 STATIC_DCL const char *NDECL(homebase);
29 STATIC_DCL void FDECL(qtext_pronoun, (CHAR_P, CHAR_P));
30 STATIC_DCL struct qtmsg *FDECL(msg_in, (struct qtmsg *, int));
31 STATIC_DCL void FDECL(convert_arg, (CHAR_P));
32 STATIC_DCL void FDECL(convert_line, (char *,char *));
33 STATIC_DCL void FDECL(deliver_by_pline, (struct qtmsg *));
34 STATIC_DCL void FDECL(deliver_by_window, (struct qtmsg *, int));
35 STATIC_DCL boolean FDECL(skip_pager, (BOOLEAN_P));
36 
37 static char cvt_buf[64];
38 static struct qtlists qt_list;
39 static dlb *msg_file;
40 /* used by ldrname() and neminame(), then copied into cvt_buf */
41 static char nambuf[sizeof cvt_buf];
42 
43 /* dump the character msg list to check appearance;
44    build with DEBUG enabled and use DEBUGFILES=questpgr.c
45    in sysconf file or environment */
46 static void
dump_qtlist()47 dump_qtlist()
48 {
49 #ifdef DEBUG
50     struct qtmsg *msg;
51 
52     if (!explicitdebug(__FILE__))
53         return;
54 
55     for (msg = qt_list.chrole; msg->msgnum > 0; msg++) {
56         (void) dlb_fseek(msg_file, msg->offset, SEEK_SET);
57         deliver_by_window(msg, NHW_MAP);
58     }
59 #endif /* DEBUG */
60     return;
61 }
62 
63 static void
Fread(ptr,size,nitems,stream)64 Fread(ptr, size, nitems, stream)
65 genericptr_t ptr;
66 int size, nitems;
67 dlb *stream;
68 {
69     int cnt;
70 
71     if ((cnt = dlb_fread(ptr, size, nitems, stream)) != nitems) {
72         panic("PREMATURE EOF ON QUEST TEXT FILE! Expected %d bytes, got %d",
73               (size * nitems), (size * cnt));
74     }
75 }
76 
77 STATIC_OVL struct qtmsg *
construct_qtlist(hdr_offset)78 construct_qtlist(hdr_offset)
79 long hdr_offset;
80 {
81     struct qtmsg *msg_list;
82     int n_msgs;
83 
84     (void) dlb_fseek(msg_file, hdr_offset, SEEK_SET);
85     Fread(&n_msgs, sizeof(int), 1, msg_file);
86     msg_list = (struct qtmsg *) alloc((unsigned) (n_msgs + 1)
87                                       * sizeof (struct qtmsg));
88 
89     /*
90      * Load up the list.
91      */
92     Fread((genericptr_t) msg_list, n_msgs * sizeof (struct qtmsg), 1,
93           msg_file);
94 
95     msg_list[n_msgs].msgnum = -1;
96     return msg_list;
97 }
98 
99 void
load_qtlist()100 load_qtlist()
101 {
102     int n_classes, i;
103     char qt_classes[N_HDR][LEN_HDR];
104     long qt_offsets[N_HDR];
105 
106     msg_file = dlb_fopen(QTEXT_FILE, RDBMODE);
107     if (!msg_file)
108         panic("CANNOT OPEN QUEST TEXT FILE %s.", QTEXT_FILE);
109 
110     /*
111      * Read in the number of classes, then the ID's & offsets for
112      * each header.
113      */
114 
115     Fread(&n_classes, sizeof (int), 1, msg_file);
116     Fread(&qt_classes[0][0], sizeof (char) * LEN_HDR, n_classes, msg_file);
117     Fread(qt_offsets, sizeof (long), n_classes, msg_file);
118 
119     /*
120      * Now construct the message lists for quick reference later
121      * on when we are actually paging the messages out.
122      */
123 
124     qt_list.common = qt_list.chrole = (struct qtmsg *) 0;
125 
126     for (i = 0; i < n_classes; i++) {
127         if (!strncmp(COMMON_ID, qt_classes[i], LEN_HDR))
128             qt_list.common = construct_qtlist(qt_offsets[i]);
129         else if (!strncmp(urole.filecode, qt_classes[i], LEN_HDR))
130             qt_list.chrole = construct_qtlist(qt_offsets[i]);
131 #if 0 /* UNUSED but available */
132         else if (!strncmp(urace.filecode, qt_classes[i], LEN_HDR))
133             qt_list.chrace = construct_qtlist(qt_offsets[i]);
134 #endif
135     }
136 
137     if (!qt_list.common || !qt_list.chrole)
138         impossible("load_qtlist: cannot load quest text.");
139     dump_qtlist();
140     return; /* no ***DON'T*** close the msg_file */
141 }
142 
143 /* called at program exit */
144 void
unload_qtlist()145 unload_qtlist()
146 {
147     if (msg_file)
148         (void) dlb_fclose(msg_file), msg_file = 0;
149     if (qt_list.common)
150         free((genericptr_t) qt_list.common), qt_list.common = 0;
151     if (qt_list.chrole)
152         free((genericptr_t) qt_list.chrole), qt_list.chrole = 0;
153     return;
154 }
155 
156 short
quest_info(typ)157 quest_info(typ)
158 int typ;
159 {
160     switch (typ) {
161     case 0:
162         return urole.questarti;
163     case MS_LEADER:
164         return urole.ldrnum;
165     case MS_NEMESIS:
166         return urole.neminum;
167     case MS_GUARDIAN:
168         return urole.guardnum;
169     default:
170         impossible("quest_info(%d)", typ);
171     }
172     return 0;
173 }
174 
175 /* return your role leader's name */
176 const char *
ldrname()177 ldrname()
178 {
179     int i = urole.ldrnum;
180 
181     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
182             mons[i].mname);
183     return nambuf;
184 }
185 
186 /* return your intermediate target string */
187 STATIC_OVL const char *
intermed()188 intermed()
189 {
190     return urole.intermed;
191 }
192 
193 boolean
is_quest_artifact(otmp)194 is_quest_artifact(otmp)
195 struct obj *otmp;
196 {
197     return (boolean) (otmp->oartifact == urole.questarti);
198 }
199 
200 STATIC_OVL struct obj *
find_qarti(ochain)201 find_qarti(ochain)
202 struct obj *ochain;
203 {
204     struct obj *otmp, *qarti;
205 
206     for (otmp = ochain; otmp; otmp = otmp->nobj) {
207         if (is_quest_artifact(otmp))
208             return otmp;
209         if (Has_contents(otmp) && (qarti = find_qarti(otmp->cobj)) != 0)
210             return qarti;
211     }
212     return (struct obj *) 0;
213 }
214 
215 /* check several object chains for the quest artifact to determine
216    whether it is present on the current level */
217 struct obj *
find_quest_artifact(whichchains)218 find_quest_artifact(whichchains)
219 unsigned whichchains;
220 {
221     struct monst *mtmp;
222     struct obj *qarti = 0;
223 
224     if ((whichchains & (1 << OBJ_INVENT)) != 0)
225         qarti = find_qarti(invent);
226     if (!qarti && (whichchains & (1 << OBJ_FLOOR)) != 0)
227         qarti = find_qarti(fobj);
228     if (!qarti && (whichchains & (1 << OBJ_MINVENT)) != 0)
229         for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
230             if (DEADMONSTER(mtmp))
231                 continue;
232             if ((qarti = find_qarti(mtmp->minvent)) != 0)
233                 break;
234         }
235     if (!qarti && (whichchains & (1 << OBJ_MIGRATING)) != 0) {
236         /* check migrating objects and minvent of migrating monsters */
237         for (mtmp = migrating_mons; mtmp; mtmp = mtmp->nmon) {
238             if (DEADMONSTER(mtmp))
239                 continue;
240             if ((qarti = find_qarti(mtmp->minvent)) != 0)
241                 break;
242         }
243         if (!qarti)
244             qarti = find_qarti(migrating_objs);
245     }
246     if (!qarti && (whichchains & (1 << OBJ_BURIED)) != 0)
247         qarti = find_qarti(level.buriedobjlist);
248 
249     return qarti;
250 }
251 
252 /* return your role nemesis' name */
253 STATIC_OVL const char *
neminame()254 neminame()
255 {
256     int i = urole.neminum;
257 
258     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
259             mons[i].mname);
260     return nambuf;
261 }
262 
263 STATIC_OVL const char *
guardname()264 guardname() /* return your role leader's guard monster name */
265 {
266     int i = urole.guardnum;
267 
268     return mons[i].mname;
269 }
270 
271 STATIC_OVL const char *
homebase()272 homebase() /* return your role leader's location */
273 {
274     return urole.homebase;
275 }
276 
277 /* replace deity, leader, nemesis, or artifact name with pronoun;
278    overwrites cvt_buf[] */
279 STATIC_OVL void
qtext_pronoun(who,which)280 qtext_pronoun(who, which)
281 char who,  /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => artifact */
282     which; /* 'h'|'H'|'i'|'I'|'j'|'J' */
283 {
284     const char *pnoun;
285     int g;
286     char lwhich = lowc(which); /* H,I,J -> h,i,j */
287 
288     /*
289      * Invalid subject (not d,l,n,o) yields neuter, singular result.
290      *
291      * For %o, treat all artifacts as neuter; some have plural names,
292      * which genders[] doesn't handle; cvt_buf[] already contains name.
293      */
294     if (who == 'o'
295         && (strstri(cvt_buf, "Eyes ")
296             || strcmpi(cvt_buf, makesingular(cvt_buf)))) {
297         pnoun = (lwhich == 'h') ? "they"
298                 : (lwhich == 'i') ? "them"
299                 : (lwhich == 'j') ? "their" : "?";
300     } else {
301         g = (who == 'd') ? quest_status.godgend
302             : (who == 'l') ? quest_status.ldrgend
303             : (who == 'n') ? quest_status.nemgend
304             : 2; /* default to neuter */
305         pnoun = (lwhich == 'h') ? genders[g].he
306                 : (lwhich == 'i') ? genders[g].him
307                 : (lwhich == 'j') ? genders[g].his : "?";
308     }
309     Strcpy(cvt_buf, pnoun);
310     /* capitalize for H,I,J */
311     if (lwhich != which)
312         cvt_buf[0] = highc(cvt_buf[0]);
313     return;
314 }
315 
316 STATIC_OVL struct qtmsg *
msg_in(qtm_list,msgnum)317 msg_in(qtm_list, msgnum)
318 struct qtmsg *qtm_list;
319 int msgnum;
320 {
321     struct qtmsg *qt_msg;
322 
323     for (qt_msg = qtm_list; qt_msg->msgnum > 0; qt_msg++)
324         if (qt_msg->msgnum == msgnum)
325             return qt_msg;
326 
327     return (struct qtmsg *) 0;
328 }
329 
330 STATIC_OVL void
convert_arg(c)331 convert_arg(c)
332 char c;
333 {
334     register const char *str;
335 
336     switch (c) {
337     case 'p':
338         str = plname;
339         break;
340     case 'c':
341         str = (flags.female && urole.name.f) ? urole.name.f : urole.name.m;
342         break;
343     case 'r':
344         str = rank_of(u.ulevel, Role_switch, flags.female);
345         break;
346     case 'R':
347         str = rank_of(MIN_QUEST_LEVEL, Role_switch, flags.female);
348         break;
349     case 's':
350         str = (flags.female) ? "sister" : "brother";
351         break;
352     case 'S':
353         str = (flags.female) ? "daughter" : "son";
354         break;
355     case 'l':
356         str = ldrname();
357         break;
358     case 'i':
359         str = intermed();
360         break;
361     case 'O':
362     case 'o':
363         str = the(artiname(urole.questarti));
364         if (c == 'O') {
365             /* shorten "the Foo of Bar" to "the Foo"
366                (buffer returned by the() is modifiable) */
367             char *p = strstri(str, " of ");
368 
369             if (p)
370                 *p = '\0';
371         }
372         break;
373     case 'n':
374         str = neminame();
375         break;
376     case 'g':
377         str = guardname();
378         break;
379     case 'G':
380         str = align_gtitle(u.ualignbase[A_ORIGINAL]);
381         break;
382     case 'H':
383         str = homebase();
384         break;
385     case 'a':
386         str = align_str(u.ualignbase[A_ORIGINAL]);
387         break;
388     case 'A':
389         str = align_str(u.ualign.type);
390         break;
391     case 'd':
392         str = align_gname(u.ualignbase[A_ORIGINAL]);
393         break;
394     case 'D':
395         str = align_gname(A_LAWFUL);
396         break;
397     case 'C':
398         str = "chaotic";
399         break;
400     case 'N':
401         str = "neutral";
402         break;
403     case 'L':
404         str = "lawful";
405         break;
406     case 'x':
407         str = Blind ? "sense" : "see";
408         break;
409     case 'Z':
410         str = dungeons[0].dname;
411         break;
412     case '%':
413         str = "%";
414         break;
415     default:
416         str = "";
417         break;
418     }
419     Strcpy(cvt_buf, str);
420 }
421 
422 STATIC_OVL void
convert_line(in_line,out_line)423 convert_line(in_line, out_line)
424 char *in_line, *out_line;
425 {
426     char *c, *cc;
427     char xbuf[BUFSZ];
428 
429     cc = out_line;
430     for (c = xcrypt(in_line, xbuf); *c; c++) {
431         *cc = 0;
432         switch (*c) {
433         case '\r':
434         case '\n':
435             *(++cc) = 0;
436             return;
437 
438         case '%':
439             if (*(c + 1)) {
440                 convert_arg(*(++c));
441                 switch (*(++c)) {
442                 /* insert "a"/"an" prefix */
443                 case 'A':
444                     Strcat(cc, An(cvt_buf));
445                     cc += strlen(cc);
446                     continue; /* for */
447                 case 'a':
448                     Strcat(cc, an(cvt_buf));
449                     cc += strlen(cc);
450                     continue; /* for */
451 
452                 /* capitalize */
453                 case 'C':
454                     cvt_buf[0] = highc(cvt_buf[0]);
455                     break;
456 
457                 /* replace name with pronoun;
458                    valid for %d, %l, %n, and %o */
459                 case 'h': /* he/she */
460                 case 'H': /* He/She */
461                 case 'i': /* him/her */
462                 case 'I':
463                 case 'j': /* his/her */
464                 case 'J':
465                     if (index("dlno", lowc(*(c - 1))))
466                         qtext_pronoun(*(c - 1), *c);
467                     else
468                         --c; /* default action */
469                     break;
470 
471                 /* pluralize */
472                 case 'P':
473                     cvt_buf[0] = highc(cvt_buf[0]);
474                     /*FALLTHRU*/
475                 case 'p':
476                     Strcpy(cvt_buf, makeplural(cvt_buf));
477                     break;
478 
479                 /* append possessive suffix */
480                 case 'S':
481                     cvt_buf[0] = highc(cvt_buf[0]);
482                     /*FALLTHRU*/
483                 case 's':
484                     Strcpy(cvt_buf, s_suffix(cvt_buf));
485                     break;
486 
487                 /* strip any "the" prefix */
488                 case 't':
489                     if (!strncmpi(cvt_buf, "the ", 4)) {
490                         Strcat(cc, &cvt_buf[4]);
491                         cc += strlen(cc);
492                         continue; /* for */
493                     }
494                     break;
495 
496                 default:
497                     --c; /* undo switch increment */
498                     break;
499                 }
500                 Strcat(cc, cvt_buf);
501                 cc += strlen(cvt_buf);
502                 break;
503             } /* else fall through */
504 
505         default:
506             *cc++ = *c;
507             break;
508         }
509     }
510     if (cc > &out_line[BUFSZ-1])
511         panic("convert_line: overflow");
512     *cc = 0;
513     return;
514 }
515 
516 STATIC_OVL void
deliver_by_pline(qt_msg)517 deliver_by_pline(qt_msg)
518 struct qtmsg *qt_msg;
519 {
520     long size;
521     char in_line[BUFSZ], out_line[BUFSZ];
522 
523     *in_line = '\0';
524     for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
525         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
526         convert_line(in_line, out_line);
527         pline("%s", out_line);
528     }
529 }
530 
531 STATIC_OVL void
deliver_by_window(qt_msg,how)532 deliver_by_window(qt_msg, how)
533 struct qtmsg *qt_msg;
534 int how;
535 {
536     long size;
537     char in_line[BUFSZ], out_line[BUFSZ];
538     boolean qtdump = (how == NHW_MAP);
539     winid datawin = create_nhwindow(qtdump ? NHW_TEXT : how);
540 
541 #ifdef DEBUG
542     if (qtdump) {
543         char buf[BUFSZ];
544 
545         /* when dumping quest messages at startup, all of them are passed to
546          * deliver_by_window(), even if normally given to deliver_by_pline()
547          */
548         Sprintf(buf, "msgnum: %d, delivery: %c",
549                 qt_msg->msgnum, qt_msg->delivery);
550         putstr(datawin, 0, buf);
551         putstr(datawin, 0, "");
552     }
553 #endif
554     for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
555         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
556         convert_line(in_line, out_line);
557         putstr(datawin, 0, out_line);
558     }
559     display_nhwindow(datawin, TRUE);
560     destroy_nhwindow(datawin);
561 
562     /* block messages delivered by window aren't kept in message history
563        but have a one-line summary which is put there for ^P recall */
564     *out_line = '\0';
565     if (qt_msg->summary_size) {
566         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
567         convert_line(in_line, out_line);
568 #if (NH_DEVEL_STATUS != NH_STATUS_RELEASED)
569     } else if (qt_msg->delivery == 'c') { /* skip for 'qtdump' of 'p' */
570         /* delivery 'c' and !summary_size, summary expected but not present;
571            this doesn't prefix the number with role code vs 'general'
572            but should be good enough for summary verification purposes */
573         Sprintf(out_line, "[missing block message summary for #%05d]",
574                 qt_msg->msgnum);
575 #endif
576     }
577     if (*out_line)
578         putmsghistory(out_line, FALSE);
579 }
580 
581 STATIC_OVL boolean
skip_pager(common)582 skip_pager(common)
583 boolean common;
584 {
585     /* WIZKIT: suppress plot feedback if starting with quest artifact */
586     if (program_state.wizkit_wishing)
587         return TRUE;
588     if (!(common ? qt_list.common : qt_list.chrole)) {
589         panic("%s: no %s quest text data available",
590               common ? "com_pager" : "qt_pager",
591               common ? "common" : "role-specific");
592         /*NOTREACHED*/
593         return TRUE;
594     }
595     return FALSE;
596 }
597 
598 void
com_pager(msgnum)599 com_pager(msgnum)
600 int msgnum;
601 {
602     struct qtmsg *qt_msg;
603 
604     if (skip_pager(TRUE))
605         return;
606 
607     if (!(qt_msg = msg_in(qt_list.common, msgnum))) {
608         impossible("com_pager: message %d not found.", msgnum);
609         return;
610     }
611 
612     (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
613     if (qt_msg->delivery == 'p')
614         deliver_by_pline(qt_msg);
615     else if (msgnum == 1)
616         deliver_by_window(qt_msg, NHW_MENU);
617     else
618         deliver_by_window(qt_msg, NHW_TEXT);
619     return;
620 }
621 
622 void
qt_pager(msgnum)623 qt_pager(msgnum)
624 int msgnum;
625 {
626     struct qtmsg *qt_msg;
627 
628     if (skip_pager(FALSE))
629         return;
630 
631     qt_msg = msg_in(qt_list.chrole, msgnum);
632     if (!qt_msg) {
633         /* some roles have an alternate message for return to the goal
634            level when the quest artifact is absent (handled by caller)
635            but some don't; for the latter, use the normal goal message;
636            note: for first visit, artifact is assumed to always be
637            present which might not be true for wizard mode but we don't
638            worry about quest message references in that situation */
639         if (msgnum == QT_ALTGOAL)
640             qt_msg = msg_in(qt_list.chrole, QT_NEXTGOAL);
641     }
642     if (!qt_msg) {
643         impossible("qt_pager: message %d not found.", msgnum);
644         return;
645     }
646 
647     (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
648     if (qt_msg->delivery == 'p' && strcmp(windowprocs.name, "X11"))
649         deliver_by_pline(qt_msg);
650     else
651         deliver_by_window(qt_msg, NHW_TEXT);
652     return;
653 }
654 
655 struct permonst *
qt_montype()656 qt_montype()
657 {
658     int qpm;
659 
660     if (rn2(5)) {
661         qpm = urole.enemy1num;
662         if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
663             return &mons[qpm];
664         return mkclass(urole.enemy1sym, 0);
665     }
666     qpm = urole.enemy2num;
667     if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
668         return &mons[qpm];
669     return mkclass(urole.enemy2sym, 0);
670 }
671 
672 /* special levels can include a custom arrival message; display it */
673 void
deliver_splev_message()674 deliver_splev_message()
675 {
676     char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
677 
678     /* there's no provision for delivering via window instead of pline */
679     if (lev_message) {
680         /* lev_message can span multiple lines using embedded newline chars;
681            any segments too long to fit within in_line[] will be truncated */
682         for (str = lev_message; *str; str = nl + 1) {
683             /* copying will stop at newline if one is present */
684             copynchars(in_line, str, (int) (sizeof in_line) - 1);
685 
686             /* convert_line() expects encrypted input */
687             (void) xcrypt(in_line, in_line);
688             convert_line(in_line, out_line);
689             pline("%s", out_line);
690 
691             if ((nl = index(str, '\n')) == 0)
692                 break; /* done if no newline */
693         }
694 
695         free((genericptr_t) lev_message);
696         lev_message = 0;
697     }
698 }
699 
700 /*questpgr.c*/
701