1 /* NetHack 3.6	rumors.c	$NHDT-Date: 1583445339 2020/03/05 21:55:39 $  $NHDT-Branch: NetHack-3.6-Mar2020 $:$NHDT-Revision: 1.38 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2012. */
4 /* NetHack may be freely redistributed.  See license for details. */
5 
6 #include "hack.h"
7 #include "lev.h"
8 #include "dlb.h"
9 
10 /*      [note: this comment is fairly old, but still accurate for 3.1]
11  * Rumors have been entirely rewritten to speed up the access.  This is
12  * essential when working from floppies.  Using fseek() the way that's done
13  * here means rumors following longer rumors are output more often than those
14  * following shorter rumors.  Also, you may see the same rumor more than once
15  * in a particular game (although the odds are highly against it), but
16  * this also happens with real fortune cookies.  -dgk
17  */
18 
19 /*      3.6
20  * The rumors file consists of a "do not edit" line, then a line containing
21  * three sets of three counts (first two in decimal, third in hexadecimal).
22  * The first set has the number of true rumors, the count in bytes for all
23  * true rumors, and the file offset to the first one.  The second set has
24  * the same group of numbers for the false rumors.  The third set has 0 for
25  * count, 0 for size, and the file offset for end-of-file.  The offset of
26  * the first true rumor plus the size of the true rumors matches the offset
27  * of the first false rumor.  Likewise, the offset of the first false rumor
28  * plus the size of the false rumors matches the offset for end-of-file.
29  */
30 
31 /*      3.1     [now obsolete for rumors but still accurate for oracles]
32  * The rumors file consists of a "do not edit" line, a hexadecimal number
33  * giving the number of bytes of useful/true rumors, followed by those
34  * true rumors (one per line), followed by the useless/false/misleading/cute
35  * rumors (also one per line).  Number of bytes of untrue rumors is derived
36  * via fseek(EOF)+ftell().
37  *
38  * The oracles file consists of a "do not edit" comment, a decimal count N
39  * and set of N+1 hexadecimal fseek offsets, followed by N multiple-line
40  * records, separated by "---" lines.  The first oracle is a special case,
41  * and placed there by 'makedefs'.
42  */
43 
44 STATIC_DCL void FDECL(init_rumors, (dlb *));
45 STATIC_DCL void FDECL(init_oracles, (dlb *));
46 STATIC_DCL void FDECL(couldnt_open_file, (const char *));
47 
48 /* rumor size variables are signed so that value -1 can be used as a flag */
49 static long true_rumor_size = 0L, false_rumor_size;
50 /* rumor start offsets are unsigned because they're handled via %lx format */
51 static unsigned long true_rumor_start, false_rumor_start;
52 /* rumor end offsets are signed because they're compared with [dlb_]ftell() */
53 static long true_rumor_end, false_rumor_end;
54 /* oracles are handled differently from rumors... */
55 static int oracle_flg = 0; /* -1=>don't use, 0=>need init, 1=>init done */
56 static unsigned oracle_cnt = 0;
57 static unsigned long *oracle_loc = 0;
58 
59 STATIC_OVL void
init_rumors(fp)60 init_rumors(fp)
61 dlb *fp;
62 {
63     static const char rumors_header[] = "%d,%ld,%lx;%d,%ld,%lx;0,0,%lx\n";
64     int true_count, false_count; /* in file but not used here */
65     unsigned long eof_offset;
66     char line[BUFSZ];
67 
68     (void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment */
69     (void) dlb_fgets(line, sizeof line, fp);
70     if (sscanf(line, rumors_header, &true_count, &true_rumor_size,
71                &true_rumor_start, &false_count, &false_rumor_size,
72                &false_rumor_start, &eof_offset) == 7
73         && true_rumor_size > 0L
74         && false_rumor_size > 0L) {
75         true_rumor_end = (long) true_rumor_start + true_rumor_size;
76         /* assert( true_rumor_end == false_rumor_start ); */
77         false_rumor_end = (long) false_rumor_start + false_rumor_size;
78         /* assert( false_rumor_end == eof_offset ); */
79     } else {
80         true_rumor_size = -1L; /* init failed */
81         (void) dlb_fclose(fp);
82     }
83 }
84 
85 /* exclude_cookie is a hack used because we sometimes want to get rumors in a
86  * context where messages such as "You swallowed the fortune!" that refer to
87  * cookies should not appear.  This has no effect for true rumors since none
88  * of them contain such references anyway.
89  */
90 char *
getrumor(truth,rumor_buf,exclude_cookie)91 getrumor(truth, rumor_buf, exclude_cookie)
92 int truth; /* 1=true, -1=false, 0=either */
93 char *rumor_buf;
94 boolean exclude_cookie;
95 {
96     dlb *rumors;
97     long tidbit, beginning;
98     char *endp, line[BUFSZ], xbuf[BUFSZ];
99 
100     rumor_buf[0] = '\0';
101     if (true_rumor_size < 0L) /* we couldn't open RUMORFILE */
102         return rumor_buf;
103 
104     rumors = dlb_fopen(RUMORFILE, "r");
105 
106     if (rumors) {
107         int count = 0;
108         int adjtruth;
109 
110         do {
111             rumor_buf[0] = '\0';
112             if (true_rumor_size == 0L) { /* if this is 1st outrumor() */
113                 init_rumors(rumors);
114                 if (true_rumor_size < 0L) { /* init failed */
115                     Sprintf(rumor_buf, "Error reading \"%.80s\".", RUMORFILE);
116                     return rumor_buf;
117                 }
118             }
119             /*
120              *  input:      1    0   -1
121              *   rn2 \ +1  2=T  1=T  0=F
122              *   adj./ +0  1=T  0=F -1=F
123              */
124             switch (adjtruth = truth + rn2(2)) {
125             case 2: /*(might let a bogus input arg sneak thru)*/
126             case 1:
127                 beginning = (long) true_rumor_start;
128                 tidbit = rn2(true_rumor_size);
129                 break;
130             case 0: /* once here, 0 => false rather than "either"*/
131             case -1:
132                 beginning = (long) false_rumor_start;
133                 tidbit = rn2(false_rumor_size);
134                 break;
135             default:
136                 impossible("strange truth value for rumor");
137                 return strcpy(rumor_buf, "Oops...");
138             }
139             (void) dlb_fseek(rumors, beginning + tidbit, SEEK_SET);
140             (void) dlb_fgets(line, sizeof line, rumors);
141             if (!dlb_fgets(line, sizeof line, rumors)
142                 || (adjtruth > 0 && dlb_ftell(rumors) > true_rumor_end)) {
143                 /* reached end of rumors -- go back to beginning */
144                 (void) dlb_fseek(rumors, beginning, SEEK_SET);
145                 (void) dlb_fgets(line, sizeof line, rumors);
146             }
147             if ((endp = index(line, '\n')) != 0)
148                 *endp = 0;
149             Strcat(rumor_buf, xcrypt(line, xbuf));
150         } while (
151             count++ < 50 && exclude_cookie
152             && (strstri(rumor_buf, "fortune") || strstri(rumor_buf, "pity")));
153         (void) dlb_fclose(rumors);
154         if (count >= 50)
155             impossible("Can't find non-cookie rumor?");
156         else if (!in_mklev) /* avoid exercizing wisdom for graffiti */
157             exercise(A_WIS, (adjtruth > 0));
158     } else {
159         couldnt_open_file(RUMORFILE);
160         true_rumor_size = -1; /* don't try to open it again */
161     }
162 
163     /* this is safe either way, so do it always since we can't get the
164      * definition out of makedefs.c
165      */
166 #define PAD_RUMORS_TO
167 #ifdef PAD_RUMORS_TO
168     /* remove padding */
169     {
170         char *x = eos(rumor_buf) - 1;
171 
172         while (x > rumor_buf && *x == '_')
173             x--;
174         *++x = '\n';
175         *x = '\0';
176     }
177 #endif
178     return rumor_buf;
179 }
180 
181 /*
182  * test that the true/false rumor boundaries are valid.
183  */
184 void
rumor_check()185 rumor_check()
186 {
187     dlb *rumors = 0;
188     winid tmpwin;
189     char *endp, line[BUFSZ], xbuf[BUFSZ], rumor_buf[BUFSZ];
190 
191     if (true_rumor_size < 0L) { /* we couldn't open RUMORFILE */
192  no_rumors:
193         pline("rumors not accessible.");
194         return;
195     }
196 
197     rumors = dlb_fopen(RUMORFILE, "r");
198 
199     if (rumors) {
200         long ftell_rumor_start = 0L;
201 
202         rumor_buf[0] = '\0';
203         if (true_rumor_size == 0L) { /* if this is 1st outrumor() */
204             init_rumors(rumors);
205             if (true_rumor_size < 0L) {
206                 rumors = (dlb *) 0; /* init_rumors() closes it upon failure */
207                 goto no_rumors; /* init failed */
208             }
209         }
210         tmpwin = create_nhwindow(NHW_TEXT);
211 
212         /*
213          * reveal the values.
214          */
215         Sprintf(rumor_buf,
216                "T start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)",
217                 (long) true_rumor_start, true_rumor_start,
218                 true_rumor_end, (unsigned long) true_rumor_end,
219                 true_rumor_size, (unsigned long) true_rumor_size);
220         putstr(tmpwin, 0, rumor_buf);
221         Sprintf(rumor_buf,
222                "F start=%06ld (%06lx), end=%06ld (%06lx), size=%06ld (%06lx)",
223                 (long) false_rumor_start, false_rumor_start,
224                 false_rumor_end, (unsigned long) false_rumor_end,
225                 false_rumor_size, (unsigned long) false_rumor_size);
226         putstr(tmpwin, 0, rumor_buf);
227 
228         /*
229          * check the first rumor (start of true rumors) by
230          * skipping the first two lines.
231          *
232          * Then seek to the start of the false rumors (based on
233          * the value read in rumors, and display it.
234          */
235         rumor_buf[0] = '\0';
236         (void) dlb_fseek(rumors, (long) true_rumor_start, SEEK_SET);
237         ftell_rumor_start = dlb_ftell(rumors);
238         (void) dlb_fgets(line, sizeof line, rumors);
239         if ((endp = index(line, '\n')) != 0)
240             *endp = 0;
241         Sprintf(rumor_buf, "T %06ld %s", ftell_rumor_start,
242                 xcrypt(line, xbuf));
243         putstr(tmpwin, 0, rumor_buf);
244         /* find last true rumor */
245         while (dlb_fgets(line, sizeof line, rumors)
246                && dlb_ftell(rumors) < true_rumor_end)
247             continue;
248         if ((endp = index(line, '\n')) != 0)
249             *endp = 0;
250         Sprintf(rumor_buf, "  %6s %s", "", xcrypt(line, xbuf));
251         putstr(tmpwin, 0, rumor_buf);
252 
253         rumor_buf[0] = '\0';
254         (void) dlb_fseek(rumors, (long) false_rumor_start, SEEK_SET);
255         ftell_rumor_start = dlb_ftell(rumors);
256         (void) dlb_fgets(line, sizeof line, rumors);
257         if ((endp = index(line, '\n')) != 0)
258             *endp = 0;
259         Sprintf(rumor_buf, "F %06ld %s", ftell_rumor_start,
260                 xcrypt(line, xbuf));
261         putstr(tmpwin, 0, rumor_buf);
262         /* find last false rumor */
263         while (dlb_fgets(line, sizeof line, rumors)
264                && dlb_ftell(rumors) < false_rumor_end)
265             continue;
266         if ((endp = index(line, '\n')) != 0)
267             *endp = 0;
268         Sprintf(rumor_buf, "  %6s %s", "", xcrypt(line, xbuf));
269         putstr(tmpwin, 0, rumor_buf);
270 
271         (void) dlb_fclose(rumors);
272         display_nhwindow(tmpwin, TRUE);
273         destroy_nhwindow(tmpwin);
274     } else {
275         couldnt_open_file(RUMORFILE);
276         true_rumor_size = -1; /* don't try to open it again */
277     }
278 }
279 
280 /* Gets a random line of text from file 'fname', and returns it.
281    rng is the random number generator to use, and should act like rn2 does. */
282 char *
get_rnd_text(fname,buf,rng)283 get_rnd_text(fname, buf, rng)
284 const char *fname;
285 char *buf;
286 int FDECL((*rng), (int));
287 {
288     dlb *fh;
289 
290     buf[0] = '\0';
291     fh = dlb_fopen(fname, "r");
292     if (fh) {
293         /* TODO: cache sizetxt, starttxt, endtxt. maybe cache file contents? */
294         long sizetxt = 0L, starttxt = 0L, endtxt = 0L, tidbit = 0L;
295         char *endp, line[BUFSZ], xbuf[BUFSZ];
296 
297         /* skip "don't edit" comment */
298         (void) dlb_fgets(line, sizeof line, fh);
299 
300         (void) dlb_fseek(fh, 0L, SEEK_CUR);
301         starttxt = dlb_ftell(fh);
302         (void) dlb_fseek(fh, 0L, SEEK_END);
303         endtxt = dlb_ftell(fh);
304         sizetxt = endtxt - starttxt;
305         /* might be zero (only if file is empty); should complain in that
306            case but if could happen over and over, also the suggestion
307            that save and restore might fix the problem wouldn't be useful */
308         if (sizetxt < 1L)
309             return buf;
310         tidbit = (*rng)(sizetxt);
311 
312         (void) dlb_fseek(fh, starttxt + tidbit, SEEK_SET);
313         (void) dlb_fgets(line, sizeof line, fh);
314         if (!dlb_fgets(line, sizeof line, fh)) {
315             (void) dlb_fseek(fh, starttxt, SEEK_SET);
316             (void) dlb_fgets(line, sizeof line, fh);
317         }
318         if ((endp = index(line, '\n')) != 0)
319             *endp = 0;
320         Strcat(buf, xcrypt(line, xbuf));
321         (void) dlb_fclose(fh);
322     } else {
323         couldnt_open_file(fname);
324     }
325 
326     return buf;
327 }
328 
329 void
outrumor(truth,mechanism)330 outrumor(truth, mechanism)
331 int truth; /* 1=true, -1=false, 0=either */
332 int mechanism;
333 {
334     static const char fortune_msg[] =
335         "This cookie has a scrap of paper inside.";
336     const char *line;
337     char buf[BUFSZ];
338     boolean reading = (mechanism == BY_COOKIE || mechanism == BY_PAPER);
339 
340     if (reading) {
341         /* deal with various things that prevent reading */
342         if (is_fainted() && mechanism == BY_COOKIE)
343             return;
344         else if (Blind) {
345             if (mechanism == BY_COOKIE)
346                 pline(fortune_msg);
347             pline("What a pity that you cannot read it!");
348             return;
349         }
350     }
351     line = getrumor(truth, buf, reading ? FALSE : TRUE);
352     if (!*line)
353         line = "NetHack rumors file closed for renovation.";
354     switch (mechanism) {
355     case BY_ORACLE:
356         /* Oracle delivers the rumor */
357         pline("True to her word, the Oracle %ssays: ",
358               (!rn2(4) ? "offhandedly "
359                        : (!rn2(3) ? "casually "
360                                   : (rn2(2) ? "nonchalantly " : ""))));
361         verbalize1(line);
362         /* [WIS exercized by getrumor()] */
363         return;
364     case BY_COOKIE:
365         pline(fortune_msg);
366     /* FALLTHRU */
367     case BY_PAPER:
368         pline("It reads:");
369         break;
370     }
371     pline1(line);
372 }
373 
374 STATIC_OVL void
init_oracles(fp)375 init_oracles(fp)
376 dlb *fp;
377 {
378     register int i;
379     char line[BUFSZ];
380     int cnt = 0;
381 
382     /* this assumes we're only called once */
383     (void) dlb_fgets(line, sizeof line, fp); /* skip "don't edit" comment*/
384     (void) dlb_fgets(line, sizeof line, fp);
385     if (sscanf(line, "%5d\n", &cnt) == 1 && cnt > 0) {
386         oracle_cnt = (unsigned) cnt;
387         oracle_loc = (unsigned long *) alloc((unsigned) cnt * sizeof(long));
388         for (i = 0; i < cnt; i++) {
389             (void) dlb_fgets(line, sizeof line, fp);
390             (void) sscanf(line, "%5lx\n", &oracle_loc[i]);
391         }
392     }
393     return;
394 }
395 
396 void
save_oracles(fd,mode)397 save_oracles(fd, mode)
398 int fd, mode;
399 {
400     if (perform_bwrite(mode)) {
401         bwrite(fd, (genericptr_t) &oracle_cnt, sizeof oracle_cnt);
402         if (oracle_cnt)
403             bwrite(fd, (genericptr_t) oracle_loc,
404                     oracle_cnt * sizeof(long));
405     }
406     if (release_data(mode)) {
407         if (oracle_cnt) {
408             free((genericptr_t) oracle_loc);
409             oracle_loc = 0, oracle_cnt = 0, oracle_flg = 0;
410         }
411     }
412 }
413 
414 void
restore_oracles(fd)415 restore_oracles(fd)
416 int fd;
417 {
418     mread(fd, (genericptr_t) &oracle_cnt, sizeof oracle_cnt);
419     if (oracle_cnt) {
420         oracle_loc = (unsigned long *) alloc(oracle_cnt * sizeof(long));
421         mread(fd, (genericptr_t) oracle_loc, oracle_cnt * sizeof(long));
422         oracle_flg = 1; /* no need to call init_oracles() */
423     }
424 }
425 
426 void
outoracle(special,delphi)427 outoracle(special, delphi)
428 boolean special;
429 boolean delphi;
430 {
431     winid tmpwin;
432     dlb *oracles;
433     int oracle_idx;
434     char *endp, line[COLNO], xbuf[BUFSZ];
435 
436     /* early return if we couldn't open ORACLEFILE on previous attempt,
437        or if all the oracularities are already exhausted */
438     if (oracle_flg < 0 || (oracle_flg > 0 && oracle_cnt == 0))
439         return;
440 
441     oracles = dlb_fopen(ORACLEFILE, "r");
442 
443     if (oracles) {
444         if (oracle_flg == 0) { /* if this is the first outoracle() */
445             init_oracles(oracles);
446             oracle_flg = 1;
447             if (oracle_cnt == 0)
448                 goto close_oracles;
449         }
450         /* oracle_loc[0] is the special oracle;
451            oracle_loc[1..oracle_cnt-1] are normal ones */
452         if (oracle_cnt <= 1 && !special)
453             goto close_oracles; /*(shouldn't happen)*/
454         oracle_idx = special ? 0 : rnd((int) oracle_cnt - 1);
455         (void) dlb_fseek(oracles, (long) oracle_loc[oracle_idx], SEEK_SET);
456         if (!special) /* move offset of very last one into this slot */
457             oracle_loc[oracle_idx] = oracle_loc[--oracle_cnt];
458 
459         tmpwin = create_nhwindow(NHW_TEXT);
460         if (delphi)
461             putstr(tmpwin, 0,
462                    special
463                      ? "The Oracle scornfully takes all your money and says:"
464                      : "The Oracle meditates for a moment and then intones:");
465         else
466             putstr(tmpwin, 0, "The message reads:");
467         putstr(tmpwin, 0, "");
468 
469         while (dlb_fgets(line, COLNO, oracles) && strcmp(line, "---\n")) {
470             if ((endp = index(line, '\n')) != 0)
471                 *endp = 0;
472             putstr(tmpwin, 0, xcrypt(line, xbuf));
473         }
474         display_nhwindow(tmpwin, TRUE);
475         destroy_nhwindow(tmpwin);
476  close_oracles:
477         (void) dlb_fclose(oracles);
478     } else {
479         couldnt_open_file(ORACLEFILE);
480         oracle_flg = -1; /* don't try to open it again */
481     }
482 }
483 
484 int
doconsult(oracl)485 doconsult(oracl)
486 struct monst *oracl;
487 {
488     long umoney;
489     int u_pay, minor_cost = 50, major_cost = 500 + 50 * u.ulevel;
490     int add_xpts;
491     char qbuf[QBUFSZ];
492 
493     multi = 0;
494     umoney = money_cnt(invent);
495 
496     if (!oracl) {
497         There("is no one here to consult.");
498         return 0;
499     } else if (!oracl->mpeaceful) {
500         pline("%s is in no mood for consultations.", Monnam(oracl));
501         return 0;
502     } else if (!umoney) {
503         You("have no money.");
504         return 0;
505     }
506 
507     Sprintf(qbuf, "\"Wilt thou settle for a minor consultation?\" (%d %s)",
508             minor_cost, currency((long) minor_cost));
509     switch (ynq(qbuf)) {
510     default:
511     case 'q':
512         return 0;
513     case 'y':
514         if (umoney < (long) minor_cost) {
515             You("don't even have enough money for that!");
516             return 0;
517         }
518         u_pay = minor_cost;
519         break;
520     case 'n':
521         if (umoney <= (long) minor_cost /* don't even ask */
522             || (oracle_cnt == 1 || oracle_flg < 0))
523             return 0;
524         Sprintf(qbuf, "\"Then dost thou desire a major one?\" (%d %s)",
525                 major_cost, currency((long) major_cost));
526         if (yn(qbuf) != 'y')
527             return 0;
528         u_pay = (umoney < (long) major_cost) ? (int) umoney : major_cost;
529         break;
530     }
531     money2mon(oracl, (long) u_pay);
532     context.botl = 1;
533     add_xpts = 0; /* first oracle of each type gives experience points */
534     if (u_pay == minor_cost) {
535         outrumor(1, BY_ORACLE);
536         if (!u.uevent.minor_oracle)
537             add_xpts = u_pay / (u.uevent.major_oracle ? 25 : 10);
538         /* 5 pts if very 1st, or 2 pts if major already done */
539         u.uevent.minor_oracle = TRUE;
540     } else {
541         boolean cheapskate = u_pay < major_cost;
542 
543         outoracle(cheapskate, TRUE);
544         if (!cheapskate && !u.uevent.major_oracle)
545             add_xpts = u_pay / (u.uevent.minor_oracle ? 25 : 10);
546         /* ~100 pts if very 1st, ~40 pts if minor already done */
547         u.uevent.major_oracle = TRUE;
548         exercise(A_WIS, !cheapskate);
549     }
550     if (add_xpts) {
551         more_experienced(add_xpts, u_pay / 50);
552         newexplevel();
553     }
554     return 1;
555 }
556 
557 STATIC_OVL void
couldnt_open_file(filename)558 couldnt_open_file(filename)
559 const char *filename;
560 {
561     int save_something = program_state.something_worth_saving;
562 
563     /* most likely the file is missing, so suppress impossible()'s
564        "saving and restoring might fix this" (unless the fuzzer,
565        which escalates impossible to panic, is running) */
566     if (!iflags.debug_fuzzer)
567         program_state.something_worth_saving = 0;
568 
569     impossible("Can't open '%s' file.", filename);
570     program_state.something_worth_saving = save_something;
571 }
572 
573 /*rumors.c*/
574