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