OSDN Git Service

import nethack-3.6.0
[jnethack/source.git] / src / questpgr.c
1 /* NetHack 3.6  questpgr.c      $NHDT-Date: 1448541043 2015/11/26 12:30:43 $  $NHDT-Branch: master $:$NHDT-Revision: 1.36 $ */
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 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));
35
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];
41
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()
47 {
48 #ifdef DEBUG
49     struct qtmsg *msg;
50
51     if (!explicitdebug(__FILE__))
52         return;
53
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);
57     }
58 #endif /* DEBUG */
59     return;
60 }
61
62 static void
63 Fread(ptr, size, nitems, stream)
64 genericptr_t ptr;
65 int size, nitems;
66 dlb *stream;
67 {
68     int cnt;
69
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));
73     }
74 }
75
76 STATIC_OVL struct qtmsg *
77 construct_qtlist(hdr_offset)
78 long hdr_offset;
79 {
80     struct qtmsg *msg_list;
81     int n_msgs;
82
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));
87
88     /*
89      * Load up the list.
90      */
91     Fread((genericptr_t) msg_list, n_msgs * sizeof (struct qtmsg), 1,
92           msg_file);
93
94     msg_list[n_msgs].msgnum = -1;
95     return msg_list;
96 }
97
98 void
99 load_qtlist()
100 {
101     int n_classes, i;
102     char qt_classes[N_HDR][LEN_HDR];
103     long qt_offsets[N_HDR];
104
105     msg_file = dlb_fopen(QTEXT_FILE, RDBMODE);
106     if (!msg_file)
107         panic("CANNOT OPEN QUEST TEXT FILE %s.", QTEXT_FILE);
108
109     /*
110      * Read in the number of classes, then the ID's & offsets for
111      * each header.
112      */
113
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);
117
118     /*
119      * Now construct the message lists for quick reference later
120      * on when we are actually paging the messages out.
121      */
122
123     qt_list.common = qt_list.chrole = (struct qtmsg *) 0;
124
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
134     }
135
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 */
140 }
141
142 /* called at program exit */
143 void
144 unload_qtlist()
145 {
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;
153 }
154
155 short
156 quest_info(typ)
157 int typ;
158 {
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);
170     }
171     return 0;
172 }
173
174 /* return your role leader's name */
175 const char *
176 ldrname()
177 {
178     int i = urole.ldrnum;
179
180     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
181             mons[i].mname);
182     return nambuf;
183 }
184
185 /* return your intermediate target string */
186 STATIC_OVL const char *
187 intermed()
188 {
189     return urole.intermed;
190 }
191
192 boolean
193 is_quest_artifact(otmp)
194 struct obj *otmp;
195 {
196     return (boolean) (otmp->oartifact == urole.questarti);
197 }
198
199 /* return your role nemesis' name */
200 STATIC_OVL const char *
201 neminame()
202 {
203     int i = urole.neminum;
204
205     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
206             mons[i].mname);
207     return nambuf;
208 }
209
210 STATIC_OVL const char *
211 guardname() /* return your role leader's guard monster name */
212 {
213     int i = urole.guardnum;
214
215     return mons[i].mname;
216 }
217
218 STATIC_OVL const char *
219 homebase() /* return your role leader's location */
220 {
221     return urole.homebase;
222 }
223
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' */
230 {
231     const char *pnoun;
232     int g;
233     char lwhich = lowc(which); /* H,I,J -> h,i,j */
234
235     /*
236      * Invalid subject (not d,l,n,o) yields neuter, singular result.
237      *
238      * For %o, treat all artifacts as neuter; some have plural names,
239      * which genders[] doesn't handle; cvt_buf[] already contains name.
240      */
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 : "?";
255     }
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;
261 }
262
263 STATIC_OVL struct qtmsg *
264 msg_in(qtm_list, msgnum)
265 struct qtmsg *qtm_list;
266 int msgnum;
267 {
268     struct qtmsg *qt_msg;
269
270     for (qt_msg = qtm_list; qt_msg->msgnum > 0; qt_msg++)
271         if (qt_msg->msgnum == msgnum)
272             return qt_msg;
273
274     return (struct qtmsg *) 0;
275 }
276
277 STATIC_OVL void
278 convert_arg(c)
279 char c;
280 {
281     register const char *str;
282
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 ");
315
316             if (p)
317                 *p = '\0';
318         }
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;
365     }
366     Strcpy(cvt_buf, str);
367 }
368
369 STATIC_OVL void
370 convert_line(in_line, out_line)
371 char *in_line, *out_line;
372 {
373     char *c, *cc;
374     char xbuf[BUFSZ];
375
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;
384
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 */
398
399                 /* capitalize */
400                 case 'C':
401                     cvt_buf[0] = highc(cvt_buf[0]);
402                     break;
403
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;
417
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;
424
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;
431
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 */
438                     }
439                     break;
440
441                 default:
442                     --c; /* undo switch increment */
443                     break;
444                 }
445                 Strcat(cc, cvt_buf);
446                 cc += strlen(cvt_buf);
447                 break;
448             } /* else fall through */
449
450         default:
451             *cc++ = *c;
452             break;
453         }
454     }
455     if (cc > &out_line[BUFSZ-1])
456         panic("convert_line: overflow");
457     *cc = 0;
458     return;
459 }
460
461 STATIC_OVL void
462 deliver_by_pline(qt_msg)
463 struct qtmsg *qt_msg;
464 {
465     long size;
466     char in_line[BUFSZ], out_line[BUFSZ];
467
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);
473     }
474 }
475
476 STATIC_OVL void
477 deliver_by_window(qt_msg, how)
478 struct qtmsg *qt_msg;
479 int how;
480 {
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);
485
486 #ifdef DEBUG
487     if (qtdump) {
488         char buf[BUFSZ];
489
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()
492          */
493         Sprintf(buf, "msgnum: %d, delivery: %c",
494                 qt_msg->msgnum, qt_msg->delivery);
495         putstr(datawin, 0, buf);
496         putstr(datawin, 0, "");
497     }
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);
503     }
504     display_nhwindow(datawin, TRUE);
505     destroy_nhwindow(datawin);
506
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
521     }
522     if (*out_line)
523         putmsghistory(out_line, FALSE);
524 }
525
526 boolean
527 skip_pager(common)
528 boolean common;
529 {
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;
539     }
540     return FALSE;
541 }
542
543 void
544 com_pager(msgnum)
545 int msgnum;
546 {
547     struct qtmsg *qt_msg;
548
549     if (skip_pager(TRUE))
550         return;
551
552     if (!(qt_msg = msg_in(qt_list.common, msgnum))) {
553         impossible("com_pager: message %d not found.", msgnum);
554         return;
555     }
556
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;
565 }
566
567 void
568 qt_pager(msgnum)
569 int msgnum;
570 {
571     struct qtmsg *qt_msg;
572
573     if (skip_pager(FALSE))
574         return;
575
576     if (!(qt_msg = msg_in(qt_list.chrole, msgnum))) {
577         impossible("qt_pager: message %d not found.", msgnum);
578         return;
579     }
580
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;
587 }
588
589 struct permonst *
590 qt_montype()
591 {
592     int qpm;
593
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);
599     }
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);
604 }
605
606 /* special levels can include a custom arrival message; display it */
607 void
608 deliver_splev_message()
609 {
610     char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
611
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);
619
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);
624
625             if ((nl = index(str, '\n')) == 0)
626                 break; /* done if no newline */
627         }
628
629         free((genericptr_t) lev_message);
630         lev_message = 0;
631     }
632 }
633
634 /*questpgr.c*/