OSDN Git Service

patch artifact
[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 #if 0 /*JP*/
181     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
182             mons[i].mname);
183 #else
184     Strcpy(nambuf, mons[i].mname);
185 #endif
186     return nambuf;
187 }
188
189 /* return your intermediate target string */
190 STATIC_OVL const char *
191 intermed()
192 {
193     return urole.intermed;
194 }
195
196 boolean
197 is_quest_artifact(otmp)
198 struct obj *otmp;
199 {
200     return (boolean) (otmp->oartifact == urole.questarti);
201 }
202
203 /* return your role nemesis' name */
204 STATIC_OVL const char *
205 neminame()
206 {
207     int i = urole.neminum;
208
209 #if 0 /*JP*/
210     Sprintf(nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
211             mons[i].mname);
212 #else
213     Strcpy(nambuf, mons[i].mname);
214 #endif
215     return nambuf;
216 }
217
218 STATIC_OVL const char *
219 guardname() /* return your role leader's guard monster name */
220 {
221     int i = urole.guardnum;
222
223     return mons[i].mname;
224 }
225
226 STATIC_OVL const char *
227 homebase() /* return your role leader's location */
228 {
229     return urole.homebase;
230 }
231
232 /* replace deity, leader, nemesis, or artifact name with pronoun;
233    overwrites cvt_buf[] */
234 STATIC_OVL void
235 qtext_pronoun(who, which)
236 char who,  /* 'd' => deity, 'l' => leader, 'n' => nemesis, 'o' => artifact */
237     which; /* 'h'|'H'|'i'|'I'|'j'|'J' */
238 {
239     const char *pnoun;
240     int g;
241     char lwhich = lowc(which); /* H,I,J -> h,i,j */
242
243     /*
244      * Invalid subject (not d,l,n,o) yields neuter, singular result.
245      *
246      * For %o, treat all artifacts as neuter; some have plural names,
247      * which genders[] doesn't handle; cvt_buf[] already contains name.
248      */
249     if (who == 'o'
250         && (strstri(cvt_buf, "Eyes ")
251             || strcmpi(cvt_buf, makesingular(cvt_buf)))) {
252         pnoun = (lwhich == 'h') ? "they"
253                 : (lwhich == 'i') ? "them"
254                 : (lwhich == 'j') ? "their" : "?";
255     } else {
256         g = (who == 'd') ? quest_status.godgend
257             : (who == 'l') ? quest_status.ldrgend
258             : (who == 'n') ? quest_status.nemgend
259             : 2; /* default to neuter */
260         pnoun = (lwhich == 'h') ? genders[g].he
261                 : (lwhich == 'i') ? genders[g].him
262                 : (lwhich == 'j') ? genders[g].his : "?";
263     }
264     Strcpy(cvt_buf, pnoun);
265     /* capitalize for H,I,J */
266     if (lwhich != which)
267         cvt_buf[0] = highc(cvt_buf[0]);
268     return;
269 }
270
271 STATIC_OVL struct qtmsg *
272 msg_in(qtm_list, msgnum)
273 struct qtmsg *qtm_list;
274 int msgnum;
275 {
276     struct qtmsg *qt_msg;
277
278     for (qt_msg = qtm_list; qt_msg->msgnum > 0; qt_msg++)
279         if (qt_msg->msgnum == msgnum)
280             return qt_msg;
281
282     return (struct qtmsg *) 0;
283 }
284
285 STATIC_OVL void
286 convert_arg(c)
287 char c;
288 {
289     register const char *str;
290
291     switch (c) {
292     case 'p':
293         str = plname;
294         break;
295     case 'c':
296         str = (flags.female && urole.name.f) ? urole.name.f : urole.name.m;
297         break;
298     case 'r':
299         str = rank_of(u.ulevel, Role_switch, flags.female);
300         break;
301     case 'R':
302         str = rank_of(MIN_QUEST_LEVEL, Role_switch, flags.female);
303         break;
304     case 's':
305 /*JP
306         str = (flags.female) ? "sister" : "brother";
307 */
308         str = (flags.female) ? "\96\85" : "\92í";
309         break;
310     case 'S':
311 /*JP
312         str = (flags.female) ? "daughter" : "son";
313 */
314         str = (flags.female) ? "\96º" : "\91§\8eq";
315         break;
316     case 'l':
317         str = ldrname();
318         break;
319     case 'i':
320         str = intermed();
321         break;
322     case 'O':
323     case 'o':
324         str = the(artiname(urole.questarti));
325         if (c == 'O') {
326             /* shorten "the Foo of Bar" to "the Foo"
327                (buffer returned by the() is modifiable) */
328             char *p = strstri(str, " of ");
329
330             if (p)
331                 *p = '\0';
332         }
333         break;
334     case 'n':
335         str = neminame();
336         break;
337     case 'g':
338         str = guardname();
339         break;
340     case 'G':
341         str = align_gtitle(u.ualignbase[A_ORIGINAL]);
342         break;
343     case 'H':
344         str = homebase();
345         break;
346     case 'a':
347         str = align_str(u.ualignbase[A_ORIGINAL]);
348         break;
349     case 'A':
350         str = align_str(u.ualign.type);
351         break;
352     case 'd':
353         str = align_gname(u.ualignbase[A_ORIGINAL]);
354         break;
355     case 'D':
356         str = align_gname(A_LAWFUL);
357         break;
358     case 'C':
359 /*JP
360         str = "chaotic";
361 */
362         str = "\8d¬\93×";
363         break;
364     case 'N':
365 /*JP
366         str = "neutral";
367 */
368         str = "\92\86\97§";
369         break;
370     case 'L':
371 /*JP
372         str = "lawful";
373 */
374         str = "\92\81\8f\98";
375         break;
376     case 'x':
377 /*JP
378         str = Blind ? "sense" : "see";
379 */
380         str = Blind ? "\8a´\82¶" : "\8c©";
381         break;
382     case 'Z':
383         str = dungeons[0].dname;
384         break;
385     case '%':
386         str = "%";
387         break;
388     default:
389         str = "";
390         break;
391     }
392     Strcpy(cvt_buf, str);
393 }
394
395 STATIC_OVL void
396 convert_line(in_line, out_line)
397 char *in_line, *out_line;
398 {
399     char *c, *cc;
400     char xbuf[BUFSZ];
401
402     cc = out_line;
403     for (c = xcrypt(in_line, xbuf); *c; c++) {
404         *cc = 0;
405         switch (*c) {
406         case '\r':
407         case '\n':
408             *(++cc) = 0;
409             return;
410
411         case '%':
412             if (*(c + 1)) {
413                 convert_arg(*(++c));
414                 switch (*(++c)) {
415                 /* insert "a"/"an" prefix */
416                 case 'A':
417                     Strcat(cc, An(cvt_buf));
418                     cc += strlen(cc);
419                     continue; /* for */
420                 case 'a':
421                     Strcat(cc, an(cvt_buf));
422                     cc += strlen(cc);
423                     continue; /* for */
424
425                 /* capitalize */
426                 case 'C':
427                     cvt_buf[0] = highc(cvt_buf[0]);
428                     break;
429
430                 /* replace name with pronoun;
431                    valid for %d, %l, %n, and %o */
432                 case 'h': /* he/she */
433                 case 'H': /* He/She */
434                 case 'i': /* him/her */
435                 case 'I':
436                 case 'j': /* his/her */
437                 case 'J':
438                     if (index("dlno", lowc(*(c - 1))))
439                         qtext_pronoun(*(c - 1), *c);
440                     else
441                         --c; /* default action */
442                     break;
443
444                 /* pluralize */
445                 case 'P':
446                     cvt_buf[0] = highc(cvt_buf[0]);
447                 case 'p':
448                     Strcpy(cvt_buf, makeplural(cvt_buf));
449                     break;
450
451                 /* append possessive suffix */
452                 case 'S':
453                     cvt_buf[0] = highc(cvt_buf[0]);
454                 case 's':
455                     Strcpy(cvt_buf, s_suffix(cvt_buf));
456                     break;
457
458                 /* strip any "the" prefix */
459                 case 't':
460                     if (!strncmpi(cvt_buf, "the ", 4)) {
461                         Strcat(cc, &cvt_buf[4]);
462                         cc += strlen(cc);
463                         continue; /* for */
464                     }
465                     break;
466
467                 default:
468                     --c; /* undo switch increment */
469                     break;
470                 }
471                 Strcat(cc, cvt_buf);
472                 cc += strlen(cvt_buf);
473                 break;
474             } /* else fall through */
475
476         default:
477             *cc++ = *c;
478             break;
479         }
480     }
481     if (cc > &out_line[BUFSZ-1])
482         panic("convert_line: overflow");
483     *cc = 0;
484     return;
485 }
486
487 STATIC_OVL void
488 deliver_by_pline(qt_msg)
489 struct qtmsg *qt_msg;
490 {
491     long size;
492     char in_line[BUFSZ], out_line[BUFSZ];
493
494     *in_line = '\0';
495     for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
496         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
497         convert_line(in_line, out_line);
498         pline("%s", out_line);
499     }
500 }
501
502 STATIC_OVL void
503 deliver_by_window(qt_msg, how)
504 struct qtmsg *qt_msg;
505 int how;
506 {
507     long size;
508     char in_line[BUFSZ], out_line[BUFSZ];
509     boolean qtdump = (how == NHW_MAP);
510     winid datawin = create_nhwindow(qtdump ? NHW_TEXT : how);
511
512 #ifdef DEBUG
513     if (qtdump) {
514         char buf[BUFSZ];
515
516         /* when dumping quest messages at startup, all of them are passed to
517          * deliver_by_window(), even if normally given to deliver_by_pline()
518          */
519         Sprintf(buf, "msgnum: %d, delivery: %c",
520                 qt_msg->msgnum, qt_msg->delivery);
521         putstr(datawin, 0, buf);
522         putstr(datawin, 0, "");
523     }
524 #endif
525     for (size = 0; size < qt_msg->size; size += (long) strlen(in_line)) {
526         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
527         convert_line(in_line, out_line);
528         putstr(datawin, 0, out_line);
529     }
530     display_nhwindow(datawin, TRUE);
531     destroy_nhwindow(datawin);
532
533     /* block messages delivered by window aren't kept in message history
534        but have a one-line summary which is put there for ^P recall */
535     *out_line = '\0';
536     if (qt_msg->summary_size) {
537         (void) dlb_fgets(in_line, sizeof in_line, msg_file);
538         convert_line(in_line, out_line);
539 #ifdef BETA
540     } else if (qt_msg->delivery == 'c') { /* skip for 'qtdump' of 'p' */
541         /* delivery 'c' and !summary_size, summary expected but not present;
542            this doesn't prefix the number with role code vs 'general'
543            but should be good enough for summary verification purposes */
544         Sprintf(out_line, "[missing block message summary for #%05d]",
545                 qt_msg->msgnum);
546 #endif
547     }
548     if (*out_line)
549         putmsghistory(out_line, FALSE);
550 }
551
552 boolean
553 skip_pager(common)
554 boolean common;
555 {
556     /* WIZKIT: suppress plot feedback if starting with quest artifact */
557     if (program_state.wizkit_wishing)
558         return TRUE;
559     if (!(common ? qt_list.common : qt_list.chrole)) {
560         panic("%s: no %s quest text data available",
561               common ? "com_pager" : "qt_pager",
562               common ? "common" : "role-specific");
563         /*NOTREACHED*/
564         return TRUE;
565     }
566     return FALSE;
567 }
568
569 void
570 com_pager(msgnum)
571 int msgnum;
572 {
573     struct qtmsg *qt_msg;
574
575     if (skip_pager(TRUE))
576         return;
577
578     if (!(qt_msg = msg_in(qt_list.common, msgnum))) {
579         impossible("com_pager: message %d not found.", msgnum);
580         return;
581     }
582
583     (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
584     if (qt_msg->delivery == 'p')
585         deliver_by_pline(qt_msg);
586     else if (msgnum == 1)
587         deliver_by_window(qt_msg, NHW_MENU);
588     else
589         deliver_by_window(qt_msg, NHW_TEXT);
590     return;
591 }
592
593 void
594 qt_pager(msgnum)
595 int msgnum;
596 {
597     struct qtmsg *qt_msg;
598
599     if (skip_pager(FALSE))
600         return;
601
602     if (!(qt_msg = msg_in(qt_list.chrole, msgnum))) {
603         impossible("qt_pager: message %d not found.", msgnum);
604         return;
605     }
606
607     (void) dlb_fseek(msg_file, qt_msg->offset, SEEK_SET);
608     if (qt_msg->delivery == 'p' && strcmp(windowprocs.name, "X11"))
609         deliver_by_pline(qt_msg);
610     else
611         deliver_by_window(qt_msg, NHW_TEXT);
612     return;
613 }
614
615 struct permonst *
616 qt_montype()
617 {
618     int qpm;
619
620     if (rn2(5)) {
621         qpm = urole.enemy1num;
622         if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
623             return &mons[qpm];
624         return mkclass(urole.enemy1sym, 0);
625     }
626     qpm = urole.enemy2num;
627     if (qpm != NON_PM && rn2(5) && !(mvitals[qpm].mvflags & G_GENOD))
628         return &mons[qpm];
629     return mkclass(urole.enemy2sym, 0);
630 }
631
632 /* special levels can include a custom arrival message; display it */
633 void
634 deliver_splev_message()
635 {
636     char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
637
638     /* there's no provision for delivering via window instead of pline */
639     if (lev_message) {
640         /* lev_message can span multiple lines using embedded newline chars;
641            any segments too long to fit within in_line[] will be truncated */
642         for (str = lev_message; *str; str = nl + 1) {
643             /* copying will stop at newline if one is present */
644             copynchars(in_line, str, (int) (sizeof in_line) - 1);
645
646             /* convert_line() expects encrypted input */
647             (void) xcrypt(in_line, in_line);
648             convert_line(in_line, out_line);
649             pline("%s", out_line);
650
651             if ((nl = index(str, '\n')) == 0)
652                 break; /* done if no newline */
653         }
654
655         free((genericptr_t) lev_message);
656         lev_message = 0;
657     }
658 }
659
660 /*questpgr.c*/