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