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