Line data Source code
1 : /*
2 : * SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
3 : * SPDX-License-Identifier: BSD-2-Clause
4 : */
5 :
6 : #include "advent.h"
7 : #include <ctype.h>
8 : #include <editline/readline.h>
9 : #include <getopt.h>
10 : #include <signal.h>
11 : #include <stdbool.h>
12 : #include <stdio.h>
13 : #include <stdlib.h>
14 : #include <string.h>
15 : #include <unistd.h>
16 :
17 : #define DIM(a) (sizeof(a) / sizeof(a[0]))
18 :
19 : #if defined ADVENT_AUTOSAVE
20 : static FILE *autosave_fp;
21 : void autosave(void) {
22 : if (autosave_fp != NULL) {
23 : rewind(autosave_fp);
24 : savefile(autosave_fp);
25 : fflush(autosave_fp);
26 : }
27 : }
28 : #endif
29 :
30 : // LCOV_EXCL_START
31 : // exclude from coverage analysis because it requires interactivity to test
32 : static void sig_handler(int signo) {
33 : if (signo == SIGINT) {
34 : if (settings.logfp != NULL) {
35 : fflush(settings.logfp);
36 : }
37 : }
38 :
39 : #if defined ADVENT_AUTOSAVE
40 : if (signo == SIGHUP || signo == SIGTERM) {
41 : autosave();
42 : }
43 : #endif
44 : exit(EXIT_FAILURE);
45 : }
46 : // LCOV_EXCL_STOP
47 :
48 21739 : char *myreadline(const char *prompt) {
49 : /*
50 : * This function isn't required for gameplay, readline() straight
51 : * up would suffice for that. It's where we interpret command-line
52 : * logfiles for testing purposes.
53 : */
54 : /* Normal case - no script arguments */
55 21739 : if (settings.argc == 0) {
56 21729 : char *ln = readline(prompt);
57 21729 : if (ln == NULL) {
58 81 : fputs(prompt, stdout);
59 : }
60 21729 : return ln;
61 : }
62 :
63 10 : char *buf = malloc(LINESIZE + 1);
64 : for (;;) {
65 12 : if (settings.scriptfp == NULL || feof(settings.scriptfp)) {
66 3 : if (settings.optind >= settings.argc) {
67 1 : free(buf);
68 1 : return NULL;
69 : }
70 :
71 2 : char *next = settings.argv[settings.optind++];
72 :
73 3 : if (settings.scriptfp != NULL &&
74 1 : feof(settings.scriptfp)) {
75 1 : fclose(settings.scriptfp);
76 : }
77 2 : if (strcmp(next, "-") == 0) {
78 : settings.scriptfp = stdin; // LCOV_EXCL_LINE
79 : } else {
80 2 : settings.scriptfp = fopen(next, "r");
81 : }
82 : }
83 :
84 11 : if (isatty(fileno(settings.scriptfp))) {
85 : free(buf); // LCOV_EXCL_LINE
86 : return readline(prompt); // LCOV_EXCL_LINE
87 : } else {
88 11 : char *ln = fgets(buf, LINESIZE, settings.scriptfp);
89 11 : if (ln != NULL) {
90 9 : fputs(prompt, stdout);
91 9 : fputs(ln, stdout);
92 9 : return ln;
93 : }
94 : }
95 : }
96 :
97 : return NULL;
98 : }
99 :
100 : /* Check if this loc is eligible for any hints. If been here int
101 : * enough, display. Ignore "HINTS" < 4 (special stuff, see database
102 : * notes). */
103 20846 : static void checkhints(void) {
104 20846 : if (conditions[game.loc] >= game.conds) {
105 52953 : for (int hint = 0; hint < NHINTS; hint++) {
106 48885 : if (game.hints[hint].used) {
107 16 : continue;
108 : }
109 48869 : if (!CNDBIT(game.loc, hint + 1 + COND_HBASE)) {
110 43910 : game.hints[hint].lc = -1;
111 : }
112 48869 : ++game.hints[hint].lc;
113 : /* Come here if he's been int enough at required loc(s)
114 : * for some unused hint. */
115 48869 : if (game.hints[hint].lc >= hints[hint].turns) {
116 : int i;
117 :
118 890 : switch (hint) {
119 2 : case 0:
120 : /* cave */
121 2 : if (game.objects[GRATE].prop ==
122 2 : GRATE_CLOSED &&
123 2 : !HERE(KEYS)) {
124 1 : break;
125 : }
126 1 : game.hints[hint].lc = 0;
127 1 : return;
128 25 : case 1: /* bird */
129 25 : if (game.objects[BIRD].place ==
130 25 : game.loc &&
131 1 : TOTING(ROD) &&
132 1 : game.oldobj == BIRD) {
133 1 : break;
134 : }
135 24 : return;
136 33 : case 2: /* snake */
137 33 : if (HERE(SNAKE) && !HERE(BIRD)) {
138 1 : break;
139 : }
140 32 : game.hints[hint].lc = 0;
141 32 : return;
142 2 : case 3: /* maze */
143 2 : if (game.locs[game.loc].atloc ==
144 1 : NO_OBJECT &&
145 1 : game.locs[game.oldloc].atloc ==
146 1 : NO_OBJECT &&
147 1 : game.locs[game.oldlc2].atloc ==
148 1 : NO_OBJECT &&
149 1 : game.holdng > 1) {
150 1 : break;
151 : }
152 1 : game.hints[hint].lc = 0;
153 1 : return;
154 11 : case 4: /* dark */
155 11 : if (!PROP_IS_NOTFOUND(EMERALD) &&
156 11 : PROP_IS_NOTFOUND(PYRAMID)) {
157 1 : break;
158 : }
159 10 : game.hints[hint].lc = 0;
160 10 : return;
161 5 : case 5: /* witt */
162 5 : break;
163 32 : case 6: /* urn */
164 32 : if (game.dflag == 0) {
165 1 : break;
166 : }
167 31 : game.hints[hint].lc = 0;
168 31 : return;
169 2 : case 7: /* woods */
170 2 : if (game.locs[game.loc].atloc ==
171 1 : NO_OBJECT &&
172 1 : game.locs[game.oldloc].atloc ==
173 1 : NO_OBJECT &&
174 1 : game.locs[game.oldlc2].atloc ==
175 : NO_OBJECT) {
176 1 : break;
177 : }
178 1 : return;
179 4 : case 8: /* ogre */
180 4 : i = atdwrf(game.loc);
181 4 : if (i < 0) {
182 1 : game.hints[hint].lc = 0;
183 1 : return;
184 : }
185 3 : if (HERE(OGRE) && i == 0) {
186 1 : break;
187 : }
188 2 : return;
189 774 : case 9: /* jade */
190 774 : if (game.tally == 1 &&
191 22 : PROP_IS_STASHED_OR_UNSEEN(JADE)) {
192 1 : break;
193 : }
194 773 : game.hints[hint].lc = 0;
195 773 : return;
196 : default: // LCOV_EXCL_LINE
197 : // Should never happen
198 : BUG(HINT_NUMBER_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
199 : }
200 :
201 : /* Fall through to hint display */
202 14 : game.hints[hint].lc = 0;
203 14 : if (!yes_or_no(hints[hint].question,
204 : arbitrary_messages[NO_MESSAGE],
205 : arbitrary_messages[OK_MAN])) {
206 4 : return;
207 : }
208 10 : rspeak(HINT_COST, hints[hint].penalty,
209 10 : hints[hint].penalty);
210 20 : game.hints[hint].used =
211 30 : yes_or_no(arbitrary_messages[WANT_HINT],
212 10 : hints[hint].hint,
213 : arbitrary_messages[OK_MAN]);
214 10 : if (game.hints[hint].used &&
215 10 : game.limit > WARNTIME) {
216 18 : game.limit +=
217 9 : WARNTIME * hints[hint].penalty;
218 : }
219 : }
220 : }
221 : }
222 : }
223 :
224 1213 : static bool spotted_by_pirate(int i) {
225 1213 : if (i != PIRATE) {
226 924 : return false;
227 : }
228 :
229 : /* The pirate's spotted him. Pirate leaves him alone once we've
230 : * found chest. K counts if a treasure is here. If not, and
231 : * tally=1 for an unseen chest, let the pirate be spotted. Note
232 : * that game.objexts,place[CHEST] = LOC_NOWHERE might mean that he's
233 : * thrown it to the troll, but in that case he's seen the chest
234 : * PROP_IS_FOUND(CHEST) == true. */
235 289 : if (game.loc == game.chloc || !PROP_IS_NOTFOUND(CHEST)) {
236 123 : return true;
237 : }
238 166 : int snarfed = 0;
239 166 : bool movechest = false, robplayer = false;
240 11620 : for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
241 11454 : if (!objects[treasure].is_treasure) {
242 8134 : continue;
243 : }
244 : /* Pirate won't take pyramid from plover room or dark
245 : * room (too easy!). */
246 3320 : if (treasure == PYRAMID &&
247 166 : (game.loc == objects[PYRAMID].plac ||
248 166 : game.loc == objects[EMERALD].plac)) {
249 1 : continue;
250 : }
251 3319 : if (TOTING(treasure) || HERE(treasure)) {
252 64 : ++snarfed;
253 : }
254 3319 : if (TOTING(treasure)) {
255 59 : movechest = true;
256 59 : robplayer = true;
257 : }
258 : }
259 : /* Force chest placement before player finds last treasure */
260 166 : if (game.tally == 1 && snarfed == 0 &&
261 49 : game.objects[CHEST].place == LOC_NOWHERE && HERE(LAMP) &&
262 1 : game.objects[LAMP].prop == LAMP_BRIGHT) {
263 1 : rspeak(PIRATE_SPOTTED);
264 1 : movechest = true;
265 : }
266 : /* Do things in this order (chest move before robbery) so chest is
267 : * listed last at the maze location. */
268 166 : if (movechest) {
269 33 : move(CHEST, game.chloc);
270 33 : move(MESSAG, game.chloc2);
271 33 : game.dwarves[PIRATE].loc = game.chloc;
272 33 : game.dwarves[PIRATE].oldloc = game.chloc;
273 33 : game.dwarves[PIRATE].seen = false;
274 : } else {
275 : /* You might get a hint of the pirate's presence even if the
276 : * chest doesn't move... */
277 231 : if (game.dwarves[PIRATE].oldloc != game.dwarves[PIRATE].loc &&
278 98 : PCT(20)) {
279 13 : rspeak(PIRATE_RUSTLES);
280 : }
281 : }
282 166 : if (robplayer) {
283 32 : rspeak(PIRATE_POUNCES);
284 2240 : for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
285 2208 : if (!objects[treasure].is_treasure) {
286 1568 : continue;
287 : }
288 640 : if (!(treasure == PYRAMID &&
289 32 : (game.loc == objects[PYRAMID].plac ||
290 32 : game.loc == objects[EMERALD].plac))) {
291 640 : if (AT(treasure) &&
292 1 : game.objects[treasure].fixed == IS_FREE) {
293 1 : carry(treasure, game.loc);
294 : }
295 640 : if (TOTING(treasure)) {
296 60 : drop(treasure, game.chloc);
297 : }
298 : }
299 : }
300 : }
301 :
302 166 : return true;
303 : }
304 :
305 15643 : static bool dwarfmove(void) {
306 : /* Dwarves move. Return true if player survives, false if he dies. */
307 : int kk, stick, attack;
308 15643 : loc_t tk[21];
309 :
310 : /* Dwarf stuff. See earlier comments for description of
311 : * variables. Remember sixth dwarf is pirate and is thus
312 : * very different except for motion rules. */
313 :
314 : /* First off, don't let the dwarves follow him into a pit or a
315 : * wall. Activate the whole mess the first time he gets as far
316 : * as the Hall of Mists (what INDEEP() tests). If game.newloc
317 : * is forbidden to pirate (in particular, if it's beyond the
318 : * troll bridge), bypass dwarf stuff. That way pirate can't
319 : * steal return toll, and dwarves can't meet the bear. Also
320 : * means dwarves won't follow him into dead end in maze, but
321 : * c'est la vie. They'll wait for him outside the dead end. */
322 30397 : if (game.loc == LOC_NOWHERE || FORCED(game.loc) ||
323 14754 : CNDBIT(game.newloc, COND_NOARRR)) {
324 1278 : return true;
325 : }
326 :
327 : /* Dwarf activity level ratchets up */
328 14365 : if (game.dflag == 0) {
329 3463 : if (INDEEP(game.loc)) {
330 78 : game.dflag = 1;
331 : }
332 3463 : return true;
333 : }
334 :
335 : /* When we encounter the first dwarf, we kill 0, 1, or 2 of
336 : * the 5 dwarves. If any of the survivors is at game.loc,
337 : * replace him with the alternate. */
338 10902 : if (game.dflag == 1) {
339 2425 : if (!INDEEP(game.loc) ||
340 2201 : (PCT(95) && (!CNDBIT(game.loc, COND_NOBACK) || PCT(85)))) {
341 1228 : return true;
342 : }
343 65 : game.dflag = 2;
344 195 : for (int i = 1; i <= 2; i++) {
345 130 : int j = 1 + randrange(NDWARVES - 1);
346 130 : if (PCT(50)) {
347 75 : game.dwarves[j].loc = 0;
348 : }
349 : }
350 :
351 : /* Alternate initial loc for dwarf, in case one of them
352 : * starts out on top of the adventurer. */
353 390 : for (int i = 1; i <= NDWARVES - 1; i++) {
354 325 : if (game.dwarves[i].loc == game.loc) {
355 7 : game.dwarves[i].loc = DALTLC;
356 : }
357 325 : game.dwarves[i].oldloc = game.dwarves[i].loc;
358 : }
359 65 : rspeak(DWARF_RAN);
360 65 : drop(AXE, game.loc);
361 65 : return true;
362 : }
363 :
364 : /* Things are in full swing. Move each dwarf at random,
365 : * except if he's seen us he sticks with us. Dwarves stay
366 : * deep inside. If wandering at random, they don't back up
367 : * unless there's no alternative. If they don't have to
368 : * move, they attack. And, of course, dead dwarves don't do
369 : * much of anything. */
370 9609 : game.dtotal = 0;
371 9609 : attack = 0;
372 9609 : stick = 0;
373 67263 : for (int i = 1; i <= NDWARVES; i++) {
374 57654 : if (game.dwarves[i].loc == 0) {
375 28335 : continue;
376 : }
377 : /* Fill tk array with all the places this dwarf might go. */
378 29319 : unsigned int j = 1;
379 29319 : kk = tkey[game.dwarves[i].loc];
380 29319 : if (kk != 0) {
381 : do {
382 150774 : enum desttype_t desttype = travel[kk].desttype;
383 150774 : game.newloc = travel[kk].destval;
384 : /* Have we avoided a dwarf encounter? */
385 150774 : if (desttype != dest_goto) {
386 12047 : continue;
387 138727 : } else if (!INDEEP(game.newloc)) {
388 2547 : continue;
389 136180 : } else if (game.newloc ==
390 136180 : game.dwarves[i].oldloc) {
391 39428 : continue;
392 96752 : } else if (j > 1 && game.newloc == tk[j - 1]) {
393 16819 : continue;
394 79933 : } else if (j >= DIM(tk) - 1) {
395 : /* This can't actually happen. */
396 : continue; // LCOV_EXCL_LINE
397 79933 : } else if (game.newloc == game.dwarves[i].loc) {
398 4213 : continue;
399 75720 : } else if (FORCED(game.newloc)) {
400 8723 : continue;
401 87491 : } else if (i == PIRATE &&
402 20494 : CNDBIT(game.newloc, COND_NOARRR)) {
403 3176 : continue;
404 63821 : } else if (travel[kk].nodwarves) {
405 401 : continue;
406 : }
407 63420 : tk[j++] = game.newloc;
408 150774 : } while (!travel[kk++].stop);
409 : }
410 29319 : tk[j] = game.dwarves[i].oldloc;
411 29319 : if (j >= 2) {
412 25494 : --j;
413 : }
414 29319 : j = 1 + randrange(j);
415 29319 : game.dwarves[i].oldloc = game.dwarves[i].loc;
416 29319 : game.dwarves[i].loc = tk[j];
417 29319 : game.dwarves[i].seen =
418 57730 : (game.dwarves[i].seen && INDEEP(game.loc)) ||
419 28411 : (game.dwarves[i].loc == game.loc ||
420 28300 : game.dwarves[i].oldloc == game.loc);
421 29319 : if (!game.dwarves[i].seen) {
422 28106 : continue;
423 : }
424 1213 : game.dwarves[i].loc = game.loc;
425 1213 : if (spotted_by_pirate(i)) {
426 289 : continue;
427 : }
428 : /* This threatening little dwarf is in the room with him! */
429 924 : ++game.dtotal;
430 924 : if (game.dwarves[i].oldloc == game.dwarves[i].loc) {
431 191 : ++attack;
432 191 : if (game.knfloc >= LOC_NOWHERE) {
433 191 : game.knfloc = game.loc;
434 : }
435 191 : if (randrange(1000) < 95 * (game.dflag - 2)) {
436 3 : ++stick;
437 : }
438 : }
439 : }
440 :
441 : /* Now we know what's happening. Let's tell the poor sucker about it.
442 : */
443 9609 : if (game.dtotal == 0) {
444 8724 : return true;
445 : }
446 885 : rspeak(game.dtotal == 1 ? DWARF_SINGLE : DWARF_PACK, game.dtotal);
447 885 : if (attack == 0) {
448 700 : return true;
449 : }
450 185 : if (game.dflag == 2) {
451 57 : game.dflag = 3;
452 : }
453 185 : if (attack > 1) {
454 6 : rspeak(THROWN_KNIVES, attack);
455 12 : rspeak(stick > 1 ? MULTIPLE_HITS
456 6 : : (stick == 1 ? ONE_HIT : NONE_HIT),
457 : stick);
458 : } else {
459 179 : rspeak(KNIFE_THROWN);
460 179 : rspeak(stick ? GETS_YOU : MISSES_YOU);
461 : }
462 185 : if (stick == 0) {
463 182 : return true;
464 : }
465 3 : game.oldlc2 = game.loc;
466 3 : return false;
467 : }
468 :
469 : /* "You're dead, Jim."
470 : *
471 : * If the current loc is zero, it means the clown got himself killed.
472 : * We'll allow this maxdie times. NDEATHS is automatically set based
473 : * on the number of snide messages available. Each death results in
474 : * a message (obituaries[n]) which offers reincarnation; if accepted,
475 : * this results in message obituaries[0], obituaries[2], etc. The
476 : * last time, if he wants another chance, he gets a snide remark as
477 : * we exit. When reincarnated, all objects being carried get dropped
478 : * at game.oldlc2 (presumably the last place prior to being killed)
479 : * without change of props. The loop runs backwards to assure that
480 : * the bird is dropped before the cage. (This kluge could be changed
481 : * once we're sure all references to bird and cage are done by
482 : * keywords.) The lamp is a special case (it wouldn't do to leave it
483 : * in the cave). It is turned off and left outside the building (only
484 : * if he was carrying it, of course). He himself is left inside the
485 : * building (and heaven help him if he tries to xyzzy back into the
486 : * cave without the lamp!). game.oldloc is zapped so he can't just
487 : * "retreat". */
488 22 : static void croak(void) {
489 : /* Okay, he's dead. Let's get on with it. */
490 22 : const char *query = obituaries[game.numdie].query;
491 22 : const char *yes_response = obituaries[game.numdie].yes_response;
492 :
493 22 : ++game.numdie;
494 :
495 22 : if (game.closng) {
496 : /* He died during closing time. No resurrection. Tally up a
497 : * death and exit. */
498 1 : rspeak(DEATH_CLOSING);
499 1 : terminate(endgame);
500 21 : } else if (!yes_or_no(query, yes_response,
501 13 : arbitrary_messages[OK_MAN]) ||
502 13 : game.numdie == NDEATHS) {
503 : /* Player is asked if he wants to try again. If not, or if
504 : * he's already used all of his lives, we end the game */
505 11 : terminate(endgame);
506 : } else {
507 : /* If player wishes to continue, we empty the liquids in the
508 : * user's inventory, turn off the lamp, and drop all items
509 : * where he died. */
510 10 : game.objects[WATER].place = game.objects[OIL].place =
511 : LOC_NOWHERE;
512 10 : if (TOTING(LAMP)) {
513 4 : game.objects[LAMP].prop = LAMP_DARK;
514 : }
515 700 : for (int j = 1; j <= NOBJECTS; j++) {
516 690 : int i = NOBJECTS + 1 - j;
517 690 : if (TOTING(i)) {
518 : /* Always leave lamp where it's accessible
519 : * aboveground */
520 14 : drop(i, (i == LAMP) ? LOC_START : game.oldlc2);
521 : }
522 : }
523 10 : game.oldloc = game.loc = game.newloc = LOC_BUILDING;
524 : }
525 10 : }
526 :
527 16028 : static void describe_location(void) {
528 : /* Describe the location to the user */
529 16028 : const char *msg = locations[game.loc].description.small;
530 :
531 16028 : if (MOD(game.locs[game.loc].abbrev, game.abbnum) == 0 ||
532 : msg == NO_MESSAGE) {
533 7828 : msg = locations[game.loc].description.big;
534 : }
535 :
536 16028 : if (!FORCED(game.loc) && DARK(game.loc)) {
537 406 : msg = arbitrary_messages[PITCH_DARK];
538 : }
539 :
540 16028 : if (TOTING(BEAR)) {
541 234 : rspeak(TAME_BEAR);
542 : }
543 :
544 16028 : speak(msg);
545 :
546 16028 : if (game.loc == LOC_Y2 && PCT(25) && !game.closng) {
547 311 : rspeak(SAYS_PLUGH);
548 : }
549 16028 : }
550 :
551 1653 : static bool traveleq(int a, int b) {
552 : /* Are two travel entries equal for purposes of skip after failed
553 : * condition? */
554 1653 : return (travel[a].condtype == travel[b].condtype) &&
555 732 : (travel[a].condarg1 == travel[b].condarg1) &&
556 656 : (travel[a].condarg2 == travel[b].condarg2) &&
557 3039 : (travel[a].desttype == travel[b].desttype) &&
558 654 : (travel[a].destval == travel[b].destval);
559 : }
560 :
561 : /* Given the current location in "game.loc", and a motion verb number in
562 : * "motion", put the new location in "game.newloc". The current loc is saved
563 : * in "game.oldloc" in case he wants to retreat. The current
564 : * game.oldloc is saved in game.oldlc2, in case he dies. (if he
565 : * does, game.newloc will be limbo, and game.oldloc will be what killed
566 : * him, so we need game.oldlc2, which is the last place he was
567 : * safe.) */
568 15460 : static void playermove(int motion) {
569 15460 : int scratchloc, travel_entry = tkey[game.loc];
570 15460 : game.newloc = game.loc;
571 15460 : if (travel_entry == 0) {
572 : BUG(LOCATION_HAS_NO_TRAVEL_ENTRIES); // LCOV_EXCL_LINE
573 : }
574 15460 : if (motion == NUL) {
575 306 : return;
576 15154 : } else if (motion == BACK) {
577 : /* Handle "go back". Look for verb which goes from game.loc to
578 : * game.oldloc, or to game.oldlc2 If game.oldloc has
579 : * forced-motion. te_tmp saves entry -> forced loc -> previous
580 : * loc. */
581 9 : motion = game.oldloc;
582 9 : if (FORCED(motion)) {
583 1 : motion = game.oldlc2;
584 : }
585 9 : game.oldlc2 = game.oldloc;
586 9 : game.oldloc = game.loc;
587 9 : if (CNDBIT(game.loc, COND_NOBACK)) {
588 2 : rspeak(TWIST_TURN);
589 2 : return;
590 : }
591 7 : if (motion == game.loc) {
592 2 : rspeak(FORGOT_PATH);
593 2 : return;
594 : }
595 :
596 5 : int te_tmp = 0;
597 44 : for (;;) {
598 49 : enum desttype_t desttype =
599 : travel[travel_entry].desttype;
600 49 : scratchloc = travel[travel_entry].destval;
601 49 : if (desttype != dest_goto || scratchloc != motion) {
602 47 : if (desttype == dest_goto) {
603 47 : if (FORCED(scratchloc) &&
604 1 : travel[tkey[scratchloc]].destval ==
605 : motion) {
606 1 : te_tmp = travel_entry;
607 : }
608 : }
609 47 : if (!travel[travel_entry].stop) {
610 44 : ++travel_entry; /* go to next travel
611 : entry for this
612 : location */
613 44 : continue;
614 : }
615 : /* we've reached the end of travel entries for
616 : * game.loc */
617 3 : travel_entry = te_tmp;
618 3 : if (travel_entry == 0) {
619 2 : rspeak(NOT_CONNECTED);
620 2 : return;
621 : }
622 : }
623 :
624 3 : motion = travel[travel_entry].motion;
625 3 : travel_entry = tkey[game.loc];
626 3 : break; /* fall through to ordinary travel */
627 : }
628 15145 : } else if (motion == LOOK) {
629 : /* Look. Can't give more detail. Pretend it wasn't dark
630 : * (though it may now be dark) so he won't fall into a
631 : * pit while staring into the gloom. */
632 125 : if (game.detail < 3) {
633 49 : rspeak(NO_MORE_DETAIL);
634 : }
635 125 : ++game.detail;
636 125 : game.wzdark = false;
637 125 : game.locs[game.loc].abbrev = 0;
638 125 : return;
639 15020 : } else if (motion == CAVE) {
640 : /* Cave. Different messages depending on whether above ground.
641 : */
642 3 : rspeak((OUTSID(game.loc) && game.loc != LOC_GRATE)
643 : ? FOLLOW_STREAM
644 : : NEED_DETAIL);
645 3 : return;
646 : } else {
647 : /* none of the specials */
648 15017 : game.oldlc2 = game.oldloc;
649 15017 : game.oldloc = game.loc;
650 : }
651 :
652 : /* Look for a way to fulfil the motion verb passed in - travel_entry
653 : * indexes the beginning of the motion entries for here (game.loc). */
654 : for (;;) {
655 54348 : if ((travel[travel_entry].motion == HERE) ||
656 53465 : travel[travel_entry].motion == motion) {
657 : break;
658 : }
659 39888 : if (travel[travel_entry].stop) {
660 : /* Couldn't find an entry matching the motion word
661 : * passed in. Various messages depending on word given.
662 : */
663 560 : switch (motion) {
664 543 : case EAST:
665 : case WEST:
666 : case SOUTH:
667 : case NORTH:
668 : case NE:
669 : case NW:
670 : case SW:
671 : case SE:
672 : case UP:
673 : case DOWN:
674 543 : rspeak(BAD_DIRECTION);
675 543 : break;
676 1 : case FORWARD:
677 : case LEFT:
678 : case RIGHT:
679 1 : rspeak(UNSURE_FACING);
680 1 : break;
681 2 : case OUTSIDE:
682 : case INSIDE:
683 2 : rspeak(NO_INOUT_HERE);
684 2 : break;
685 7 : case XYZZY:
686 : case PLUGH:
687 7 : rspeak(NOTHING_HAPPENS);
688 7 : break;
689 1 : case CRAWL:
690 1 : rspeak(WHICH_WAY);
691 1 : break;
692 6 : default:
693 6 : rspeak(CANT_APPLY);
694 : }
695 560 : return;
696 : }
697 39328 : ++travel_entry;
698 : }
699 :
700 : /* (ESR) We've found a destination that goes with the motion verb.
701 : * Next we need to check any conditional(s) on this destination, and
702 : * possibly on following entries. */
703 : do {
704 1 : for (;;) { /* L12 loop */
705 1007 : for (;;) {
706 15468 : enum condtype_t condtype =
707 15468 : travel[travel_entry].condtype;
708 15468 : int condarg1 = travel[travel_entry].condarg1;
709 15468 : int condarg2 = travel[travel_entry].condarg2;
710 15468 : if (condtype < cond_not) {
711 : /* YAML N and [pct N] conditionals */
712 14485 : if (condtype == cond_goto ||
713 : condtype == cond_pct) {
714 14308 : if (condarg1 == 0 ||
715 388 : PCT(condarg1)) {
716 : break;
717 : }
718 : /* else fall through */
719 : }
720 : /* YAML [with OBJ] clause */
721 565 : else if (TOTING(condarg1) ||
722 170 : (condtype == cond_with &&
723 170 : AT(condarg1))) {
724 : break;
725 : }
726 : /* else fall through to check [not OBJ
727 : * STATE] */
728 983 : } else if (game.objects[condarg1].prop !=
729 : condarg2) {
730 523 : break;
731 : }
732 :
733 : /* We arrive here on conditional failure.
734 : * Skip to next non-matching destination */
735 1007 : int te_tmp = travel_entry;
736 : do {
737 1652 : if (travel[te_tmp].stop) {
738 : BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
739 : }
740 1652 : ++te_tmp;
741 1652 : } while (traveleq(travel_entry, te_tmp));
742 1007 : travel_entry = te_tmp;
743 : }
744 :
745 : /* Found an eligible rule, now execute it */
746 14461 : enum desttype_t desttype =
747 : travel[travel_entry].desttype;
748 14461 : game.newloc = travel[travel_entry].destval;
749 14461 : if (desttype == dest_goto) {
750 14021 : return;
751 : }
752 :
753 440 : if (desttype == dest_speak) {
754 : /* Execute a speak rule */
755 258 : rspeak(game.newloc);
756 258 : game.newloc = game.loc;
757 258 : return;
758 : } else {
759 182 : switch (game.newloc) {
760 81 : case 1:
761 : /* Special travel 1. Plover-alcove
762 : * passage. Can carry only emerald.
763 : * Note: travel table must include
764 : * "useless" entries going through
765 : * passage, which can never be used for
766 : * actual motion, but can be spotted by
767 : * "go back". */
768 162 : game.newloc = (game.loc == LOC_PLOVER)
769 : ? LOC_ALCOVE
770 81 : : LOC_PLOVER;
771 81 : if (game.holdng > 1 ||
772 80 : (game.holdng == 1 &&
773 40 : !TOTING(EMERALD))) {
774 2 : game.newloc = game.loc;
775 2 : rspeak(MUST_DROP);
776 : }
777 81 : return;
778 1 : case 2:
779 : /* Special travel 2. Plover transport.
780 : * Drop the emerald (only use special
781 : * travel if toting it), so he's forced
782 : * to use the plover-passage to get it
783 : * out. Having dropped it, go back and
784 : * pretend he wasn't carrying it after
785 : * all. */
786 1 : drop(EMERALD, game.loc);
787 : {
788 1 : int te_tmp = travel_entry;
789 : do {
790 1 : if (travel[te_tmp]
791 1 : .stop) {
792 : BUG(CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION); // LCOV_EXCL_LINE
793 : }
794 1 : ++te_tmp;
795 1 : } while (traveleq(travel_entry,
796 : te_tmp));
797 1 : travel_entry = te_tmp;
798 : }
799 1 : continue; /* goto L12 */
800 100 : case 3:
801 : /* Special travel 3. Troll bridge. Must
802 : * be done only as special motion so
803 : * that dwarves won't wander across and
804 : * encounter the bear. (They won't
805 : * follow the player there because that
806 : * region is forbidden to the pirate.)
807 : * If game.prop[TROLL]=TROLL_PAIDONCE,
808 : * he's crossed since paying, so step
809 : * out and block him. (standard travel
810 : * entries check for
811 : * game.prop[TROLL]=TROLL_UNPAID.)
812 : * Special stuff for bear. */
813 100 : if (game.objects[TROLL].prop ==
814 : TROLL_PAIDONCE) {
815 33 : pspeak(TROLL, look, true,
816 : TROLL_PAIDONCE);
817 33 : game.objects[TROLL].prop =
818 : TROLL_UNPAID;
819 33 : DESTROY(TROLL2);
820 33 : move(TROLL2 + NOBJECTS,
821 : IS_FREE);
822 33 : move(TROLL,
823 33 : objects[TROLL].plac);
824 33 : move(TROLL + NOBJECTS,
825 33 : objects[TROLL].fixd);
826 33 : juggle(CHASM);
827 33 : game.newloc = game.loc;
828 33 : return;
829 : } else {
830 134 : game.newloc =
831 67 : objects[TROLL].plac +
832 67 : objects[TROLL].fixd -
833 67 : game.loc;
834 67 : if (game.objects[TROLL].prop ==
835 : TROLL_UNPAID) {
836 : game.objects[TROLL]
837 35 : .prop =
838 : TROLL_PAIDONCE;
839 : }
840 67 : if (!TOTING(BEAR)) {
841 65 : return;
842 : }
843 2 : state_change(CHASM,
844 : BRIDGE_WRECKED);
845 2 : game.objects[TROLL].prop =
846 : TROLL_GONE;
847 2 : drop(BEAR, game.newloc);
848 2 : game.objects[BEAR].fixed =
849 : IS_FIXED;
850 2 : game.objects[BEAR].prop =
851 : BEAR_DEAD;
852 2 : game.oldlc2 = game.newloc;
853 2 : croak();
854 1 : return;
855 : }
856 : default: // LCOV_EXCL_LINE
857 : BUG(SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
858 : }
859 : }
860 : break; /* Leave L12 loop */
861 : }
862 : } while (false);
863 : }
864 :
865 20732 : static void lampcheck(void) {
866 : /* Check game limit and lamp timers */
867 20732 : if (game.objects[LAMP].prop == LAMP_BRIGHT) {
868 14008 : --game.limit;
869 : }
870 :
871 : /* Another way we can force an end to things is by having the
872 : * lamp give out. When it gets close, we come here to warn him.
873 : * First following arm checks if the lamp and fresh batteries are
874 : * here, in which case we replace the batteries and continue.
875 : * Second is for other cases of lamp dying. Even after it goes
876 : * out, he can explore outside for a while if desired. */
877 20732 : if (game.limit <= WARNTIME) {
878 1071 : if (HERE(BATTERY) &&
879 2 : game.objects[BATTERY].prop == FRESH_BATTERIES &&
880 2 : HERE(LAMP)) {
881 2 : rspeak(REPLACE_BATTERIES);
882 2 : game.objects[BATTERY].prop = DEAD_BATTERIES;
883 : #ifdef __unused__
884 : /* This code from the original game seems to have been
885 : * faulty. No tests ever passed the guard, and with the
886 : * guard removed the game hangs when the lamp limit is
887 : * reached.
888 : */
889 : if (TOTING(BATTERY)) {
890 : drop(BATTERY, game.loc);
891 : }
892 : #endif
893 2 : game.limit += BATTERYLIFE;
894 2 : game.lmwarn = false;
895 1069 : } else if (!game.lmwarn && HERE(LAMP)) {
896 21 : game.lmwarn = true;
897 21 : if (game.objects[BATTERY].prop == DEAD_BATTERIES) {
898 1 : rspeak(MISSING_BATTERIES);
899 20 : } else if (game.objects[BATTERY].place == LOC_NOWHERE) {
900 19 : rspeak(LAMP_DIM);
901 : } else {
902 1 : rspeak(GET_BATTERIES);
903 : }
904 : }
905 : }
906 20732 : if (game.limit == 0) {
907 15 : game.limit = -1;
908 15 : game.objects[LAMP].prop = LAMP_DARK;
909 15 : if (HERE(LAMP)) {
910 15 : rspeak(LAMP_OUT);
911 : }
912 : }
913 20732 : }
914 :
915 : /* Handle the closing of the cave. The cave closes "clock1" turns
916 : * after the last treasure has been located (including the pirate's
917 : * chest, which may of course never show up). Note that the
918 : * treasures need not have been taken yet, just located. Hence
919 : * clock1 must be large enough to get out of the cave (it only ticks
920 : * while inside the cave). When it hits zero, we start closing the
921 : * cave, and then sit back and wait for him to try to get out. If he
922 : * doesn't within clock2 turns, we close the cave; if he does try, we
923 : * assume he panics, and give him a few additional turns to get
924 : * frantic before we close. When clock2 hits zero, we transport him
925 : * into the final puzzle. Note that the puzzle depends upon all
926 : * sorts of random things. For instance, there must be no water or
927 : * oil, since there are beanstalks which we don't want to be able to
928 : * water, since the code can't handle it. Also, we can have no keys,
929 : * since there is a grate (having moved the fixed object!) there
930 : * separating him from all the treasures. Most of these problems
931 : * arise from the use of negative prop numbers to suppress the object
932 : * descriptions until he's actually moved the objects. */
933 20764 : static bool closecheck(void) {
934 : /* If a turn threshold has been met, apply penalties and tell
935 : * the player about it. */
936 103820 : for (int i = 0; i < NTHRESHOLDS; ++i) {
937 83056 : if (game.turns == turn_thresholds[i].threshold + 1) {
938 26 : game.trnluz += turn_thresholds[i].point_loss;
939 26 : speak(turn_thresholds[i].message);
940 : }
941 : }
942 :
943 : /* Don't tick game.clock1 unless well into cave (and not at Y2). */
944 20764 : if (game.tally == 0 && INDEEP(game.loc) && game.loc != LOC_Y2) {
945 1288 : --game.clock1;
946 : }
947 :
948 : /* When the first warning comes, we lock the grate, destroy
949 : * the bridge, kill all the dwarves (and the pirate), remove
950 : * the troll and bear (unless dead), and set "closng" to
951 : * true. Leave the dragon; too much trouble to move it.
952 : * from now until clock2 runs out, he cannot unlock the
953 : * grate, move to any location outside the cave, or create
954 : * the bridge. Nor can he be resurrected if he dies. Note
955 : * that the snake is already gone, since he got to the
956 : * treasure accessible only via the hall of the mountain
957 : * king. Also, he's been in giant room (to get eggs), so we
958 : * can refer to it. Also also, he's gotten the pearl, so we
959 : * know the bivalve is an oyster. *And*, the dwarves must
960 : * have been activated, since we've found chest. */
961 20764 : if (game.clock1 == 0) {
962 18 : game.objects[GRATE].prop = GRATE_CLOSED;
963 18 : game.objects[FISSURE].prop = UNBRIDGED;
964 126 : for (int i = 1; i <= NDWARVES; i++) {
965 108 : game.dwarves[i].seen = false;
966 108 : game.dwarves[i].loc = LOC_NOWHERE;
967 : }
968 18 : DESTROY(TROLL);
969 18 : move(TROLL + NOBJECTS, IS_FREE);
970 18 : move(TROLL2, objects[TROLL].plac);
971 18 : move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
972 18 : juggle(CHASM);
973 18 : if (game.objects[BEAR].prop != BEAR_DEAD) {
974 18 : DESTROY(BEAR);
975 : }
976 18 : game.objects[CHAIN].prop = CHAIN_HEAP;
977 18 : game.objects[CHAIN].fixed = IS_FREE;
978 18 : game.objects[AXE].prop = AXE_HERE;
979 18 : game.objects[AXE].fixed = IS_FREE;
980 18 : rspeak(CAVE_CLOSING);
981 18 : game.clock1 = -1;
982 18 : game.closng = true;
983 18 : return game.closed;
984 20746 : } else if (game.clock1 < 0) {
985 722 : --game.clock2;
986 : }
987 20746 : if (game.clock2 == 0) {
988 : /* Once he's panicked, and clock2 has run out, we come here
989 : * to set up the storage room. The room has two locs,
990 : * hardwired as LOC_NE and LOC_SW. At the ne end, we
991 : * place empty bottles, a nursery of plants, a bed of
992 : * oysters, a pile of lamps, rods with stars, sleeping
993 : * dwarves, and him. At the sw end we place grate over
994 : * treasures, snake pit, covey of caged birds, more rods, and
995 : * pillows. A mirror stretches across one wall. Many of the
996 : * objects come from known locations and/or states (e.g. the
997 : * snake is known to have been destroyed and needn't be
998 : * carried away from its old "place"), making the various
999 : * objects be handled differently. We also drop all other
1000 : * objects he might be carrying (lest he has some which
1001 : * could cause trouble, such as the keys). We describe the
1002 : * flash of light and trundle back. */
1003 14 : put(BOTTLE, LOC_NE, EMPTY_BOTTLE);
1004 14 : put(PLANT, LOC_NE, PLANT_THIRSTY);
1005 14 : put(OYSTER, LOC_NE, STATE_FOUND);
1006 14 : put(LAMP, LOC_NE, LAMP_DARK);
1007 14 : put(ROD, LOC_NE, STATE_FOUND);
1008 14 : put(DWARF, LOC_NE, STATE_FOUND);
1009 14 : game.loc = LOC_NE;
1010 14 : game.oldloc = LOC_NE;
1011 14 : game.newloc = LOC_NE;
1012 : /* Leave the grate with normal (non-negative) property.
1013 : * Reuse sign. */
1014 14 : move(GRATE, LOC_SW);
1015 14 : move(SIGN, LOC_SW);
1016 14 : game.objects[SIGN].prop = ENDGAME_SIGN;
1017 14 : put(SNAKE, LOC_SW, SNAKE_CHASED);
1018 14 : put(BIRD, LOC_SW, BIRD_CAGED);
1019 14 : put(CAGE, LOC_SW, STATE_FOUND);
1020 14 : put(ROD2, LOC_SW, STATE_FOUND);
1021 14 : put(PILLOW, LOC_SW, STATE_FOUND);
1022 :
1023 14 : put(MIRROR, LOC_NE, STATE_FOUND);
1024 14 : game.objects[MIRROR].fixed = LOC_SW;
1025 :
1026 980 : for (int i = 1; i <= NOBJECTS; i++) {
1027 966 : if (TOTING(i)) {
1028 13 : DESTROY(i);
1029 : }
1030 : }
1031 :
1032 14 : rspeak(CAVE_CLOSED);
1033 14 : game.closed = true;
1034 14 : return game.closed;
1035 : }
1036 :
1037 20732 : lampcheck();
1038 20732 : return false;
1039 : }
1040 :
1041 15145 : static void listobjects(void) {
1042 : /* Print out descriptions of objects at this location. If
1043 : * not closing and property value is negative, tally off
1044 : * another treasure. Rug is special case; once seen, its
1045 : * game.prop is RUG_DRAGON (dragon on it) till dragon is killed.
1046 : * Similarly for chain; game.prop is initially CHAINING_BEAR (locked to
1047 : * bear). These hacks are because game.prop=0 is needed to
1048 : * get full score. */
1049 15145 : if (!DARK(game.loc)) {
1050 14739 : ++game.locs[game.loc].abbrev;
1051 30214 : for (int i = game.locs[game.loc].atloc; i != 0;
1052 15475 : i = game.link[i]) {
1053 15475 : obj_t obj = i;
1054 15475 : if (obj > NOBJECTS) {
1055 1276 : obj = obj - NOBJECTS;
1056 : }
1057 15475 : if (obj == STEPS && TOTING(NUGGET)) {
1058 45 : continue;
1059 : }
1060 : /* (ESR) Warning: it looks like you could get away with
1061 : * running this code only on objects with the treasure
1062 : * property set. Nope. There is mystery here.
1063 : */
1064 15430 : if (PROP_IS_STASHED_OR_UNSEEN(obj)) {
1065 1050 : if (game.closed) {
1066 224 : continue;
1067 : }
1068 826 : PROP_SET_FOUND(obj);
1069 826 : if (obj == RUG) {
1070 61 : game.objects[RUG].prop = RUG_DRAGON;
1071 : }
1072 826 : if (obj == CHAIN) {
1073 35 : game.objects[CHAIN].prop =
1074 : CHAINING_BEAR;
1075 : }
1076 826 : if (obj == EGGS) {
1077 45 : game.seenbigwords = true;
1078 : }
1079 826 : --game.tally;
1080 : /* Note: There used to be a test here to see
1081 : * whether the player had blown it so badly that
1082 : * he could never ever see the remaining
1083 : * treasures, and if so the lamp was zapped to
1084 : * 35 turns. But the tests were too
1085 : * simple-minded; things like killing the bird
1086 : * before the snake was gone (can never see
1087 : * jewelry), and doing it "right" was hopeless.
1088 : * E.G., could cross troll bridge several times,
1089 : * using up all available treasures, breaking
1090 : * vase, using coins to buy batteries, etc., and
1091 : * eventually never be able to get across again.
1092 : * If bottle were left on far side, could then
1093 : * never get eggs or trident, and the effects
1094 : * propagate. So the whole thing was flushed.
1095 : * anyone who makes such a gross blunder isn't
1096 : * likely to find everything else anyway (so
1097 : * goes the rationalisation). */
1098 : }
1099 15206 : int kk = game.objects[obj].prop;
1100 15206 : if (obj == STEPS) {
1101 510 : kk = (game.loc == game.objects[STEPS].fixed)
1102 : ? STEPS_UP
1103 510 : : STEPS_DOWN;
1104 : }
1105 15206 : pspeak(obj, look, true, kk);
1106 : }
1107 : }
1108 15145 : }
1109 :
1110 : /* Pre-processes a command input to see if we need to tease out a few specific
1111 : * cases:
1112 : * - "enter water" or "enter stream":
1113 : * weird specific case that gets the user wet, and then kicks us back to get
1114 : * another command
1115 : * - <object> <verb>:
1116 : * Irregular form of input, but should be allowed. We switch back to <verb>
1117 : * <object> form for further processing.
1118 : * - "grate":
1119 : * If in location with grate, we move to that grate. If we're in a number of
1120 : * other places, we move to the entrance.
1121 : * - "water plant", "oil plant", "water door", "oil door":
1122 : * Change to "pour water" or "pour oil" based on context
1123 : * - "cage bird":
1124 : * If bird is present, we change to "carry bird"
1125 : *
1126 : * Returns true if pre-processing is complete, and we're ready to move to the
1127 : * primary command processing, false otherwise. */
1128 20766 : static bool preprocess_command(command_t *command) {
1129 20766 : if (command->word[0].type == MOTION && command->word[0].id == ENTER &&
1130 6 : (command->word[1].id == STREAM || command->word[1].id == WATER)) {
1131 2 : if (LIQLOC(game.loc) == WATER) {
1132 1 : rspeak(FEET_WET);
1133 : } else {
1134 1 : rspeak(WHERE_QUERY);
1135 : }
1136 : } else {
1137 20764 : if (command->word[0].type == OBJECT) {
1138 : /* From OV to VO form */
1139 158 : if (command->word[1].type == ACTION) {
1140 1 : command_word_t stage = command->word[0];
1141 1 : command->word[0] = command->word[1];
1142 1 : command->word[1] = stage;
1143 : }
1144 :
1145 158 : if (command->word[0].id == GRATE) {
1146 2 : command->word[0].type = MOTION;
1147 2 : if (game.loc == LOC_START ||
1148 2 : game.loc == LOC_VALLEY ||
1149 1 : game.loc == LOC_SLIT) {
1150 1 : command->word[0].id = DEPRESSION;
1151 : }
1152 2 : if (game.loc == LOC_COBBLE ||
1153 2 : game.loc == LOC_DEBRIS ||
1154 2 : game.loc == LOC_AWKWARD ||
1155 2 : game.loc == LOC_BIRDCHAMBER ||
1156 1 : game.loc == LOC_PITTOP) {
1157 1 : command->word[0].id = ENTRANCE;
1158 : }
1159 : }
1160 158 : if ((command->word[0].id == WATER ||
1161 61 : command->word[0].id == OIL) &&
1162 143 : (command->word[1].id == PLANT ||
1163 44 : command->word[1].id == DOOR)) {
1164 143 : if (AT(command->word[1].id)) {
1165 142 : command->word[1] = command->word[0];
1166 142 : command->word[0].id = POUR;
1167 142 : command->word[0].type = ACTION;
1168 142 : strncpy(command->word[0].raw, "pour",
1169 : LINESIZE - 1);
1170 : }
1171 : }
1172 158 : if (command->word[0].id == CAGE &&
1173 6 : command->word[1].id == BIRD && HERE(CAGE) &&
1174 6 : HERE(BIRD)) {
1175 6 : command->word[0].id = CARRY;
1176 6 : command->word[0].type = ACTION;
1177 : }
1178 : }
1179 :
1180 : /* If no word type is given for the first word, we assume it's a
1181 : * motion. */
1182 20764 : if (command->word[0].type == NO_WORD_TYPE) {
1183 13 : command->word[0].type = MOTION;
1184 : }
1185 :
1186 20764 : command->state = PREPROCESSED;
1187 20764 : return true;
1188 : }
1189 2 : return false;
1190 : }
1191 :
1192 15643 : static bool do_move(void) {
1193 : /* Actually execute the move to the new location and dwarf movement */
1194 : /* Can't leave cave once it's closing (except by main office). */
1195 15643 : if (OUTSID(game.newloc) && game.newloc != 0 && game.closng) {
1196 4 : rspeak(EXIT_CLOSED);
1197 4 : game.newloc = game.loc;
1198 4 : if (!game.panic) {
1199 4 : game.clock2 = PANICTIME;
1200 : }
1201 4 : game.panic = true;
1202 : }
1203 :
1204 : /* See if a dwarf has seen him and has come from where he
1205 : * wants to go. If so, the dwarf's blocking his way. If
1206 : * coming from place forbidden to pirate (dwarves rooted in
1207 : * place) let him get out (and attacked). */
1208 15643 : if (game.newloc != game.loc && !FORCED(game.loc) &&
1209 13292 : !CNDBIT(game.loc, COND_NOARRR)) {
1210 77635 : for (size_t i = 1; i <= NDWARVES - 1; i++) {
1211 64698 : if (game.dwarves[i].oldloc == game.newloc &&
1212 416 : game.dwarves[i].seen) {
1213 7 : game.newloc = game.loc;
1214 7 : rspeak(DWARF_BLOCK);
1215 7 : break;
1216 : }
1217 : }
1218 : }
1219 15643 : game.loc = game.newloc;
1220 :
1221 15643 : if (!dwarfmove()) {
1222 3 : croak();
1223 : }
1224 :
1225 15640 : if (game.loc == LOC_NOWHERE) {
1226 6 : croak();
1227 : }
1228 :
1229 : /* The easiest way to get killed is to fall into a pit in
1230 : * pitch darkness. */
1231 15659 : if (!FORCED(game.loc) && DARK(game.loc) && game.wzdark &&
1232 22 : PCT(PIT_KILL_PROB)) {
1233 11 : rspeak(PIT_FALL);
1234 11 : game.oldlc2 = game.loc;
1235 11 : croak();
1236 6 : return false;
1237 : }
1238 :
1239 15626 : return true;
1240 : }
1241 :
1242 15626 : static bool do_command(void) {
1243 : /* Get and execute a command */
1244 : static command_t command;
1245 15626 : clear_command(&command);
1246 :
1247 : /* Describe the current location and (maybe) get next command. */
1248 30658 : while (command.state != EXECUTED) {
1249 16028 : describe_location();
1250 :
1251 16028 : if (FORCED(game.loc)) {
1252 883 : playermove(HERE);
1253 883 : return true;
1254 : }
1255 :
1256 15145 : listobjects();
1257 :
1258 : /* Command not yet given; keep getting commands from user
1259 : * until valid command is both given and executed. */
1260 15145 : clear_command(&command);
1261 35876 : while (command.state <= GIVEN) {
1262 :
1263 20844 : if (game.closed) {
1264 : /* If closing time, check for any stashed
1265 : * objects being toted and unstash them. This
1266 : * way objects won't be described until they've
1267 : * been picked up and put down separate from
1268 : * their respective piles. */
1269 85 : if ((PROP_IS_NOTFOUND(OYSTER) ||
1270 25 : PROP_IS_STASHED(OYSTER)) &&
1271 60 : TOTING(OYSTER)) {
1272 3 : pspeak(OYSTER, look, true, 1);
1273 : }
1274 5950 : for (size_t i = 1; i <= NOBJECTS; i++) {
1275 5865 : if (TOTING(i) && (PROP_IS_NOTFOUND(i) ||
1276 36 : PROP_IS_STASHED(i))) {
1277 18 : game.objects[i].prop =
1278 18 : PROP_STASHED(i);
1279 : }
1280 : }
1281 : }
1282 :
1283 : /* Check to see if the room is dark. If the knife is
1284 : * here, and it's dark, the knife permanently disappears
1285 : */
1286 20844 : game.wzdark = DARK(game.loc);
1287 20844 : if (game.knfloc != LOC_NOWHERE &&
1288 483 : game.knfloc != game.loc) {
1289 157 : game.knfloc = LOC_NOWHERE;
1290 : }
1291 :
1292 : /* Check some for hints, get input from user, increment
1293 : * turn, and pre-process commands. Keep going until
1294 : * pre-processing is done. */
1295 41610 : while (command.state < PREPROCESSED) {
1296 20846 : checkhints();
1297 :
1298 : /* Get command input from user */
1299 20846 : if (!get_command_input(&command)) {
1300 80 : return false;
1301 : }
1302 :
1303 : /* Every input, check "foobar" flag. If zero,
1304 : * nothing's going on. If pos, make neg. If neg,
1305 : * he skipped a word, so make it zero.
1306 : */
1307 41532 : game.foobar = (game.foobar > WORD_EMPTY)
1308 108 : ? -game.foobar
1309 20874 : : WORD_EMPTY;
1310 :
1311 20766 : ++game.turns;
1312 20766 : preprocess_command(&command);
1313 : }
1314 :
1315 : /* check if game is closed, and exit if it is */
1316 20764 : if (closecheck()) {
1317 14 : return true;
1318 : }
1319 :
1320 : /* loop until all words in command are processed */
1321 46705 : while (command.state == PREPROCESSED) {
1322 25974 : command.state = PROCESSING;
1323 :
1324 25974 : if (command.word[0].id == WORD_NOT_FOUND) {
1325 : /* Gee, I don't understand. */
1326 16 : sspeak(DONT_KNOW, command.word[0].raw);
1327 16 : clear_command(&command);
1328 16 : continue;
1329 : }
1330 :
1331 : /* Give user hints of shortcuts */
1332 25958 : if (strncasecmp(command.word[0].raw, "west",
1333 : sizeof("west")) == 0) {
1334 97 : if (++game.iwest == 10) {
1335 2 : rspeak(W_IS_WEST);
1336 : }
1337 : }
1338 25958 : if (strncasecmp(command.word[0].raw, "go",
1339 16 : sizeof("go")) == 0 &&
1340 16 : command.word[1].id != WORD_EMPTY) {
1341 16 : if (++game.igo == 10) {
1342 1 : rspeak(GO_UNNEEDED);
1343 : }
1344 : }
1345 :
1346 25958 : switch (command.word[0].type) {
1347 14271 : case MOTION:
1348 14271 : playermove(command.word[0].id);
1349 14270 : command.state = EXECUTED;
1350 14270 : continue;
1351 5184 : case OBJECT:
1352 5184 : command.part = unknown;
1353 5184 : command.obj = command.word[0].id;
1354 5184 : break;
1355 6501 : case ACTION:
1356 6501 : if (command.word[1].type == NUMERIC) {
1357 99 : command.part = transitive;
1358 : } else {
1359 6402 : command.part = intransitive;
1360 : }
1361 6501 : command.verb = command.word[0].id;
1362 6501 : break;
1363 2 : case NUMERIC:
1364 2 : if (!settings.oldstyle) {
1365 2 : sspeak(DONT_KNOW,
1366 : command.word[0].raw);
1367 2 : clear_command(&command);
1368 2 : continue;
1369 : }
1370 : break; // LCOV_EXCL_LINE
1371 : default: // LCOV_EXCL_LINE
1372 : case NO_WORD_TYPE: // LCOV_EXCL_LINE
1373 : BUG(VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3); // LCOV_EXCL_LINE
1374 : }
1375 :
1376 11685 : switch (action(command)) {
1377 54 : case GO_TERMINATE:
1378 54 : command.state = EXECUTED;
1379 54 : break;
1380 306 : case GO_MOVE:
1381 306 : playermove(NUL);
1382 306 : command.state = EXECUTED;
1383 306 : break;
1384 5224 : case GO_WORD2:
1385 : #ifdef GDEBUG
1386 : printf("Word shift\n");
1387 : #endif /* GDEBUG */
1388 : /* Get second word for analysis. */
1389 5224 : command.word[0] = command.word[1];
1390 5224 : command.word[1] = empty_command_word;
1391 5224 : command.state = PREPROCESSED;
1392 5224 : break;
1393 28 : case GO_UNKNOWN:
1394 : /* Random intransitive verbs come here.
1395 : * Clear obj just in case (see
1396 : * attack()). */
1397 28 : command.word[0].raw[0] =
1398 28 : toupper(command.word[0].raw[0]);
1399 28 : sspeak(DO_WHAT, command.word[0].raw);
1400 28 : command.obj = NO_OBJECT;
1401 :
1402 : /* object cleared; we need to go back to
1403 : * the preprocessing step */
1404 28 : command.state = GIVEN;
1405 28 : break;
1406 4 : case GO_CHECKHINT: // FIXME: re-name to be more
1407 : // contextual; this was
1408 : // previously a label
1409 4 : command.state = GIVEN;
1410 4 : break;
1411 5 : case GO_DWARFWAKE:
1412 : /* Oh dear, he's disturbed the dwarves.
1413 : */
1414 5 : rspeak(DWARVES_AWAKEN);
1415 5 : terminate(endgame);
1416 5649 : case GO_CLEAROBJ: // FIXME: re-name to be more
1417 : // contextual; this was
1418 : // previously a label
1419 5649 : clear_command(&command);
1420 5649 : break;
1421 402 : case GO_TOP: // FIXME: re-name to be more
1422 : // contextual; this was previously
1423 : // a label
1424 402 : break;
1425 : default: // LCOV_EXCL_LINE
1426 : BUG(ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH); // LCOV_EXCL_LINE
1427 : }
1428 : } /* while command has not been fully processed */
1429 : } /* while command is not yet given */
1430 : } /* while command is not executed */
1431 :
1432 : /* command completely executed; we return true. */
1433 14630 : return true;
1434 : }
1435 :
1436 : /*
1437 : * MAIN PROGRAM
1438 : *
1439 : * Adventure (rev 2: 20 treasures)
1440 : * History: Original idea & 5-treasure version (adventures) by Willie Crowther
1441 : * 15-treasure version (adventure) by Don Woods, April-June 1977
1442 : * 20-treasure version (rev 2) by Don Woods, August 1978
1443 : * Errata fixed: 78/12/25
1444 : * Revived 2017 as Open Adventure.
1445 : */
1446 :
1447 111 : int main(int argc, char *argv[]) {
1448 : int ch;
1449 :
1450 : /* Options. */
1451 :
1452 : #if defined ADVENT_AUTOSAVE
1453 : const char *opts = "dl:oa:";
1454 : const char *usage =
1455 : "Usage: %s [-l logfilename] [-o] [-a filename] [script...]\n";
1456 : FILE *rfp = NULL;
1457 : const char *autosave_filename = NULL;
1458 : #elif !defined ADVENT_NOSAVE
1459 111 : const char *opts = "dl:or:";
1460 111 : const char *usage = "Usage: %s [-l logfilename] [-o] [-r "
1461 : "restorefilename] [script...]\n";
1462 111 : FILE *rfp = NULL;
1463 : #else
1464 : const char *opts = "dl:o";
1465 : const char *usage = "Usage: %s [-l logfilename] [-o] [script...]\n";
1466 : #endif
1467 117 : while ((ch = getopt(argc, argv, opts)) != EOF) {
1468 7 : switch (ch) {
1469 : case 'd': // LCOV_EXCL_LINE
1470 : settings.debug += 1; // LCOV_EXCL_LINE
1471 : break; // LCOV_EXCL_LINE
1472 2 : case 'l':
1473 2 : settings.logfp = fopen(optarg, "w");
1474 2 : if (settings.logfp == NULL) {
1475 1 : fprintf(
1476 : stderr,
1477 : "advent: can't open logfile %s for write\n",
1478 : optarg);
1479 : }
1480 2 : signal(SIGINT, sig_handler);
1481 2 : break;
1482 1 : case 'o':
1483 1 : settings.oldstyle = true;
1484 1 : settings.prompt = false;
1485 1 : break;
1486 : #ifdef ADVENT_AUTOSAVE
1487 : case 'a':
1488 : rfp = fopen(optarg, READ_MODE);
1489 : autosave_filename = optarg;
1490 : signal(SIGHUP, sig_handler);
1491 : signal(SIGTERM, sig_handler);
1492 : break;
1493 : #elif !defined ADVENT_NOSAVE
1494 3 : case 'r':
1495 3 : rfp = fopen(optarg, "r");
1496 3 : if (rfp == NULL) {
1497 1 : fprintf(stderr,
1498 : "advent: can't open save file %s for "
1499 : "read\n",
1500 : optarg);
1501 : }
1502 3 : break;
1503 : #endif
1504 1 : default:
1505 1 : fprintf(stderr, usage, argv[0]);
1506 1 : fprintf(stderr, " -l create a log file of your "
1507 : "game named as specified'\n");
1508 1 : fprintf(stderr,
1509 : " -o 'oldstyle' (no prompt, no command "
1510 : "editing, displays 'Initialising...')\n");
1511 : #if defined ADVENT_AUTOSAVE
1512 : fprintf(stderr, " -a automatic save/restore "
1513 : "from specified saved game file\n");
1514 : #elif !defined ADVENT_NOSAVE
1515 1 : fprintf(stderr, " -r restore from specified "
1516 : "saved game file\n");
1517 : #endif
1518 1 : exit(EXIT_FAILURE);
1519 : break;
1520 : }
1521 : }
1522 :
1523 : /* copy invocation line part after switches */
1524 110 : settings.argc = argc - optind;
1525 110 : settings.argv = argv + optind;
1526 110 : settings.optind = 0;
1527 :
1528 : /* Initialize game variables */
1529 110 : int seedval = initialise();
1530 :
1531 : #if !defined ADVENT_NOSAVE
1532 110 : if (!rfp) {
1533 108 : game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU],
1534 : arbitrary_messages[CAVE_NEARBY],
1535 : arbitrary_messages[NO_MESSAGE]);
1536 108 : if (game.novice) {
1537 1 : game.limit = NOVICELIMIT;
1538 : }
1539 : } else {
1540 2 : restore(rfp);
1541 : #if defined ADVENT_AUTOSAVE
1542 : score(scoregame);
1543 : #endif
1544 : }
1545 : #if defined ADVENT_AUTOSAVE
1546 : if (autosave_filename != NULL) {
1547 : if ((autosave_fp = fopen(autosave_filename, WRITE_MODE)) ==
1548 : NULL) {
1549 : perror(autosave_filename);
1550 : return EXIT_FAILURE;
1551 : }
1552 : autosave();
1553 : }
1554 : #endif
1555 : #else
1556 : game.novice = yes_or_no(arbitrary_messages[WELCOME_YOU],
1557 : arbitrary_messages[CAVE_NEARBY],
1558 : arbitrary_messages[NO_MESSAGE]);
1559 : if (game.novice) {
1560 : game.limit = NOVICELIMIT;
1561 : }
1562 : #endif
1563 :
1564 110 : if (settings.logfp) {
1565 1 : fprintf(settings.logfp, "seed %d\n", seedval);
1566 : }
1567 :
1568 : /* interpret commands until EOF or interrupt */
1569 : for (;;) {
1570 : // if we're supposed to move, move
1571 15643 : if (!do_move()) {
1572 6 : continue;
1573 : }
1574 :
1575 : // get command
1576 15626 : if (!do_command()) {
1577 80 : break;
1578 : }
1579 : }
1580 : /* show score and exit */
1581 80 : terminate(quitgame);
1582 : }
1583 :
1584 : /* end */
|