Line data Source code
1 : /*
2 : * Actions for the dungeon-running code.
3 : *
4 : * SPDX-FileCopyrightText: (C) 1977, 2005 Will Crowther and Don Woods
5 : * SPDX-License-Identifier: BSD-2-Clause
6 : */
7 :
8 : #include <inttypes.h>
9 : #include <stdbool.h>
10 : #include <stdlib.h>
11 : #include <string.h>
12 :
13 : #include "advent.h"
14 :
15 : static phase_codes_t fill(verb_t, obj_t);
16 :
17 196 : static phase_codes_t attack(command_t command) {
18 : /* Attack. Assume target if unambiguous. "Throw" also links here.
19 : * Attackable objects fall into two categories: enemies (snake,
20 : * dwarf, etc.) and others (bird, clam, machine). Ambiguous if 2
21 : * enemies, or no enemies but 2 others. */
22 196 : verb_t verb = command.verb;
23 196 : obj_t obj = command.obj;
24 :
25 196 : if (obj == INTRANSITIVE) {
26 24 : int changes = 0;
27 24 : if (atdwrf(game.loc) > 0) {
28 3 : obj = DWARF;
29 3 : ++changes;
30 : }
31 24 : if (HERE(SNAKE)) {
32 1 : obj = SNAKE;
33 1 : ++changes;
34 : }
35 24 : if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
36 6 : obj = DRAGON;
37 6 : ++changes;
38 : }
39 24 : if (AT(TROLL)) {
40 1 : obj = TROLL;
41 1 : ++changes;
42 : }
43 24 : if (AT(OGRE)) {
44 3 : obj = OGRE;
45 3 : ++changes;
46 : }
47 24 : if (HERE(BEAR) && game.objects[BEAR].prop == UNTAMED_BEAR) {
48 2 : obj = BEAR;
49 2 : ++changes;
50 : }
51 : /* check for low-priority targets */
52 24 : if (obj == INTRANSITIVE) {
53 : /* Can't attack bird or machine by throwing axe. */
54 10 : if (HERE(BIRD) && verb != THROW) {
55 2 : obj = BIRD;
56 2 : ++changes;
57 : }
58 10 : if (HERE(VEND) && verb != THROW) {
59 1 : obj = VEND;
60 1 : ++changes;
61 : }
62 : /* Clam and oyster both treated as clam for intransitive
63 : * case; no harm done. */
64 10 : if (HERE(CLAM) || HERE(OYSTER)) {
65 1 : obj = CLAM;
66 1 : ++changes;
67 : }
68 : }
69 24 : if (changes >= 2) {
70 3 : return GO_UNKNOWN;
71 : }
72 : }
73 :
74 193 : if (obj == BIRD) {
75 3 : if (game.closed) {
76 1 : rspeak(UNHAPPY_BIRD);
77 : } else {
78 2 : DESTROY(BIRD);
79 2 : rspeak(BIRD_DEAD);
80 : }
81 3 : return GO_CLEAROBJ;
82 : }
83 190 : if (obj == VEND) {
84 57 : state_change(VEND, game.objects[VEND].prop == VEND_BLOCKS
85 : ? VEND_UNBLOCKS
86 : : VEND_BLOCKS);
87 :
88 57 : return GO_CLEAROBJ;
89 : }
90 :
91 133 : if (obj == BEAR) {
92 9 : switch (game.objects[BEAR].prop) {
93 3 : case UNTAMED_BEAR:
94 3 : rspeak(BEAR_HANDS);
95 3 : break;
96 1 : case SITTING_BEAR:
97 1 : rspeak(BEAR_CONFUSED);
98 1 : break;
99 4 : case CONTENTED_BEAR:
100 4 : rspeak(BEAR_CONFUSED);
101 4 : break;
102 1 : case BEAR_DEAD:
103 1 : rspeak(ALREADY_DEAD);
104 1 : break;
105 : }
106 9 : return GO_CLEAROBJ;
107 : }
108 124 : if (obj == DRAGON && game.objects[DRAGON].prop == DRAGON_BARS) {
109 : /* Fun stuff for dragon. If he insists on attacking it, win!
110 : * Set game.prop to dead, move dragon to central loc (still
111 : * fixed), move rug there (not fixed), and move him there,
112 : * too. Then do a null motion to get new description. */
113 61 : rspeak(BARE_HANDS_QUERY);
114 61 : if (!silent_yes_or_no()) {
115 1 : speak(arbitrary_messages[NASTY_DRAGON]);
116 1 : return GO_MOVE;
117 : }
118 60 : state_change(DRAGON, DRAGON_DEAD);
119 60 : game.objects[RUG].prop = RUG_FLOOR;
120 : /* Hardcoding LOC_SECRET5 as the dragon's death location is
121 : * ugly. The way it was computed before was worse; it depended
122 : * on the two dragon locations being LOC_SECRET4 and LOC_SECRET6
123 : * and LOC_SECRET5 being right between them.
124 : */
125 60 : move(DRAGON + NOBJECTS, IS_FIXED);
126 60 : move(RUG + NOBJECTS, IS_FREE);
127 60 : move(DRAGON, LOC_SECRET5);
128 60 : move(RUG, LOC_SECRET5);
129 60 : drop(BLOOD, LOC_SECRET5);
130 4200 : for (obj_t i = 1; i <= NOBJECTS; i++) {
131 4140 : if (game.objects[i].place == objects[DRAGON].plac ||
132 4140 : game.objects[i].place == objects[DRAGON].fixd) {
133 1 : move(i, LOC_SECRET5);
134 : }
135 : }
136 60 : game.loc = LOC_SECRET5;
137 60 : return GO_MOVE;
138 : }
139 :
140 63 : if (obj == OGRE) {
141 48 : rspeak(OGRE_DODGE);
142 48 : if (atdwrf(game.loc) == 0) {
143 2 : return GO_CLEAROBJ;
144 : }
145 46 : rspeak(KNIFE_THROWN);
146 46 : DESTROY(OGRE);
147 46 : int dwarves = 0;
148 276 : for (int i = 1; i < PIRATE; i++) {
149 230 : if (game.dwarves[i].loc == game.loc) {
150 47 : ++dwarves;
151 47 : game.dwarves[i].loc = LOC_LONGWEST;
152 47 : game.dwarves[i].seen = false;
153 : }
154 : }
155 46 : rspeak((dwarves > 1) ? OGRE_PANIC1 : OGRE_PANIC2);
156 46 : return GO_CLEAROBJ;
157 : }
158 :
159 15 : switch (obj) {
160 7 : case INTRANSITIVE:
161 7 : rspeak(NO_TARGET);
162 7 : break;
163 1 : case CLAM:
164 : case OYSTER:
165 1 : rspeak(SHELL_IMPERVIOUS);
166 1 : break;
167 1 : case SNAKE:
168 1 : rspeak(SNAKE_WARNING);
169 1 : break;
170 3 : case DWARF:
171 3 : if (game.closed) {
172 1 : return GO_DWARFWAKE;
173 : }
174 2 : rspeak(BARE_HANDS_QUERY);
175 2 : break;
176 1 : case DRAGON:
177 1 : rspeak(ALREADY_DEAD);
178 1 : break;
179 1 : case TROLL:
180 1 : rspeak(ROCKY_TROLL);
181 1 : break;
182 1 : default:
183 1 : speak(actions[verb].message);
184 : }
185 14 : return GO_CLEAROBJ;
186 : }
187 :
188 147 : static phase_codes_t bigwords(vocab_t id) {
189 : /* Only called on FEE FIE FOE FOO (AND FUM). Advance to next state if
190 : * given in proper order. Look up foo in special section of vocab to
191 : * determine which word we've got. Last word zips the eggs back to the
192 : * giant room (unless already there). */
193 147 : int foobar = abs(game.foobar);
194 :
195 : /* Only FEE can start a magic-word sequence. */
196 147 : if ((foobar == WORD_EMPTY) &&
197 39 : (id == FIE || id == FOE || id == FOO || id == FUM)) {
198 3 : rspeak(NOTHING_HAPPENS);
199 3 : return GO_CLEAROBJ;
200 : }
201 :
202 144 : if ((foobar == WORD_EMPTY && id == FEE) ||
203 108 : (foobar == FEE && id == FIE) || (foobar == FIE && id == FOE) ||
204 36 : (foobar == FOE && id == FOO)) {
205 140 : game.foobar = id;
206 140 : if (id != FOO) {
207 108 : rspeak(OK_MAN);
208 108 : return GO_CLEAROBJ;
209 : }
210 32 : game.foobar = WORD_EMPTY;
211 32 : if (game.objects[EGGS].place == objects[EGGS].plac ||
212 31 : (TOTING(EGGS) && game.loc == objects[EGGS].plac)) {
213 2 : rspeak(NOTHING_HAPPENS);
214 2 : return GO_CLEAROBJ;
215 : } else {
216 : /* Bring back troll if we steal the eggs back from him
217 : * before crossing. */
218 30 : if (game.objects[EGGS].place == LOC_NOWHERE &&
219 28 : game.objects[TROLL].place == LOC_NOWHERE &&
220 28 : game.objects[TROLL].prop == TROLL_UNPAID) {
221 1 : game.objects[TROLL].prop = TROLL_PAIDONCE;
222 : }
223 30 : if (HERE(EGGS)) {
224 1 : pspeak(EGGS, look, true, EGGS_VANISHED);
225 29 : } else if (game.loc == objects[EGGS].plac) {
226 24 : pspeak(EGGS, look, true, EGGS_HERE);
227 : } else {
228 5 : pspeak(EGGS, look, true, EGGS_DONE);
229 : }
230 30 : move(EGGS, objects[EGGS].plac);
231 :
232 30 : return GO_CLEAROBJ;
233 : }
234 : } else {
235 : /* Magic-word sequence was started but is incorrect */
236 4 : if (settings.oldstyle || game.seenbigwords) {
237 3 : rspeak(START_OVER);
238 : } else {
239 1 : rspeak(WELL_POINTLESS);
240 : }
241 4 : game.foobar = WORD_EMPTY;
242 4 : return GO_CLEAROBJ;
243 : }
244 : }
245 :
246 8 : static void blast(void) {
247 : /* Blast. No effect unless you've got dynamite, which is a neat trick!
248 : */
249 8 : if (PROP_IS_NOTFOUND(ROD2) || !game.closed) {
250 3 : rspeak(REQUIRES_DYNAMITE);
251 : } else {
252 5 : if (HERE(ROD2)) {
253 1 : game.bonus = splatter;
254 1 : rspeak(SPLATTER_MESSAGE);
255 4 : } else if (game.loc == LOC_NE) {
256 1 : game.bonus = defeat;
257 1 : rspeak(DEFEAT_MESSAGE);
258 : } else {
259 3 : game.bonus = victory;
260 3 : rspeak(VICTORY_MESSAGE);
261 : }
262 5 : terminate(endgame);
263 : }
264 3 : }
265 :
266 8 : static phase_codes_t vbreak(verb_t verb, obj_t obj) {
267 : /* Break. Only works for mirror in repository and, of course, the
268 : * vase. */
269 8 : switch (obj) {
270 3 : case MIRROR:
271 3 : if (game.closed) {
272 1 : state_change(MIRROR, MIRROR_BROKEN);
273 1 : return GO_DWARFWAKE;
274 : } else {
275 2 : rspeak(TOO_FAR);
276 2 : break;
277 : }
278 4 : case VASE:
279 4 : if (game.objects[VASE].prop == VASE_WHOLE) {
280 2 : if (TOTING(VASE)) {
281 2 : drop(VASE, game.loc);
282 : }
283 2 : state_change(VASE, VASE_BROKEN);
284 2 : game.objects[VASE].fixed = IS_FIXED;
285 2 : break;
286 : }
287 : /* FALLTHRU */
288 : default:
289 3 : speak(actions[verb].message);
290 : }
291 7 : return (GO_CLEAROBJ);
292 : }
293 :
294 1 : static phase_codes_t brief(void) {
295 : /* Brief. Intransitive only. Suppress full descriptions after first
296 : * time. */
297 1 : game.abbnum = 10000;
298 1 : game.detail = 3;
299 1 : rspeak(BRIEF_CONFIRM);
300 1 : return GO_CLEAROBJ;
301 : }
302 :
303 2414 : static phase_codes_t vcarry(verb_t verb, obj_t obj) {
304 : /* Carry an object. Special cases for bird and cage (if bird in cage,
305 : * can't take one without the other). Liquids also special, since they
306 : * depend on status of bottle. Also various side effects, etc. */
307 2414 : if (obj == INTRANSITIVE) {
308 : /* Carry, no object given yet. OK if only one object present.
309 : */
310 36 : if (game.locs[game.loc].atloc == NO_OBJECT ||
311 64 : game.link[game.locs[game.loc].atloc] != 0 ||
312 30 : atdwrf(game.loc) > 0) {
313 7 : return GO_UNKNOWN;
314 : }
315 29 : obj = game.locs[game.loc].atloc;
316 : }
317 :
318 2407 : if (TOTING(obj)) {
319 9 : speak(actions[verb].message);
320 9 : return GO_CLEAROBJ;
321 : }
322 :
323 2398 : if (obj == MESSAG) {
324 1 : rspeak(REMOVE_MESSAGE);
325 1 : DESTROY(MESSAG);
326 1 : return GO_CLEAROBJ;
327 : }
328 :
329 2397 : if (game.objects[obj].fixed != IS_FREE) {
330 16 : switch (obj) {
331 2 : case PLANT:
332 : /* Next guard tests whether plant is tiny or stashed */
333 2 : rspeak(game.objects[PLANT].prop <= PLANT_THIRSTY
334 : ? DEEP_ROOTS
335 : : YOU_JOKING);
336 2 : break;
337 2 : case BEAR:
338 2 : rspeak(game.objects[BEAR].prop == SITTING_BEAR
339 : ? BEAR_CHAINED
340 : : YOU_JOKING);
341 2 : break;
342 4 : case CHAIN:
343 4 : rspeak(game.objects[BEAR].prop != UNTAMED_BEAR
344 : ? STILL_LOCKED
345 : : YOU_JOKING);
346 4 : break;
347 1 : case RUG:
348 1 : rspeak(game.objects[RUG].prop == RUG_HOVER
349 : ? RUG_HOVERS
350 : : YOU_JOKING);
351 1 : break;
352 1 : case URN:
353 1 : rspeak(URN_NOBUDGE);
354 1 : break;
355 1 : case CAVITY:
356 1 : rspeak(DOUGHNUT_HOLES);
357 1 : break;
358 1 : case BLOOD:
359 1 : rspeak(FEW_DROPS);
360 1 : break;
361 1 : case SIGN:
362 1 : rspeak(HAND_PASSTHROUGH);
363 1 : break;
364 3 : default:
365 3 : rspeak(YOU_JOKING);
366 : }
367 16 : return GO_CLEAROBJ;
368 : }
369 :
370 2381 : if (obj == WATER || obj == OIL) {
371 173 : if (!HERE(BOTTLE) || LIQUID() != obj) {
372 115 : if (!TOTING(BOTTLE)) {
373 1 : rspeak(NO_CONTAINER);
374 1 : return GO_CLEAROBJ;
375 : }
376 114 : if (game.objects[BOTTLE].prop == EMPTY_BOTTLE) {
377 113 : return (fill(verb, BOTTLE));
378 : } else {
379 1 : rspeak(BOTTLE_FULL);
380 : }
381 1 : return GO_CLEAROBJ;
382 : }
383 58 : obj = BOTTLE;
384 : }
385 :
386 2266 : if (game.holdng >= INVLIMIT) {
387 11 : rspeak(CARRY_LIMIT);
388 11 : return GO_CLEAROBJ;
389 : }
390 :
391 2255 : if (obj == BIRD && game.objects[BIRD].prop != BIRD_CAGED &&
392 175 : !PROP_IS_STASHED(BIRD)) {
393 174 : if (game.objects[BIRD].prop == BIRD_FOREST_UNCAGED) {
394 1 : DESTROY(BIRD);
395 1 : rspeak(BIRD_CRAP);
396 1 : return GO_CLEAROBJ;
397 : }
398 173 : if (!TOTING(CAGE)) {
399 1 : rspeak(CANNOT_CARRY);
400 1 : return GO_CLEAROBJ;
401 : }
402 172 : if (TOTING(ROD)) {
403 1 : rspeak(BIRD_EVADES);
404 1 : return GO_CLEAROBJ;
405 : }
406 171 : game.objects[BIRD].prop = BIRD_CAGED;
407 : }
408 2252 : if ((obj == BIRD || obj == CAGE) &&
409 282 : (game.objects[BIRD].prop == BIRD_CAGED ||
410 107 : PROP_STASHED(BIRD) == BIRD_CAGED)) {
411 : /* expression maps BIRD to CAGE and CAGE to BIRD */
412 178 : carry(BIRD + CAGE - obj, game.loc);
413 : }
414 :
415 2252 : carry(obj, game.loc);
416 :
417 2252 : if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
418 67 : game.objects[LIQUID()].place = CARRIED;
419 : }
420 :
421 2252 : if (GSTONE(obj) && !PROP_IS_FOUND(obj)) {
422 73 : PROP_SET_FOUND(obj);
423 73 : game.objects[CAVITY].prop = CAVITY_EMPTY;
424 : }
425 2252 : rspeak(OK_MAN);
426 2252 : return GO_CLEAROBJ;
427 : }
428 :
429 44 : static int chain(verb_t verb) {
430 : /* Do something to the bear's chain */
431 44 : if (verb != LOCK) {
432 40 : if (game.objects[BEAR].prop == UNTAMED_BEAR) {
433 2 : rspeak(BEAR_BLOCKS);
434 2 : return GO_CLEAROBJ;
435 : }
436 38 : if (game.objects[CHAIN].prop == CHAIN_HEAP) {
437 3 : rspeak(ALREADY_UNLOCKED);
438 3 : return GO_CLEAROBJ;
439 : }
440 35 : game.objects[CHAIN].prop = CHAIN_HEAP;
441 35 : game.objects[CHAIN].fixed = IS_FREE;
442 35 : if (game.objects[BEAR].prop != BEAR_DEAD) {
443 35 : game.objects[BEAR].prop = CONTENTED_BEAR;
444 : }
445 :
446 35 : switch (game.objects[BEAR].prop) {
447 : // LCOV_EXCL_START
448 : case BEAR_DEAD:
449 : /* Can't be reached until the bear can die in some way
450 : * other than a bridge collapse. Leave in in case this
451 : * changes, but exclude from coverage testing. */
452 : game.objects[BEAR].fixed = IS_FIXED;
453 : break;
454 : // LCOV_EXCL_STOP
455 35 : default:
456 35 : game.objects[BEAR].fixed = IS_FREE;
457 : }
458 35 : rspeak(CHAIN_UNLOCKED);
459 35 : return GO_CLEAROBJ;
460 : }
461 :
462 4 : if (game.objects[CHAIN].prop != CHAIN_HEAP) {
463 1 : rspeak(ALREADY_LOCKED);
464 1 : return GO_CLEAROBJ;
465 : }
466 3 : if (game.loc != objects[CHAIN].plac) {
467 1 : rspeak(NO_LOCKSITE);
468 1 : return GO_CLEAROBJ;
469 : }
470 :
471 2 : game.objects[CHAIN].prop = CHAIN_FIXED;
472 :
473 2 : if (TOTING(CHAIN)) {
474 1 : drop(CHAIN, game.loc);
475 : }
476 2 : game.objects[CHAIN].fixed = IS_FIXED;
477 :
478 2 : rspeak(CHAIN_LOCKED);
479 2 : return GO_CLEAROBJ;
480 : }
481 :
482 1691 : static phase_codes_t discard(verb_t verb, obj_t obj) {
483 : /* Discard object. "Throw" also comes here for most objects. Special
484 : * cases for bird (might attack snake or dragon) and cage (might contain
485 : * bird) and vase. Drop coins at vending machine for extra batteries. */
486 1691 : if (obj == ROD && !TOTING(ROD) && TOTING(ROD2)) {
487 3 : obj = ROD2;
488 : }
489 :
490 1691 : if (!TOTING(obj)) {
491 1 : speak(actions[verb].message);
492 1 : return GO_CLEAROBJ;
493 : }
494 :
495 1690 : if (GSTONE(obj) && AT(CAVITY) &&
496 51 : game.objects[CAVITY].prop != CAVITY_FULL) {
497 51 : rspeak(GEM_FITS);
498 51 : game.objects[obj].prop = STATE_IN_CAVITY;
499 51 : game.objects[CAVITY].prop = CAVITY_FULL;
500 51 : if (HERE(RUG) &&
501 51 : ((obj == EMERALD && game.objects[RUG].prop != RUG_HOVER) ||
502 23 : (obj == RUBY && game.objects[RUG].prop == RUG_HOVER))) {
503 50 : if (obj == RUBY) {
504 23 : rspeak(RUG_SETTLES);
505 27 : } else if (TOTING(RUG)) {
506 1 : rspeak(RUG_WIGGLES);
507 : } else {
508 26 : rspeak(RUG_RISES);
509 : }
510 50 : if (!TOTING(RUG) || obj == RUBY) {
511 49 : int k = (game.objects[RUG].prop == RUG_HOVER)
512 : ? RUG_FLOOR
513 49 : : RUG_HOVER;
514 49 : game.objects[RUG].prop = k;
515 49 : if (k == RUG_HOVER) {
516 26 : k = objects[SAPPH].plac;
517 : }
518 49 : move(RUG + NOBJECTS, k);
519 : }
520 : }
521 51 : drop(obj, game.loc);
522 51 : return GO_CLEAROBJ;
523 : }
524 :
525 1639 : if (obj == COINS && HERE(VEND)) {
526 3 : DESTROY(COINS);
527 3 : drop(BATTERY, game.loc);
528 3 : pspeak(BATTERY, look, true, FRESH_BATTERIES);
529 3 : return GO_CLEAROBJ;
530 : }
531 :
532 1636 : if (LIQUID() == obj) {
533 2 : obj = BOTTLE;
534 : }
535 1636 : if (obj == BOTTLE && LIQUID() != NO_OBJECT) {
536 12 : game.objects[LIQUID()].place = LOC_NOWHERE;
537 : }
538 :
539 1636 : if (obj == BEAR && AT(TROLL)) {
540 32 : state_change(TROLL, TROLL_GONE);
541 32 : move(TROLL, LOC_NOWHERE);
542 32 : move(TROLL + NOBJECTS, IS_FREE);
543 32 : move(TROLL2, objects[TROLL].plac);
544 32 : move(TROLL2 + NOBJECTS, objects[TROLL].fixd);
545 32 : juggle(CHASM);
546 32 : drop(obj, game.loc);
547 32 : return GO_CLEAROBJ;
548 : }
549 :
550 1604 : if (obj == VASE) {
551 33 : if (game.loc != objects[PILLOW].plac) {
552 32 : state_change(VASE,
553 32 : AT(PILLOW) ? VASE_WHOLE : VASE_DROPPED);
554 32 : if (game.objects[VASE].prop != VASE_WHOLE) {
555 1 : game.objects[VASE].fixed = IS_FIXED;
556 : }
557 32 : drop(obj, game.loc);
558 32 : return GO_CLEAROBJ;
559 : }
560 : }
561 :
562 1572 : if (obj == CAGE && game.objects[BIRD].prop == BIRD_CAGED) {
563 5 : drop(BIRD, game.loc);
564 : }
565 :
566 1572 : if (obj == BIRD) {
567 163 : if (AT(DRAGON) && game.objects[DRAGON].prop == DRAGON_BARS) {
568 1 : rspeak(BIRD_BURNT);
569 1 : DESTROY(BIRD);
570 1 : return GO_CLEAROBJ;
571 : }
572 162 : if (HERE(SNAKE)) {
573 67 : rspeak(BIRD_ATTACKS);
574 67 : if (game.closed) {
575 1 : return GO_DWARFWAKE;
576 : }
577 66 : DESTROY(SNAKE);
578 : /* Set game.prop for use by travel options */
579 66 : game.objects[SNAKE].prop = SNAKE_CHASED;
580 : } else {
581 95 : rspeak(OK_MAN);
582 : }
583 :
584 161 : game.objects[BIRD].prop =
585 161 : FOREST(game.loc) ? BIRD_FOREST_UNCAGED : BIRD_UNCAGED;
586 161 : drop(obj, game.loc);
587 161 : return GO_CLEAROBJ;
588 : }
589 :
590 1409 : rspeak(OK_MAN);
591 1409 : drop(obj, game.loc);
592 1409 : return GO_CLEAROBJ;
593 : }
594 :
595 56 : static phase_codes_t drink(verb_t verb, obj_t obj) {
596 : /* Drink. If no object, assume water and look for it here. If water
597 : * is in the bottle, drink that, else must be at a water loc, so drink
598 : * stream. */
599 56 : if (obj == INTRANSITIVE && LIQLOC(game.loc) != WATER &&
600 1 : (LIQUID() != WATER || !HERE(BOTTLE))) {
601 1 : return GO_UNKNOWN;
602 : }
603 :
604 55 : if (obj == BLOOD) {
605 44 : DESTROY(BLOOD);
606 44 : state_change(DRAGON, DRAGON_BLOODLESS);
607 44 : game.blooded = true;
608 44 : return GO_CLEAROBJ;
609 : }
610 :
611 11 : if (obj != INTRANSITIVE && obj != WATER) {
612 6 : rspeak(RIDICULOUS_ATTEMPT);
613 6 : return GO_CLEAROBJ;
614 : }
615 5 : if (LIQUID() == WATER && HERE(BOTTLE)) {
616 3 : game.objects[WATER].place = LOC_NOWHERE;
617 3 : state_change(BOTTLE, EMPTY_BOTTLE);
618 3 : return GO_CLEAROBJ;
619 : }
620 :
621 2 : speak(actions[verb].message);
622 2 : return GO_CLEAROBJ;
623 : }
624 :
625 10 : static phase_codes_t eat(verb_t verb, obj_t obj) {
626 : /* Eat. Intransitive: assume food if present, else ask what.
627 : * Transitive: food ok, some things lose appetite, rest are ridiculous.
628 : */
629 10 : switch (obj) {
630 2 : case INTRANSITIVE:
631 2 : if (!HERE(FOOD)) {
632 1 : return GO_UNKNOWN;
633 : }
634 : /* FALLTHRU */
635 : case FOOD:
636 2 : DESTROY(FOOD);
637 2 : rspeak(THANKS_DELICIOUS);
638 2 : break;
639 2 : case BIRD:
640 : case SNAKE:
641 : case CLAM:
642 : case OYSTER:
643 : case DWARF:
644 : case DRAGON:
645 : case TROLL:
646 : case BEAR:
647 : case OGRE:
648 2 : rspeak(LOST_APPETITE);
649 2 : break;
650 5 : default:
651 5 : speak(actions[verb].message);
652 : }
653 9 : return GO_CLEAROBJ;
654 : }
655 :
656 233 : static phase_codes_t extinguish(verb_t verb, obj_t obj) {
657 : /* Extinguish. Lamp, urn, dragon/volcano (nice try). */
658 233 : if (obj == INTRANSITIVE) {
659 197 : if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_BRIGHT) {
660 195 : obj = LAMP;
661 : }
662 197 : if (HERE(URN) && game.objects[URN].prop == URN_LIT) {
663 1 : obj = URN;
664 : }
665 197 : if (obj == INTRANSITIVE) {
666 1 : return GO_UNKNOWN;
667 : }
668 : }
669 :
670 232 : switch (obj) {
671 3 : case URN:
672 3 : if (game.objects[URN].prop != URN_EMPTY) {
673 2 : state_change(URN, URN_DARK);
674 : } else {
675 1 : pspeak(URN, change, true, URN_DARK);
676 : }
677 3 : break;
678 225 : case LAMP:
679 225 : state_change(LAMP, LAMP_DARK);
680 225 : rspeak(DARK(game.loc) ? PITCH_DARK : NO_MESSAGE);
681 225 : break;
682 2 : case DRAGON:
683 : case VOLCANO:
684 2 : rspeak(BEYOND_POWER);
685 2 : break;
686 2 : default:
687 2 : speak(actions[verb].message);
688 : }
689 232 : return GO_CLEAROBJ;
690 : }
691 :
692 52 : static phase_codes_t feed(verb_t verb, obj_t obj) {
693 : /* Feed. If bird, no seed. Snake, dragon, troll: quip. If dwarf,
694 : * make him mad. Bear, special. */
695 52 : switch (obj) {
696 1 : case BIRD:
697 1 : rspeak(BIRD_PINING);
698 1 : break;
699 4 : case DRAGON:
700 4 : if (game.objects[DRAGON].prop != DRAGON_BARS) {
701 2 : rspeak(RIDICULOUS_ATTEMPT);
702 : } else {
703 2 : rspeak(NOTHING_EDIBLE);
704 : }
705 4 : break;
706 2 : case SNAKE:
707 2 : if (!game.closed && HERE(BIRD)) {
708 1 : DESTROY(BIRD);
709 1 : rspeak(BIRD_DEVOURED);
710 : } else {
711 1 : rspeak(NOTHING_EDIBLE);
712 : }
713 2 : break;
714 1 : case TROLL:
715 1 : rspeak(TROLL_VICES);
716 1 : break;
717 2 : case DWARF:
718 2 : if (HERE(FOOD)) {
719 1 : game.dflag += 2;
720 1 : rspeak(REALLY_MAD);
721 : } else {
722 1 : speak(actions[verb].message);
723 : }
724 2 : break;
725 39 : case BEAR:
726 39 : if (game.objects[BEAR].prop == BEAR_DEAD) {
727 1 : rspeak(RIDICULOUS_ATTEMPT);
728 1 : break;
729 : }
730 38 : if (game.objects[BEAR].prop == UNTAMED_BEAR) {
731 36 : if (HERE(FOOD)) {
732 34 : DESTROY(FOOD);
733 34 : game.objects[AXE].fixed = IS_FREE;
734 34 : game.objects[AXE].prop = AXE_HERE;
735 34 : state_change(BEAR, SITTING_BEAR);
736 : } else {
737 2 : rspeak(NOTHING_EDIBLE);
738 : }
739 36 : break;
740 : }
741 2 : speak(actions[verb].message);
742 2 : break;
743 2 : case OGRE:
744 2 : if (HERE(FOOD)) {
745 1 : rspeak(OGRE_FULL);
746 : } else {
747 1 : speak(actions[verb].message);
748 : }
749 2 : break;
750 1 : default:
751 1 : rspeak(AM_GAME);
752 : }
753 52 : return GO_CLEAROBJ;
754 : }
755 :
756 165 : phase_codes_t fill(verb_t verb, obj_t obj) {
757 : /* Fill. Bottle or urn must be empty, and liquid available. (Vase
758 : * is nasty.) */
759 165 : if (obj == VASE) {
760 3 : if (LIQLOC(game.loc) == NO_OBJECT) {
761 1 : rspeak(FILL_INVALID);
762 1 : return GO_CLEAROBJ;
763 : }
764 2 : if (!TOTING(VASE)) {
765 1 : rspeak(ARENT_CARRYING);
766 1 : return GO_CLEAROBJ;
767 : }
768 1 : rspeak(SHATTER_VASE);
769 1 : game.objects[VASE].prop = VASE_BROKEN;
770 1 : game.objects[VASE].fixed = IS_FIXED;
771 1 : drop(VASE, game.loc);
772 1 : return GO_CLEAROBJ;
773 : }
774 :
775 162 : if (obj == URN) {
776 31 : if (game.objects[URN].prop != URN_EMPTY) {
777 1 : rspeak(FULL_URN);
778 1 : return GO_CLEAROBJ;
779 : }
780 30 : if (!HERE(BOTTLE)) {
781 1 : rspeak(FILL_INVALID);
782 1 : return GO_CLEAROBJ;
783 : }
784 29 : int k = LIQUID();
785 29 : switch (k) {
786 1 : case WATER:
787 1 : game.objects[BOTTLE].prop = EMPTY_BOTTLE;
788 1 : rspeak(WATER_URN);
789 1 : break;
790 27 : case OIL:
791 27 : game.objects[URN].prop = URN_DARK;
792 27 : game.objects[BOTTLE].prop = EMPTY_BOTTLE;
793 27 : rspeak(OIL_URN);
794 27 : break;
795 1 : case NO_OBJECT:
796 : default:
797 1 : rspeak(FILL_INVALID);
798 1 : return GO_CLEAROBJ;
799 : }
800 28 : game.objects[k].place = LOC_NOWHERE;
801 28 : return GO_CLEAROBJ;
802 : }
803 131 : if (obj != INTRANSITIVE && obj != BOTTLE) {
804 1 : speak(actions[verb].message);
805 1 : return GO_CLEAROBJ;
806 : }
807 130 : if (obj == INTRANSITIVE && !HERE(BOTTLE)) {
808 1 : return GO_UNKNOWN;
809 : }
810 :
811 129 : if (HERE(URN) && game.objects[URN].prop != URN_EMPTY) {
812 1 : rspeak(URN_NOPOUR);
813 1 : return GO_CLEAROBJ;
814 : }
815 128 : if (LIQUID() != NO_OBJECT) {
816 1 : rspeak(BOTTLE_FULL);
817 1 : return GO_CLEAROBJ;
818 : }
819 127 : if (LIQLOC(game.loc) == NO_OBJECT) {
820 1 : rspeak(NO_LIQUID);
821 1 : return GO_CLEAROBJ;
822 : }
823 :
824 252 : state_change(BOTTLE,
825 252 : (LIQLOC(game.loc) == OIL) ? OIL_BOTTLE : WATER_BOTTLE);
826 126 : if (TOTING(BOTTLE)) {
827 126 : game.objects[LIQUID()].place = CARRIED;
828 : }
829 126 : return GO_CLEAROBJ;
830 : }
831 :
832 8 : static phase_codes_t find(verb_t verb, obj_t obj) {
833 : /* Find. Might be carrying it, or it might be here. Else give caveat.
834 : */
835 8 : if (TOTING(obj)) {
836 2 : rspeak(ALREADY_CARRYING);
837 2 : return GO_CLEAROBJ;
838 : }
839 :
840 6 : if (game.closed) {
841 1 : rspeak(NEEDED_NEARBY);
842 1 : return GO_CLEAROBJ;
843 : }
844 :
845 9 : if (AT(obj) || (LIQUID() == obj && AT(BOTTLE)) ||
846 8 : obj == LIQLOC(game.loc) || (obj == DWARF && atdwrf(game.loc) > 0)) {
847 2 : rspeak(YOU_HAVEIT);
848 2 : return GO_CLEAROBJ;
849 : }
850 :
851 3 : speak(actions[verb].message);
852 3 : return GO_CLEAROBJ;
853 : }
854 :
855 57 : static phase_codes_t fly(verb_t verb, obj_t obj) {
856 : /* Fly. Snide remarks unless hovering rug is here. */
857 57 : if (obj == INTRANSITIVE) {
858 13 : if (!HERE(RUG)) {
859 1 : rspeak(FLAP_ARMS);
860 1 : return GO_CLEAROBJ;
861 : }
862 12 : if (game.objects[RUG].prop != RUG_HOVER) {
863 1 : rspeak(RUG_NOTHING2);
864 1 : return GO_CLEAROBJ;
865 : }
866 11 : obj = RUG;
867 : }
868 :
869 55 : if (obj != RUG) {
870 1 : speak(actions[verb].message);
871 1 : return GO_CLEAROBJ;
872 : }
873 54 : if (game.objects[RUG].prop != RUG_HOVER) {
874 1 : rspeak(RUG_NOTHING1);
875 1 : return GO_CLEAROBJ;
876 : }
877 :
878 53 : if (game.loc == LOC_CLIFF) {
879 27 : game.oldlc2 = game.oldloc;
880 27 : game.oldloc = game.loc;
881 27 : game.newloc = LOC_LEDGE;
882 27 : rspeak(RUG_GOES);
883 26 : } else if (game.loc == LOC_LEDGE) {
884 26 : game.oldlc2 = game.oldloc;
885 26 : game.oldloc = game.loc;
886 26 : game.newloc = LOC_CLIFF;
887 26 : rspeak(RUG_RETURNS);
888 : } else {
889 : // LCOV_EXCL_START
890 : /* should never happen */
891 : rspeak(NOTHING_HAPPENS);
892 : // LCOV_EXCL_STOP
893 : }
894 53 : return GO_TERMINATE;
895 : }
896 :
897 102 : static phase_codes_t inven(void) {
898 : /* Inventory. If object, treat same as find. Else report on current
899 : * burden. */
900 102 : bool empty = true;
901 7140 : for (obj_t i = 1; i <= NOBJECTS; i++) {
902 7038 : if (i == BEAR || !TOTING(i)) {
903 6515 : continue;
904 : }
905 523 : if (empty) {
906 101 : rspeak(NOW_HOLDING);
907 101 : empty = false;
908 : }
909 523 : pspeak(i, touch, false, -1);
910 : }
911 102 : if (TOTING(BEAR)) {
912 1 : rspeak(TAME_BEAR);
913 : }
914 102 : if (empty) {
915 1 : rspeak(NO_CARRY);
916 : }
917 102 : return GO_CLEAROBJ;
918 : }
919 :
920 333 : static phase_codes_t light(verb_t verb, obj_t obj) {
921 : /* Light. Applicable only to lamp and urn. */
922 333 : if (obj == INTRANSITIVE) {
923 296 : int selects = 0;
924 296 : if (HERE(LAMP) && game.objects[LAMP].prop == LAMP_DARK &&
925 295 : game.limit >= 0) {
926 295 : obj = LAMP;
927 295 : selects++;
928 : }
929 296 : if (HERE(URN) && game.objects[URN].prop == URN_DARK) {
930 2 : obj = URN;
931 2 : selects++;
932 : }
933 296 : if (selects != 1) {
934 1 : return GO_UNKNOWN;
935 : }
936 : }
937 :
938 332 : switch (obj) {
939 29 : case URN:
940 29 : state_change(URN, game.objects[URN].prop == URN_EMPTY
941 : ? URN_EMPTY
942 : : URN_LIT);
943 29 : break;
944 301 : case LAMP:
945 301 : if (game.limit < 0) {
946 1 : rspeak(LAMP_OUT);
947 1 : break;
948 : }
949 300 : state_change(LAMP, LAMP_BRIGHT);
950 300 : if (game.wzdark) {
951 293 : return GO_TOP;
952 : }
953 7 : break;
954 2 : default:
955 2 : speak(actions[verb].message);
956 : }
957 39 : return GO_CLEAROBJ;
958 : }
959 :
960 59 : static phase_codes_t listen(void) {
961 : /* Listen. Intransitive only. Print stuff based on object sound
962 : * properties. */
963 59 : bool soundlatch = false;
964 59 : vocab_t sound = locations[game.loc].sound;
965 59 : if (sound != SILENT) {
966 13 : rspeak(sound);
967 13 : if (!locations[game.loc].loud) {
968 12 : rspeak(NO_MESSAGE);
969 : }
970 13 : soundlatch = true;
971 : }
972 4130 : for (obj_t i = 1; i <= NOBJECTS; i++) {
973 4071 : if (!HERE(i) || objects[i].sounds[0] == NULL ||
974 52 : PROP_IS_STASHED_OR_UNSEEN(i)) {
975 4022 : continue;
976 : }
977 49 : int mi = game.objects[i].prop;
978 : /* (ESR) Some unpleasant magic on object states here. Ideally
979 : * we'd have liked the bird to be a normal object that we can
980 : * use state_change() on; can't do it, because there are
981 : * actually two different series of per-state birdsounds
982 : * depending on whether player has drunk dragon's blood. */
983 49 : if (i == BIRD) {
984 46 : mi += 3 * game.blooded;
985 : }
986 49 : pspeak(i, hear, true, mi, game.zzword);
987 49 : rspeak(NO_MESSAGE);
988 49 : if (i == BIRD && mi == BIRD_ENDSTATE) {
989 43 : DESTROY(BIRD);
990 : }
991 49 : soundlatch = true;
992 : }
993 59 : if (!soundlatch) {
994 1 : rspeak(ALL_SILENT);
995 : }
996 59 : return GO_CLEAROBJ;
997 : }
998 :
999 118 : static phase_codes_t lock(verb_t verb, obj_t obj) {
1000 : /* Lock, unlock, no object given. Assume various things if present. */
1001 118 : if (obj == INTRANSITIVE) {
1002 15 : if (HERE(CLAM)) {
1003 2 : obj = CLAM;
1004 : }
1005 15 : if (HERE(OYSTER)) {
1006 1 : obj = OYSTER;
1007 : }
1008 15 : if (AT(DOOR)) {
1009 2 : obj = DOOR;
1010 : }
1011 15 : if (AT(GRATE)) {
1012 2 : obj = GRATE;
1013 : }
1014 15 : if (HERE(CHAIN)) {
1015 5 : obj = CHAIN;
1016 : }
1017 15 : if (obj == INTRANSITIVE) {
1018 3 : rspeak(NOTHING_LOCKED);
1019 3 : return GO_CLEAROBJ;
1020 : }
1021 : }
1022 :
1023 : /* Lock, unlock object. Special stuff for opening clam/oyster
1024 : * and for chain. */
1025 :
1026 115 : switch (obj) {
1027 45 : case CHAIN:
1028 45 : if (HERE(KEYS)) {
1029 44 : return chain(verb);
1030 : } else {
1031 1 : rspeak(NO_KEYS);
1032 : }
1033 1 : break;
1034 12 : case GRATE:
1035 12 : if (HERE(KEYS)) {
1036 9 : if (game.closng) {
1037 1 : rspeak(EXIT_CLOSED);
1038 1 : if (!game.panic) {
1039 1 : game.clock2 = PANICTIME;
1040 : }
1041 1 : game.panic = true;
1042 : } else {
1043 8 : state_change(GRATE, (verb == LOCK)
1044 : ? GRATE_CLOSED
1045 : : GRATE_OPEN);
1046 : }
1047 : } else {
1048 3 : rspeak(NO_KEYS);
1049 : }
1050 12 : break;
1051 41 : case CLAM:
1052 41 : if (verb == LOCK) {
1053 1 : rspeak(HUH_MAN);
1054 40 : } else if (TOTING(CLAM)) {
1055 2 : rspeak(DROP_CLAM);
1056 38 : } else if (!TOTING(TRIDENT)) {
1057 1 : rspeak(CLAM_OPENER);
1058 : } else {
1059 37 : DESTROY(CLAM);
1060 37 : drop(OYSTER, game.loc);
1061 37 : drop(PEARL, LOC_CULDESAC);
1062 37 : rspeak(PEARL_FALLS);
1063 : }
1064 41 : break;
1065 6 : case OYSTER:
1066 6 : if (verb == LOCK) {
1067 2 : rspeak(HUH_MAN);
1068 4 : } else if (TOTING(OYSTER)) {
1069 1 : rspeak(DROP_OYSTER);
1070 3 : } else if (!TOTING(TRIDENT)) {
1071 2 : rspeak(OYSTER_OPENER);
1072 : } else {
1073 1 : rspeak(OYSTER_OPENS);
1074 : }
1075 6 : break;
1076 6 : case DOOR:
1077 6 : rspeak((game.objects[DOOR].prop == DOOR_UNRUSTED) ? OK_MAN
1078 : : RUSTY_DOOR);
1079 6 : break;
1080 2 : case CAGE:
1081 2 : rspeak(NO_LOCK);
1082 2 : break;
1083 1 : case KEYS:
1084 1 : rspeak(CANNOT_UNLOCK);
1085 1 : break;
1086 2 : default:
1087 2 : speak(actions[verb].message);
1088 : }
1089 :
1090 71 : return GO_CLEAROBJ;
1091 : }
1092 :
1093 149 : static phase_codes_t pour(verb_t verb, obj_t obj) {
1094 : /* Pour. If no object, or object is bottle, assume contents of bottle.
1095 : * special tests for pouring water or oil on plant or rusty door. */
1096 149 : if (obj == BOTTLE || obj == INTRANSITIVE) {
1097 5 : obj = LIQUID();
1098 : }
1099 149 : if (obj == NO_OBJECT) {
1100 1 : return GO_UNKNOWN;
1101 : }
1102 148 : if (!TOTING(obj)) {
1103 1 : speak(actions[verb].message);
1104 1 : return GO_CLEAROBJ;
1105 : }
1106 :
1107 147 : if (obj != OIL && obj != WATER) {
1108 1 : rspeak(CANT_POUR);
1109 1 : return GO_CLEAROBJ;
1110 : }
1111 146 : if (HERE(URN) && game.objects[URN].prop == URN_EMPTY) {
1112 1 : return fill(verb, URN);
1113 : }
1114 145 : game.objects[BOTTLE].prop = EMPTY_BOTTLE;
1115 145 : game.objects[obj].place = LOC_NOWHERE;
1116 145 : if (!(AT(PLANT) || AT(DOOR))) {
1117 2 : rspeak(GROUND_WET);
1118 2 : return GO_CLEAROBJ;
1119 : }
1120 143 : if (!AT(DOOR)) {
1121 99 : if (obj == WATER) {
1122 : /* cycle through the three plant states */
1123 98 : state_change(PLANT,
1124 98 : MOD(game.objects[PLANT].prop + 1, 3));
1125 98 : game.objects[PLANT2].prop = game.objects[PLANT].prop;
1126 98 : return GO_MOVE;
1127 : } else {
1128 1 : rspeak(SHAKING_LEAVES);
1129 1 : return GO_CLEAROBJ;
1130 : }
1131 : } else {
1132 44 : state_change(DOOR, (obj == OIL) ? DOOR_UNRUSTED : DOOR_RUSTED);
1133 44 : return GO_CLEAROBJ;
1134 : }
1135 : }
1136 :
1137 6 : static phase_codes_t quit(void) {
1138 : /* Quit. Intransitive only. Verify intent and exit if that's what he
1139 : * wants. */
1140 6 : if (yes_or_no(arbitrary_messages[REALLY_QUIT],
1141 : arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) {
1142 5 : terminate(quitgame);
1143 : }
1144 1 : return GO_CLEAROBJ;
1145 : }
1146 :
1147 15 : static phase_codes_t read(command_t command)
1148 : /* Read. Print stuff based on objtxt. Oyster (?) is special case. */
1149 : {
1150 15 : if (command.obj == INTRANSITIVE) {
1151 3 : command.obj = NO_OBJECT;
1152 210 : for (int i = 1; i <= NOBJECTS; i++) {
1153 207 : if (HERE(i) && objects[i].texts[0] != NULL &&
1154 3 : !PROP_IS_STASHED(i)) {
1155 3 : command.obj = command.obj * NOBJECTS + i;
1156 : }
1157 : }
1158 3 : if (command.obj > NOBJECTS || command.obj == NO_OBJECT ||
1159 3 : DARK(game.loc)) {
1160 2 : return GO_UNKNOWN;
1161 : }
1162 : }
1163 :
1164 13 : if (DARK(game.loc)) {
1165 1 : sspeak(NO_SEE, command.word[0].raw);
1166 12 : } else if (command.obj == OYSTER) {
1167 4 : if (!TOTING(OYSTER) || !game.closed) {
1168 1 : rspeak(DONT_UNDERSTAND);
1169 3 : } else if (!game.clshnt) {
1170 2 : game.clshnt = yes_or_no(arbitrary_messages[CLUE_QUERY],
1171 : arbitrary_messages[WAYOUT_CLUE],
1172 : arbitrary_messages[OK_MAN]);
1173 : } else {
1174 1 : pspeak(OYSTER, hear, true,
1175 : 1); // Not really a sound, but oh well.
1176 : }
1177 8 : } else if (objects[command.obj].texts[0] == NULL ||
1178 5 : PROP_IS_NOTFOUND(command.obj)) {
1179 3 : speak(actions[command.verb].message);
1180 : } else {
1181 5 : pspeak(command.obj, study, true,
1182 5 : game.objects[command.obj].prop);
1183 : }
1184 13 : return GO_CLEAROBJ;
1185 : }
1186 :
1187 54 : static phase_codes_t reservoir(void) {
1188 : /* Z'ZZZ (word gets recomputed at startup; different each game). */
1189 54 : if (!AT(RESER) && game.loc != LOC_RESBOTTOM) {
1190 4 : rspeak(NOTHING_HAPPENS);
1191 4 : return GO_CLEAROBJ;
1192 : } else {
1193 50 : state_change(RESER, game.objects[RESER].prop == WATERS_PARTED
1194 : ? WATERS_UNPARTED
1195 : : WATERS_PARTED);
1196 50 : if (AT(RESER)) {
1197 49 : return GO_CLEAROBJ;
1198 : } else {
1199 1 : game.oldlc2 = game.loc;
1200 1 : game.newloc = LOC_NOWHERE;
1201 1 : rspeak(NOT_BRIGHT);
1202 1 : return GO_TERMINATE;
1203 : }
1204 : }
1205 : }
1206 :
1207 28 : static phase_codes_t rub(verb_t verb, obj_t obj) {
1208 : /* Rub. Yields various snide remarks except for lit urn. */
1209 28 : if (obj == URN && game.objects[URN].prop == URN_LIT) {
1210 26 : DESTROY(URN);
1211 26 : drop(AMBER, game.loc);
1212 26 : game.objects[AMBER].prop = AMBER_IN_ROCK;
1213 26 : --game.tally;
1214 26 : drop(CAVITY, game.loc);
1215 26 : rspeak(URN_GENIES);
1216 2 : } else if (obj != LAMP) {
1217 1 : rspeak(PECULIAR_NOTHING);
1218 : } else {
1219 1 : speak(actions[verb].message);
1220 : }
1221 28 : return GO_CLEAROBJ;
1222 : }
1223 :
1224 36 : static phase_codes_t say(command_t command) {
1225 : /* Say. Echo WD2. Magic words override. */
1226 36 : if (command.word[1].type == MOTION &&
1227 17 : (command.word[1].id == XYZZY || command.word[1].id == PLUGH ||
1228 3 : command.word[1].id == PLOVER)) {
1229 17 : return GO_WORD2;
1230 : }
1231 19 : if (command.word[1].type == ACTION && command.word[1].id == PART) {
1232 3 : return reservoir();
1233 : }
1234 :
1235 16 : if (command.word[1].type == ACTION &&
1236 11 : (command.word[1].id == FEE || command.word[1].id == FIE ||
1237 7 : command.word[1].id == FOE || command.word[1].id == FOO ||
1238 3 : command.word[1].id == FUM || command.word[1].id == PART)) {
1239 9 : return bigwords(command.word[1].id);
1240 : }
1241 7 : sspeak(OKEY_DOKEY, command.word[1].raw);
1242 7 : return GO_CLEAROBJ;
1243 : }
1244 :
1245 147 : static phase_codes_t throw_support(vocab_t spk) {
1246 147 : rspeak(spk);
1247 147 : drop(AXE, game.loc);
1248 147 : return GO_MOVE;
1249 : }
1250 :
1251 199 : static phase_codes_t throwit(command_t command) {
1252 : /* Throw. Same as discard unless axe. Then same as attack except
1253 : * ignore bird, and if dwarf is present then one might be killed.
1254 : * (Only way to do so!) Axe also special for dragon, bear, and
1255 : * troll. Treasures special for troll. */
1256 199 : if (!TOTING(command.obj)) {
1257 2 : speak(actions[command.verb].message);
1258 2 : return GO_CLEAROBJ;
1259 : }
1260 197 : if (objects[command.obj].is_treasure && AT(TROLL)) {
1261 : /* Snarf a treasure for the troll. */
1262 36 : drop(command.obj, LOC_NOWHERE);
1263 36 : move(TROLL, LOC_NOWHERE);
1264 36 : move(TROLL + NOBJECTS, IS_FREE);
1265 36 : drop(TROLL2, objects[TROLL].plac);
1266 36 : drop(TROLL2 + NOBJECTS, objects[TROLL].fixd);
1267 36 : juggle(CHASM);
1268 36 : rspeak(TROLL_SATISFIED);
1269 36 : return GO_CLEAROBJ;
1270 : }
1271 161 : if (command.obj == FOOD && HERE(BEAR)) {
1272 : /* But throwing food is another story. */
1273 4 : command.obj = BEAR;
1274 4 : return (feed(command.verb, command.obj));
1275 : }
1276 157 : if (command.obj != AXE) {
1277 3 : return (discard(command.verb, command.obj));
1278 : } else {
1279 154 : if (atdwrf(game.loc) <= 0) {
1280 10 : if (AT(DRAGON) &&
1281 1 : game.objects[DRAGON].prop == DRAGON_BARS) {
1282 1 : return throw_support(DRAGON_SCALES);
1283 : }
1284 9 : if (AT(TROLL)) {
1285 1 : return throw_support(TROLL_RETURNS);
1286 : }
1287 8 : if (AT(OGRE)) {
1288 1 : return throw_support(OGRE_DODGE);
1289 : }
1290 7 : if (HERE(BEAR) &&
1291 2 : game.objects[BEAR].prop == UNTAMED_BEAR) {
1292 : /* This'll teach him to throw the axe at the
1293 : * bear! */
1294 2 : drop(AXE, game.loc);
1295 2 : game.objects[AXE].fixed = IS_FIXED;
1296 2 : juggle(BEAR);
1297 2 : state_change(AXE, AXE_LOST);
1298 2 : return GO_CLEAROBJ;
1299 : }
1300 5 : command.obj = INTRANSITIVE;
1301 5 : return (attack(command));
1302 : }
1303 :
1304 144 : if (randrange(NDWARVES + 1) < game.dflag) {
1305 9 : return throw_support(DWARF_DODGES);
1306 : } else {
1307 135 : int i = atdwrf(game.loc);
1308 135 : game.dwarves[i].seen = false;
1309 135 : game.dwarves[i].loc = LOC_NOWHERE;
1310 135 : return throw_support(
1311 135 : (++game.dkill == 1) ? DWARF_SMOKE : KILLED_DWARF);
1312 : }
1313 : }
1314 : }
1315 :
1316 2 : static phase_codes_t wake(verb_t verb, obj_t obj) {
1317 : /* Wake. Only use is to disturb the dwarves. */
1318 2 : if (obj != DWARF || !game.closed) {
1319 1 : speak(actions[verb].message);
1320 1 : return GO_CLEAROBJ;
1321 : } else {
1322 1 : rspeak(PROD_DWARF);
1323 1 : return GO_DWARFWAKE;
1324 : }
1325 : }
1326 :
1327 98 : static phase_codes_t seed(verb_t verb, const char *arg) {
1328 : /* Set seed */
1329 98 : int32_t seed = strtol(arg, NULL, 10);
1330 98 : speak(actions[verb].message, seed);
1331 98 : set_seed(seed);
1332 98 : --game.turns;
1333 98 : return GO_TOP;
1334 : }
1335 :
1336 1 : static phase_codes_t waste(verb_t verb, turn_t turns) {
1337 : /* Burn turns */
1338 1 : game.limit -= turns;
1339 1 : speak(actions[verb].message, (int)game.limit);
1340 1 : return GO_TOP;
1341 : }
1342 :
1343 123 : static phase_codes_t wave(verb_t verb, obj_t obj) {
1344 : /* Wave. No effect unless waving rod at fissure or at bird. */
1345 123 : if (obj != ROD || !TOTING(obj) ||
1346 119 : (!HERE(BIRD) && (game.closng || !AT(FISSURE)))) {
1347 5 : speak(((!TOTING(obj)) && (obj != ROD || !TOTING(ROD2)))
1348 : ? arbitrary_messages[ARENT_CARRYING]
1349 : : actions[verb].message);
1350 5 : return GO_CLEAROBJ;
1351 : }
1352 :
1353 118 : if (game.objects[BIRD].prop == BIRD_UNCAGED &&
1354 65 : game.loc == game.objects[STEPS].place && PROP_IS_NOTFOUND(JADE)) {
1355 51 : drop(JADE, game.loc);
1356 51 : PROP_SET_FOUND(JADE);
1357 51 : --game.tally;
1358 51 : rspeak(NECKLACE_FLY);
1359 51 : return GO_CLEAROBJ;
1360 : } else {
1361 67 : if (game.closed) {
1362 1 : rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1363 : ? CAGE_FLY
1364 : : FREE_FLY);
1365 1 : return GO_DWARFWAKE;
1366 : }
1367 66 : if (game.closng || !AT(FISSURE)) {
1368 2 : rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1369 : ? CAGE_FLY
1370 : : FREE_FLY);
1371 2 : return GO_CLEAROBJ;
1372 : }
1373 64 : if (HERE(BIRD)) {
1374 17 : rspeak((game.objects[BIRD].prop == BIRD_CAGED)
1375 : ? CAGE_FLY
1376 : : FREE_FLY);
1377 : }
1378 :
1379 64 : state_change(FISSURE, game.objects[FISSURE].prop == BRIDGED
1380 : ? UNBRIDGED
1381 : : BRIDGED);
1382 64 : return GO_CLEAROBJ;
1383 : }
1384 : }
1385 :
1386 11685 : phase_codes_t action(command_t command) {
1387 : /* Analyse a verb. Remember what it was, go back for object if second
1388 : * word unless verb is "say", which snarfs arbitrary second word.
1389 : */
1390 : /* Previously, actions that result in a message, but don't do anything
1391 : * further were called "specials". Now they're handled here as normal
1392 : * actions. If noaction is true, then we spit out the message and return
1393 : */
1394 11685 : if (actions[command.verb].noaction) {
1395 18 : speak(actions[command.verb].message);
1396 18 : return GO_CLEAROBJ;
1397 : }
1398 :
1399 11667 : if (command.part == unknown) {
1400 : /* Analyse an object word. See if the thing is here, whether
1401 : * we've got a verb yet, and so on. Object must be here
1402 : * unless verb is "find" or "invent(ory)" (and no new verb
1403 : * yet to be analysed). Water and oil are also funny, since
1404 : * they are never actually dropped at any location, but might
1405 : * be here inside the bottle or urn or as a feature of the
1406 : * location. */
1407 5184 : if (HERE(command.obj)) {
1408 : /* FALL THROUGH */;
1409 217 : } else if (command.obj == DWARF && atdwrf(game.loc) > 0) {
1410 : /* FALL THROUGH */;
1411 211 : } else if (!game.closed &&
1412 350 : ((LIQUID() == command.obj && HERE(BOTTLE)) ||
1413 146 : command.obj == LIQLOC(game.loc))) {
1414 : /* FALL THROUGH */;
1415 37 : } else if (command.obj == OIL && HERE(URN) &&
1416 1 : game.objects[URN].prop != URN_EMPTY) {
1417 1 : command.obj = URN;
1418 : /* FALL THROUGH */;
1419 36 : } else if (command.obj == PLANT && AT(PLANT2) &&
1420 2 : game.objects[PLANT2].prop != PLANT_THIRSTY) {
1421 2 : command.obj = PLANT2;
1422 : /* FALL THROUGH */;
1423 34 : } else if (command.obj == KNIFE && game.knfloc == game.loc) {
1424 1 : game.knfloc = -1;
1425 1 : rspeak(KNIVES_VANISH);
1426 1 : return GO_CLEAROBJ;
1427 33 : } else if (command.obj == ROD && HERE(ROD2)) {
1428 6 : command.obj = ROD2;
1429 : /* FALL THROUGH */;
1430 27 : } else if ((command.verb == FIND ||
1431 25 : command.verb == INVENTORY) &&
1432 3 : (command.word[1].id == WORD_EMPTY ||
1433 0 : command.word[1].id == WORD_NOT_FOUND)) {
1434 : /* FALL THROUGH */;
1435 : } else {
1436 24 : sspeak(NO_SEE, command.word[0].raw);
1437 24 : return GO_CLEAROBJ;
1438 : }
1439 :
1440 5159 : if (command.verb != 0) {
1441 5155 : command.part = transitive;
1442 : }
1443 : }
1444 :
1445 11642 : switch (command.part) {
1446 6384 : case intransitive:
1447 6384 : if (command.word[1].raw[0] != '\0' && command.verb != SAY) {
1448 5207 : return GO_WORD2;
1449 : }
1450 1177 : if (command.verb == SAY) {
1451 : /* KEYS is not special, anything not NO_OBJECT or
1452 : * INTRANSITIVE will do here. We're preventing
1453 : * interpretation as an intransitive verb when the word
1454 : * is unknown. */
1455 37 : command.obj =
1456 37 : command.word[1].raw[0] != '\0' ? KEYS : NO_OBJECT;
1457 : }
1458 1177 : if (command.obj == NO_OBJECT || command.obj == INTRANSITIVE) {
1459 : /* Analyse an intransitive verb (ie, no object given
1460 : * yet). */
1461 1140 : switch (command.verb) {
1462 36 : case CARRY:
1463 36 : return vcarry(command.verb, INTRANSITIVE);
1464 1 : case DROP:
1465 1 : return GO_UNKNOWN;
1466 1 : case SAY:
1467 1 : return GO_UNKNOWN;
1468 11 : case UNLOCK:
1469 11 : return lock(command.verb, INTRANSITIVE);
1470 158 : case NOTHING: {
1471 158 : rspeak(OK_MAN);
1472 158 : return (GO_CLEAROBJ);
1473 : }
1474 4 : case LOCK:
1475 4 : return lock(command.verb, INTRANSITIVE);
1476 296 : case LIGHT:
1477 296 : return light(command.verb, INTRANSITIVE);
1478 197 : case EXTINGUISH:
1479 197 : return extinguish(command.verb, INTRANSITIVE);
1480 1 : case WAVE:
1481 1 : return GO_UNKNOWN;
1482 1 : case TAME:
1483 1 : return GO_UNKNOWN;
1484 2 : case GO: {
1485 2 : speak(actions[command.verb].message);
1486 2 : return GO_CLEAROBJ;
1487 : }
1488 19 : case ATTACK:
1489 19 : command.obj = INTRANSITIVE;
1490 19 : return attack(command);
1491 1 : case POUR:
1492 1 : return pour(command.verb, INTRANSITIVE);
1493 2 : case EAT:
1494 2 : return eat(command.verb, INTRANSITIVE);
1495 4 : case DRINK:
1496 4 : return drink(command.verb, INTRANSITIVE);
1497 1 : case RUB:
1498 1 : return GO_UNKNOWN;
1499 1 : case THROW:
1500 1 : return GO_UNKNOWN;
1501 6 : case QUIT:
1502 6 : return quit();
1503 1 : case FIND:
1504 1 : return GO_UNKNOWN;
1505 102 : case INVENTORY:
1506 102 : return inven();
1507 1 : case FEED:
1508 1 : return GO_UNKNOWN;
1509 1 : case FILL:
1510 1 : return fill(command.verb, INTRANSITIVE);
1511 6 : case BLAST:
1512 6 : blast();
1513 2 : return GO_CLEAROBJ;
1514 5 : case SCORE:
1515 5 : score(scoregame);
1516 5 : return GO_CLEAROBJ;
1517 138 : case FEE:
1518 : case FIE:
1519 : case FOE:
1520 : case FOO:
1521 : case FUM:
1522 138 : return bigwords(command.word[0].id);
1523 1 : case BRIEF:
1524 1 : return brief();
1525 3 : case READ:
1526 3 : command.obj = INTRANSITIVE;
1527 3 : return read(command);
1528 1 : case BREAK:
1529 1 : return GO_UNKNOWN;
1530 1 : case WAKE:
1531 1 : return GO_UNKNOWN;
1532 4 : case SAVE:
1533 4 : return suspend();
1534 9 : case RESUME:
1535 9 : return resume();
1536 13 : case FLY:
1537 13 : return fly(command.verb, INTRANSITIVE);
1538 59 : case LISTEN:
1539 59 : return listen();
1540 51 : case PART:
1541 51 : return reservoir();
1542 2 : case SEED:
1543 : case WASTE:
1544 2 : rspeak(NUMERIC_REQUIRED);
1545 2 : return GO_TOP;
1546 : default: // LCOV_EXCL_LINE
1547 : BUG(INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1548 : }
1549 : }
1550 : /* FALLTHRU */
1551 : case transitive:
1552 : /* Analyse a transitive verb. */
1553 5291 : switch (command.verb) {
1554 2378 : case CARRY:
1555 2378 : return vcarry(command.verb, command.obj);
1556 1688 : case DROP:
1557 1688 : return discard(command.verb, command.obj);
1558 36 : case SAY:
1559 36 : return say(command);
1560 92 : case UNLOCK:
1561 92 : return lock(command.verb, command.obj);
1562 1 : case NOTHING: {
1563 1 : rspeak(OK_MAN);
1564 1 : return (GO_CLEAROBJ);
1565 : }
1566 11 : case LOCK:
1567 11 : return lock(command.verb, command.obj);
1568 37 : case LIGHT:
1569 37 : return light(command.verb, command.obj);
1570 36 : case EXTINGUISH:
1571 36 : return extinguish(command.verb, command.obj);
1572 123 : case WAVE:
1573 123 : return wave(command.verb, command.obj);
1574 1 : case TAME: {
1575 1 : speak(actions[command.verb].message);
1576 1 : return GO_CLEAROBJ;
1577 : }
1578 1 : case GO: {
1579 1 : speak(actions[command.verb].message);
1580 1 : return GO_CLEAROBJ;
1581 : }
1582 172 : case ATTACK:
1583 172 : return attack(command);
1584 148 : case POUR:
1585 148 : return pour(command.verb, command.obj);
1586 8 : case EAT:
1587 8 : return eat(command.verb, command.obj);
1588 52 : case DRINK:
1589 52 : return drink(command.verb, command.obj);
1590 28 : case RUB:
1591 28 : return rub(command.verb, command.obj);
1592 199 : case THROW:
1593 199 : return throwit(command);
1594 1 : case QUIT:
1595 1 : speak(actions[command.verb].message);
1596 1 : return GO_CLEAROBJ;
1597 7 : case FIND:
1598 7 : return find(command.verb, command.obj);
1599 1 : case INVENTORY:
1600 1 : return find(command.verb, command.obj);
1601 48 : case FEED:
1602 48 : return feed(command.verb, command.obj);
1603 50 : case FILL:
1604 50 : return fill(command.verb, command.obj);
1605 2 : case BLAST:
1606 2 : blast();
1607 1 : return GO_CLEAROBJ;
1608 1 : case SCORE:
1609 1 : speak(actions[command.verb].message);
1610 1 : return GO_CLEAROBJ;
1611 1 : case FEE:
1612 : case FIE:
1613 : case FOE:
1614 : case FOO:
1615 : case FUM:
1616 1 : speak(actions[command.verb].message);
1617 1 : return GO_CLEAROBJ;
1618 1 : case BRIEF:
1619 1 : speak(actions[command.verb].message);
1620 1 : return GO_CLEAROBJ;
1621 12 : case READ:
1622 12 : return read(command);
1623 8 : case BREAK:
1624 8 : return vbreak(command.verb, command.obj);
1625 2 : case WAKE:
1626 2 : return wake(command.verb, command.obj);
1627 1 : case SAVE:
1628 1 : speak(actions[command.verb].message);
1629 1 : return GO_CLEAROBJ;
1630 1 : case RESUME:
1631 1 : speak(actions[command.verb].message);
1632 1 : return GO_CLEAROBJ;
1633 44 : case FLY:
1634 44 : return fly(command.verb, command.obj);
1635 1 : case LISTEN:
1636 1 : speak(actions[command.verb].message);
1637 1 : return GO_CLEAROBJ;
1638 : // LCOV_EXCL_START
1639 : // This case should never happen - here only as placeholder
1640 : case PART:
1641 : return reservoir();
1642 : // LCOV_EXCL_STOP
1643 98 : case SEED:
1644 98 : return seed(command.verb, command.word[1].raw);
1645 1 : case WASTE:
1646 1 : return waste(command.verb,
1647 1 : (turn_t)atol(command.word[1].raw));
1648 : default: // LCOV_EXCL_LINE
1649 : BUG(TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST); // LCOV_EXCL_LINE
1650 : }
1651 4 : case unknown:
1652 : /* Unknown verb, couldn't deduce object - might need hint */
1653 4 : sspeak(WHAT_DO, command.word[0].raw);
1654 4 : return GO_CHECKHINT;
1655 : default: // LCOV_EXCL_LINE
1656 : BUG(SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN); // LCOV_EXCL_LINE
1657 : }
1658 : }
1659 :
1660 : // end
|