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