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