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