NHDT->ANH, nethack->anethack, nhdat->anhdat
[aNetHack.git] / src / questpgr.c
blob186c18fa3f7f44a39932810931dc9aa3407121a8
1 /* aNetHack 0.0.1 questpgr.c $ANH-Date: 1448541043 2015/11/26 12:30:43 $ $ANH-Branch: master $:$ANH-Revision: 1.36 $ */
2 /* Copyright 1991, M. Stephenson */
3 /* aNetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
6 #include "dlb.h"
8 /* quest-specific pager routines. */
10 #include "qtext.h"
12 #define QTEXT_FILE "quest.dat"
14 #ifdef TTY_GRAPHICS
15 #include "wintty.h"
16 #endif
18 /* from sp_lev.c, for deliver_splev_message() */
19 extern char *lev_message;
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 const char *NDECL(neminame);
26 STATIC_DCL const char *NDECL(guardname);
27 STATIC_DCL const char *NDECL(homebase);
28 STATIC_DCL void FDECL(qtext_pronoun, (CHAR_P, CHAR_P));
29 STATIC_DCL struct qtmsg *FDECL(msg_in, (struct qtmsg *, int));
30 STATIC_DCL void FDECL(convert_arg, (CHAR_P));
31 STATIC_DCL void FDECL(convert_line, (char *,char *));
32 STATIC_DCL void FDECL(deliver_by_pline, (struct qtmsg *));
33 STATIC_DCL void FDECL(deliver_by_window, (struct qtmsg *, int));
34 STATIC_DCL boolean FDECL(skip_pager, (BOOLEAN_P));
36 static char cvt_buf[64];
37 static struct qtlists qt_list;
38 static dlb *msg_file;
39 /* used by ldrname() and neminame(), then copied into cvt_buf */
40 static char nambuf[sizeof cvt_buf];
42 /* dump the character msg list to check appearance;
43 build with DEBUG enabled and use DEBUGFILES=questpgr.c
44 in sysconf file or environment */
45 static void
46 dump_qtlist()
48 #ifdef DEBUG
49 struct qtmsg *msg;
51 if (!explicitdebug(__FILE__))
52 return;
54 for (msg = qt_list.chrole; msg->msgnum > 0; msg++) {
55 (void) dlb_fseek(msg_file, msg->offset, SEEK_SET);
56 deliver_by_window(msg, NHW_MAP);
58 #endif /* DEBUG */
59 return;
62 static void
63 Fread(ptr, size, nitems, stream)
64 genericptr_t ptr;
65 int size, nitems;
66 dlb *stream;
68 int cnt;
70 if ((cnt = dlb_fread(ptr, size, nitems, stream)) != nitems) {
71 panic("PREMATURE EOF ON QUEST TEXT FILE! Expected %d bytes, got %d",
72 (size * nitems), (size * cnt));
76 STATIC_OVL struct qtmsg *
77 construct_qtlist(hdr_offset)
78 long hdr_offset;
80 struct qtmsg *msg_list;
81 int n_msgs;
83 (void) dlb_fseek(msg_file, hdr_offset, SEEK_SET);
84 Fread(&n_msgs, sizeof(int), 1, msg_file);
85 msg_list = (struct qtmsg *) alloc((unsigned) (n_msgs + 1)
86 * sizeof (struct qtmsg));
89 * Load up the list.
91 Fread((genericptr_t) msg_list, n_msgs * sizeof (struct qtmsg), 1,
92 msg_file);
94 msg_list[n_msgs].msgnum = -1;
95 return msg_list;
98 void
99 load_qtlist()
101 int n_classes, i;
102 char qt_classes[N_HDR][LEN_HDR];
103 long qt_offsets[N_HDR];
105 msg_file = dlb_fopen(QTEXT_FILE, RDBMODE);
106 if (!msg_file)
107 panic("CANNOT OPEN QUEST TEXT FILE %s.", QTEXT_FILE);
110 * Read in the number of classes, then the ID's & offsets for
111 * each header.
114 Fread(&n_classes, sizeof (int), 1, msg_file);
115 Fread(&qt_classes[0][0], sizeof (char) * LEN_HDR, n_classes, msg_file);
116 Fread(qt_offsets, sizeof (long), n_classes, msg_file);
119 * Now construct the message lists for quick reference later
120 * on when we are actually paging the messages out.
123 qt_list.common = qt_list.chrole = (struct qtmsg *) 0;
125 for (i = 0; i < n_classes; i++) {
126 if (!strncmp(COMMON_ID, qt_classes[i], LEN_HDR))
127 qt_list.common = construct_qtlist(qt_offsets[i]);
128 else if (!strncmp(urole.filecode, qt_classes[i], LEN_HDR))
129 qt_list.chrole = construct_qtlist(qt_offsets[i]);
130 #if 0 /* UNUSED but available */
131 else if (!strncmp(urace.filecode, qt_classes[i], LEN_HDR))
132 qt_list.chrace = construct_qtlist(qt_offsets[i]);
133 #endif
136 if (!qt_list.common || !qt_list.chrole)
137 impossible("load_qtlist: cannot load quest text.");
138 dump_qtlist();
139 return; /* no ***DON'T*** close the msg_file */
142 /* called at program exit */
143 void
144 unload_qtlist()
146 if (msg_file)
147 (void) dlb_fclose(msg_file), msg_file = 0;
148 if (qt_list.common)
149 free((genericptr_t) qt_list.common), qt_list.common = 0;
150 if (qt_list.chrole)
151 free((genericptr_t) qt_list.chrole), qt_list.chrole = 0;
152 return;
155 short
156 quest_info(typ)
157 int typ;
159 switch (typ) {
160 case 0:
161 return urole.questarti;
162 case MS_LEADER:
163 return urole.ldrnum;
164 case MS_NEMESIS:
165 return urole.neminum;
166 case MS_GUARDIAN:
167 return urole.guardnum;
168 default:
169 impossible("quest_info(%d)", typ);
171 return 0;
174 /* return your role leader's name */
175 const char *
176 ldrname()
178 int i = urole.ldrnum;
180 Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
181 mons[i].mname);
182 return nambuf;
185 /* return your intermediate target string */
186 STATIC_OVL const char *
187 intermed()
189 return urole.intermed;
192 boolean
193 is_quest_artifact(otmp)
194 struct obj *otmp;
196 return (boolean) (otmp->oartifact == urole.questarti);
199 /* return your role nemesis' name */
200 STATIC_OVL const char *
201 neminame()
203 int i = urole.neminum;
205 Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
206 mons[i].mname);
207 return nambuf;
210 STATIC_OVL const char *
211 guardname() /* return your role leader's guard monster name */
213 int i = urole.guardnum;
215 return mons[i].mname;
218 STATIC_OVL const char *
219 homebase() /* return your role leader's location */
221 return urole.homebase;
224 /* replace deity, leader, nemesis, or artifact name with pronoun;
225 overwrites cvt_buf[] */
226 STATIC_OVL void
227 qtext_pronoun(who, which)
228 char who, /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => artifact */
229 which; /* 'h'|'H'|'i'|'I'|'j'|'J' */
231 const char *pnoun;
232 int g;
233 char lwhich = lowc(which); /* H,I,J -> h,i,j */
236 * Invalid subject (not d,l,n,o) yields neuter, singular result.
238 * For %o, treat all artifacts as neuter; some have plural names,
239 * which genders[] doesn't handle; cvt_buf[] already contains name.
241 if (who == 'o'
242 && (strstri(cvt_buf, "Eyes ")
243 || strcmpi(cvt_buf, makesingular(cvt_buf)))) {
244 pnoun = (lwhich == 'h') ? "they"
245 : (lwhich == 'i') ? "them"
246 : (lwhich == 'j') ? "their" : "?";
247 } else {
248 g = (who == 'd') ? quest_status.godgend
249 : (who == 'l') ? quest_status.ldrgend
250 : (who == 'n') ? quest_status.nemgend
251 : 2; /* default to neuter */
252 pnoun = (lwhich == 'h') ? genders[g].he
253 : (lwhich == 'i') ? genders[g].him
254 : (lwhich == 'j') ? genders[g].his : "?";
256 Strcpy(cvt_buf, pnoun);
257 /* capitalize for H,I,J */
258 if (lwhich != which)
259 cvt_buf[0] = highc(cvt_buf[0]);
260 return;
263 STATIC_OVL struct qtmsg *
264 msg_in(qtm_list, msgnum)
265 struct qtmsg *qtm_list;
266 int msgnum;
268 struct qtmsg *qt_msg;
270 for (qt_msg = qtm_list; qt_msg->msgnum > 0; qt_msg++)
271 if (qt_msg->msgnum == msgnum)
272 return qt_msg;
274 return (struct qtmsg *) 0;
277 STATIC_OVL void
278 convert_arg(c)
279 char c;
281 register const char *str;
283 switch (c) {
284 case 'p':
285 str = plname;
286 break;
287 case 'c':
288 str = (flags.female && urole.name.f) ? urole.name.f : urole.name.m;
289 break;
290 case 'r':
291 str = rank_of(u.ulevel, Role_switch, flags.female);
292 break;
293 case 'R':
294 str = rank_of(MIN_QUEST_LEVEL, Role_switch, flags.female);
295 break;
296 case 's':
297 str = (flags.female) ? "sister" : "brother";
298 break;
299 case 'S':
300 str = (flags.female) ? "daughter" : "son";
301 break;
302 case 'l':
303 str = ldrname();
304 break;
305 case 'i':
306 str = intermed();
307 break;
308 case 'O':
309 case 'o':
310 str = the(artiname(urole.questarti));
311 if (c == 'O') {
312 /* shorten "the Foo of Bar" to "the Foo"
313 (buffer returned by the() is modifiable) */
314 char *p = strstri(str, " of ");
316 if (p)
317 *p = '\0';
319 break;
320 case 'n':
321 str = neminame();
322 break;
323 case 'g':
324 str = guardname();
325 break;
326 case 'G':
327 str = align_gtitle(u.ualignbase[A_ORIGINAL]);
328 break;
329 case 'H':
330 str = homebase();
331 break;
332 case 'a':
333 str = align_str(u.ualignbase[A_ORIGINAL]);
334 break;
335 case 'A':
336 str = align_str(u.ualign.type);
337 break;
338 case 'd':
339 str = align_gname(u.ualignbase[A_ORIGINAL]);
340 break;
341 case 'D':
342 str = align_gname(A_LAWFUL);
343 break;
344 case 'C':
345 str = "chaotic";
346 break;
347 case 'N':
348 str = "neutral";
349 break;
350 case 'L':
351 str = "lawful";
352 break;
353 case 'x':
354 str = Blind ? "sense" : "see";
355 break;
356 case 'Z':
357 str = dungeons[0].dname;
358 break;
359 case '%':
360 str = "%";
361 break;
362 default:
363 str = "";
364 break;
366 Strcpy(cvt_buf, str);
369 STATIC_OVL void
370 convert_line(in_line, out_line)
371 char *in_line, *out_line;
373 char *c, *cc;
374 char xbuf[BUFSZ];
376 cc = out_line;
377 for (c = xcrypt(in_line, xbuf); *c; c++) {
378 *cc = 0;
379 switch (*c) {
380 case '\r':
381 case '\n':
382 *(++cc) = 0;
383 return;
385 case '%':
386 if (*(c + 1)) {
387 convert_arg(*(++c));
388 switch (*(++c)) {
389 /* insert "a"/"an" prefix */
390 case 'A':
391 Strcat(cc, An(cvt_buf));
392 cc += strlen(cc);
393 continue; /* for */
394 case 'a':
395 Strcat(cc, an(cvt_buf));
396 cc += strlen(cc);
397 continue; /* for */
399 /* capitalize */
400 case 'C':
401 cvt_buf[0] = highc(cvt_buf[0]);
402 break;
404 /* replace name with pronoun;
405 valid for %d, %l, %n, and %o */
406 case 'h': /* he/she */
407 case 'H': /* He/She */
408 case 'i': /* him/her */
409 case 'I':
410 case 'j': /* his/her */
411 case 'J':
412 if (index("dlno", lowc(*(c - 1))))
413 qtext_pronoun(*(c - 1), *c);
414 else
415 --c; /* default action */
416 break;
418 /* pluralize */
419 case 'P':
420 cvt_buf[0] = highc(cvt_buf[0]);
421 case 'p':
422 Strcpy(cvt_buf, makeplural(cvt_buf));
423 break;
425 /* append possessive suffix */
426 case 'S':
427 cvt_buf[0] = highc(cvt_buf[0]);
428 case 's':
429 Strcpy(cvt_buf, s_suffix(cvt_buf));
430 break;
432 /* strip any "the" prefix */
433 case 't':
434 if (!strncmpi(cvt_buf, "the ", 4)) {
435 Strcat(cc, &cvt_buf[4]);
436 cc += strlen(cc);
437 continue; /* for */
439 break;
441 default:
442 --c; /* undo switch increment */
443 break;
445 Strcat(cc, cvt_buf);
446 cc += strlen(cvt_buf);
447 break;
448 } /* else fall through */
450 default:
451 *cc++ = *c;
452 break;
455 if (cc > &out_line[BUFSZ-1])
456 panic("convert_line: overflow");
457 *cc = 0;
458 return;
461 STATIC_OVL void
462 deliver_by_pline(qt_msg)
463 struct qtmsg *qt_msg;
465 long size;
466 char in_line[BUFSZ], out_line[BUFSZ];
468 *in_line = '\0';
469 for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
470 (void) dlb_fgets(in_line, sizeof in_line, msg_file);
471 convert_line(in_line, out_line);
472 pline("%s", out_line);
476 STATIC_OVL void
477 deliver_by_window(qt_msg, how)
478 struct qtmsg *qt_msg;
479 int how;
481 long size;
482 char in_line[BUFSZ], out_line[BUFSZ];
483 boolean qtdump = (how == NHW_MAP);
484 winid datawin = create_nhwindow(qtdump ? NHW_TEXT : how);
486 #ifdef DEBUG
487 if (qtdump) {
488 char buf[BUFSZ];
490 /* when dumping quest messages at startup, all of them are passed to
491 * deliver_by_window(), even if normally given to deliver_by_pline()
493 Sprintf(buf, "msgnum: %d, delivery: %c",
494 qt_msg->msgnum, qt_msg->delivery);
495 putstr(datawin, 0, buf);
496 putstr(datawin, 0, "");
498 #endif
499 for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
500 (void) dlb_fgets(in_line, sizeof in_line, msg_file);
501 convert_line(in_line, out_line);
502 putstr(datawin, 0, out_line);
504 display_nhwindow(datawin, TRUE);
505 destroy_nhwindow(datawin);
507 /* block messages delivered by window aren't kept in message history
508 but have a one-line summary which is put there for ^P recall */
509 *out_line = '\0';
510 if (qt_msg->summary_size) {
511 (void) dlb_fgets(in_line, sizeof in_line, msg_file);
512 convert_line(in_line, out_line);
513 #ifdef BETA
514 } else if (qt_msg->delivery == 'c') { /* skip for 'qtdump' of 'p' */
515 /* delivery 'c' and !summary_size, summary expected but not present;
516 this doesn't prefix the number with role code vs 'general'
517 but should be good enough for summary verification purposes */
518 Sprintf(out_line, "[missing block message summary for #%05d]",
519 qt_msg->msgnum);
520 #endif
522 if (*out_line)
523 putmsghistory(out_line, FALSE);
526 boolean
527 skip_pager(common)
528 boolean common;
530 /* WIZKIT: suppress plot feedback if starting with quest artifact */
531 if (program_state.wizkit_wishing)
532 return TRUE;
533 if (!(common ? qt_list.common : qt_list.chrole)) {
534 panic("%s: no %s quest text data available",
535 common ? "com_pager" : "qt_pager",
536 common ? "common" : "role-specific");
537 /*NOTREACHED*/
538 return TRUE;
540 return FALSE;
543 void
544 com_pager(msgnum)
545 int msgnum;
547 struct qtmsg *qt_msg;
549 if (skip_pager(TRUE))
550 return;
552 if (!(qt_msg = msg_in(qt_list.common, msgnum))) {
553 impossible("com_pager: message %d not found.", msgnum);
554 return;
557 (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
558 if (qt_msg->delivery == 'p')
559 deliver_by_pline(qt_msg);
560 else if (msgnum == 1)
561 deliver_by_window(qt_msg, NHW_MENU);
562 else
563 deliver_by_window(qt_msg, NHW_TEXT);
564 return;
567 void
568 qt_pager(msgnum)
569 int msgnum;
571 struct qtmsg *qt_msg;
573 if (skip_pager(FALSE))
574 return;
576 if (!(qt_msg = msg_in(qt_list.chrole, msgnum))) {
577 impossible("qt_pager: message %d not found.", msgnum);
578 return;
581 (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
582 if (qt_msg->delivery == 'p' && strcmp(windowprocs.name, "X11"))
583 deliver_by_pline(qt_msg);
584 else
585 deliver_by_window(qt_msg, NHW_TEXT);
586 return;
589 struct permonst *
590 qt_montype()
592 int qpm;
594 if (rn2(5)) {
595 qpm = urole.enemy1num;
596 if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
597 return &mons[qpm];
598 return mkclass(urole.enemy1sym, 0);
600 qpm = urole.enemy2num;
601 if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
602 return &mons[qpm];
603 return mkclass(urole.enemy2sym, 0);
606 /* special levels can include a custom arrival message; display it */
607 void
608 deliver_splev_message()
610 char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
612 /* there's no provision for delivering via window instead of pline */
613 if (lev_message) {
614 /* lev_message can span multiple lines using embedded newline chars;
615 any segments too long to fit within in_line[] will be truncated */
616 for (str = lev_message; *str; str = nl + 1) {
617 /* copying will stop at newline if one is present */
618 copynchars(in_line, str, (int) (sizeof in_line) - 1);
620 /* convert_line() expects encrypted input */
621 (void) xcrypt(in_line, in_line);
622 convert_line(in_line, out_line);
623 pline("%s", out_line);
625 if ((nl = index(str, '\n')) == 0)
626 break; /* done if no newline */
629 free((genericptr_t) lev_message);
630 lev_message = 0;
634 /*questpgr.c*/