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