1 /* NetHack 3.7 questpgr.c $NHDT-Date: 1596498201 2020/08/03 23:43:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.72 $ */
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 #define QTEXT_FILE "quest.lua"
11
12 #ifdef TTY_GRAPHICS
13 #include "wintty.h"
14 #endif
15
16 static const char *intermed(void);
17 static struct obj *find_qarti(struct obj *);
18 static const char *neminame(void);
19 static const char *guardname(void);
20 static const char *homebase(void);
21 static void qtext_pronoun(char, char);
22 static void convert_arg(char);
23 static void deliver_by_pline(const char *);
24 static void deliver_by_window(const char *, int);
25 static boolean skip_pager(boolean);
26 static boolean com_pager_core(const char *, const char *, boolean);
27
28 short
quest_info(int typ)29 quest_info(int typ)
30 {
31 switch (typ) {
32 case 0:
33 return g.urole.questarti;
34 case MS_LEADER:
35 return g.urole.ldrnum;
36 case MS_NEMESIS:
37 return g.urole.neminum;
38 case MS_GUARDIAN:
39 return g.urole.guardnum;
40 default:
41 impossible("quest_info(%d)", typ);
42 }
43 return 0;
44 }
45
46 /* return your role leader's name */
47 const char *
ldrname(void)48 ldrname(void)
49 {
50 int i = g.urole.ldrnum;
51
52 Sprintf(g.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
53 mons[i].pmnames[NEUTRAL]);
54 return g.nambuf;
55 }
56
57 /* return your intermediate target string */
58 static const char *
intermed(void)59 intermed(void)
60 {
61 return g.urole.intermed;
62 }
63
64 boolean
is_quest_artifact(struct obj * otmp)65 is_quest_artifact(struct obj *otmp)
66 {
67 return (boolean) (otmp->oartifact == g.urole.questarti);
68 }
69
70 static struct obj *
find_qarti(struct obj * ochain)71 find_qarti(struct obj *ochain)
72 {
73 struct obj *otmp, *qarti;
74
75 for (otmp = ochain; otmp; otmp = otmp->nobj) {
76 if (is_quest_artifact(otmp))
77 return otmp;
78 if (Has_contents(otmp) && (qarti = find_qarti(otmp->cobj)) != 0)
79 return qarti;
80 }
81 return (struct obj *) 0;
82 }
83
84 /* check several object chains for the quest artifact to determine
85 whether it is present on the current level */
86 struct obj *
find_quest_artifact(unsigned whichchains)87 find_quest_artifact(unsigned whichchains)
88 {
89 struct monst *mtmp;
90 struct obj *qarti = 0;
91
92 if ((whichchains & (1 << OBJ_INVENT)) != 0)
93 qarti = find_qarti(g.invent);
94 if (!qarti && (whichchains & (1 << OBJ_FLOOR)) != 0)
95 qarti = find_qarti(fobj);
96 if (!qarti && (whichchains & (1 << OBJ_MINVENT)) != 0)
97 for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
98 if (DEADMONSTER(mtmp))
99 continue;
100 if ((qarti = find_qarti(mtmp->minvent)) != 0)
101 break;
102 }
103 if (!qarti && (whichchains & (1 << OBJ_MIGRATING)) != 0) {
104 /* check migrating objects and minvent of migrating monsters */
105 for (mtmp = g.migrating_mons; mtmp; mtmp = mtmp->nmon) {
106 if (DEADMONSTER(mtmp))
107 continue;
108 if ((qarti = find_qarti(mtmp->minvent)) != 0)
109 break;
110 }
111 if (!qarti)
112 qarti = find_qarti(g.migrating_objs);
113 }
114 if (!qarti && (whichchains & (1 << OBJ_BURIED)) != 0)
115 qarti = find_qarti(g.level.buriedobjlist);
116
117 return qarti;
118 }
119
120 /* return your role nemesis' name */
121 static const char *
neminame(void)122 neminame(void)
123 {
124 int i = g.urole.neminum;
125
126 Sprintf(g.nambuf, "%s%s", type_is_pname(&mons[i]) ? "" : "the ",
127 mons[i].pmnames[NEUTRAL]);
128 return g.nambuf;
129 }
130
131 static const char *
guardname(void)132 guardname(void) /* return your role leader's guard monster name */
133 {
134 int i = g.urole.guardnum;
135
136 return mons[i].pmnames[NEUTRAL];
137 }
138
139 static const char *
homebase(void)140 homebase(void) /* return your role leader's location */
141 {
142 return g.urole.homebase;
143 }
144
145 /* replace deity, leader, nemesis, or artifact name with pronoun;
146 overwrites cvt_buf[] */
147 static void
qtext_pronoun(char who,char which)148 qtext_pronoun(char who, /* 'd' => deity, 'l' => leader, 'n' => nemesis,
149 'o' => artifact */
150 char which) /* 'h'|'H'|'i'|'I'|'j'|'J' */
151 {
152 const char *pnoun;
153 int godgend;
154 char lwhich = lowc(which); /* H,I,J -> h,i,j */
155
156 /*
157 * Invalid subject (not d,l,n,o) yields neuter, singular result.
158 *
159 * For %o, treat all artifacts as neuter; some have plural names,
160 * which genders[] doesn't handle; cvt_buf[] already contains name.
161 */
162 if (who == 'o'
163 && (strstri(g.cvt_buf, "Eyes ")
164 || strcmpi(g.cvt_buf, makesingular(g.cvt_buf)))) {
165 pnoun = (lwhich == 'h') ? "they"
166 : (lwhich == 'i') ? "them"
167 : (lwhich == 'j') ? "their" : "?";
168 } else {
169 godgend = (who == 'd') ? g.quest_status.godgend
170 : (who == 'l') ? g.quest_status.ldrgend
171 : (who == 'n') ? g.quest_status.nemgend
172 : 2; /* default to neuter */
173 pnoun = (lwhich == 'h') ? genders[godgend].he
174 : (lwhich == 'i') ? genders[godgend].him
175 : (lwhich == 'j') ? genders[godgend].his : "?";
176 }
177 Strcpy(g.cvt_buf, pnoun);
178 /* capitalize for H,I,J */
179 if (lwhich != which)
180 g.cvt_buf[0] = highc(g.cvt_buf[0]);
181 return;
182 }
183
184 static void
convert_arg(char c)185 convert_arg(char c)
186 {
187 register const char *str;
188
189 switch (c) {
190 case 'p':
191 str = g.plname;
192 break;
193 case 'c':
194 str = (flags.female && g.urole.name.f) ? g.urole.name.f : g.urole.name.m;
195 break;
196 case 'r':
197 str = rank_of(u.ulevel, Role_switch, flags.female);
198 break;
199 case 'R':
200 str = rank_of(MIN_QUEST_LEVEL, Role_switch, flags.female);
201 break;
202 case 's':
203 str = (flags.female) ? "sister" : "brother";
204 break;
205 case 'S':
206 str = (flags.female) ? "daughter" : "son";
207 break;
208 case 'l':
209 str = ldrname();
210 break;
211 case 'i':
212 str = intermed();
213 break;
214 case 'O':
215 case 'o':
216 str = the(artiname(g.urole.questarti));
217 if (c == 'O') {
218 /* shorten "the Foo of Bar" to "the Foo"
219 (buffer returned by the() is modifiable) */
220 char *p = strstri(str, " of ");
221
222 if (p)
223 *p = '\0';
224 }
225 break;
226 case 'n':
227 str = neminame();
228 break;
229 case 'g':
230 str = guardname();
231 break;
232 case 'G':
233 str = align_gtitle(u.ualignbase[A_ORIGINAL]);
234 break;
235 case 'H':
236 str = homebase();
237 break;
238 case 'a':
239 str = align_str(u.ualignbase[A_ORIGINAL]);
240 break;
241 case 'A':
242 str = align_str(u.ualign.type);
243 break;
244 case 'd':
245 str = align_gname(u.ualignbase[A_ORIGINAL]);
246 break;
247 case 'D':
248 str = align_gname(A_LAWFUL);
249 break;
250 case 'C':
251 str = "chaotic";
252 break;
253 case 'N':
254 str = "neutral";
255 break;
256 case 'L':
257 str = "lawful";
258 break;
259 case 'x':
260 str = Blind ? "sense" : "see";
261 break;
262 case 'Z':
263 str = g.dungeons[0].dname;
264 break;
265 case '%':
266 str = "%";
267 break;
268 default:
269 str = "";
270 break;
271 }
272 Strcpy(g.cvt_buf, str);
273 }
274
275 void
convert_line(const char * in_line,char * out_line)276 convert_line(const char *in_line, char *out_line)
277 {
278 const char *c;
279 char *cc;
280
281 cc = out_line;
282 for (c = in_line; *c; c++) {
283 *cc = 0;
284 switch (*c) {
285 case '\r':
286 case '\n':
287 *(++cc) = 0;
288 return;
289
290 case '%':
291 if (*(c + 1)) {
292 convert_arg(*(++c));
293 switch (*(++c)) {
294 /* insert "a"/"an" prefix */
295 case 'A':
296 Strcat(cc, An(g.cvt_buf));
297 cc += strlen(cc);
298 continue; /* for */
299 case 'a':
300 Strcat(cc, an(g.cvt_buf));
301 cc += strlen(cc);
302 continue; /* for */
303
304 /* capitalize */
305 case 'C':
306 g.cvt_buf[0] = highc(g.cvt_buf[0]);
307 break;
308
309 /* replace name with pronoun;
310 valid for %d, %l, %n, and %o */
311 case 'h': /* he/she */
312 case 'H': /* He/She */
313 case 'i': /* him/her */
314 case 'I':
315 case 'j': /* his/her */
316 case 'J':
317 if (index("dlno", lowc(*(c - 1))))
318 qtext_pronoun(*(c - 1), *c);
319 else
320 --c; /* default action */
321 break;
322
323 /* pluralize */
324 case 'P':
325 g.cvt_buf[0] = highc(g.cvt_buf[0]);
326 /*FALLTHRU*/
327 case 'p':
328 Strcpy(g.cvt_buf, makeplural(g.cvt_buf));
329 break;
330
331 /* append possessive suffix */
332 case 'S':
333 g.cvt_buf[0] = highc(g.cvt_buf[0]);
334 /*FALLTHRU*/
335 case 's':
336 Strcpy(g.cvt_buf, s_suffix(g.cvt_buf));
337 break;
338
339 /* strip any "the" prefix */
340 case 't':
341 if (!strncmpi(g.cvt_buf, "the ", 4)) {
342 Strcat(cc, &g.cvt_buf[4]);
343 cc += strlen(cc);
344 continue; /* for */
345 }
346 break;
347
348 default:
349 --c; /* undo switch increment */
350 break;
351 }
352 Strcat(cc, g.cvt_buf);
353 cc += strlen(g.cvt_buf);
354 break;
355 } /* else fall through */
356
357 default:
358 *cc++ = *c;
359 break;
360 }
361 if (cc > &out_line[BUFSZ - 1])
362 panic("convert_line: overflow");
363 }
364 *cc = 0;
365 return;
366 }
367
368 static void
deliver_by_pline(const char * str)369 deliver_by_pline(const char *str)
370 {
371 const char *msgp = str;
372 const char *msgend = eos((char *)str);
373 char in_line[BUFSZ], out_line[BUFSZ];
374
375 while (msgp && msgp != msgend) {
376 int i = 0;
377 while (*msgp != '\0' && *msgp != '\n' && (i < BUFSZ-2)) {
378 in_line[i] = *msgp;
379 i++;
380 msgp++;
381 }
382 if (*msgp == '\n')
383 msgp++;
384 in_line[i] = '\0';
385 convert_line(in_line, out_line);
386 pline("%s", out_line);
387 }
388 }
389
390 static void
deliver_by_window(const char * msg,int how)391 deliver_by_window(const char *msg, int how)
392 {
393 const char *msgp = msg;
394 const char *msgend = eos((char *)msg);
395 char in_line[BUFSZ], out_line[BUFSZ];
396 winid datawin = create_nhwindow(how);
397
398 while (msgp && msgp != msgend) {
399 int i = 0;
400 while (*msgp != '\0' && *msgp != '\n' && (i < BUFSZ-2)) {
401 in_line[i] = *msgp;
402 i++;
403 msgp++;
404 }
405 if (*msgp == '\n')
406 msgp++;
407 in_line[i] = '\0';
408 convert_line(in_line, out_line);
409 putstr(datawin, 0, out_line);
410 }
411
412 display_nhwindow(datawin, TRUE);
413 destroy_nhwindow(datawin);
414 }
415
416 static boolean
skip_pager(boolean common UNUSED)417 skip_pager(boolean common UNUSED)
418 {
419 /* WIZKIT: suppress plot feedback if starting with quest artifact */
420 if (g.program_state.wizkit_wishing)
421 return TRUE;
422 return FALSE;
423 }
424
425 static boolean
com_pager_core(const char * section,const char * msgid,boolean showerror)426 com_pager_core(const char *section, const char *msgid, boolean showerror)
427 {
428 static const char *const howtoput[] = {
429 "pline", "window", "text", "menu", "default", NULL
430 };
431 static const int howtoput2i[] = { 1, 2, 2, 3, 0, 0 };
432 int output;
433 lua_State *L;
434 char *text = NULL, *synopsis = NULL, *fallback_msgid = NULL;
435 boolean res = FALSE;
436
437 if (skip_pager(TRUE))
438 return FALSE;
439
440 L = nhl_init();
441
442 if (!nhl_loadlua(L, QTEXT_FILE)) {
443 if (showerror)
444 impossible("com_pager: %s not found.", QTEXT_FILE);
445 goto compagerdone;
446 }
447
448 lua_settop(L, 0);
449 lua_getglobal(L, "questtext");
450 if (!lua_istable(L, -1)) {
451 if (showerror)
452 impossible("com_pager: questtext in %s is not a lua table",
453 QTEXT_FILE);
454 goto compagerdone;
455 }
456
457 lua_getfield(L, -1, section);
458 if (!lua_istable(L, -1)) {
459 if (showerror)
460 impossible("com_pager: questtext[%s] in %s is not a lua table",
461 section, QTEXT_FILE);
462 goto compagerdone;
463 }
464
465 tryagain:
466 lua_getfield(L, -1, fallback_msgid ? fallback_msgid : msgid);
467 if (!lua_istable(L, -1)) {
468 if (!fallback_msgid) {
469 /* Do we have questtxt[msg_fallbacks][<msgid>]? */
470 lua_getfield(L, -3, "msg_fallbacks");
471 if (lua_istable(L, -1)) {
472 fallback_msgid = get_table_str_opt(L, msgid, NULL);
473 lua_pop(L, 2);
474 if (fallback_msgid)
475 goto tryagain;
476 }
477 }
478 if (showerror) {
479 if (!fallback_msgid)
480 impossible(
481 "com_pager: questtext[%s][%s] in %s is not a lua table",
482 section, msgid, QTEXT_FILE);
483 else
484 impossible(
485 "com_pager: questtext[%s][%s] and [][%s] in %s are not lua tables",
486 section, msgid, fallback_msgid, QTEXT_FILE);
487 }
488 goto compagerdone;
489 }
490
491 synopsis = get_table_str_opt(L, "synopsis", NULL);
492 text = get_table_str_opt(L, "text", NULL);
493 output = howtoput2i[get_table_option(L, "output", "default", howtoput)];
494
495 if (!text) {
496 int nelems;
497
498 lua_len(L, -1);
499 nelems = (int) lua_tointeger(L, -1);
500 lua_pop(L, 1);
501 if (nelems < 2) {
502 if (showerror)
503 impossible(
504 "com_pager: questtext[%s][%s] in %s in not an array of strings",
505 section, fallback_msgid ? fallback_msgid : msgid,
506 QTEXT_FILE);
507 goto compagerdone;
508 }
509 nelems = rn2(nelems) + 1;
510 lua_pushinteger(L, nelems);
511 lua_gettable(L, -2);
512 text = dupstr(luaL_checkstring(L, -1));
513 }
514
515 if (output == 0 && (index(text, '\n') || (strlen(text) >= (BUFSZ - 1))))
516 output = 2;
517
518 if (output == 0 || output == 1)
519 deliver_by_pline(text);
520 else
521 deliver_by_window(text, (output == 3) ? NHW_MENU : NHW_TEXT);
522
523 if (synopsis) {
524 char in_line[BUFSZ], out_line[BUFSZ];
525
526 #if 0 /* not yet -- brackets need to be removed from quest.lua */
527 Sprintf(in_line, "[%.*s]",
528 (int) (sizeof in_line - sizeof "[]"), synopsis);
529 #else
530 Strcpy(in_line, synopsis);
531 #endif
532 convert_line(in_line, out_line);
533 /* bypass message delivery but be available for ^P recall */
534 putmsghistory(out_line, FALSE);
535 }
536 res = TRUE;
537
538 compagerdone:
539 if (text)
540 free((genericptr_t) text);
541 if (synopsis)
542 free((genericptr_t) synopsis);
543 if (fallback_msgid)
544 free((genericptr_t) fallback_msgid);
545 nhl_done(L);
546 return res;
547 }
548
549 void
com_pager(const char * msgid)550 com_pager(const char *msgid)
551 {
552 com_pager_core("common", msgid, TRUE);
553 }
554
555 void
qt_pager(const char * msgid)556 qt_pager(const char *msgid)
557 {
558 if (!com_pager_core(g.urole.filecode, msgid, FALSE))
559 com_pager_core("common", msgid, TRUE);
560 }
561
562 struct permonst *
qt_montype(void)563 qt_montype(void)
564 {
565 int qpm;
566
567 if (rn2(5)) {
568 qpm = g.urole.enemy1num;
569 if (qpm != NON_PM && rn2(5) && !(g.mvitals[qpm].mvflags & G_GENOD))
570 return &mons[qpm];
571 return mkclass(g.urole.enemy1sym, 0);
572 }
573 qpm = g.urole.enemy2num;
574 if (qpm != NON_PM && rn2(5) && !(g.mvitals[qpm].mvflags & G_GENOD))
575 return &mons[qpm];
576 return mkclass(g.urole.enemy2sym, 0);
577 }
578
579 /* special levels can include a custom arrival message; display it */
580 void
deliver_splev_message(void)581 deliver_splev_message(void)
582 {
583 char *str, *nl, in_line[BUFSZ], out_line[BUFSZ];
584
585 /* there's no provision for delivering via window instead of pline */
586 if (g.lev_message) {
587 /* lev_message can span multiple lines using embedded newline chars;
588 any segments too long to fit within in_line[] will be truncated */
589 for (str = g.lev_message; *str; str = nl + 1) {
590 /* copying will stop at newline if one is present */
591 copynchars(in_line, str, (int) (sizeof in_line) - 1);
592
593 convert_line(in_line, out_line);
594 pline("%s", out_line);
595
596 if ((nl = index(str, '\n')) == 0)
597 break; /* done if no newline */
598 }
599
600 free((genericptr_t) g.lev_message);
601 g.lev_message = NULL;
602 }
603 }
604
605 /*questpgr.c*/
606